diff --git a/maintainer/release-windows-packages b/maintainer/release-windows-packages index bf6a33dbf..ecc09b191 100755 --- a/maintainer/release-windows-packages +++ b/maintainer/release-windows-packages @@ -1,187 +1,209 @@ #!/bin/sh # # unpack windows rpm's from opensuse download server, upload files to kde.org and file a related release ticket # # Author: Ralf Habacker # # requirements: # # osc - opensuse build service command line client # # syntax: release-windows-packages # # run ./release-windows-packages to see all modes # NAME=umbrello PACKAGENAME32=mingw32-$NAME ROOT32=windows\:mingw\:win32 SRCROOT32=${ROOT32} ARCHOUT32=i686-w64-mingw32 PACKAGENAME64=mingw64-$NAME ROOT64=windows\:mingw\:win64 SRCROOT64=${ROOT64} ARCHOUT64=x86_64-w64-mingw32 REPO=openSUSE_Leap_42.3 SRCREPO=$REPO -VERSION=2.25.3 PHABURL=https://phabricator.kde.org oscoptions="-A https://api.opensuse.org" apitoken=cli-uxo23l4q5qrzoyscbz5kp4zcngqp options='projectPHIDs[]=PHID-PROJ-3qa4tomwgrmcmp4ym2ow' # abort on errors set -e echo2() { printf "%s\n" "$*" >&2; } which 7z >/dev/null 2>&1 if test $? -ne 0; then echo "7z not found, run 'zypper install p7zip'" exit 1 fi self=$(realpath $0) if ! test -d "work"; then mkdir work fi echo2 "running mode $1" case $1 in clean) ## clean working area rm -rf work/* ;; download) ## download rpm packages cd work rm -rf binaries osc $oscoptions getbinaries $ROOT32 $PACKAGENAME32:$PACKAGENAME32-installer $REPO x86_64 if test -n "$ROOT64"; then osc $oscoptions getbinaries $ROOT64 $PACKAGENAME64:$PACKAGENAME64-installer $REPO x86_64 fi cd .. $self downloadsrc + $self fetchversion touch work/$1.finished ;; +fetchversion) + cd work + VERSION=$(find binaries/ -name "*$PACKAGENAME32-installer*" | sed "s,^.*$PACKAGENAME32-installer-,,g;s,-.*$,,g") + echo $VERSION > VERSION + touch $1.finished + ;; + downloadsrc) ## download source cd work # fetch source package src32pkg=$(osc $oscoptions ls -b -r $SRCREPO -a x86_64 $SRCROOT32 $PACKAGENAME32 | grep src) osc $oscoptions getbinaries --sources $SRCROOT32 $PACKAGENAME32 $SRCREPO x86_64 $src32pkg # we only need once source package #src64pkg=$(osc $oscoptions ls -b -r $SRCREPO -a x86_64 $SRCROOT64 mingw64-umbrello | grep src) #osc $oscoptions getbinaries --sources $SRCROOT64 mingw64-umbrello $SRCREPO x86_64 $src64pkg # fetch debug packages debug32pkg=$(osc $oscoptions ls -b -r $SRCREPO -a x86_64 $SRCROOT32 $PACKAGENAME32 | grep debug) osc $oscoptions getbinaries $SRCROOT32 $PACKAGENAME32 $SRCREPO x86_64 $debug32pkg if test -n "$ROOT64"; then debug64pkg=$(osc $oscoptions ls -b -r $SRCREPO -a x86_64 $SRCROOT64 $PACKAGENAME64 | grep debug) osc $oscoptions getbinaries $SRCROOT64 $PACKAGENAME64 $SRCREPO x86_64 $debug64pkg fi touch $1.finished ;; unpack) ## unpack rpm files + if ! test -f work/VERSION; then + echo "no version found" + exit 1; + fi + VERSION=$(cat work/VERSION) cd work files=$(cd binaries; find -name '*installer*' -o -name '*portable*' -o -name '*src*' -o -name '*debugpackage*' | grep "$VERSION" | sed 's,^.,binaries,g') if test -d tmp; then rm -rf tmp fi mkdir -p tmp for i in $(echo $files); do (cd tmp; rpm2cpio ../$i | cpio -idmv) done touch $1.finished ;; movepackage) ## move windows binary packages into upload folder cd work rm -rf out mkdir -p out find tmp/ -type f -name '*.exe' -exec cp {} out \; find tmp/ -type f -name '*.7z' -exec cp {} out \; touch $1.finished ;; repacksource) ## repackage source tar ball to 7z # repackage source package - srcfile=$(find work/tmp -name "$NAME*.xz") + srcfile=$(find work/tmp -name "$NAME-[0-9]*.xz") outfile=$(basename $srcfile | sed 's,\.tar\.xz,\.7z,g') (mkdir -p work/srctmp; cd work/srctmp; tar -xJf ../../$srcfile; 7za a ../out/$outfile *; cd ..; rm -rf srctmp) touch work/$1.finished ;; createsha) ## create sha256sums (cd work/out; find -type f -name '*.7z' -o -name '*.exe' | sed 's,\./,,g' | sort | xargs sha256sum > $NAME.sha256sum) touch work/$1.finished ;; upload) ## upload files to staging area for i in $(find work/out -name '*.7z' -o -name '*.exe'); do set +e curl -T $i ftp://upload.kde.org/incoming/ set -e done touch work/$1.finished ;; createdescription) ## create ticket description + if ! test -f work/VERSION; then + echo "no version found" + exit 1; + fi + VERSION=$(cat work/VERSION) description="Please move the $NAME related files which has been uploaded to upload.kde.org/incoming to download mirror 'stable/$NAME/$VERSION' location and please update the symbolic link 'stable/$NAME/latest' to 'stable/$NAME/$VERSION'" sums=$(cat work/out/$NAME.sha256sum | gawk 'BEGIN { print "dir shasum file"} $2 ~ /i686/ { print "win32 " $0 } $2 ~ /x86_64/ { print "win64 " $0 } $2 ~ /[a-z]+-[0-9]/ { print "src " $0 }') echo -e "$description\n\n$sums" touch work/$1.finished ;; ticket) ## submit phabricator ticket description=$($0 createdescription) + if ! test -f work/createdescription.finished; then + echo "no description found" + exit 1; + fi + VERSION=$(cat work/VERSION) curl $PHABURL/api/maniphest.createtask \ -d api.token=$apitoken \ -d "title=tarball move request for stable/$NAME/$VERSION" \ -d "description=$description" \ -d "$options" touch work/$1.finished ;; sf) ## run all required targets for releasing on sourceforge $self clean $self download $self unpack $self movepackage $self repacksource $self createsha echo "All release related files are located in work/out" ls work/out touch work/$1.finished ;; kde) ## run all required targets for releasing on download.kde.org $self clean $self download $self unpack $self movepackage $self repacksource $self createsha $self upload echo2 "Content for ticket creating:" $self createdescription echo2 run "$self ticket" to submit ticket touch work/$1.finished ;; *) echo2 "Make sure to setup VERSION inside $0 and run" echo2 "$0 all" echo2 echo2 "or run single targets" echo2 gawk '$0 ~ /^[a-z].*) ##/ { sub(/) ##/,"",$0); a = $1; $1 = ""; printf(" %-20s - %s\n",a, $0); }' $0 >&2 ;; esac exit 0 diff --git a/models/diagrams/component/all-associations.xmi b/models/diagrams/component/all-associations.xmi new file mode 100644 index 000000000..585bd8de3 --- /dev/null +++ b/models/diagrams/component/all-associations.xmi @@ -0,0 +1,248 @@ + + + + + umbrello uml modeller http://umbrello.kde.org + 1.6.16 + UnicodeUTF8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/models/diagrams/component/all-elements.xmi b/models/diagrams/component/all-elements.xmi new file mode 100644 index 000000000..2b2cdac0e --- /dev/null +++ b/models/diagrams/component/all-elements.xmi @@ -0,0 +1,170 @@ + + + + + umbrello uml modeller http://umbrello.kde.org + 1.6.16 + UnicodeUTF8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/umbrello/dialogs/pages/classgeneralpage.cpp b/umbrello/dialogs/pages/classgeneralpage.cpp index ae0ddac27..a15db5280 100644 --- a/umbrello/dialogs/pages/classgeneralpage.cpp +++ b/umbrello/dialogs/pages/classgeneralpage.cpp @@ -1,414 +1,414 @@ /*************************************************************************** * 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 * ***************************************************************************/ // my own header #include "classgeneralpage.h" // app includes #include "debug_utils.h" #include "documentationwidget.h" #include "dialog_utils.h" #include "classifier.h" #include "datatype.h" #include "instance.h" #include "umlobject.h" #include "objectwidget.h" #include "uml.h" #include "umldoc.h" #include "artifact.h" #include "component.h" #include "umlview.h" #include "stereotype.h" #include "umlpackagelist.h" #include "model_utils.h" #include "package.h" #include "folder.h" #include "import_utils.h" #include "umlscene.h" #include "umlobjectnamewidget.h" #include "umlpackagewidget.h" #include "umlstereotypewidget.h" #include "umlartifacttypewidget.h" #include "visibilityenumwidget.h" // kde includes #include #include #include #include // qt includes #include #include #include #include #include #include #include #include ClassGeneralPage::ClassGeneralPage(UMLDoc* d, QWidget* parent, UMLObject* o) : DialogPageBase(parent), m_pObject(o), m_pWidget(0), m_pInstanceWidget(0), m_pUmldoc(d), m_pInstanceL(0), m_pStereoTypeL(0), m_pMultiCB(0), m_pDrawActorCB(0), m_pAbstractCB(0), m_pDeconCB(0), m_pExecutableCB(0), m_docWidget(0), m_nameWidget(0), m_instanceNameWidget(0), m_stereotypeWidget(0), m_packageWidget(0), m_artifactTypeWidget(0), m_visibilityEnumWidget(0) { if (!m_pObject) { uWarning() << "Given UMLObject is NULL."; return; } setMinimumSize(310, 330); QVBoxLayout * topLayout = new QVBoxLayout(this); topLayout->setSpacing(6); // setup name UMLObject::ObjectType t = m_pObject->baseType(); QGridLayout * m_pNameLayout = new QGridLayout(); m_pNameLayout->setSpacing(6); topLayout->addLayout(m_pNameLayout, 4); if( t == UMLObject::ot_Instance) { Q_ASSERT(m_pObject->asUMLInstance()); QString name = UMLObject::toI18nString(t); m_instanceNameWidget = new UMLObjectNameWidget(name, m_pObject->name()); m_instanceNameWidget->addToLayout(m_pNameLayout, 0); setFocusProxy(m_instanceNameWidget); QString className = UMLObject::toI18nString(UMLObject::ot_Class); m_nameWidget = new UMLObjectNameWidget(className, m_pObject->asUMLInstance()->classifier()->name()); m_nameWidget->addToLayout(m_pNameLayout, 1); } else { QString name = UMLObject::toI18nString(t); m_nameWidget = new UMLObjectNameWidget(name, m_pObject->name()); m_nameWidget->addToLayout(m_pNameLayout, 0); setFocusProxy(m_nameWidget); } if (t != UMLObject::ot_Stereotype && t!= UMLObject::ot_Instance) { m_stereotypeWidget = new UMLStereotypeWidget(m_pObject); if (t == UMLObject::ot_Interface || t == UMLObject::ot_Datatype || t == UMLObject::ot_Enum) { m_stereotypeWidget->setEditable(false); } m_stereotypeWidget->addToLayout(m_pNameLayout, 1); } int row = 2; if (m_pObject->isUMLDatatype()) { UMLDatatype *d = m_pObject->asUMLDatatype(); if (d && d->isReference() && d->originType()) { QLabel *label = new QLabel(i18n("Reference:"), this); m_pNameLayout->addWidget(label, row, 0); QLabel *reference = new QLabel(d->originType()->name(), this); m_pNameLayout->addWidget(reference, row, 1); ++row; } } - if (t == UMLObject::ot_Class || t == UMLObject::ot_Interface || t == UMLObject::ot_Enum) { + if (t == UMLObject::ot_Class || t == UMLObject::ot_Interface || t == UMLObject::ot_Enum || t == UMLObject::ot_Entity) { m_packageWidget = new UMLPackageWidget(m_pObject); m_packageWidget->addToLayout(m_pNameLayout, row); ++row; } if (t == UMLObject::ot_Class || t == UMLObject::ot_UseCase) { QString abstractCaption; if (t == UMLObject::ot_Class) { abstractCaption = i18n("A&bstract class"); } else { abstractCaption = i18n("A&bstract use case"); } m_pAbstractCB = new QCheckBox(abstractCaption, this); m_pAbstractCB->setChecked(m_pObject->isAbstract()); m_pNameLayout->addWidget(m_pAbstractCB, row, 0); ++row; } if (t == UMLObject::ot_Component) { m_pExecutableCB = new QCheckBox(i18nc("component is executable", "&Executable"), this); m_pExecutableCB->setChecked((o->asUMLComponent())->getExecutable()); m_pNameLayout->addWidget(m_pExecutableCB, row, 0); ++row; } if (t == UMLObject::ot_Artifact) { m_artifactTypeWidget = new UMLArtifactTypeWidget(o->asUMLArtifact()); m_artifactTypeWidget->addToLayout(topLayout); } // setup scope if (t != UMLObject::ot_Stereotype && t!= UMLObject::ot_Instance) { m_visibilityEnumWidget = new VisibilityEnumWidget(m_pObject, this); m_visibilityEnumWidget->addToLayout(topLayout); } m_docWidget = new DocumentationWidget(m_pObject, this); topLayout->addWidget(m_docWidget); } ClassGeneralPage::ClassGeneralPage(UMLDoc* d, QWidget* parent, ObjectWidget* o) : DialogPageBase(parent), m_pObject(0), m_pWidget(o), m_pInstanceWidget(0), m_pUmldoc(d), m_pInstanceL(0), m_pStereoTypeL(0), m_pMultiCB(0), m_pDrawActorCB(0), m_pAbstractCB(0), m_pDeconCB(0), m_pExecutableCB(0), m_docWidget(0), m_nameWidget(0), m_instanceNameWidget(0), m_stereotypeWidget(0), m_packageWidget(0), m_artifactTypeWidget(0), m_visibilityEnumWidget(0) { if (!m_pWidget) { uWarning() << "Given ObjectWidget is NULL."; return; } setMinimumSize(310, 330); QVBoxLayout * topLayout = new QVBoxLayout(this); topLayout->setSpacing(6); // setup name QGridLayout * m_pNameLayout = new QGridLayout(); m_pNameLayout->setSpacing(6); topLayout->addLayout(m_pNameLayout, 4); QString name = UMLObject::toI18nString(UMLObject::ot_Instance); m_instanceNameWidget = new UMLObjectNameWidget(name , m_pWidget->instanceName()); m_instanceNameWidget->addToLayout(m_pNameLayout, 0); setFocusProxy(m_instanceNameWidget); QString className = UMLObject::toI18nString(UMLObject::ot_Class); m_nameWidget = new UMLObjectNameWidget(className, m_pWidget->name()); m_nameWidget->addToLayout(m_pNameLayout, 1); UMLView *view = UMLApp::app()->currentView(); m_pDrawActorCB = new QCheckBox(i18n("Draw as actor"), this); m_pDrawActorCB->setChecked(m_pWidget->drawAsActor()); m_pNameLayout->addWidget(m_pDrawActorCB, 2, 0); if (view->umlScene()->type() == Uml::DiagramType::Collaboration) { m_pMultiCB = new QCheckBox(i18n("Multiple instance"), this); m_pMultiCB->setChecked(m_pWidget->multipleInstance()); m_pNameLayout->addWidget(m_pMultiCB, 2, 1); if (m_pDrawActorCB->isChecked()) m_pMultiCB->setEnabled(false); } else { // sequence diagram m_pDeconCB = new QCheckBox(i18n("Show destruction"), this); m_pDeconCB->setChecked(m_pWidget->showDestruction()); m_pNameLayout->addWidget(m_pDeconCB, 2, 1); } m_docWidget = new DocumentationWidget(m_pWidget, this); topLayout->addWidget(m_docWidget); if (m_pMultiCB) { connect(m_pDrawActorCB, SIGNAL(toggled(bool)), this, SLOT(slotActorToggled(bool))); } } ClassGeneralPage::ClassGeneralPage(UMLDoc* d, QWidget* parent, UMLWidget* widget) : DialogPageBase(parent), m_pObject(0), m_pWidget(0), m_pInstanceWidget(widget), m_pUmldoc(d), m_pInstanceL(0), m_pStereoTypeL(0), m_pMultiCB(0), m_pDrawActorCB(0), m_pAbstractCB(0), m_pDeconCB(0), m_pExecutableCB(0), m_docWidget(0), m_nameWidget(0), m_instanceNameWidget(0), m_stereotypeWidget(0), m_packageWidget(0), m_artifactTypeWidget(0), m_visibilityEnumWidget(0) { setMinimumSize(310, 330); QVBoxLayout * topLayout = new QVBoxLayout(this); topLayout->setSpacing(6); // setup name QGridLayout * m_pNameLayout = new QGridLayout(); m_pNameLayout->setSpacing(6); topLayout->addLayout(m_pNameLayout, 4); QString typeName = UMLWidget::toI18nString(widget->baseType()); m_nameWidget = new UMLObjectNameWidget(typeName, widget->name()); m_nameWidget->addToLayout(m_pNameLayout, 0); setFocusProxy(m_nameWidget); if (widget->umlObject()) { m_stereotypeWidget = new UMLStereotypeWidget(widget->umlObject()); m_stereotypeWidget->addToLayout(m_pNameLayout, 1); } m_instanceNameWidget = new UMLObjectNameWidget( UMLObject::toI18nString(UMLObject::ot_Instance), widget->instanceName()); m_instanceNameWidget->addToLayout(m_pNameLayout, 2); m_docWidget = new DocumentationWidget(widget, this); topLayout->addWidget(m_docWidget); } ClassGeneralPage::~ClassGeneralPage() { } /** * Will move information from the dialog into the object. * Call when the ok or apply button is pressed. */ void ClassGeneralPage::apply() { QString name = m_nameWidget->text(); m_docWidget->apply(); if (m_stereotypeWidget) { m_stereotypeWidget->apply(); } if (m_pObject) { UMLObject::ObjectType t = m_pObject->baseType(); - if (t == UMLObject::ot_Class || t == UMLObject::ot_Interface || t == UMLObject::ot_Enum) { + if (t == UMLObject::ot_Class || t == UMLObject::ot_Interface || t == UMLObject::ot_Enum || t == UMLObject::ot_Entity) { m_packageWidget->apply(); } if (m_pAbstractCB) { m_pObject->setAbstract(m_pAbstractCB->isChecked()); } if(m_instanceNameWidget && m_pObject->isUMLInstance()) { m_pObject->asUMLInstance()->setName(m_instanceNameWidget->text()); m_pObject->asUMLInstance()->setClassifierName(m_nameWidget->text()); } //make sure unique name if(m_pObject->baseType() != UMLObject::ot_Instance) { UMLObject *o = m_pUmldoc->findUMLObject(name); if (o && m_pObject != o) { KMessageBox::sorry(this, i18n("The name you have chosen\nis already being used.\nThe name has been reset."), i18n("Name is Not Unique"), 0); m_nameWidget->reset(); } else { m_pObject->setName(name); } } if (t != UMLObject::ot_Stereotype) { if (m_visibilityEnumWidget) m_visibilityEnumWidget->apply(); } if (m_pObject->baseType() == UMLObject::ot_Component) { (m_pObject->asUMLComponent())->setExecutable(m_pExecutableCB->isChecked()); } if (m_pObject->baseType() == UMLObject::ot_Artifact) { m_artifactTypeWidget->apply(); m_pObject->emitModified(); } } // end if m_pObject else if (m_pWidget) { m_pWidget->setInstanceName(m_instanceNameWidget->text()); if (m_pMultiCB) { m_pWidget->setMultipleInstance(m_pMultiCB->isChecked()); } m_pWidget->setDrawAsActor(m_pDrawActorCB->isChecked()); if (m_pDeconCB) { m_pWidget->setShowDestruction(m_pDeconCB->isChecked()); } UMLObject * o = m_pWidget->umlObject(); if (!o) { uError() << "UML object of widget is zero."; return; } UMLObject * old = m_pUmldoc->findUMLObject(name); if (old && o != old) { KMessageBox::sorry(this, i18n("The name you have chosen\nis already being used.\nThe name has been reset."), i18n("Name is Not Unique"), 0); m_nameWidget->reset(); } else { o->setName(name); } } // end if m_pWidget else if (m_pInstanceWidget) { m_pInstanceWidget->setInstanceName(m_instanceNameWidget->text()); UMLObject* o = m_pInstanceWidget->umlObject(); if (!o) { uError() << "UML object of instance widget is zero."; setInstanceWidgetNameIfApplicable(name); return; } UMLObject* old = m_pUmldoc->findUMLObject(name); if (old && o != old) { KMessageBox::sorry(this, i18n("The name you have chosen\nis already being used.\nThe name has been reset."), i18n("Name is Not Unique"), 0); m_nameWidget->reset(); } else { o->setName(name); } } // end if m_pInstanceWidget } /** * When the draw as actor check box is toggled, the draw * as multi instance need to be enabled/disabled. They * both can't be available at the same time. */ void ClassGeneralPage::slotActorToggled(bool state) { if (m_pMultiCB) { m_pMultiCB->setEnabled(!state); } } /** * Sets the input name to the instance widget if the change is applicable. * @param name The name of the widget */ void ClassGeneralPage::setInstanceWidgetNameIfApplicable(const QString& name) const { if(!m_pInstanceWidget) return; if(m_pInstanceWidget->isCombinedFragmentWidget() || m_pInstanceWidget->isFloatingDashLineWidget()) { m_pInstanceWidget->setName(name); } } diff --git a/umbrello/dialogs/umlforeignkeyconstraintdialog.cpp b/umbrello/dialogs/umlforeignkeyconstraintdialog.cpp index 40e1a5a4d..6afe77956 100644 --- a/umbrello/dialogs/umlforeignkeyconstraintdialog.cpp +++ b/umbrello/dialogs/umlforeignkeyconstraintdialog.cpp @@ -1,478 +1,482 @@ /*************************************************************************** * 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) 2003-2014 * * Umbrello UML Modeller Authors * ***************************************************************************/ #include "umlforeignkeyconstraintdialog.h" #include "attribute.h" #include "classifier.h" #include "classifierlistitem.h" #include "debug_utils.h" #include "dialog_utils.h" #include "entityattribute.h" #include "enumliteral.h" #include "enum.h" #include "entity.h" #include "foreignkeyconstraint.h" #include "object_factory.h" #include "operation.h" #include "template.h" #include "uml.h" #include "umldoc.h" #include "umlentitylist.h" #include "uniqueconstraint.h" #include "icon_utils.h" #include #if QT_VERSION < 0x050000 #include #endif #include #include #include #include #include #include #include #include #include #include #include -typedef QPair EntityAttributePair; - /** * Sets up the UMLForeignKeyConstraintDialog * * @param parent The parent to the UMLForeignKeyConstraintDialog. * @param pForeignKeyConstraint The Unique Constraint to show the properties of */ UMLForeignKeyConstraintDialog::UMLForeignKeyConstraintDialog(QWidget* parent, UMLForeignKeyConstraint* pForeignKeyConstraint) : MultiPageDialogBase(parent), m_doc(UMLApp::app()->document()), m_pForeignKeyConstraint(pForeignKeyConstraint) { setCaption(i18n("Foreign Key Setup")); setupGeneralPage(); setupColumnPage(); connect(this, SIGNAL(okClicked()), this, SLOT(slotOk())); connect(this, SIGNAL(applyClicked()), this, SLOT(slotApply())); } /** * Standard destructor. */ UMLForeignKeyConstraintDialog::~UMLForeignKeyConstraintDialog() { } /** * Adds pair to the list. */ void UMLForeignKeyConstraintDialog::slotAddPair() { // get the index of the selected local column and referenced column int indexL = m_ColumnWidgets.localColumnCB->currentIndex(); int indexR = m_ColumnWidgets.referencedColumnCB->currentIndex(); if (indexL == -1 || indexR == -1) { return; } // local entity attribute UMLEntityAttribute* localColumn = m_pLocalAttributeList.at(indexL); // referenced entity attribute UMLEntityAttribute* referencedColumn = m_pReferencedAttributeList.at(indexR); // remove from combo boxes m_ColumnWidgets.localColumnCB->removeItem(indexL); m_ColumnWidgets.referencedColumnCB->removeItem(indexR); // remove from local cache m_pLocalAttributeList.removeAt(indexL); m_pReferencedAttributeList.removeAt(indexR); // add to local cache of mapping EntityAttributePair pair = qMakePair(localColumn, referencedColumn); m_pAttributeMapList.append(pair); // update mapping view QTreeWidgetItem* mapping = new QTreeWidgetItem(m_ColumnWidgets.mappingTW); mapping->setText(0, localColumn->toString(Uml::SignatureType::SigNoVis)); mapping->setText(1, referencedColumn->toString(Uml::SignatureType::SigNoVis)); m_ColumnWidgets.mappingTW->addTopLevelItem(mapping); slotResetWidgetState(); } /** * Deletes a pair from the list. */ void UMLForeignKeyConstraintDialog::slotDeletePair() { // get the index of the selected pair in the view QTreeWidgetItem* twi = m_ColumnWidgets.mappingTW->currentItem(); int indexP = m_ColumnWidgets.mappingTW->indexOfTopLevelItem(twi); if (indexP == -1) { return; } //find pair in local cache EntityAttributePair pair = m_pAttributeMapList.at(indexP); // remove them from the view and the list m_ColumnWidgets.mappingTW->takeTopLevelItem(indexP); m_pAttributeMapList.removeAt(indexP); // add the attributes to the local caches m_pLocalAttributeList.append(pair.first); m_pReferencedAttributeList.append(pair.second); // add them to the view (combo boxes) uDebug() << (pair.first) << (pair.second); m_ColumnWidgets.localColumnCB->addItem((pair.first)->toString(Uml::SignatureType::SigNoVis)); m_ColumnWidgets.referencedColumnCB->addItem((pair.second)->toString(Uml::SignatureType::SigNoVis)); foreach(const EntityAttributePair& p, m_pAttributeMapList) { uDebug() << (p.first)->name() << " " << (p.first)->baseType() << " " << (p.second)->name() << " " << (p.second)->baseType(); } slotResetWidgetState(); } /** * Checks if changes are valid and applies them if they are, * else returns false. */ bool UMLForeignKeyConstraintDialog::apply() { // set the Referenced Entity QString entityName = m_GeneralWidgets.referencedEntityCB->currentText(); - UMLObject* uo = m_doc->findUMLObject(entityName, UMLObject::ot_Entity); + UMLObject* uo = m_doc->findUMLObjectRecursive(Uml::ModelType::EntityRelationship, + entityName, + UMLObject::ot_Entity); UMLEntity* ue = uo->asUMLEntity(); if (ue == 0) { uDebug() << " Could not find UML Entity with name " << entityName; return false; } m_pForeignKeyConstraint->setReferencedEntity(ue); // set all the update and delete actions UMLForeignKeyConstraint::UpdateDeleteAction updateAction, deleteAction; updateAction = (UMLForeignKeyConstraint::UpdateDeleteAction) m_GeneralWidgets.updateActionCB->currentIndex(); deleteAction = (UMLForeignKeyConstraint::UpdateDeleteAction) m_GeneralWidgets.deleteActionCB->currentIndex(); m_pForeignKeyConstraint->setUpdateAction(updateAction); m_pForeignKeyConstraint->setDeleteAction(deleteAction); // remove all existing mappings first m_pForeignKeyConstraint->clearMappings(); // add all mappings present in local cache foreach(const EntityAttributePair& pair, m_pAttributeMapList) { if (!m_pForeignKeyConstraint->addEntityAttributePair(pair.first, pair.second)) { return false; } } // set the name m_pForeignKeyConstraint->setName(m_GeneralWidgets.nameT->text()); + // propagate changes to tree view + m_pForeignKeyConstraint->emitModified(); + return true; } /** * Setup the General Page. */ void UMLForeignKeyConstraintDialog::setupGeneralPage() { //setup General page QWidget* page = new QWidget(); QVBoxLayout* topLayout = new QVBoxLayout(); page->setLayout(topLayout); pageGeneral = createPage(i18nc("general page title", "General"), i18n("General Settings"), Icon_Utils::it_Properties_General, page); m_GeneralWidgets.generalGB = new QGroupBox(i18nc("general group title", "General")); topLayout->addWidget(m_GeneralWidgets.generalGB); QGridLayout* generalLayout = new QGridLayout(m_GeneralWidgets.generalGB); generalLayout->setSpacing(spacingHint()); generalLayout->setMargin(fontMetrics().height()); Dialog_Utils::makeLabeledEditField(generalLayout, 0, m_GeneralWidgets.nameL, i18nc("label for entering name", "Name"), m_GeneralWidgets.nameT); m_GeneralWidgets.referencedEntityL = new QLabel(i18n("Referenced Entity")); generalLayout->addWidget(m_GeneralWidgets.referencedEntityL, 1, 0); m_GeneralWidgets.referencedEntityCB = new KComboBox(); generalLayout->addWidget(m_GeneralWidgets.referencedEntityCB, 1, 1); m_GeneralWidgets.actionGB = new QGroupBox(i18n("Actions")); topLayout->addWidget(m_GeneralWidgets.actionGB); QGridLayout* actionLayout = new QGridLayout(m_GeneralWidgets.actionGB); generalLayout->setSpacing(spacingHint()); generalLayout->setMargin(fontMetrics().height()); m_GeneralWidgets.onUpdateL = new QLabel(i18n("On Update")); actionLayout->addWidget(m_GeneralWidgets.onUpdateL, 0, 0); m_GeneralWidgets.updateActionCB = new KComboBox(page); actionLayout->addWidget(m_GeneralWidgets.updateActionCB, 0, 1); m_GeneralWidgets.onDeleteL = new QLabel(i18n("On Delete")); actionLayout->addWidget(m_GeneralWidgets.onDeleteL, 1, 0); m_GeneralWidgets.deleteActionCB = new KComboBox(); actionLayout->addWidget(m_GeneralWidgets.deleteActionCB, 1, 1); // set the name m_GeneralWidgets.nameT->setText(m_pForeignKeyConstraint->name()); // fill up the combo boxes // reference entity combo box UMLEntityList entList = m_doc->entities(); foreach(UMLEntity* ent, entList) { m_GeneralWidgets.referencedEntityCB->addItem(ent->name()); } UMLEntity* referencedEntity = m_pForeignKeyConstraint->getReferencedEntity(); int index; if (referencedEntity != 0) { index = m_GeneralWidgets.referencedEntityCB->findText(referencedEntity->name()); if (index != -1) m_GeneralWidgets.referencedEntityCB->setCurrentIndex(index); } m_pReferencedEntityIndex = m_GeneralWidgets.referencedEntityCB->currentIndex(); // action combo boxes // do not change order. It is according to enum specification in foreignkeyconstraint.h QStringList actions; actions << i18n("No Action") << i18n("Restrict") << i18n("Cascade") << i18n("Set Null") << i18n("Set Default"); m_GeneralWidgets.updateActionCB->addItems(actions); m_GeneralWidgets.deleteActionCB->addItems(actions); m_GeneralWidgets.updateActionCB->setCurrentIndex(m_pForeignKeyConstraint->getUpdateAction()); m_GeneralWidgets.deleteActionCB->setCurrentIndex(m_pForeignKeyConstraint->getDeleteAction()); connect(m_GeneralWidgets.referencedEntityCB, SIGNAL(activated(int)), this, SLOT(slotReferencedEntityChanged(int))); } /** * Setup Column Page. */ void UMLForeignKeyConstraintDialog::setupColumnPage() { //setup Columns page QWidget* page = new QWidget(); QVBoxLayout* topLayout = new QVBoxLayout(); page->setLayout(topLayout); pageColumn = createPage(i18n("Columns"), i18n("Columns"), Icon_Utils::it_Properties_Columns, page); m_ColumnWidgets.mappingTW = new QTreeWidget(); topLayout->addWidget(m_ColumnWidgets.mappingTW); QStringList headers; headers << i18nc("column header local", "Local") << i18nc("column header referenced", "Referenced"); m_ColumnWidgets.mappingTW->setHeaderLabels(headers); QWidget* columns = new QWidget(); topLayout->addWidget(columns); QGridLayout* columnsLayout = new QGridLayout(columns); m_ColumnWidgets.localColumnL = new QLabel(i18n("Local Column")); columnsLayout->addWidget(m_ColumnWidgets.localColumnL, 0, 0); m_ColumnWidgets.localColumnCB = new KComboBox(); columnsLayout->addWidget(m_ColumnWidgets.localColumnCB, 0, 1); m_ColumnWidgets.referencedColumnL = new QLabel(i18n("Referenced Column")); columnsLayout->addWidget(m_ColumnWidgets.referencedColumnL, 1, 0); m_ColumnWidgets.referencedColumnCB = new KComboBox(); columnsLayout->addWidget(m_ColumnWidgets.referencedColumnCB, 1, 1); #if QT_VERSION >= 0x050000 QDialogButtonBox* buttonBox = new QDialogButtonBox(); m_ColumnWidgets.addPB = buttonBox->addButton(i18n("&Add"), QDialogButtonBox::ActionRole); connect(m_ColumnWidgets.addPB, SIGNAL(clicked()), this, SLOT(slotAddPair())); m_ColumnWidgets.removePB = buttonBox->addButton(i18n("&Delete"), QDialogButtonBox::ActionRole); connect(m_ColumnWidgets.removePB, SIGNAL(clicked()), this, SLOT(slotDeletePair())); #else KDialogButtonBox* buttonBox = new KDialogButtonBox(page); m_ColumnWidgets.addPB = buttonBox->addButton(i18n("&Add"), KDialogButtonBox::ActionRole, this, SLOT(slotAddPair())); m_ColumnWidgets.removePB = buttonBox->addButton(i18n("&Delete"), KDialogButtonBox::ActionRole, this, SLOT(slotDeletePair())); #endif columnsLayout->addWidget(buttonBox, 2, 1); // fill the column boxes and their local cache. refillLocalAttributeCB(); refillReferencedAttributeCB(); QMap::iterator i; QMap map = m_pForeignKeyConstraint->getEntityAttributePairs(); for (i = map.begin(); i != map.end() ; ++i) { UMLEntityAttribute* localColumn, *referencedColumn; localColumn = const_cast(i.key()); referencedColumn = const_cast(i.value()); // remove these columns from local cache int indexL = m_pLocalAttributeList.indexOf(localColumn); int indexR = m_pReferencedAttributeList.indexOf(referencedColumn); m_pLocalAttributeList.removeAt(indexL); m_pReferencedAttributeList.removeAt(indexR); // remove them from combo boxes // the conditions may never be violated . Just for safety though if (indexL >= 0 && indexL < (m_ColumnWidgets.localColumnCB)->count()) m_ColumnWidgets.localColumnCB->removeItem(indexL); if (indexR >= 0 && indexR < (m_ColumnWidgets.referencedColumnCB)->count()) m_ColumnWidgets.referencedColumnCB->removeItem(indexR); // add to local cache m_pAttributeMapList.append(qMakePair(localColumn, referencedColumn)); // add to view QTreeWidgetItem* mapping = new QTreeWidgetItem(m_ColumnWidgets.mappingTW); mapping->setText(0, localColumn->toString(Uml::SignatureType::SigNoVis)); mapping->setText(1, referencedColumn->toString(Uml::SignatureType::SigNoVis)); m_ColumnWidgets.mappingTW->insertTopLevelItem(0, mapping); } slotResetWidgetState(); connect(m_ColumnWidgets.mappingTW, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(slotResetWidgetState())); } /** * Used when the Apply button is clicked. */ void UMLForeignKeyConstraintDialog::slotApply() { apply(); } /** * Used when the OK button is clicked. Calls apply(). */ void UMLForeignKeyConstraintDialog::slotOk() { if (apply()) { accept(); } } void UMLForeignKeyConstraintDialog::slotReferencedEntityChanged(int index) { if (index == m_pReferencedEntityIndex) { return; } if (!m_pAttributeMapList.empty()) { int result = KMessageBox::questionYesNo(this, i18n("You are attempting to change the Referenced Entity of this ForeignKey Constraint. Any unapplied changes to the mappings between local and referenced entities will be lost. Are you sure you want to continue ?")); if (result != KMessageBox::Yes) { // revert back to old index m_GeneralWidgets.referencedEntityCB->setCurrentIndex(m_pReferencedEntityIndex); return; } } // set the referenced entity index to current index m_pReferencedEntityIndex = index; m_ColumnWidgets.mappingTW->clear(); refillReferencedAttributeCB(); refillLocalAttributeCB(); } void UMLForeignKeyConstraintDialog::refillReferencedAttributeCB() { m_pReferencedAttributeList.clear(); m_ColumnWidgets.referencedColumnCB->clear(); // fill the combo boxes - UMLObject* uo = m_doc->findUMLObject(m_GeneralWidgets.referencedEntityCB->currentText(), - UMLObject::ot_Entity); + UMLObject* uo = m_doc->findUMLObjectRecursive(Uml::ModelType::EntityRelationship, + m_GeneralWidgets.referencedEntityCB->currentText(), + UMLObject::ot_Entity); UMLEntity* ue = uo->asUMLEntity(); if (ue) { UMLClassifierListItemList ual = ue->getFilteredList(UMLObject::ot_EntityAttribute); foreach(UMLClassifierListItem* att, ual) { m_pReferencedAttributeList.append(att->asUMLEntityAttribute()); m_ColumnWidgets.referencedColumnCB->addItem(att->toString(Uml::SignatureType::SigNoVis)); } } } void UMLForeignKeyConstraintDialog::refillLocalAttributeCB() { m_pLocalAttributeList.clear(); m_ColumnWidgets.localColumnCB->clear(); // fill the combo boxes UMLEntity* ue = m_pForeignKeyConstraint->umlParent()->asUMLEntity(); if (ue) { UMLClassifierListItemList ual = ue->getFilteredList(UMLObject::ot_EntityAttribute); foreach(UMLClassifierListItem* att, ual) { m_pLocalAttributeList.append(att->asUMLEntityAttribute()); m_ColumnWidgets.localColumnCB->addItem(att->toString(Uml::SignatureType::SigNoVis)); } } } /** * Enable/Disable the widgets in the Dialog Box. */ void UMLForeignKeyConstraintDialog::slotResetWidgetState() { m_ColumnWidgets.addPB->setEnabled(true); m_ColumnWidgets.removePB->setEnabled(true); m_ColumnWidgets.localColumnCB->setEnabled(true); m_ColumnWidgets.referencedColumnCB->setEnabled(true); // If one of the Combo Boxes is empty, then disable the Combo Box if (m_ColumnWidgets.localColumnCB->count() == 0 || m_ColumnWidgets.referencedColumnCB->count() == 0) { m_ColumnWidgets.localColumnCB->setEnabled(false); m_ColumnWidgets.referencedColumnCB->setEnabled(false); m_ColumnWidgets.addPB->setEnabled(false); } // get index of selected Attribute in List Box if (m_ColumnWidgets.mappingTW->currentItem() == 0) { m_ColumnWidgets.removePB->setEnabled(false); } } diff --git a/umbrello/dialogs/umlforeignkeyconstraintdialog.h b/umbrello/dialogs/umlforeignkeyconstraintdialog.h index 01201192e..93dcdcc90 100644 --- a/umbrello/dialogs/umlforeignkeyconstraintdialog.h +++ b/umbrello/dialogs/umlforeignkeyconstraintdialog.h @@ -1,117 +1,120 @@ /*************************************************************************** * 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) 2003-2014 * * Umbrello UML Modeller Authors * ***************************************************************************/ #ifndef UMLFOREIGNKEYCONSTRAINTDIALOG_H #define UMLFOREIGNKEYCONSTRAINTDIALOG_H //app includes #include "multipagedialogbase.h" #include "umlentityattributelist.h" //kde includes //qt includes #include class KComboBox; class KLineEdit; class UMLDoc; class UMLForeignKeyConstraint; class QGroupBox; class QLabel; class QPushButton; class QTreeWidget; +typedef QPair EntityAttributePair; +typedef QList EntityAttributePairList; + /** * A dialog page to display foreignkey constraint properties. * * @short A dialog page to display foreignkey constraint properties. * @author Sharan Rao * Bugs and comments to umbrello-devel@kde.org or http://bugs.kde.org */ class UMLForeignKeyConstraintDialog : public MultiPageDialogBase { Q_OBJECT public: UMLForeignKeyConstraintDialog(QWidget* parent, UMLForeignKeyConstraint* pForeignKeyConstraint); ~UMLForeignKeyConstraintDialog(); private: bool apply(); void setupGeneralPage(); void setupColumnPage(); void refillReferencedAttributeCB(); void refillLocalAttributeCB(); UMLDoc* m_doc; ///< the UMLDocument where all objects live UMLForeignKeyConstraint* m_pForeignKeyConstraint; struct GeneralWidgets { QGroupBox* generalGB; QGroupBox* actionGB; QLabel* referencedEntityL; QLabel* nameL; KLineEdit* nameT; KComboBox* referencedEntityCB; QLabel* onUpdateL; QLabel* onDeleteL; KComboBox* updateActionCB; KComboBox* deleteActionCB; }; // end general widgets struct ColumnWidgets { QTreeWidget* mappingTW; KComboBox* localColumnCB; KComboBox* referencedColumnCB; QLabel* localColumnL; QLabel* referencedColumnL; QPushButton* addPB, *removePB; }; // end column widgets // these attributes store the local cache UMLEntityAttributeList m_pLocalAttributeList; UMLEntityAttributeList m_pReferencedAttributeList; - QList< QPair > m_pAttributeMapList; + EntityAttributePairList m_pAttributeMapList; /** * Temporary Storage for entity index in referencedColumnCB. * Used for reverting back a change in referenced entities. */ int m_pReferencedEntityIndex; // end of local cache GeneralWidgets m_GeneralWidgets; ColumnWidgets m_ColumnWidgets; KPageWidgetItem *pageGeneral, *pageColumn; public slots: void slotResetWidgetState(); void slotApply(); void slotOk(); void slotAddPair(); void slotDeletePair(); void slotReferencedEntityChanged(int index); }; #endif diff --git a/umbrello/dialogs/widgets/umlpackagewidget.cpp b/umbrello/dialogs/widgets/umlpackagewidget.cpp index b62ed3708..8caecebbc 100644 --- a/umbrello/dialogs/widgets/umlpackagewidget.cpp +++ b/umbrello/dialogs/widgets/umlpackagewidget.cpp @@ -1,101 +1,103 @@ /*************************************************************************** * 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 "umlpackagewidget.h" #include "import_utils.h" #include "model_utils.h" #include "uml.h" #include "umldoc.h" #include "folder.h" #include "package.h" #include #include #include #include UMLPackageWidget::UMLPackageWidget(UMLObject *o, QWidget *parent) : QWidget(parent), m_object(o) { Q_ASSERT(o); QHBoxLayout *layout = new QHBoxLayout; layout->setContentsMargins(0,0,0,0); m_label = new QLabel(i18n("Package path:"), this); layout->addWidget(m_label); m_editField = new KComboBox(this); m_editField->setEditable(true); layout->addWidget(m_editField, 2); setLayout(layout); m_label->setBuddy(m_editField); - UMLPackageList packageList = UMLApp::app()->document()->packages(); + Uml::ModelType::Enum guess = Model_Utils::guessContainer(o); + UMLPackageList packageList = UMLApp::app()->document()->packages(true, guess); QStringList packages; foreach(UMLPackage* package, packageList) { packages << package->name(); } packages.sort(); m_editField->insertItems(-1, packages); QString packagePath = o->package(); UMLPackage* parentPackage = o->umlPackage(); UMLPackage* folderLogicalView = UMLApp::app()->document()->rootFolder(Uml::ModelType::Logical)->asUMLPackage(); if (parentPackage == 0 || parentPackage == folderLogicalView) { m_editField->setEditText(QString()); } else { m_editField->setEditText(packagePath); } } UMLPackageWidget::~UMLPackageWidget() { delete m_editField; delete m_label; } /** * Add this widget to a given grid layout. Umbrello dialogs places labels in column 0 * and the editable field in column 1. * @param layout The layout to which the widget should be added * @param row The row in the grid layout where the widget should be placed */ void UMLPackageWidget::addToLayout(QGridLayout *layout, int row) { layout->addWidget(m_label, row, 0); layout->addWidget(m_editField, row, 1); } /** * Apply changes to the related UMLObject. */ void UMLPackageWidget::apply() { QString packageName = m_editField->currentText().trimmed(); UMLObject* newPackage = 0; if (!packageName.isEmpty()) { if ((newPackage = UMLApp::app()->document()->findUMLObject(packageName, UMLObject::ot_Package)) == 0) { newPackage = Import_Utils::createUMLObject(UMLObject::ot_Package, packageName); } } else { - newPackage = UMLApp::app()->document()->rootFolder(Uml::ModelType::Logical); + Uml::ModelType::Enum guess = Model_Utils::guessContainer(m_object); + newPackage = UMLApp::app()->document()->rootFolder(guess); } // adjust list view items Model_Utils::treeViewMoveObjectTo(newPackage, m_object); } diff --git a/umbrello/model_utils.cpp b/umbrello/model_utils.cpp index 9fa7f3efa..992baead3 100644 --- a/umbrello/model_utils.cpp +++ b/umbrello/model_utils.cpp @@ -1,2095 +1,2129 @@ /*************************************************************************** * 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 "model_utils.h" // app includes #include "floatingtextwidget.h" #include "debug_utils.h" #include "umlobject.h" #include "umlpackagelist.h" #include "uniqueconstraint.h" #include "package.h" #include "folder.h" #include "classifier.h" #include "enum.h" #include "entity.h" #include "template.h" #include "operation.h" #include "attribute.h" #include "association.h" #include "umlrole.h" #include "umldoc.h" #include "uml.h" #include "umllistview.h" #include "umllistviewitem.h" #include "umlscene.h" #include "umlview.h" #include "codegenerator.h" // kde includes #include // qt includes #include #include namespace Model_Utils { /** * Determines whether the given widget type is cloneable. * * @param type The input WidgetType. * @return True if the given type is cloneable. */ bool isCloneable(WidgetBase::WidgetType type) { switch (type) { case WidgetBase::wt_Actor: case WidgetBase::wt_UseCase: case WidgetBase::wt_Class: case WidgetBase::wt_Interface: case WidgetBase::wt_Enum: case WidgetBase::wt_Datatype: case WidgetBase::wt_Package: case WidgetBase::wt_Component: case WidgetBase::wt_Port: case WidgetBase::wt_Node: case WidgetBase::wt_Artifact: case WidgetBase::wt_Instance: case WidgetBase::wt_Entity: return true; default: return false; } } /** * Seek the given id in the given list of objects. * Each list element may itself contain other objects * and the search is done recursively. * * @param id The unique ID to seek. * @param inList The UMLObjectList in which to search. * @return Pointer to the UMLObject that matches the ID (NULL if none matches). */ UMLObject* findObjectInList(Uml::ID::Type id, const UMLObjectList& inList) { for (UMLObjectListIt oit(inList); oit.hasNext();) { UMLObject *obj = oit.next(); if (obj->id() == id) return obj; UMLObject *o; UMLObject::ObjectType t = obj->baseType(); switch (t) { case UMLObject::ot_Folder: case UMLObject::ot_Package: case UMLObject::ot_Component: o = obj->asUMLPackage()->findObjectById(id); if (o) return o; break; case UMLObject::ot_Interface: case UMLObject::ot_Class: case UMLObject::ot_Enum: case UMLObject::ot_Entity: case UMLObject::ot_Instance: o = obj->asUMLClassifier()->findChildObjectById(id); if (o == 0 && (t == UMLObject::ot_Interface || t == UMLObject::ot_Class)) o = ((UMLPackage*)obj)->findObjectById(id); if (o) return o; break; case UMLObject::ot_Association: { UMLAssociation *assoc = obj->asUMLAssociation(); UMLRole *rA = assoc->getUMLRole(Uml::RoleType::A); if (rA->id() == id) return rA; UMLRole *rB = assoc->getUMLRole(Uml::RoleType::B); if (rB->id() == id) return rB; } break; default: break; } } return 0; } /** * Find the UML object of the given type and name in the passed-in list. * * @param inList List in which to seek the object. * @param inName Name of the object to find. * @param type ObjectType of the object to find (optional.) * When the given type is ot_UMLObject the type is * disregarded, i.e. the given name is the only * search criterion. * @param currentObj Object relative to which to search (optional.) * If given then the enclosing scope(s) of this * object are searched before the global scope. * @return Pointer to the UMLObject found, or NULL if not found. */ UMLObject* findUMLObject(const UMLObjectList& inList, const QString& inName, UMLObject::ObjectType type /* = ot_UMLObject */, UMLObject *currentObj /* = 0 */) { const bool caseSensitive = UMLApp::app()->activeLanguageIsCaseSensitive(); QString name = inName; const bool atGlobalScope = name.startsWith(QLatin1String("::")); if (atGlobalScope) { name = name.mid(2); currentObj = 0; } QStringList components; #ifdef TRY_BUGFIX_120682 // If we have a pointer or a reference in cpp we need to remove // the asterisks and ampersands in order to find the appropriate object if (UMLApp::app()->getActiveLanguage() == Uml::pl_Cpp) { if (name.endsWith(QLatin1Char('*'))) name.remove(QLatin1Char('*')); else if (name.contains(QLatin1Char('&'))) name.remove(QLatin1Char('&')); } #endif QString scopeSeparator = UMLApp::app()->activeLanguageScopeSeparator(); if (name.contains(scopeSeparator)) components = name.split(scopeSeparator); QString nameWithoutFirstPrefix; if (components.size() > 1) { name = components.front(); components.pop_front(); nameWithoutFirstPrefix = components.join(scopeSeparator); } if (currentObj) { UMLPackage *pkg = 0; if (currentObj->asUMLClassifierListItem()) { currentObj = currentObj->umlParent(); } pkg = currentObj->asUMLPackage(); if (pkg == 0 || pkg->baseType() == UMLObject::ot_Association) pkg = currentObj->umlPackage(); // Remember packages that we've seen - for avoiding cycles. UMLPackageList seenPkgs; for (; pkg; pkg = currentObj->umlPackage()) { if (nameWithoutFirstPrefix.isEmpty() && (type == UMLObject::ot_UMLObject || type == UMLObject::ot_Folder || type == UMLObject::ot_Package || type == pkg->baseType())) { if (caseSensitive) { if (pkg->name() == name) return pkg; } else if (pkg->name().toLower() == name.toLower()) { return pkg; } } if (seenPkgs.indexOf(pkg) != -1) { uError() << "findUMLObject(" << name << "): " << "breaking out of cycle involving " << pkg->name(); break; } seenPkgs.append(pkg); // exclude non package type // pg->asUMLPackage() fails for unknown reason // see https://bugs.kde.org/show_bug.cgi?id=341709 UMLObject::ObjectType foundType = pkg->baseType(); if (foundType != UMLObject::ot_Package && foundType != UMLObject::ot_Folder && foundType != UMLObject::ot_Class && foundType != UMLObject::ot_Interface && foundType != UMLObject::ot_Component) { continue; } UMLObjectList &objectsInCurrentScope = pkg->containedObjects(); for (UMLObjectListIt oit(objectsInCurrentScope); oit.hasNext();) { UMLObject *obj = oit.next(); uIgnoreZeroPointer(obj); if (caseSensitive) { if (obj->name() != name) continue; } else if (obj->name().toLower() != name.toLower()) { continue; } UMLObject::ObjectType foundType = obj->baseType(); if (nameWithoutFirstPrefix.isEmpty()) { if (type != UMLObject::ot_UMLObject && type != foundType) { uDebug() << "type mismatch for " << name << " (seeking type: " << UMLObject::toString(type) << ", found type: " << UMLObject::toString(foundType) << ")"; // Class, Interface, and Datatype are all Classifiers // and are considered equivalent. // The caller must be prepared to handle possible mismatches. if ((type == UMLObject::ot_Class || type == UMLObject::ot_Interface || type == UMLObject::ot_Datatype) && (foundType == UMLObject::ot_Class || foundType == UMLObject::ot_Interface || foundType == UMLObject::ot_Datatype)) { return obj; } continue; } return obj; } if (foundType != UMLObject::ot_Package && foundType != UMLObject::ot_Folder && foundType != UMLObject::ot_Class && foundType != UMLObject::ot_Interface && foundType != UMLObject::ot_Component) { uDebug() << "found " << UMLObject::toString(foundType) << name << " is not a package (?)"; continue; } UMLPackage *pkg = obj->asUMLPackage(); return findUMLObject(pkg->containedObjects(), nameWithoutFirstPrefix, type); } currentObj = pkg; } } for (UMLObjectListIt oit(inList); oit.hasNext();) { UMLObject *obj = oit.next(); uIgnoreZeroPointer(obj); if (caseSensitive) { if (obj->name() != name) continue; } else if (obj->name().toLower() != name.toLower()) { continue; } UMLObject::ObjectType foundType = obj->baseType(); if (nameWithoutFirstPrefix.isEmpty()) { if (type != UMLObject::ot_UMLObject && type != foundType) { uDebug() << "type mismatch for " << name << " (seeking type: " << UMLObject::toString(type) << ", found type: " << UMLObject::toString(foundType) << ")"; continue; } return obj; } if (foundType != UMLObject::ot_Package && foundType != UMLObject::ot_Folder && foundType != UMLObject::ot_Class && foundType != UMLObject::ot_Interface && foundType != UMLObject::ot_Component) { uDebug() << "found " << name << "(" << UMLObject::toString(foundType) << ")" << " is not a package (?)"; continue; } UMLPackage *pkg = obj->asUMLPackage(); return findUMLObject(pkg->containedObjects(), nameWithoutFirstPrefix, type); } return 0; } /** * Find the UML object of the given type and name in the passed-in list. * This method searches for the raw name. * * @param inList List in which to seek the object. * @param name Name of the object to find. * @param type ObjectType of the object to find (optional.) * When the given type is ot_UMLObject the type is * disregarded, i.e. the given name is the only * search criterion. * @param currentObj Object relative to which to search (optional.) * If given then the enclosing scope(s) of this * object are searched before the global scope. * @return Pointer to the UMLObject found, or NULL if not found. */ UMLObject* findUMLObjectRaw(const UMLObjectList& inList, const QString& name, UMLObject::ObjectType type /* = ot_UMLObject */, UMLObject *currentObj /*= 0*/) { Q_UNUSED(currentObj); for (UMLObjectListIt oit(inList); oit.hasNext();) { UMLObject *obj = oit.next(); if (obj->name() == name && type == obj->baseType()) return obj; } return 0; } +/** + * Find the UML object of the given type and name in the passed-in list. + * This method searches for the raw name. + * + * @param inList List in which to seek the object. + * @param name Name of the object to find. + * @param type ObjectType of the object to find (optional.) + * When the given type is ot_UMLObject the type is + * disregarded, i.e. the given name is the only + * search criterion. + * @return Pointer to the UMLObject found, or NULL if not found. + */ +UMLObject* findUMLObjectRecursive(const UMLObjectList& inList, + const QString& name, + UMLObject::ObjectType type /* = ot_UMLObject */) +{ + foreach(UMLObject *obj, inList) { + if (obj->name() == name && type == obj->baseType()) + return obj; + UMLPackage *pkg = obj->asUMLPackage(); + if (pkg && pkg->containedObjects().size() > 0) { + UMLObject *o = findUMLObjectRecursive(pkg->containedObjects(), name, type); + if (o) + return o; + } + } + return 0; +} + /** * Get the root folder of the given UMLObject. */ UMLPackage* rootPackage(UMLObject* obj) { if (obj == 0) return 0; UMLPackage* root = obj->umlPackage(); if (root == 0) { root = obj->asUMLPackage(); } else { while (root->umlPackage() != 0) { root = root->umlPackage(); } } return root; } /** * Add the given list of views to the tree view. * @param viewList the list of views to add */ void treeViewAddViews(const UMLViewList& viewList) { UMLListView* tree = UMLApp::app()->listView(); foreach (UMLView* v, viewList) { if (tree->findItem(v->umlScene()->ID()) != 0) { continue; } tree->createDiagramItem(v); } } /** * Change an icon of an object in the tree view. * @param object the object in the treeViewAddViews * @param to the new icon type for the given object */ void treeViewChangeIcon(UMLObject* object, Icon_Utils::IconType to) { UMLListView* tree = UMLApp::app()->listView(); tree->changeIconOf(object, to); } /** * Set the given object to the current item in the tree view. * @param object the object which will be the current item */ void treeViewSetCurrentItem(UMLObject* object) { UMLListView* tree = UMLApp::app()->listView(); UMLListViewItem* item = tree->findUMLObject(object); tree->setCurrentItem(item); } /** * Move an object to a new container in the tree view. * @param container the new container for the object * @param object the to be moved object */ void treeViewMoveObjectTo(UMLObject* container, UMLObject* object) { UMLListView *listView = UMLApp::app()->listView(); UMLListViewItem *newParent = listView->findUMLObject(container); listView->moveObject(object->id(), Model_Utils::convert_OT_LVT(object), newParent); } /** * Return the current UMLObject from the tree view. * @return the UML object of the current item */ UMLObject* treeViewGetCurrentObject() { UMLListView *listView = UMLApp::app()->listView(); UMLListViewItem *current = static_cast(listView->currentItem()); return current->umlObject(); } /** * Return the UMLPackage if the current item * in the tree view is a package. Return the * closest package in the tree view or NULL otherwise * * @return the package or NULL */ UMLPackage* treeViewGetPackageFromCurrent() { UMLListView *listView = UMLApp::app()->listView(); UMLListViewItem *parentItem = (UMLListViewItem*)listView->currentItem(); while (parentItem) { UMLListViewItem::ListViewType lvt = parentItem->type(); if (Model_Utils::typeIsContainer(lvt)) { UMLObject *o = parentItem->umlObject(); return o->asUMLPackage(); } // selected item is not a container, try to find the // container higher up in the tree view parentItem = static_cast(parentItem->parent()); } return 0; } /** * Build the diagram name from the tree view. * The function returns a relative path constructed from the folder hierarchy. * @param id the id of the diaram * @return the constructed diagram name */ QString treeViewBuildDiagramName(Uml::ID::Type id) { UMLListView *listView = UMLApp::app()->listView(); UMLListViewItem* listViewItem = listView->findItem(id); if (listViewItem) { QString name = listViewItem->text(0); listViewItem = static_cast(listViewItem->parent()); // Relies on the tree structure of the UMLListView. There are a base "Views" folder // and five children, one for each view type (Logical, use case, components, deployment // and entity relationship) while (listView->rootView(listViewItem->type()) == 0) { name.insert(0, listViewItem->text(0) + QLatin1Char('/')); listViewItem = static_cast(listViewItem->parent()); if (listViewItem == 0) break; } return name; } else { uWarning() << "diagram not found - returning empty name!"; return QString(); } } /** * Returns a name for the new object, appended with a number * if the default name is taken e.g. new_actor, new_actor_1 * etc. * @param type The object type. * @param parentPkg The package in which to compare the name. * @param prefix The prefix to use (optional) * If no prefix is given then a type related * prefix will be chosen internally. */ QString uniqObjectName(UMLObject::ObjectType type, UMLPackage *parentPkg, QString prefix) { QString currentName = prefix; if (currentName.isEmpty()) { if(type == UMLObject::ot_Class) currentName = i18n("new_class"); else if(type == UMLObject::ot_Actor) currentName = i18n("new_actor"); else if(type == UMLObject::ot_UseCase) currentName = i18n("new_usecase"); else if(type == UMLObject::ot_Package) currentName = i18n("new_package"); else if(type == UMLObject::ot_Component) currentName = i18n("new_component"); else if(type == UMLObject::ot_Port) currentName = i18n("new_port"); else if(type == UMLObject::ot_Node) currentName = i18n("new_node"); else if(type == UMLObject::ot_Artifact) currentName = i18n("new_artifact"); else if(type == UMLObject::ot_Interface) currentName = i18n("new_interface"); else if(type == UMLObject::ot_Datatype) currentName = i18n("new_datatype"); else if(type == UMLObject::ot_Enum) currentName = i18n("new_enum"); else if(type == UMLObject::ot_Entity) currentName = i18n("new_entity"); else if(type == UMLObject::ot_Folder) currentName = i18n("new_folder"); else if(type == UMLObject::ot_Association) currentName = i18n("new_association"); else if(type == UMLObject::ot_Category) currentName = i18n("new_category"); else if(type == UMLObject::ot_Instance) currentName = i18n("new_object"); else { currentName = i18n("new_object"); uWarning() << "unknown object type in umldoc::uniqObjectName()"; } } UMLDoc *doc = UMLApp::app()->document(); QString name = currentName; for (int number = 1; !doc->isUnique(name, parentPkg); ++number) { name = currentName + QLatin1Char('_') + QString::number(number); } return name; } /** * Return the xmi.id (XMI-1) or xmi:id (XMI-2) of a QDomElement. */ QString getXmiId(QDomElement element) { QString idStr = element.attribute(QLatin1String("xmi.id")); if (idStr.isEmpty()) idStr = element.attribute(QLatin1String("xmi:id")); return idStr; } /** * Return true if the given tag is one of the common XMI * attributes, such as: * "name" | "visibility" | "isRoot" | "isLeaf" | "isAbstract" | * "isActive" | "ownerScope" */ bool isCommonXMI1Attribute(const QString &tag) { bool retval = (UMLDoc::tagEq(tag, QLatin1String("name")) || UMLDoc::tagEq(tag, QLatin1String("visibility")) || UMLDoc::tagEq(tag, QLatin1String("isRoot")) || UMLDoc::tagEq(tag, QLatin1String("isLeaf")) || UMLDoc::tagEq(tag, QLatin1String("isAbstract")) || UMLDoc::tagEq(tag, QLatin1String("isSpecification")) || UMLDoc::tagEq(tag, QLatin1String("isActive")) || UMLDoc::tagEq(tag, QLatin1String("namespace")) || UMLDoc::tagEq(tag, QLatin1String("ownerScope")) || UMLDoc::tagEq(tag, QLatin1String("ModelElement.stereotype")) || UMLDoc::tagEq(tag, QLatin1String("GeneralizableElement.generalization")) || UMLDoc::tagEq(tag, QLatin1String("specialization")) || //NYI UMLDoc::tagEq(tag, QLatin1String("clientDependency")) || //NYI UMLDoc::tagEq(tag, QLatin1String("supplierDependency")) //NYI ); return retval; } /** * Return true if the given type is common among the majority * of programming languages, such as "bool" or "boolean". * TODO: Make this depend on the active programming language. */ bool isCommonDataType(QString type) { CodeGenerator *gen = UMLApp::app()->generator(); if (gen == 0) return false; const bool caseSensitive = UMLApp::app()->activeLanguageIsCaseSensitive(); const QStringList dataTypes = gen->defaultDatatypes(); QStringList::ConstIterator end(dataTypes.end()); for (QStringList::ConstIterator it = dataTypes.begin(); it != end; ++it) { if (caseSensitive) { if (type == *it) return true; } else if (type.toLower() == (*it).toLower()) { return true; } } return false; } /** * Return true if the given object type is a classifier list item type. */ bool isClassifierListitem(UMLObject::ObjectType type) { if (type == UMLObject::ot_Attribute || type == UMLObject::ot_Operation || type == UMLObject::ot_Template || type == UMLObject::ot_EntityAttribute || type == UMLObject::ot_EnumLiteral || type == UMLObject::ot_UniqueConstraint || type == UMLObject::ot_ForeignKeyConstraint || type == UMLObject::ot_CheckConstraint || type == UMLObject::ot_InstanceAttribute ) { return true; } else { return false; } } /** * Try to guess the correct container folder type of an UMLObject. * Object types that can't be guessed are mapped to Uml::ModelType::Logical. * NOTE: This function exists mainly for handling pre-1.5.5 files * and should not be used for new code. */ Uml::ModelType::Enum guessContainer(UMLObject *o) { UMLObject::ObjectType ot = o->baseType(); if (ot == UMLObject::ot_Package && o->stereotype() == QLatin1String("subsystem")) return Uml::ModelType::Component; Uml::ModelType::Enum mt = Uml::ModelType::N_MODELTYPES; switch (ot) { case UMLObject::ot_Package: // trouble: package can also appear in Component view case UMLObject::ot_Interface: case UMLObject::ot_Datatype: case UMLObject::ot_Enum: case UMLObject::ot_Class: case UMLObject::ot_Attribute: case UMLObject::ot_Operation: case UMLObject::ot_EnumLiteral: case UMLObject::ot_Template: case UMLObject::ot_Instance: case UMLObject::ot_InstanceAttribute: mt = Uml::ModelType::Logical; break; case UMLObject::ot_Actor: case UMLObject::ot_UseCase: mt = Uml::ModelType::UseCase; break; case UMLObject::ot_Component: case UMLObject::ot_Port: case UMLObject::ot_Artifact: // trouble: artifact can also appear at Deployment mt = Uml::ModelType::Component; break; case UMLObject::ot_Node: mt = Uml::ModelType::Deployment; break; case UMLObject::ot_Entity: case UMLObject::ot_EntityAttribute: case UMLObject::ot_UniqueConstraint: case UMLObject::ot_ForeignKeyConstraint: case UMLObject::ot_CheckConstraint: case UMLObject::ot_Category: mt = Uml::ModelType::EntityRelationship; break; case UMLObject::ot_Association: { UMLAssociation *assoc = o->asUMLAssociation(); UMLDoc *umldoc = UMLApp::app()->document(); for (int r = Uml::RoleType::A; r <= Uml::RoleType::B; ++r) { UMLObject *roleObj = assoc->getObject(Uml::RoleType::fromInt(r)); if (roleObj == 0) { // Ouch! we have been called while types are not yet resolved return Uml::ModelType::N_MODELTYPES; } UMLPackage *pkg = roleObj->umlPackage(); if (pkg) { while (pkg->umlPackage()) { // wind back to root pkg = pkg->umlPackage(); } const Uml::ModelType::Enum m = umldoc->rootFolderType(pkg); if (m != Uml::ModelType::N_MODELTYPES) return m; } mt = guessContainer(roleObj); if (mt != Uml::ModelType::Logical) break; } } break; default: break; } return mt; } /** * Parse a direction string into the Uml::ParameterDirection::Enum. * * @param input The string to parse: "in", "out", or "inout" * optionally followed by whitespace. * @param result The corresponding Uml::ParameterDirection::Enum. * @return Length of the string matched, excluding the optional * whitespace. */ int stringToDirection(QString input, Uml::ParameterDirection::Enum & result) { QRegExp dirx(QLatin1String("^(in|out|inout)")); int pos = dirx.indexIn(input); if (pos == -1) return 0; const QString dirStr = dirx.capturedTexts().first(); int dirLen = dirStr.length(); if (input.length() > dirLen && !input[dirLen].isSpace()) return 0; // no match after all. if (dirStr == QLatin1String("out")) result = Uml::ParameterDirection::Out; else if (dirStr == QLatin1String("inout")) result = Uml::ParameterDirection::InOut; else result = Uml::ParameterDirection::In; return dirLen; } /** * Parses a template parameter given in UML syntax. * * @param t Input text of the template parameter. * Example: parname : partype * or just: parname (for class type) * @param nmTp NameAndType returned by this method. * @param owningScope Pointer to the owning scope of the template param. * @return Error status of the parse, PS_OK for success. */ Parse_Status parseTemplate(QString t, NameAndType& nmTp, UMLClassifier *owningScope) { UMLDoc *pDoc = UMLApp::app()->document(); t = t.trimmed(); if (t.isEmpty()) return PS_Empty; QStringList nameAndType = t.split(QRegExp(QLatin1String("\\s*:\\s*"))); if (nameAndType.count() == 2) { UMLObject *pType = 0; if (nameAndType[1] != QLatin1String("class")) { pType = pDoc->findUMLObject(nameAndType[1], UMLObject::ot_UMLObject, owningScope); if (pType == 0) return PS_Unknown_ArgType; } nmTp = NameAndType(nameAndType[0], pType); } else { nmTp = NameAndType(t, 0); } return PS_OK; } /** * Parses an attribute given in UML syntax. * * @param a Input text of the attribute in UML syntax. * Example: argname : argtype * @param nmTp NameAndType returned by this method. * @param owningScope Pointer to the owning scope of the attribute. * @param vis Optional pointer to visibility (return value.) * The visibility may be given at the beginning of the * attribute text in mnemonic form as follows: * "+" stands for public * "#" stands for protected * "-" stands for private * "~" stands for implementation level visibility * * @return Error status of the parse, PS_OK for success. */ Parse_Status parseAttribute(QString a, NameAndType& nmTp, UMLClassifier *owningScope, Uml::Visibility::Enum *vis /* = 0 */) { UMLDoc *pDoc = UMLApp::app()->document(); a = a.simplified(); if (a.isEmpty()) return PS_Empty; int colonPos = a.indexOf(QLatin1Char(':')); if (colonPos < 0) { nmTp = NameAndType(a, 0); return PS_OK; } QString name = a.left(colonPos).trimmed(); if (vis) { QRegExp mnemonicVis(QLatin1String("^([\\+\\#\\-\\~] *)")); int pos = mnemonicVis.indexIn(name); if (pos == -1) { *vis = Uml::Visibility::Private; // default value } else { QString caption = mnemonicVis.cap(1); QString strVis = caption.left(1); if (strVis == QLatin1String("+")) *vis = Uml::Visibility::Public; else if (strVis == QLatin1String("#")) *vis = Uml::Visibility::Protected; else if (strVis == QLatin1String("-")) *vis = Uml::Visibility::Private; else *vis = Uml::Visibility::Implementation; } name.remove(mnemonicVis); } Uml::ParameterDirection::Enum pd = Uml::ParameterDirection::In; if (name.startsWith(QLatin1String(QLatin1String("in ")))) { pd = Uml::ParameterDirection::In; name = name.mid(3); } else if (name.startsWith(QLatin1String(QLatin1String("inout ")))) { pd = Uml::ParameterDirection::InOut; name = name.mid(6); } else if (name.startsWith(QLatin1String(QLatin1String("out ")))) { pd = Uml::ParameterDirection::Out; name = name.mid(4); } a = a.mid(colonPos + 1).trimmed(); if (a.isEmpty()) { nmTp = NameAndType(name, 0, pd); return PS_OK; } QStringList typeAndInitialValue = a.split(QRegExp(QLatin1String("\\s*=\\s*"))); const QString &type = typeAndInitialValue[0]; UMLObject *pType = pDoc->findUMLObject(type, UMLObject::ot_UMLObject, owningScope); if (pType == 0) { nmTp = NameAndType(name, 0, pd); return PS_Unknown_ArgType; } QString initialValue; if (typeAndInitialValue.count() == 2) { initialValue = typeAndInitialValue[1]; } nmTp = NameAndType(name, pType, pd, initialValue); return PS_OK; } /** * Parses an operation given in UML syntax. * * @param m Input text of the operation in UML syntax. * Example of a two-argument operation returning "void": * methodname (arg1name : arg1type, arg2name : arg2type) : void * @param desc OpDescriptor returned by this method. * @param owningScope Pointer to the owning scope of the operation. * @return Error status of the parse, PS_OK for success. */ Parse_Status parseOperation(QString m, OpDescriptor& desc, UMLClassifier *owningScope) { UMLDoc *pDoc = UMLApp::app()->document(); m = m.simplified(); if (m.isEmpty()) return PS_Empty; if (m.contains(QRegExp(QLatin1String("operator *()")))) { // C++ special case: two sets of parentheses desc.m_name = QLatin1String("operator()"); m.remove(QRegExp(QLatin1String("operator *()"))); } else { /** * The search pattern includes everything up to the opening parenthesis * because UML also permits non programming-language oriented designs * using narrative names, for example "check water temperature". */ QRegExp beginningUpToOpenParenth(QLatin1String("^([^\\(]+)")); int pos = beginningUpToOpenParenth.indexIn(m); if (pos == -1) return PS_Illegal_MethodName; desc.m_name = beginningUpToOpenParenth.cap(1); } desc.m_pReturnType = 0; QRegExp pat = QRegExp(QLatin1String("\\) *:(.*)$")); int pos = pat.indexIn(m); if (pos != -1) { // return type is optional QString retType = pat.cap(1); retType = retType.trimmed(); if (retType != QLatin1String("void")) { UMLObject *pRetType = owningScope ? owningScope->findTemplate(retType) : 0; if (pRetType == 0) { pRetType = pDoc->findUMLObject(retType, UMLObject::ot_UMLObject, owningScope); if (pRetType == 0) return PS_Unknown_ReturnType; } desc.m_pReturnType = pRetType; } } // Remove possible empty parentheses () m.remove(QRegExp(QLatin1String("\\s*\\(\\s*\\)"))); desc.m_args.clear(); pat = QRegExp(QLatin1String("\\((.*)\\)")); pos = pat.indexIn(m); if (pos == -1) // argument list is optional return PS_OK; QString arglist = pat.cap(1); arglist = arglist.trimmed(); if (arglist.isEmpty()) return PS_OK; const QStringList args = arglist.split(QRegExp(QLatin1String("\\s*, \\s*"))); for (QStringList::ConstIterator lit = args.begin(); lit != args.end(); ++lit) { NameAndType nmTp; Parse_Status ps = parseAttribute(*lit, nmTp, owningScope); if (ps) return ps; desc.m_args.append(nmTp); } return PS_OK; } /** * Parses a constraint. * * @param m Input text of the constraint * * @param name The name returned by this method * @param owningScope Pointer to the owning scope of the constraint * @return Error status of the parse, PS_OK for success. */ Parse_Status parseConstraint(QString m, QString& name, UMLEntity* owningScope) { Q_UNUSED(owningScope); m = m.simplified(); if (m.isEmpty()) return PS_Empty; int colonPos = m.indexOf(QLatin1Char(':')); if (colonPos < 0) { name = m; return PS_OK; } name = m.left(colonPos).trimmed(); return PS_OK; } /** * Returns the Parse_Status as a text. */ QString psText(Parse_Status value) { const QString text[] = { i18n("OK"), i18nc("parse status", "Empty"), i18n("Malformed argument"), i18n("Unknown argument type"), i18n("Illegal method name"), i18n("Unknown return type"), i18n("Unspecified error") }; return text[(unsigned) value]; } /** * Return true if the listview type is one of the predefined root views * (root, logical, usecase, component, deployment, datatype, or entity- * relationship view.) */ bool typeIsRootView(UMLListViewItem::ListViewType type) { switch (type) { case UMLListViewItem::lvt_View: case UMLListViewItem::lvt_Logical_View: case UMLListViewItem::lvt_UseCase_View: case UMLListViewItem::lvt_Component_View: case UMLListViewItem::lvt_Deployment_View: case UMLListViewItem::lvt_EntityRelationship_Model: return true; break; default: break; } return false; } /** * Return true if the listview type also has a widget representation in diagrams. */ bool typeIsCanvasWidget(UMLListViewItem::ListViewType type) { switch (type) { case UMLListViewItem::lvt_Actor: case UMLListViewItem::lvt_UseCase: case UMLListViewItem::lvt_Class: case UMLListViewItem::lvt_Package: case UMLListViewItem::lvt_Logical_Folder: case UMLListViewItem::lvt_UseCase_Folder: case UMLListViewItem::lvt_Component_Folder: case UMLListViewItem::lvt_Deployment_Folder: case UMLListViewItem::lvt_EntityRelationship_Folder: case UMLListViewItem::lvt_Subsystem: case UMLListViewItem::lvt_Component: case UMLListViewItem::lvt_Port: case UMLListViewItem::lvt_Node: case UMLListViewItem::lvt_Artifact: case UMLListViewItem::lvt_Interface: case UMLListViewItem::lvt_Datatype: case UMLListViewItem::lvt_Enum: case UMLListViewItem::lvt_Entity: case UMLListViewItem::lvt_Category: return true; break; default: break; } return false; } /** * Return true if the listview type is a logical, usecase or component folder. */ bool typeIsFolder(UMLListViewItem::ListViewType type) { if (typeIsRootView(type) || type == UMLListViewItem::lvt_Datatype_Folder || type == UMLListViewItem::lvt_Logical_Folder || type == UMLListViewItem::lvt_UseCase_Folder || type == UMLListViewItem::lvt_Component_Folder || type == UMLListViewItem::lvt_Deployment_Folder || type == UMLListViewItem::lvt_EntityRelationship_Folder) { return true; } else { return false; } } /** * Return true if the listview type may act as a container for other objects, * i.e. if it is a folder, package, subsystem, or component. */ bool typeIsContainer(UMLListViewItem::ListViewType type) { if (typeIsFolder(type)) return true; return (type == UMLListViewItem::lvt_Package || type == UMLListViewItem::lvt_Subsystem || type == UMLListViewItem::lvt_Component || type == UMLListViewItem::lvt_Class || type == UMLListViewItem::lvt_Interface); } /** * Return true if the listview type is an attribute, operation, or template. */ bool typeIsClassifierList(UMLListViewItem::ListViewType type) { if (type == UMLListViewItem::lvt_Attribute || type == UMLListViewItem::lvt_Instance || type == UMLListViewItem::lvt_Operation || type == UMLListViewItem::lvt_Template || type == UMLListViewItem::lvt_EntityAttribute || type == UMLListViewItem::lvt_UniqueConstraint || type == UMLListViewItem::lvt_ForeignKeyConstraint || type == UMLListViewItem::lvt_PrimaryKeyConstraint || type == UMLListViewItem::lvt_CheckConstraint || type == UMLListViewItem::lvt_EnumLiteral || type == UMLListViewItem::lvt_InstanteAttribute) { return true; } else { return false; } } /** * Return true if the listview type is a classifier (Class, Entity, Enum) */ bool typeIsClassifier(UMLListViewItem::ListViewType type) { if (type == UMLListViewItem::lvt_Class || type == UMLListViewItem::lvt_Interface || type == UMLListViewItem::lvt_Entity || type == UMLListViewItem::lvt_Enum) { return true; } return false; } /** * Return true if the listview type is a settings entry. */ bool typeIsProperties(UMLListViewItem::ListViewType type) { switch (type) { case UMLListViewItem::lvt_Properties: case UMLListViewItem::lvt_Properties_AutoLayout: case UMLListViewItem::lvt_Properties_Class: case UMLListViewItem::lvt_Properties_CodeImport: case UMLListViewItem::lvt_Properties_CodeGeneration: case UMLListViewItem::lvt_Properties_CodeViewer: case UMLListViewItem::lvt_Properties_Font: case UMLListViewItem::lvt_Properties_General: case UMLListViewItem::lvt_Properties_UserInterface: return true; break; default: break; } return false; } /** * Check if a listviewitem of type childType is allowed * as child of type parentType */ bool typeIsAllowedInType(UMLListViewItem::ListViewType childType, UMLListViewItem::ListViewType parentType) { switch (childType) { case UMLListViewItem::lvt_Class: case UMLListViewItem::lvt_Package: case UMLListViewItem::lvt_Interface: case UMLListViewItem::lvt_Enum: case UMLListViewItem::lvt_Instance: return parentType == UMLListViewItem::lvt_Logical_View || parentType == UMLListViewItem::lvt_Class || parentType == UMLListViewItem::lvt_Package || parentType == UMLListViewItem::lvt_Logical_Folder; case UMLListViewItem::lvt_Attribute: case UMLListViewItem::lvt_EntityAttribute: case UMLListViewItem::lvt_InstanteAttribute: return parentType == UMLListViewItem::lvt_Entity; case UMLListViewItem::lvt_Operation: return parentType == UMLListViewItem::lvt_Class || parentType == UMLListViewItem::lvt_Interface; case UMLListViewItem::lvt_Datatype: return parentType == UMLListViewItem::lvt_Logical_Folder || parentType == UMLListViewItem::lvt_Datatype_Folder || parentType == UMLListViewItem::lvt_Class || parentType == UMLListViewItem::lvt_Interface || parentType == UMLListViewItem::lvt_Package; case UMLListViewItem::lvt_Class_Diagram: case UMLListViewItem::lvt_Collaboration_Diagram: case UMLListViewItem::lvt_State_Diagram: case UMLListViewItem::lvt_Activity_Diagram: case UMLListViewItem::lvt_Sequence_Diagram: case UMLListViewItem::lvt_Object_Diagram: return parentType == UMLListViewItem::lvt_Logical_Folder || parentType == UMLListViewItem::lvt_Logical_View; case UMLListViewItem::lvt_Logical_Folder: return parentType == UMLListViewItem::lvt_Logical_Folder || parentType == UMLListViewItem::lvt_Logical_View; case UMLListViewItem::lvt_UseCase_Folder: return parentType == UMLListViewItem::lvt_UseCase_Folder || parentType == UMLListViewItem::lvt_UseCase_View; case UMLListViewItem::lvt_Component_Folder: return parentType == UMLListViewItem::lvt_Component_Folder || parentType == UMLListViewItem::lvt_Component_View; case UMLListViewItem::lvt_Deployment_Folder: return parentType == UMLListViewItem::lvt_Deployment_Folder || parentType == UMLListViewItem::lvt_Deployment_View; case UMLListViewItem::lvt_EntityRelationship_Folder: return parentType == UMLListViewItem::lvt_EntityRelationship_Folder || parentType == UMLListViewItem::lvt_EntityRelationship_Model; case UMLListViewItem::lvt_Actor: case UMLListViewItem::lvt_UseCase: case UMLListViewItem::lvt_UseCase_Diagram: return parentType == UMLListViewItem::lvt_UseCase_Folder || parentType == UMLListViewItem::lvt_UseCase_View; case UMLListViewItem::lvt_Subsystem: return parentType == UMLListViewItem::lvt_Component_Folder || - parentType == UMLListViewItem::lvt_Subsystem; + parentType == UMLListViewItem::lvt_Subsystem || + parentType == UMLListViewItem::lvt_Component_View; case UMLListViewItem::lvt_Component: - case UMLListViewItem::lvt_Port: return parentType == UMLListViewItem::lvt_Component_Folder || parentType == UMLListViewItem::lvt_Component || + parentType == UMLListViewItem::lvt_Subsystem || + parentType == UMLListViewItem::lvt_Component_View; + case UMLListViewItem::lvt_Port: + return parentType == UMLListViewItem::lvt_Component || parentType == UMLListViewItem::lvt_Subsystem; case UMLListViewItem::lvt_Artifact: case UMLListViewItem::lvt_Component_Diagram: return parentType == UMLListViewItem::lvt_Component_Folder || parentType == UMLListViewItem::lvt_Component_View; - break; case UMLListViewItem::lvt_Node: case UMLListViewItem::lvt_Deployment_Diagram: - return parentType == UMLListViewItem::lvt_Deployment_Folder; + return parentType == UMLListViewItem::lvt_Deployment_Folder || + parentType == UMLListViewItem::lvt_Deployment_View; case UMLListViewItem::lvt_Entity: case UMLListViewItem::lvt_EntityRelationship_Diagram: case UMLListViewItem::lvt_Category: - return parentType == UMLListViewItem::lvt_EntityRelationship_Folder; + return parentType == UMLListViewItem::lvt_EntityRelationship_Folder || + parentType == UMLListViewItem::lvt_EntityRelationship_Model; default: return false; } } /** * Return true if the listview type is a diagram. */ bool typeIsDiagram(UMLListViewItem::ListViewType type) { if (type == UMLListViewItem::lvt_Class_Diagram || type == UMLListViewItem::lvt_Collaboration_Diagram || type == UMLListViewItem::lvt_State_Diagram || type == UMLListViewItem::lvt_Activity_Diagram || type == UMLListViewItem::lvt_Sequence_Diagram || type == UMLListViewItem::lvt_UseCase_Diagram || type == UMLListViewItem::lvt_Component_Diagram || type == UMLListViewItem::lvt_Deployment_Diagram || type == UMLListViewItem::lvt_EntityRelationship_Diagram || type == UMLListViewItem::lvt_Object_Diagram ){ return true; } else { return false; } } /** * Return the Model_Type which corresponds to the given DiagramType. */ Uml::ModelType::Enum convert_DT_MT(Uml::DiagramType::Enum dt) { Uml::ModelType::Enum mt; switch (dt) { case Uml::DiagramType::UseCase: mt = Uml::ModelType::UseCase; break; case Uml::DiagramType::Collaboration: case Uml::DiagramType::Class: case Uml::DiagramType::Object: case Uml::DiagramType::Sequence: case Uml::DiagramType::State: case Uml::DiagramType::Activity: mt = Uml::ModelType::Logical; break; case Uml::DiagramType::Component: mt = Uml::ModelType::Component; break; case Uml::DiagramType::Deployment: mt = Uml::ModelType::Deployment; break; case Uml::DiagramType::EntityRelationship: mt = Uml::ModelType::EntityRelationship; break; default: uError() << "Model_Utils::convert_DT_MT: illegal input value " << dt; mt = Uml::ModelType::N_MODELTYPES; break; } return mt; } /** * Return the ListViewType which corresponds to the given Model_Type. */ UMLListViewItem::ListViewType convert_MT_LVT(Uml::ModelType::Enum mt) { UMLListViewItem::ListViewType lvt = UMLListViewItem::lvt_Unknown; switch (mt) { case Uml::ModelType::Logical: lvt = UMLListViewItem::lvt_Logical_View; break; case Uml::ModelType::UseCase: lvt = UMLListViewItem::lvt_UseCase_View; break; case Uml::ModelType::Component: lvt = UMLListViewItem::lvt_Component_View; break; case Uml::ModelType::Deployment: lvt = UMLListViewItem::lvt_Deployment_View; break; case Uml::ModelType::EntityRelationship: lvt = UMLListViewItem::lvt_EntityRelationship_Model; break; default: break; } return lvt; } /** * Return the Model_Type which corresponds to the given ListViewType. * Returns Uml::N_MODELTYPES if the list view type given does not map * to a Model_Type. */ Uml::ModelType::Enum convert_LVT_MT(UMLListViewItem::ListViewType lvt) { Uml::ModelType::Enum mt = Uml::ModelType::N_MODELTYPES; switch (lvt) { case UMLListViewItem::lvt_Logical_View: mt = Uml::ModelType::Logical; break; case UMLListViewItem::lvt_UseCase_View: mt = Uml::ModelType::UseCase; break; case UMLListViewItem::lvt_Component_View: mt = Uml::ModelType::Component; break; case UMLListViewItem::lvt_Deployment_View: mt = Uml::ModelType::Deployment; break; case UMLListViewItem::lvt_EntityRelationship_Model: mt = Uml::ModelType::EntityRelationship; break; default: break; } return mt; } /** * Convert a diagram type enum to the equivalent list view type. */ UMLListViewItem::ListViewType convert_DT_LVT(Uml::DiagramType::Enum dt) { UMLListViewItem::ListViewType type = UMLListViewItem::lvt_Unknown; switch(dt) { case Uml::DiagramType::UseCase: type = UMLListViewItem::lvt_UseCase_Diagram; break; case Uml::DiagramType::Class: type = UMLListViewItem::lvt_Class_Diagram; break; case Uml::DiagramType::Object: type = UMLListViewItem::lvt_Object_Diagram; break; case Uml::DiagramType::Sequence: type = UMLListViewItem::lvt_Sequence_Diagram; break; case Uml::DiagramType::Collaboration: type = UMLListViewItem::lvt_Collaboration_Diagram; break; case Uml::DiagramType::State: type = UMLListViewItem::lvt_State_Diagram; break; case Uml::DiagramType::Activity: type = UMLListViewItem::lvt_Activity_Diagram; break; case Uml::DiagramType::Component: type = UMLListViewItem::lvt_Component_Diagram; break; case Uml::DiagramType::Deployment: type = UMLListViewItem::lvt_Deployment_Diagram; break; case Uml::DiagramType::EntityRelationship: type = UMLListViewItem::lvt_EntityRelationship_Diagram; break; default: uWarning() << "convert_DT_LVT() called on unknown diagram type"; } return type; } /** * Convert an object's type to the equivalent list view type * * @param o Pointer to the UMLObject whose type shall be converted * to the equivalent ListViewType. We cannot just * pass in a UMLObject::ObjectType because a UMLFolder is mapped * to different ListViewType values, depending on its * location in one of the predefined modelviews (Logical/ * UseCase/etc.) * @return The equivalent ListViewType. */ UMLListViewItem::ListViewType convert_OT_LVT(UMLObject *o) { UMLObject::ObjectType ot = o->baseType(); UMLListViewItem::ListViewType type = UMLListViewItem::lvt_Unknown; switch(ot) { case UMLObject::ot_UseCase: type = UMLListViewItem::lvt_UseCase; break; case UMLObject::ot_Actor: type = UMLListViewItem::lvt_Actor; break; case UMLObject::ot_Class: type = UMLListViewItem::lvt_Class; break; case UMLObject::ot_Package: if (o->stereotype() == QLatin1String("subsystem")) type = UMLListViewItem::lvt_Subsystem; else type = UMLListViewItem::lvt_Package; break; case UMLObject::ot_Folder: { UMLDoc *umldoc = UMLApp::app()->document(); UMLFolder *f = o->asUMLFolder(); do { const Uml::ModelType::Enum mt = umldoc->rootFolderType(f); if (mt != Uml::ModelType::N_MODELTYPES) { switch (mt) { case Uml::ModelType::Logical: type = UMLListViewItem::lvt_Logical_Folder; break; case Uml::ModelType::UseCase: type = UMLListViewItem::lvt_UseCase_Folder; break; case Uml::ModelType::Component: type = UMLListViewItem::lvt_Component_Folder; break; case Uml::ModelType::Deployment: type = UMLListViewItem::lvt_Deployment_Folder; break; case Uml::ModelType::EntityRelationship: type = UMLListViewItem::lvt_EntityRelationship_Folder; break; default: break; } return type; } } while ((f = f->umlPackage()->asUMLFolder()) != 0); uError() << "convert_OT_LVT(" << o->name() << "): internal error - object is not properly nested in folder"; } break; case UMLObject::ot_Component: type = UMLListViewItem::lvt_Component; break; case UMLObject::ot_Port: type = UMLListViewItem::lvt_Port; break; case UMLObject::ot_Node: type = UMLListViewItem::lvt_Node; break; case UMLObject::ot_Artifact: type = UMLListViewItem::lvt_Artifact; break; case UMLObject::ot_Interface: type = UMLListViewItem::lvt_Interface; break; case UMLObject::ot_Datatype: type = UMLListViewItem::lvt_Datatype; break; case UMLObject::ot_Enum: type = UMLListViewItem::lvt_Enum; break; case UMLObject::ot_EnumLiteral: type = UMLListViewItem::lvt_EnumLiteral; break; case UMLObject::ot_Entity: type = UMLListViewItem::lvt_Entity; break; case UMLObject::ot_Category: type = UMLListViewItem::lvt_Category; break; case UMLObject::ot_EntityAttribute: type = UMLListViewItem::lvt_EntityAttribute; break; case UMLObject::ot_UniqueConstraint: { UMLEntity* ent = o->umlParent()->asUMLEntity(); UMLUniqueConstraint* uc = o->asUMLUniqueConstraint(); if (ent->isPrimaryKey(uc)) { type = UMLListViewItem::lvt_PrimaryKeyConstraint; } else { type = UMLListViewItem::lvt_UniqueConstraint; } break; } case UMLObject::ot_ForeignKeyConstraint: type = UMLListViewItem::lvt_ForeignKeyConstraint; break; case UMLObject::ot_CheckConstraint: type = UMLListViewItem::lvt_CheckConstraint; break; case UMLObject::ot_Attribute: type = UMLListViewItem::lvt_Attribute; break; case UMLObject::ot_Operation: type = UMLListViewItem::lvt_Operation; break; case UMLObject::ot_Template: type = UMLListViewItem::lvt_Template; break; case UMLObject::ot_Association: type = UMLListViewItem::lvt_Association; break; case UMLObject::ot_Instance: type = UMLListViewItem::lvt_Instance; break; case UMLObject::ot_InstanceAttribute: type = UMLListViewItem::lvt_InstanteAttribute; break; default: break; } return type; } /** * Converts a list view type enum to the equivalent object type. * * @param lvt The ListViewType to convert. * @return The converted ObjectType if the listview type * has a UMLObject::ObjectType representation, else 0. */ UMLObject::ObjectType convert_LVT_OT(UMLListViewItem::ListViewType lvt) { UMLObject::ObjectType ot = (UMLObject::ObjectType)0; switch (lvt) { case UMLListViewItem::lvt_UseCase: ot = UMLObject::ot_UseCase; break; case UMLListViewItem::lvt_Actor: ot = UMLObject::ot_Actor; break; case UMLListViewItem::lvt_Class: ot = UMLObject::ot_Class; break; case UMLListViewItem::lvt_Package: case UMLListViewItem::lvt_Subsystem: ot = UMLObject::ot_Package; break; case UMLListViewItem::lvt_Component: ot = UMLObject::ot_Component; break; case UMLListViewItem::lvt_Port: ot = UMLObject::ot_Port; break; case UMLListViewItem::lvt_Node: ot = UMLObject::ot_Node; break; case UMLListViewItem::lvt_Artifact: ot = UMLObject::ot_Artifact; break; case UMLListViewItem::lvt_Interface: ot = UMLObject::ot_Interface; break; case UMLListViewItem::lvt_Datatype: ot = UMLObject::ot_Datatype; break; case UMLListViewItem::lvt_Enum: ot = UMLObject::ot_Enum; break; case UMLListViewItem::lvt_Entity: ot = UMLObject::ot_Entity; break; case UMLListViewItem::lvt_Category: ot = UMLObject::ot_Category; break; case UMLListViewItem::lvt_EntityAttribute: ot = UMLObject::ot_EntityAttribute; break; case UMLListViewItem::lvt_UniqueConstraint: ot = UMLObject::ot_UniqueConstraint; break; case UMLListViewItem::lvt_PrimaryKeyConstraint: ot = UMLObject::ot_UniqueConstraint; break; case UMLListViewItem::lvt_ForeignKeyConstraint: ot = UMLObject::ot_ForeignKeyConstraint; break; case UMLListViewItem::lvt_CheckConstraint: ot = UMLObject::ot_CheckConstraint; break; case UMLListViewItem::lvt_Attribute: ot = UMLObject::ot_Attribute; break; case UMLListViewItem::lvt_Operation: ot = UMLObject::ot_Operation; break; case UMLListViewItem::lvt_Template: ot = UMLObject::ot_Template; break; case UMLListViewItem::lvt_EnumLiteral: ot = UMLObject::ot_EnumLiteral; break; case UMLListViewItem::lvt_Instance: ot = UMLObject::ot_Instance; break; case UMLListViewItem::lvt_InstanteAttribute: ot = UMLObject::ot_InstanceAttribute; break; default: if (typeIsFolder(lvt)) ot = UMLObject::ot_Folder; break; } return ot; } /** * Return the IconType which corresponds to the given listview type. * * @param lvt ListViewType to convert. * @return The Icon_Utils::IconType corresponding to the lvt. * Returns it_Home in case no mapping to IconType exists. */ Icon_Utils::IconType convert_LVT_IT(UMLListViewItem::ListViewType lvt, UMLObject *o) { Icon_Utils::IconType icon = Icon_Utils::it_Home; switch (lvt) { case UMLListViewItem::lvt_UseCase_View: case UMLListViewItem::lvt_UseCase_Folder: icon = Icon_Utils::it_Folder_Grey; break; case UMLListViewItem::lvt_Logical_View: case UMLListViewItem::lvt_Logical_Folder: icon = Icon_Utils::it_Folder_Green; break; case UMLListViewItem::lvt_Datatype_Folder: icon = Icon_Utils::it_Folder_Orange; break; case UMLListViewItem::lvt_Component_View: case UMLListViewItem::lvt_Component_Folder: icon = Icon_Utils::it_Folder_Red; break; case UMLListViewItem::lvt_Deployment_View: case UMLListViewItem::lvt_Deployment_Folder: icon = Icon_Utils::it_Folder_Violet; break; case UMLListViewItem::lvt_EntityRelationship_Model: case UMLListViewItem::lvt_EntityRelationship_Folder: icon = Icon_Utils::it_Folder_Cyan; break; case UMLListViewItem::lvt_Actor: icon = Icon_Utils::it_Actor; break; case UMLListViewItem::lvt_Association: icon = Icon_Utils::it_Association; break; case UMLListViewItem::lvt_UseCase: icon = Icon_Utils::it_UseCase; break; case UMLListViewItem::lvt_Class: if (o && o->stereotype() == QLatin1String("class-or-package")) icon = Icon_Utils::it_ClassOrPackage; else icon = Icon_Utils::it_Class; break; case UMLListViewItem::lvt_Package: icon = Icon_Utils::it_Package; break; case UMLListViewItem::lvt_Subsystem: icon = Icon_Utils::it_Subsystem; break; case UMLListViewItem::lvt_Component: icon = Icon_Utils::it_Component; break; case UMLListViewItem::lvt_Port: icon = Icon_Utils::it_Port; break; case UMLListViewItem::lvt_Node: icon = Icon_Utils::it_Node; break; case UMLListViewItem::lvt_Artifact: icon = Icon_Utils::it_Artifact; break; case UMLListViewItem::lvt_Interface: icon = Icon_Utils::it_Interface; break; case UMLListViewItem::lvt_Datatype: icon = Icon_Utils::it_Datatype; break; case UMLListViewItem::lvt_Enum: icon = Icon_Utils::it_Enum; break; case UMLListViewItem::lvt_Entity: icon = Icon_Utils::it_Entity; break; case UMLListViewItem::lvt_Category: icon = Icon_Utils::it_Category; break; case UMLListViewItem::lvt_Template: icon = Icon_Utils::it_Template; break; case UMLListViewItem::lvt_Attribute: icon = Icon_Utils::it_Private_Attribute; break; case UMLListViewItem::lvt_EntityAttribute: icon = Icon_Utils::it_Private_Attribute; break; case UMLListViewItem::lvt_EnumLiteral: icon = Icon_Utils::it_Public_Attribute; break; case UMLListViewItem::lvt_Operation: icon = Icon_Utils::it_Public_Method; break; case UMLListViewItem::lvt_UniqueConstraint: icon = Icon_Utils::it_Unique_Constraint; break; case UMLListViewItem::lvt_PrimaryKeyConstraint: icon = Icon_Utils::it_PrimaryKey_Constraint; break; case UMLListViewItem::lvt_ForeignKeyConstraint: icon = Icon_Utils::it_ForeignKey_Constraint; break; case UMLListViewItem::lvt_CheckConstraint: icon = Icon_Utils::it_Check_Constraint; break; case UMLListViewItem::lvt_Class_Diagram: icon = Icon_Utils::it_Diagram_Class; break; case UMLListViewItem::lvt_Object_Diagram: icon = Icon_Utils::it_Diagram_Object; break; case UMLListViewItem::lvt_UseCase_Diagram: icon = Icon_Utils::it_Diagram_Usecase; break; case UMLListViewItem::lvt_Sequence_Diagram: icon = Icon_Utils::it_Diagram_Sequence; break; case UMLListViewItem::lvt_Collaboration_Diagram: icon = Icon_Utils::it_Diagram_Collaboration; break; case UMLListViewItem::lvt_State_Diagram: icon = Icon_Utils::it_Diagram_State; break; case UMLListViewItem::lvt_Activity_Diagram: icon = Icon_Utils::it_Diagram_Activity; break; case UMLListViewItem::lvt_Component_Diagram: icon = Icon_Utils::it_Diagram_Component; break; case UMLListViewItem::lvt_Deployment_Diagram: icon = Icon_Utils::it_Diagram_Deployment; break; case UMLListViewItem::lvt_EntityRelationship_Diagram: icon = Icon_Utils::it_Diagram_EntityRelationship; break; case UMLListViewItem::lvt_Properties: icon = Icon_Utils::it_Properties; break; case UMLListViewItem::lvt_Properties_AutoLayout: icon = Icon_Utils::it_Properties_AutoLayout; break; case UMLListViewItem::lvt_Properties_Class: icon = Icon_Utils::it_Properties_Class; break; case UMLListViewItem::lvt_Properties_CodeImport: icon = Icon_Utils::it_Properties_CodeImport; break; case UMLListViewItem::lvt_Properties_CodeGeneration: icon = Icon_Utils::it_Properties_CodeGeneration; break; case UMLListViewItem::lvt_Properties_CodeViewer: icon = Icon_Utils::it_Properties_CodeViewer; break; case UMLListViewItem::lvt_Properties_Font: icon = Icon_Utils::it_Properties_Font; break; case UMLListViewItem::lvt_Properties_General: icon = Icon_Utils::it_Properties_General; break; case UMLListViewItem::lvt_Properties_UserInterface: icon = Icon_Utils::it_Properties_UserInterface; break; case UMLListViewItem::lvt_Instance: icon = Icon_Utils::it_Instance; break; case UMLListViewItem::lvt_InstanteAttribute: icon = Icon_Utils::it_Private_Attribute; break; default: break; } return icon; } /** * Return the DiagramType which corresponds to the given listview type. * * @param lvt ListViewType to convert. * @return The Uml::DiagramType corresponding to the lvt. * Returns dt_Undefined in case no mapping to DiagramType exists. */ Uml::DiagramType::Enum convert_LVT_DT(UMLListViewItem::ListViewType lvt) { Uml::DiagramType::Enum dt = Uml::DiagramType::Undefined; switch (lvt) { case UMLListViewItem::lvt_Class_Diagram: dt = Uml::DiagramType::Class; break; case UMLListViewItem::lvt_UseCase_Diagram: dt = Uml::DiagramType::UseCase; break; case UMLListViewItem::lvt_Sequence_Diagram: dt = Uml::DiagramType::Sequence; break; case UMLListViewItem::lvt_Collaboration_Diagram: dt = Uml::DiagramType::Collaboration; break; case UMLListViewItem::lvt_State_Diagram: dt = Uml::DiagramType::State; break; case UMLListViewItem::lvt_Activity_Diagram: dt = Uml::DiagramType::Activity; break; case UMLListViewItem::lvt_Component_Diagram: dt = Uml::DiagramType::Component; break; case UMLListViewItem::lvt_Deployment_Diagram: dt = Uml::DiagramType::Deployment; break; case UMLListViewItem::lvt_EntityRelationship_Diagram: dt = Uml::DiagramType::EntityRelationship; break; case UMLListViewItem::lvt_Object_Diagram: dt = Uml::DiagramType::Object; break; default: break; } return dt; } /** * Converts a list view type enum to the equivalent settings dialog type. * * @param type The ListViewType to convert. * @return The converted settings dialog type */ MultiPageDialogBase::PageType convert_LVT_PT(UMLListViewItem::ListViewType type) { MultiPageDialogBase::PageType pt = MultiPageDialogBase::GeneralPage; switch (type) { case UMLListViewItem::lvt_Properties: pt = MultiPageDialogBase::GeneralPage; break; case UMLListViewItem::lvt_Properties_AutoLayout: pt = MultiPageDialogBase::AutoLayoutPage; break; case UMLListViewItem::lvt_Properties_Class: pt = MultiPageDialogBase::ClassPage; break; case UMLListViewItem::lvt_Properties_CodeImport: pt = MultiPageDialogBase::CodeImportPage; break; case UMLListViewItem::lvt_Properties_CodeGeneration: pt = MultiPageDialogBase::CodeGenerationPage; break; case UMLListViewItem::lvt_Properties_CodeViewer: pt = MultiPageDialogBase::CodeViewerPage; break; case UMLListViewItem::lvt_Properties_Font: pt = MultiPageDialogBase::FontPage; break; case UMLListViewItem::lvt_Properties_General: pt = MultiPageDialogBase::GeneralPage; break; case UMLListViewItem::lvt_Properties_UserInterface: pt = MultiPageDialogBase::UserInterfacePage; break; default: break; } return pt; } /** * Return the Model_Type which corresponds to the given ObjectType. */ Uml::ModelType::Enum convert_OT_MT(UMLObject::ObjectType ot) { Uml::ModelType::Enum mt = Uml::ModelType::N_MODELTYPES; switch (ot) { case UMLObject::ot_Actor: case UMLObject::ot_UseCase: mt = Uml::ModelType::UseCase; break; case UMLObject::ot_Component: case UMLObject::ot_Port: case UMLObject::ot_Artifact: mt = Uml::ModelType::Component; break; case UMLObject::ot_Node: mt = Uml::ModelType::Deployment; break; case UMLObject::ot_Entity: case UMLObject::ot_EntityAttribute: case UMLObject::ot_UniqueConstraint: case UMLObject::ot_ForeignKeyConstraint: case UMLObject::ot_CheckConstraint: case UMLObject::ot_Category: mt = Uml::ModelType::EntityRelationship; break; default: mt = Uml::ModelType::Logical; break; } return mt; } /** * Converts from the UpdateDeleteAction enum to a QString * @param uda The UpdateDeleteAction enum literal */ QString updateDeleteActionToString(UMLForeignKeyConstraint::UpdateDeleteAction uda) { switch(uda) { case UMLForeignKeyConstraint::uda_NoAction: return QLatin1String("NO ACTION"); case UMLForeignKeyConstraint::uda_Restrict: return QLatin1String("RESTRICT"); case UMLForeignKeyConstraint::uda_Cascade: return QLatin1String("CASCADE"); case UMLForeignKeyConstraint::uda_SetNull: return QLatin1String("SET NULL"); case UMLForeignKeyConstraint::uda_SetDefault: return QLatin1String("SET DEFAULT"); default: return QString(); } } /** * Return true if the object type is allowed in the related diagram * @param o UML object instance * @param scene diagram instance * @return true type is allowed * @return false type is not allowed */ bool typeIsAllowedInDiagram(UMLObject* o, UMLScene *scene) { //make sure dragging item onto correct diagram // concept - class, seq, coll diagram // actor, usecase - usecase diagram UMLObject::ObjectType ot = o->baseType(); Uml::ID::Type id = o->id(); Uml::DiagramType::Enum diagramType = scene->type(); bool bAccept = true; switch (diagramType) { case Uml::DiagramType::UseCase: if ((scene->widgetOnDiagram(id) && ot == UMLObject::ot_Actor) || (ot != UMLObject::ot_Actor && ot != UMLObject::ot_UseCase)) bAccept = false; break; case Uml::DiagramType::Class: if (scene->widgetOnDiagram(id) || (ot != UMLObject::ot_Class && ot != UMLObject::ot_Package && ot != UMLObject::ot_Interface && ot != UMLObject::ot_Enum && ot != UMLObject::ot_Datatype)) { bAccept = false; } break; case Uml::DiagramType::Object: if( scene->widgetOnDiagram(id) || (ot != UMLObject::ot_Instance)) bAccept = false; break; case Uml::DiagramType::Sequence: case Uml::DiagramType::Collaboration: if (ot != UMLObject::ot_Class && ot != UMLObject::ot_Interface && ot != UMLObject::ot_Actor) bAccept = false; break; case Uml::DiagramType::Deployment: if (scene->widgetOnDiagram(id)) bAccept = false; else if (ot != UMLObject::ot_Interface && ot != UMLObject::ot_Package && ot != UMLObject::ot_Component && ot != UMLObject::ot_Class && ot != UMLObject::ot_Node) bAccept = false; else if (ot == UMLObject::ot_Package && o->stereotype() != QLatin1String("subsystem")) bAccept = false; break; case Uml::DiagramType::Component: if (scene->widgetOnDiagram(id) || (ot != UMLObject::ot_Interface && ot != UMLObject::ot_Package && ot != UMLObject::ot_Component && ot != UMLObject::ot_Port && ot != UMLObject::ot_Artifact && ot != UMLObject::ot_Class)) bAccept = false; else if (ot == UMLObject::ot_Class && !o->isAbstract()) bAccept = false; else if (ot == UMLObject::ot_Port) { const bool componentOnDiagram = scene->widgetOnDiagram(o->umlPackage()->id()); bAccept = componentOnDiagram; } break; case Uml::DiagramType::EntityRelationship: if (scene->widgetOnDiagram(id) || (ot != UMLObject::ot_Entity && ot != UMLObject::ot_Category)) bAccept = false; break; default: break; } return bAccept; } /** * Return true if the widget type is allowed in the related diagram * @param w UML widget object * @param scene diagram instance * @return true type is allowed * @return false type is not allowed */ bool typeIsAllowedInDiagram(UMLWidget* w, UMLScene *scene) { UMLWidget::WidgetType wt = w->baseType(); Uml::DiagramType::Enum diagramType = scene->type(); bool bAccept = true; // TODO: check additional widgets switch (diagramType) { case Uml::DiagramType::Activity: case Uml::DiagramType::Class: case Uml::DiagramType::Object: case Uml::DiagramType::Collaboration: case Uml::DiagramType::Component: case Uml::DiagramType::Deployment: case Uml::DiagramType::EntityRelationship: case Uml::DiagramType::Sequence: case Uml::DiagramType::State: case Uml::DiagramType::UseCase: default: switch(wt) { case WidgetBase::wt_Note: break; case WidgetBase::wt_Text: { FloatingTextWidget *ft = w->asFloatingTextWidget(); if (ft && ft->textRole() != Uml::TextRole::Floating) { bAccept = false; } } break; default: bAccept = false; break; } break; } return bAccept; } /** * return true if given object type supports associatons * @param type uml object type to check */ bool hasAssociations(UMLObject::ObjectType type) { switch (type) { case UMLObject::ot_Actor: case UMLObject::ot_UseCase: case UMLObject::ot_Class: case UMLObject::ot_Package: case UMLObject::ot_Component: case UMLObject::ot_Node: case UMLObject::ot_Artifact: case UMLObject::ot_Interface: case UMLObject::ot_Enum: case UMLObject::ot_Entity: case UMLObject::ot_Datatype: case UMLObject::ot_Category: case UMLObject::ot_Instance: return true; default: return false; } } } // namespace Model_Utils diff --git a/umbrello/model_utils.h b/umbrello/model_utils.h index 150098f3b..322662337 100644 --- a/umbrello/model_utils.h +++ b/umbrello/model_utils.h @@ -1,139 +1,143 @@ /*************************************************************************** * 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 MODEL_UTILS_H #define MODEL_UTILS_H #include "basictypes.h" #include "umllistviewitem.h" #include "umlobjectlist.h" #include "umlviewlist.h" #include "foreignkeyconstraint.h" #include "icon_utils.h" #include "multipagedialogbase.h" #include "widgetbase.h" #include #include // forward declarations class UMLClassifier; class UMLPackage; class UMLEntity; /** * General purpose model utilities. * @author Oliver Kellogg * Bugs and comments to umbrello-devel@kde.org or http://bugs.kde.org */ namespace Model_Utils { bool isCloneable(WidgetBase::WidgetType type); UMLObject* findObjectInList(Uml::ID::Type id, const UMLObjectList& inList); UMLObject* findUMLObject(const UMLObjectList& inList, const QString& name, UMLObject::ObjectType type = UMLObject::ot_UMLObject, UMLObject *currentObj = 0); UMLObject* findUMLObjectRaw(const UMLObjectList& inList, const QString& name, UMLObject::ObjectType type = UMLObject::ot_UMLObject, UMLObject *currentObj = 0); +UMLObject* findUMLObjectRecursive(const UMLObjectList& inList, + const QString& name, + UMLObject::ObjectType type = UMLObject::ot_UMLObject); + UMLPackage* rootPackage(UMLObject* obj); void treeViewAddViews(const UMLViewList& viewList); void treeViewChangeIcon(UMLObject* object, Icon_Utils::IconType to); void treeViewSetCurrentItem(UMLObject* object); void treeViewMoveObjectTo(UMLObject* container, UMLObject* object); UMLObject* treeViewGetCurrentObject(); UMLPackage* treeViewGetPackageFromCurrent(); QString treeViewBuildDiagramName(Uml::ID::Type id); QString uniqObjectName(UMLObject::ObjectType type, UMLPackage *parentPkg, QString prefix = QString()); QString getXmiId(QDomElement element); bool isCommonXMI1Attribute(const QString &tag); bool isCommonDataType(QString type); bool isClassifierListitem(UMLObject::ObjectType ot); bool typeIsCanvasWidget(UMLListViewItem::ListViewType type); bool typeIsRootView(UMLListViewItem::ListViewType type); bool typeIsFolder(UMLListViewItem::ListViewType type); bool typeIsContainer(UMLListViewItem::ListViewType type); bool typeIsDiagram(UMLListViewItem::ListViewType type); bool typeIsClassifierList(UMLListViewItem::ListViewType type); bool typeIsClassifier(UMLListViewItem::ListViewType type); bool typeIsProperties(UMLListViewItem::ListViewType type); bool typeIsAllowedInType(UMLListViewItem::ListViewType childType, UMLListViewItem::ListViewType parentType); bool typeIsAllowedInDiagram(UMLObject *o, UMLScene *scene); bool typeIsAllowedInDiagram(UMLWidget *w, UMLScene *scene); bool hasAssociations(UMLObject::ObjectType type); Uml::ModelType::Enum convert_DT_MT(Uml::DiagramType::Enum dt); UMLListViewItem::ListViewType convert_MT_LVT(Uml::ModelType::Enum mt); Uml::ModelType::Enum convert_LVT_MT(UMLListViewItem::ListViewType lvt); UMLListViewItem::ListViewType convert_DT_LVT(Uml::DiagramType::Enum dt); UMLObject::ObjectType convert_LVT_OT(UMLListViewItem::ListViewType lvt); UMLListViewItem::ListViewType convert_OT_LVT(UMLObject *o); Icon_Utils::IconType convert_LVT_IT(UMLListViewItem::ListViewType lvt, UMLObject *o=0); Uml::DiagramType::Enum convert_LVT_DT(UMLListViewItem::ListViewType lvt); MultiPageDialogBase::PageType convert_LVT_PT(UMLListViewItem::ListViewType type); Uml::ModelType::Enum convert_OT_MT(UMLObject::ObjectType ot); Uml::ModelType::Enum guessContainer(UMLObject *o); // deprecated ! int stringToDirection(QString input, Uml::ParameterDirection::Enum & result); enum Parse_Status { ///< Return type of parseOperation(). PS_OK, PS_Empty, PS_Malformed_Arg, PS_Unknown_ArgType, PS_Illegal_MethodName, PS_Unknown_ReturnType, PS_Unspecified_Error }; struct NameAndType { ///< Data structure filled by parseAttribute(). QString m_name; UMLObject *m_type; Uml::ParameterDirection::Enum m_direction; QString m_initialValue; NameAndType() : m_type(0), m_direction(Uml::ParameterDirection::In) {} NameAndType(QString name, UMLObject *type, Uml::ParameterDirection::Enum direction = Uml::ParameterDirection::In, QString initialValue = QString()) : m_name(name), m_type(type), m_direction(direction), m_initialValue(initialValue) {} }; typedef QLinkedList NameAndType_List; ///< Auxiliary type for OpDescriptor. typedef QLinkedList::iterator NameAndType_ListIt; ///< Auxiliary type for OpDescriptor. struct OpDescriptor { ///< Data structure filled by parseOperation(). QString m_name; NameAndType_List m_args; UMLObject *m_pReturnType; }; Parse_Status parseTemplate(QString t, NameAndType& nmTp, UMLClassifier *owningScope); Parse_Status parseAttribute(QString a, NameAndType& nmTp, UMLClassifier *owningScope, Uml::Visibility::Enum *vis = 0); Parse_Status parseOperation(QString m, OpDescriptor& desc, UMLClassifier *owningScope); Parse_Status parseConstraint(QString m, QString& name, UMLEntity* owningScope); QString psText(Parse_Status value); QString updateDeleteActionToString(UMLForeignKeyConstraint::UpdateDeleteAction uda); } #endif diff --git a/umbrello/toolbarstatemessages.cpp b/umbrello/toolbarstatemessages.cpp index 632fb6a24..e81611053 100644 --- a/umbrello/toolbarstatemessages.cpp +++ b/umbrello/toolbarstatemessages.cpp @@ -1,322 +1,319 @@ /*************************************************************************** * 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 "toolbarstatemessages.h" // local includes #include "debug_utils.h" #include "floatingtextwidget.h" #include "messagewidget.h" #include "objectwidget.h" #include "uml.h" #include "umldoc.h" #include "umlview.h" #include "umlscene.h" // kde includes #include #include /** * Creates a new ToolBarStateMessages. * * @param umlScene The UMLScene to use. */ ToolBarStateMessages::ToolBarStateMessages(UMLScene *umlScene) : ToolBarStatePool(umlScene), m_firstObject(0), m_messageLine(0), m_isObjectWidgetLine(false), xclick(0), yclick(0) { } /** * Destroys this ToolBarStateMessages. */ ToolBarStateMessages::~ToolBarStateMessages() { cleanMessage(); } /** * Goes back to the initial state. */ void ToolBarStateMessages::init() { ToolBarStatePool::init(); cleanMessage(); } /** * Called when the current tool is changed to use another tool. * Executes base method and cleans the message. */ void ToolBarStateMessages::cleanBeforeChange() { ToolBarStatePool::cleanBeforeChange(); cleanMessage(); } /** * Called when a mouse event happened. * It executes the base method and then updates the position of the * message line, if any. */ void ToolBarStateMessages::mouseMove(QGraphicsSceneMouseEvent* ome) { ToolBarStatePool::mouseMove(ome); if (m_messageLine) { QPointF sp = m_messageLine->line().p1(); m_messageLine->setLine(sp.x(), sp.y(), m_pMouseEvent->scenePos().x(), m_pMouseEvent->scenePos().y()); } } /** * 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 ToolBarStateMessages::slotWidgetRemoved(UMLWidget* widget) { ToolBarState::slotWidgetRemoved(widget); if (widget == m_firstObject) { cleanMessage(); } } /** * 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 ToolBarStateMessages::setCurrentElement() { m_isObjectWidgetLine = false; ObjectWidget* objectWidgetLine = m_pUMLScene->onWidgetLine(m_pMouseEvent->scenePos()); if (objectWidgetLine) { uDebug() << Q_FUNC_INFO << "Object detected"; setCurrentWidget(objectWidgetLine); m_isObjectWidgetLine = true; return; } uDebug() << Q_FUNC_INFO << "Object NOT detected"; //commit 515177 fixed a setting creation messages only working properly at 100% zoom //However, the applied patch doesn't seem to be necessary no more, so it was removed //The widgets weren't got from UMLView, but from a method in this class similarto the //one in UMLView but containing special code to handle the zoom 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 ToolBarStateMessages::mouseReleaseWidget() { //TODO When an association between UMLObjects of invalid types is made, an error message //is shown. Shouldn't also a message be used here? if (m_pMouseEvent->button() != Qt::LeftButton || !currentWidget()->isObjectWidget()) { cleanMessage(); return; } if (!m_isObjectWidgetLine && !m_firstObject) { return; } if (!m_isObjectWidgetLine) { setSecondWidget(static_cast(currentWidget()), CreationMessage); return; } if (!m_firstObject) { setFirstWidget(static_cast(currentWidget())); } else { setSecondWidget(static_cast(currentWidget()), NormalMessage); } } /** * 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 ToolBarStateMessages::mouseReleaseEmpty() { Uml::SequenceMessage::Enum msgType = getMessageType(); if (m_firstObject && msgType == Uml::SequenceMessage::Lost) { xclick = m_pMouseEvent->scenePos().x(); yclick = m_pMouseEvent->scenePos().y(); MessageWidget* message = new MessageWidget(m_pUMLScene, m_firstObject, xclick, yclick, msgType); setupMessageWidget(message); cleanMessage(); xclick = 0; yclick = 0; } else if (!m_firstObject && msgType == Uml::SequenceMessage::Found && xclick == 0 && yclick == 0) { xclick = m_pMouseEvent->scenePos().x(); yclick = m_pMouseEvent->scenePos().y(); cleanMessage(); m_messageLine = new QGraphicsLineItem(); m_pUMLScene->addItem(m_messageLine); qreal x = m_pMouseEvent->scenePos().x(); qreal y = m_pMouseEvent->scenePos().y(); m_messageLine->setLine(x, y, x, y); m_messageLine->setPen(QPen(m_pUMLScene->lineColor(), m_pUMLScene->lineWidth(), Qt::DashLine)); m_messageLine->setVisible(true); m_pUMLScene->activeView()->viewport()->setMouseTracking(true); } else { cleanMessage(); } } /** * 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 ToolBarStateMessages::setFirstWidget(ObjectWidget* firstObject) { m_firstObject = firstObject; Uml::SequenceMessage::Enum msgType = getMessageType(); if (msgType == Uml::SequenceMessage::Found && xclick!=0 && yclick!=0) { MessageWidget* message = new MessageWidget(m_pUMLScene, m_firstObject, xclick, yclick, msgType); setupMessageWidget(message); cleanMessage(); xclick = 0; yclick = 0; } else { // TODO use cleanMessage() if (m_messageLine) delete m_messageLine; m_messageLine = new QGraphicsLineItem(); m_pUMLScene->addItem(m_messageLine); qreal x = m_pMouseEvent->scenePos().x(); qreal y = m_pMouseEvent->scenePos().y(); m_messageLine->setLine(x, y, x, y); m_messageLine->setPen(QPen(m_pUMLScene->lineColor(), m_pUMLScene->lineWidth(), Qt::DashLine)); m_messageLine->setVisible(true); m_pUMLScene->activeView()->viewport()->setMouseTracking(true); } } /** * Sets the second object of the message using the specified widget and * creates the message. * The association is created and added to the view. The dialog to select * the operation of the message is shown. * * @param secondObject The second object of the message. * @param messageType The type of the message to create. */ void ToolBarStateMessages::setSecondWidget(ObjectWidget* secondObject, MessageType messageType) { Uml::SequenceMessage::Enum msgType = getMessageType(); //There shouldn't be second widget for a lost or a found message if (msgType == Uml::SequenceMessage::Lost || msgType == Uml::SequenceMessage::Found) { cleanMessage(); xclick = 0; yclick = 0; return; } - //TODO shouldn't start position in the first widget be used also for normal messages - //and not only for creation? - qreal y = m_pMouseEvent->scenePos().y(); + qreal y = m_messageLine->line().p1().y(); if (messageType == CreationMessage) { msgType = Uml::SequenceMessage::Creation; - y = m_messageLine->line().p1().y(); } MessageWidget* message = new MessageWidget(m_pUMLScene, m_firstObject, secondObject, y, msgType); setupMessageWidget(message); cleanMessage(); } /** * Returns the message type of this tool. * * @return The message type of this tool. */ Uml::SequenceMessage::Enum ToolBarStateMessages::getMessageType() { if (getButton() == WorkToolBar::tbb_Seq_Message_Synchronous) { return Uml::SequenceMessage::Synchronous; } else if (getButton() == WorkToolBar::tbb_Seq_Message_Found) { return Uml::SequenceMessage::Found; } else if (getButton() == WorkToolBar::tbb_Seq_Message_Lost) { return Uml::SequenceMessage::Lost; } return Uml::SequenceMessage::Asynchronous; } /** * Cleans the first widget and the temporal message line, if any. * Both are set to null, and the message line is also deleted. */ void ToolBarStateMessages::cleanMessage() { m_firstObject = 0; delete m_messageLine; m_messageLine = 0; } void ToolBarStateMessages::setupMessageWidget(MessageWidget *message) { m_pUMLScene->addWidgetCmd(message); message->activate(); FloatingTextWidget *ft = message->floatingTextWidget(); //TODO cancel doesn't cancel the creation of the message, only cancels setting an operation. //Shouldn't it cancel also the whole creation? ft->showOperationDialog(); message->setTextPosition(); m_pUMLScene->addWidgetCmd(ft); UMLApp::app()->document()->setModified(); } diff --git a/umbrello/umldoc.cpp b/umbrello/umldoc.cpp index 402013607..00aadbc2b 100644 --- a/umbrello/umldoc.cpp +++ b/umbrello/umldoc.cpp @@ -1,3474 +1,3509 @@ /*************************************************************************** * 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 "umldoc.h" // app includes #include "debug_utils.h" #include "uniqueid.h" #include "association.h" #include "package.h" #include "folder.h" #include "codegenerator.h" #include "classifier.h" #include "dialog_utils.h" #include "enum.h" #include "entity.h" #include "docwindow.h" #include "operation.h" #include "attribute.h" #include "template.h" #include "enumliteral.h" #include "stereotype.h" #include "classifierlistitem.h" #include "object_factory.h" #include "import_argo.h" #include "import_rose.h" #include "model_utils.h" #include "uml.h" #include "umllistview.h" #include "umllistviewitem.h" #include "umlview.h" #include "entityconstraint.h" #include "idchangelog.h" #include "umllistviewpopupmenu.h" #include "cmds.h" #include "diagramprintpage.h" #include "umlscene.h" #include "version.h" #include "worktoolbar.h" #include "models/diagramsmodel.h" #include "models/objectsmodel.h" #include "models/stereotypesmodel.h" // kde includes #include #if QT_VERSION < 0x050000 #include #endif #if QT_VERSION >= 0x050000 #include #endif #include #include #if QT_VERSION < 0x050000 #include #endif #include #if QT_VERSION < 0x050000 #include #include #include #endif // qt includes #include #include #include #include #include #include #include #include #if QT_VERSION >= 0x050000 #include #endif #include #include #include #if QT_VERSION >= 0x050000 #include #include #endif #include #include DEBUG_REGISTER(UMLDoc) /** * Constructor for the fileclass of the application. */ UMLDoc::UMLDoc() : m_datatypeRoot(0), m_stereoList(UMLStereotypeList()), m_Name(i18n("UML Model")), m_modelID("m1"), m_count(0), m_modified(false), #if QT_VERSION >= 0x050000 m_doc_url(QUrl()), #else m_doc_url(KUrl()), #endif m_pChangeLog(0), m_bLoading(false), m_importing(false), m_Doc(QString()), m_pAutoSaveTimer(0), m_nViewID(Uml::ID::None), m_bTypesAreResolved(true), m_pCurrentRoot(0), m_bClosing(false), m_diagramsModel(new DiagramsModel), m_objectsModel(new ObjectsModel), m_stereotypesModel(new StereotypesModel(&m_stereoList)), m_resolution(0.0) { for (int i = 0; i < Uml::ModelType::N_MODELTYPES; ++i) m_root[i] = 0; } /** * Initialize the UMLDoc. * To be called after the constructor, before anything else. */ void UMLDoc::init() { // Initialize predefined folders. const char* nativeRootName[Uml::ModelType::N_MODELTYPES] = { "Logical View", "Use Case View", "Component View", "Deployment View", "Entity Relationship Model" }; const QString localizedRootName[Uml::ModelType::N_MODELTYPES] = { i18n("Logical View"), i18n("Use Case View"), i18n("Component View"), i18n("Deployment View"), i18n("Entity Relationship Model") }; for (int i = 0; i < Uml::ModelType::N_MODELTYPES; ++i) { const QString rootName = QString::fromLatin1(nativeRootName[i]); QString id = rootName; id.replace(QLatin1Char(' '), QLatin1Char('_')); m_root[i] = new UMLFolder(rootName, Uml::ID::fromString(id)); m_root[i]->setLocalName(localizedRootName[i]); } createDatatypeFolder(); // Connect signals. UMLApp * pApp = UMLApp::app(); connect(this, SIGNAL(sigDiagramCreated(Uml::ID::Type)), pApp, SLOT(slotUpdateViews())); connect(this, SIGNAL(sigDiagramRemoved(Uml::ID::Type)), pApp, SLOT(slotUpdateViews())); connect(this, SIGNAL(sigDiagramRenamed(Uml::ID::Type)), pApp, SLOT(slotUpdateViews())); connect(this, SIGNAL(sigCurrentViewChanged()), pApp, SLOT(slotCurrentViewChanged())); } /** * Create the datatype folder and add it to the logical folder. */ void UMLDoc::createDatatypeFolder() { delete m_datatypeRoot; m_datatypeRoot = new UMLFolder(QLatin1String("Datatypes"), "Datatypes"); m_datatypeRoot->setLocalName(i18n("Datatypes")); m_datatypeRoot->setUMLPackage(m_root[Uml::ModelType::Logical]); Q_ASSERT(m_root[Uml::ModelType::Logical]); m_root[Uml::ModelType::Logical]->addObject(m_datatypeRoot); } /** * Destructor for the fileclass of the application. */ UMLDoc::~UMLDoc() { UMLApp * pApp = UMLApp::app(); disconnect(this, SIGNAL(sigDiagramCreated(Uml::ID::Type)), pApp, SLOT(slotUpdateViews())); disconnect(this, SIGNAL(sigDiagramRemoved(Uml::ID::Type)), pApp, SLOT(slotUpdateViews())); disconnect(this, SIGNAL(sigDiagramRenamed(Uml::ID::Type)), pApp, SLOT(slotUpdateViews())); disconnect(this, SIGNAL(sigCurrentViewChanged()), pApp, SLOT(slotCurrentViewChanged())); disconnect(m_pAutoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); delete m_pAutoSaveTimer; m_root[Uml::ModelType::Logical]->removeObject(m_datatypeRoot); delete m_datatypeRoot; for (int i = 0; i < Uml::ModelType::N_MODELTYPES; ++i) { delete m_root[i]; } delete m_pChangeLog; qDeleteAll(m_stereoList); delete m_stereotypesModel; delete m_diagramsModel; delete m_objectsModel; } /** * Adds a view to the document which represents the document * contents. Usually this is your main view. * * @param view Pointer to the UMLView to add. */ void UMLDoc::addView(UMLView *view) { if (view == 0) { uError() << "argument is NULL"; return; } UMLFolder *f = view->umlScene()->folder(); if (f == 0) { uError() << "view folder is not set"; return; } DEBUG(DBG_SRC) << view->umlScene()->name() << " to folder " << *f << " (" << f->name() << ")"; f->addView(view); m_diagramsModel->addDiagram(view); UMLApp * pApp = UMLApp::app(); if (pApp->listView()) { connect(this, SIGNAL(sigObjectRemoved(UMLObject*)), view->umlScene(), SLOT(slotObjectRemoved(UMLObject*))); } if (!m_bLoading || pApp->currentView() == 0) { pApp->setCurrentView(view); } if (!m_bLoading) { view->show(); emit sigDiagramChanged(view->umlScene()->type()); } pApp->setDiagramMenuItemsState(true); pApp->slotUpdateViews(); } /** * Removes a view from the list of currently connected views. * * @param view Pointer to the UMLView to remove. * @param enforceCurrentView Switch to determine if we have a current view or not. * Most of the time, we DO want this, except when exiting the program. */ void UMLDoc::removeView(UMLView *view, bool enforceCurrentView) { if (!view) { uError() << "UMLDoc::removeView(UMLView *view) called with view = 0"; return; } DEBUG(DBG_SRC) << "<" << view->umlScene()->name() << ">"; if (UMLApp::app()->listView()) { disconnect(this, SIGNAL(sigObjectRemoved(UMLObject*)), view->umlScene(), SLOT(slotObjectRemoved(UMLObject*))); } view->hide(); UMLFolder *f = view->umlScene()->folder(); if (f == 0) { uError() << view->umlScene()->name() << ": view->getFolder() returns NULL"; return; } m_diagramsModel->removeDiagram(view); f->removeView(view); UMLView *currentView = UMLApp::app()->currentView(); if (currentView == view) { UMLApp::app()->setCurrentView(0); UMLViewList viewList; m_root[Uml::ModelType::Logical]->appendViews(viewList); UMLView* firstView = 0; if (!viewList.isEmpty()) { firstView = viewList.first(); } if (!firstView && enforceCurrentView) { //create a diagram QString name = createDiagramName(Uml::DiagramType::Class, false); createDiagram(m_root[Uml::ModelType::Logical], Uml::DiagramType::Class, name); qApp->processEvents(); m_root[Uml::ModelType::Logical]->appendViews(viewList); firstView = viewList.first(); } if (firstView) { changeCurrentView(firstView->umlScene()->ID()); UMLApp::app()->setDiagramMenuItemsState(true); } } delete view; } /** * Sets the URL of the document. * * @param url The KUrl to set. */ #if QT_VERSION >= 0x050000 void UMLDoc::setUrl(const QUrl &url) #else void UMLDoc::setUrl(const KUrl &url) #endif { m_doc_url = url; } /** * Returns the KUrl of the document. * * @return The KUrl of this UMLDoc. */ #if QT_VERSION >= 0x050000 const QUrl& UMLDoc::url() const #else const KUrl& UMLDoc::url() const #endif { return m_doc_url; } /** * Sets the URL of the document to "Untitled". */ void UMLDoc::setUrlUntitled() { #if QT_VERSION >= 0x050000 m_doc_url.setUrl(m_doc_url.toString(QUrl::RemoveFilename) + i18n("Untitled")); #else m_doc_url.setFileName(i18n("Untitled")); #endif } /** * "save modified" - Asks the user for saving if the document * is modified. * * @return True if document can be closed. */ bool UMLDoc::saveModified() { bool completed(true); if (!m_modified) { return completed; } UMLApp *win = UMLApp::app(); int want_save = KMessageBox::warningYesNoCancel(win, i18n("The current file has been modified.\nDo you want to save it?"), i18nc("warning message", "Warning"), KStandardGuiItem::save(), KStandardGuiItem::discard()); switch(want_save) { case KMessageBox::Yes: if (m_doc_url.fileName() == i18n("Untitled")) { if (win->slotFileSaveAs()) { closeDocument(); completed=true; } else { completed=false; } } else { saveDocument(url()); closeDocument(); completed=true; } break; case KMessageBox::No: setModified(false); closeDocument(); completed=true; break; case KMessageBox::Cancel: completed=false; break; default: completed=false; break; } return completed; } /** * Closes the current document. */ void UMLDoc::closeDocument() { m_bClosing = true; UMLApp::app()->setGenerator(Uml::ProgrammingLanguage::Reserved); // delete the codegen m_Doc = QString(); DocWindow* dw = UMLApp::app()->docWindow(); if (dw) { dw->reset(); } UMLApp::app()->logWindow()->clear(); UMLListView *listView = UMLApp::app()->listView(); if (listView) { listView->clean(); // store old setting - for restore of last setting bool m_bLoading_old = m_bLoading; m_bLoading = true; // This is to prevent document becoming modified. // For reference, here is an example of a call sequence that would // otherwise result in futile addToUndoStack() calls: // removeAllViews() => // UMLView::removeAllAssociations() => // UMLView::removeAssoc() => // UMLDoc::setModified(true, true) => // addToUndoStack(). removeAllViews(); m_bLoading = m_bLoading_old; // Remove all objects from the predefined folders. // @fixme With advanced code generation enabled, this crashes. removeAllObjects(); // Remove any stereotypes. if (stereotypes().count() > 0) { foreach(UMLStereotype *s, stereotypes()) { m_stereotypesModel->removeStereotype(s); delete s; } m_stereoList.clear(); } // Restore the datatype folder, it has been deleted above. createDatatypeFolder(); // this creates to much items only Logical View should be created listView->init(); } m_bClosing = false; } /** * Initializes the document generally. * * @return True if operation successful. */ bool UMLDoc::newDocument() { bool state = UMLApp::app()->document()->loading(); UMLApp::app()->document()->setLoading(true); closeDocument(); UMLApp::app()->setCurrentView(0); setUrlUntitled(); setResolution(qApp->desktop()->logicalDpiX()); //see if we need to start with a new diagram Settings::OptionState optionState = Settings::optionState(); Uml::DiagramType::Enum dt = optionState.generalState.diagram; Uml::ModelType::Enum mt = Model_Utils::convert_DT_MT(dt); if (mt == Uml::ModelType::N_MODELTYPES) { // don't allow no diagram dt = Uml::DiagramType::Class; mt = Uml::ModelType::Logical; } QString name = createDiagramName(dt, false); createDiagram(m_root[mt], dt, name); UMLApp::app()->initGenerator(); setModified(false); initSaveTimer(); UMLApp::app()->enableUndoAction(false); UMLApp::app()->clearUndoStack(); UMLApp::app()->document()->setLoading(state); return true; } /** * Loads the document by filename and format and emits the * updateViews() signal. * * @param url The filename in KUrl format. * @param format The format (optional.) * @return True if operation successful. */ #if QT_VERSION >= 0x050000 bool UMLDoc::openDocument(const QUrl& url, const char* format /* =0 */) #else bool UMLDoc::openDocument(const KUrl& url, const char* format /* =0 */) #endif { Q_UNUSED(format); if (url.fileName().length() == 0) { newDocument(); return false; } m_doc_url = url; closeDocument(); setResolution(0.0); // IMPORTANT: set m_bLoading to true // _AFTER_ the call of UMLDoc::closeDocument() // as it sets m_bLoading to false after it was temporarily // changed to true to block recording of changes in redo-buffer m_bLoading = true; #if QT_VERSION >= 0x050000 QTemporaryFile tmpfile; tmpfile.open(); QUrl dest(QUrl::fromLocalFile(tmpfile.fileName())); DEBUG(DBG_SRC) << "UMLDoc::openDocument: copy from " << url << " to " << dest << "."; KIO::FileCopyJob *job = KIO::file_copy(url, dest, -1, KIO::Overwrite); KJobWidgets::setWindow(job, UMLApp::app()); job->exec(); QFile file(tmpfile.fileName()); if (job->error() || !tmpfile.exists()) { if (!tmpfile.exists()) DEBUG(DBG_SRC) << "UMLDoc::openDocument: temporary file <" << tmpfile.fileName() << "> failed!"; if (job->error()) DEBUG(DBG_SRC) << "UMLDoc::openDocument: " << job->errorString(); KMessageBox::error(0, i18n("The file <%1> does not exist.", url.toString()), i18n("Load Error")); setUrlUntitled(); m_bLoading = false; newDocument(); return false; } #else QString tmpfile; KIO::NetAccess::download(url, tmpfile, UMLApp::app()); QFile file(tmpfile); if (!file.exists()) { KMessageBox::error(0, i18n("The file %1 does not exist.", url.pathOrUrl()), i18n("Load Error")); setUrlUntitled(); m_bLoading = false; newDocument(); return false; } #endif // status of XMI loading bool status = false; // check if the xmi file is a compressed archive like tar.bzip2 or tar.gz QString filetype = m_doc_url.fileName(); QString mimetype; if (filetype.endsWith(QLatin1String(".tgz")) || filetype.endsWith(QLatin1String(".tar.gz"))) { mimetype = QLatin1String("application/x-gzip"); } else if (filetype.endsWith(QLatin1String(".tar.bz2"))) { mimetype = QLatin1String("application/x-bzip"); } if (mimetype.isEmpty() == false) { #if QT_VERSION >= 0x050000 KTar archive(tmpfile.fileName(), mimetype); #else KTar archive(tmpfile, mimetype); #endif if (archive.open(QIODevice::ReadOnly) == false) { #if QT_VERSION >= 0x050000 KMessageBox::error(0, i18n("The file %1 seems to be corrupted.", url.toString()), i18n("Load Error")); #else KMessageBox::error(0, i18n("The file %1 seems to be corrupted.", url.pathOrUrl()), i18n("Load Error")); KIO::NetAccess::removeTempFile(tmpfile); #endif setUrlUntitled(); m_bLoading = false; newDocument(); return false; } // get the root directory and all entries in const KArchiveDirectory * rootDir = archive.directory(); const QStringList entries = rootDir->entries(); QString entryMimeType; bool foundXMI = false; QStringList::ConstIterator it; QStringList::ConstIterator end(entries.end()); // now go through all entries till we find an xmi file for (it = entries.begin(); it != end; ++it) { // only check files, we do not go in subdirectories if (rootDir->entry(*it)->isFile() == true) { // we found a file, check the mimetype #if QT_VERSION >= 0x050000 QMimeDatabase db; entryMimeType = db.mimeTypeForFile(*it, QMimeDatabase::MatchExtension).name(); #else entryMimeType = KMimeType::findByPath(*it, 0, true)->name(); #endif if (entryMimeType == QLatin1String("application/x-uml")) { foundXMI = true; break; } } } // if we found an XMI file, we have to extract it to a temporary file if (foundXMI == true) { #if QT_VERSION >= 0x050000 QTemporaryDir tmp_dir; #else KTempDir tmp_dir; #endif KArchiveEntry * entry; KArchiveFile * fileEntry; // try to cast the file entry in the archive to an archive entry entry = const_cast(rootDir->entry(*it)); if (entry == 0) { #if QT_VERSION >= 0x050000 KMessageBox::error(0, i18n("There was no XMI file found in the compressed file %1.", url.toString()), i18n("Load Error")); #else KMessageBox::error(0, i18n("There was no XMI file found in the compressed file %1.", url.pathOrUrl()), i18n("Load Error")); KIO::NetAccess::removeTempFile(tmpfile); #endif setUrlUntitled(); m_bLoading = false; newDocument(); return false; } // now try to cast the archive entry to a file entry, so that we can // extract the file fileEntry = dynamic_cast(entry); if (fileEntry == 0) { #if QT_VERSION >= 0x050000 KMessageBox::error(0, i18n("There was no XMI file found in the compressed file %1.", url.toString()), i18n("Load Error")); #else KMessageBox::error(0, i18n("There was no XMI file found in the compressed file %1.", url.pathOrUrl()), i18n("Load Error")); KIO::NetAccess::removeTempFile(tmpfile); #endif setUrlUntitled(); m_bLoading = false; newDocument(); return false; } // now we can extract the file to the temporary directory #if QT_VERSION >= 0x050000 fileEntry->copyTo(tmp_dir.path() + QLatin1Char('/')); // now open the extracted file for reading QFile xmi_file(tmp_dir.path() + QLatin1Char('/') + *it); #else fileEntry->copyTo(tmp_dir.name()); // now open the extracted file for reading QFile xmi_file(tmp_dir.name() + *it); #endif if(!xmi_file.open(QIODevice::ReadOnly)) { #if QT_VERSION >= 0x050000 KMessageBox::error(0, i18n("There was a problem loading the extracted file: %1", url.toString()), i18n("Load Error")); #else KMessageBox::error(0, i18n("There was a problem loading the extracted file: %1", url.pathOrUrl()), i18n("Load Error")); KIO::NetAccess::removeTempFile(tmpfile); #endif setUrlUntitled(); m_bLoading = false; newDocument(); return false; } m_bTypesAreResolved = false; status = loadFromXMI1(xmi_file, ENC_UNKNOWN); // close the extracted file and the temporary directory xmi_file.close(); } else { #if QT_VERSION >= 0x050000 KMessageBox::error(0, i18n("There was no XMI file found in the compressed file %1.", url.toString()), i18n("Load Error")); #else KMessageBox::error(0, i18n("There was no XMI file found in the compressed file %1.", url.pathOrUrl()), i18n("Load Error")); KIO::NetAccess::removeTempFile(tmpfile); #endif setUrlUntitled(); m_bLoading = false; newDocument(); return false; } archive.close(); } else { // no, it seems to be an ordinary file if (!file.open(QIODevice::ReadOnly)) { #if QT_VERSION >= 0x050000 KMessageBox::error(0, i18n("There was a problem loading file: %1", url.toString()), i18n("Load Error")); #else KMessageBox::error(0, i18n("There was a problem loading file: %1", url.pathOrUrl()), i18n("Load Error")); KIO::NetAccess::removeTempFile(tmpfile); #endif setUrlUntitled(); m_bLoading = false; newDocument(); return false; } if (filetype.endsWith(QLatin1String(".mdl"))) { setUrlUntitled(); m_bTypesAreResolved = false; status = Import_Rose::loadFromMDL(file); if (status) { if (UMLApp::app()->currentView() == 0) { QString name = createDiagramName(Uml::DiagramType::Class, false); createDiagram(m_root[Uml::ModelType::Logical], Uml::DiagramType::Class, name); setCurrentRoot(Uml::ModelType::Logical); } } } else if (filetype.endsWith(QLatin1String(".zargo"))) { setUrlUntitled(); status = Import_Argo::loadFromZArgoFile(file); } else { m_bTypesAreResolved = false; status = loadFromXMI1(file, ENC_UNKNOWN); } } if (file.isOpen()) file.close(); #if QT_VERSION < 0x050000 KIO::NetAccess::removeTempFile(tmpfile); #endif m_bLoading = false; m_bTypesAreResolved = true; if (!status) { #if QT_VERSION >= 0x050000 KMessageBox::error(0, i18n("There was a problem loading file: %1", url.toString()), i18n("Load Error")); #else KMessageBox::error(0, i18n("There was a problem loading file: %1", url.pathOrUrl()), i18n("Load Error")); #endif newDocument(); return false; } setModified(false); initSaveTimer(); UMLApp::app()->enableUndoAction(false); UMLApp::app()->clearUndoStack(); // for compatibility addDefaultStereotypes(); return true; } /** * Saves the document using the given filename and format. * * @param url The filename in KUrl format. * @param format The format (optional.) * @return True if operation successful. */ #if QT_VERSION >= 0x050000 bool UMLDoc::saveDocument(const QUrl& url, const char * format) #else bool UMLDoc::saveDocument(const KUrl& url, const char * format) #endif { Q_UNUSED(format); m_doc_url = url; bool uploaded = true; // first, we have to find out which format to use #if QT_VERSION >= 0x050000 QString strFileName = url.path(); #else QString strFileName = url.path(KUrl::RemoveTrailingSlash); #endif QFileInfo fileInfo(strFileName); QString fileExt = fileInfo.completeSuffix(); QString fileFormat = QLatin1String("xmi"); if (fileExt == QLatin1String("xmi") || fileExt == QLatin1String("bak.xmi")) { fileFormat = QLatin1String("xmi"); } else if (fileExt == QLatin1String("xmi.tgz") || fileExt == QLatin1String("bak.xmi.tgz")) { fileFormat = QLatin1String("tgz"); } else if (fileExt == QLatin1String("xmi.tar.bz2") || fileExt == QLatin1String("bak.xmi.tar.bz2")) { fileFormat = QLatin1String("bz2"); } else { fileFormat = QLatin1String("xmi"); } initSaveTimer(); if (fileFormat == QLatin1String("tgz") || fileFormat == QLatin1String("bz2")) { KTar * archive; #if QT_VERSION >= 0x050000 QTemporaryFile tmp_tgz_file; #else KTemporaryFile tmp_tgz_file; #endif tmp_tgz_file.setAutoRemove(false); tmp_tgz_file.open(); // first we have to check if we are saving to a local or remote file if (url.isLocalFile()) { if (fileFormat == QLatin1String("tgz")) { // check tgz or bzip archive = new KTar(url.toLocalFile(), QLatin1String("application/x-gzip")); } else { archive = new KTar(url.toLocalFile(), QLatin1String("application/x-bzip")); } } else { if (fileFormat == QLatin1String("tgz")) { // check tgz or bzip2 archive = new KTar(tmp_tgz_file.fileName(), QLatin1String("application/x-gzip")); } else { archive = new KTar(tmp_tgz_file.fileName(), QLatin1String("application/x-bzip")); } } // now check if we can write to the file if (archive->open(QIODevice::WriteOnly) == false) { uError() << "could not open" << archive->fileName(); #if QT_VERSION >= 0x050000 KMessageBox::error(0, i18n("There was a problem saving: %1", url.url(QUrl::PreferLocalFile)), i18n("Save Error")); #else KMessageBox::error(0, i18n("There was a problem saving file: %1", url.pathOrUrl()), i18n("Save Error")); #endif delete archive; return false; } // we have to create a temporary xmi file // we will add this file later to the archive #if QT_VERSION >= 0x050000 QTemporaryFile tmp_xmi_file; #else KTemporaryFile tmp_xmi_file; #endif tmp_xmi_file.setAutoRemove(false); if (!tmp_xmi_file.open()) { uError() << "could not open" << tmp_xmi_file.fileName(); #if QT_VERSION >= 0x050000 KMessageBox::error(0, i18n("There was a problem saving: %1", url.url(QUrl::PreferLocalFile)), i18n("Save Error")); #else KMessageBox::error(0, i18n("There was a problem saving file: %1", url.pathOrUrl()), i18n("Save Error")); #endif delete archive; return false; } saveToXMI1(tmp_xmi_file); // save XMI to this file... // now add this file to the archive, but without the extension QString tmpQString = url.fileName(); if (fileFormat == QLatin1String("tgz")) { tmpQString.remove(QRegExp(QLatin1String("\\.tgz$"))); } else { tmpQString.remove(QRegExp(QLatin1String("\\.tar\\.bz2$"))); } archive->addLocalFile(tmp_xmi_file.fileName(), tmpQString); if (!archive->close()) { uError() << "could not close" << archive->fileName(); #if QT_VERSION >= 0x050000 KMessageBox::error(0, i18n("There was a problem saving: %1", url.url(QUrl::PreferLocalFile)), i18n("Save Error")); #else KMessageBox::error(0, i18n("There was a problem saving file: %1", url.pathOrUrl()), i18n("Save Error")); #endif delete archive; return false; } // now the xmi file was added to the archive, so we can delete it tmp_xmi_file.setAutoRemove(true); // now we have to check, if we have to upload the file if (!url.isLocalFile()) { #if QT_VERSION >= 0x050000 KIO::FileCopyJob *job = KIO::file_copy(QUrl::fromLocalFile(tmp_tgz_file.fileName()), m_doc_url); KJobWidgets::setWindow(job, UMLApp::app()); job->exec(); uploaded = !job->error(); #else uploaded = KIO::NetAccess::upload(tmp_tgz_file.fileName(), m_doc_url, UMLApp::app()); #endif if (!uploaded) uError() << "could not upload file" << tmp_tgz_file.fileName() << "to" << url; } // now the archive was written to disk (or remote) so we can delete the // objects tmp_tgz_file.setAutoRemove(true); delete archive; } else { // save as normal uncompressed XMI #if QT_VERSION >= 0x050000 QTemporaryFile tmpfile; // we need this tmp file if we are writing to a remote file #else KTemporaryFile tmpfile; // we need this tmp file if we are writing to a remote file #endif tmpfile.setAutoRemove(false); // save in _any_ case to a temp file // -> if something goes wrong during saveToXMI1, the // original content is preserved // (e.g. if umbrello dies in the middle of the document model parsing // for saveToXMI1 due to some problems) /// @todo insert some checks in saveToXMI1 to detect a failed save attempt // lets open the file for writing if (!tmpfile.open()) { uError() << "could not open" << tmpfile.fileName(); #if QT_VERSION >= 0x050000 KMessageBox::error(0, i18n("There was a problem saving: %1", url.url(QUrl::PreferLocalFile)), i18n("Save Error")); #else KMessageBox::error(0, i18n("There was a problem saving file: %1", url.pathOrUrl()), i18n("Save Error")); #endif return false; } saveToXMI1(tmpfile); // save the xmi stuff to it // if it is a remote file, we have to upload the tmp file if (!url.isLocalFile()) { #if QT_VERSION >= 0x050000 KIO::FileCopyJob *job = KIO::file_copy(QUrl::fromLocalFile(tmpfile.fileName()), m_doc_url); KJobWidgets::setWindow(job, UMLApp::app()); job->exec(); uploaded = !job->error(); #else uploaded = KIO::NetAccess::upload(tmpfile.fileName(), m_doc_url, UMLApp::app()); #endif if (!uploaded) uError() << "could not upload file" << tmpfile.fileName() << "to" << url; } else { // now remove the original file #ifdef Q_OS_WIN tmpfile.setAutoRemove(true); #if QT_VERSION >= 0x050000 KIO::FileCopyJob* fcj = KIO::file_copy(QUrl::fromLocalFile(tmpfile.fileName()), url, -1, KIO::Overwrite); #else KIO::FileCopyJob* fcj = KIO::file_copy(tmpfile.fileName(), url, -1, KIO::Overwrite); #endif #else #if QT_VERSION >= 0x050000 KIO::FileCopyJob* fcj = KIO::file_move(QUrl::fromLocalFile(tmpfile.fileName()), url, -1, KIO::Overwrite); #else KIO::FileCopyJob* fcj = KIO::file_move(tmpfile.fileName(), url, -1, KIO::Overwrite); #endif #endif #if QT_VERSION >= 0x050000 KJobWidgets::setWindow(fcj, (QWidget*)UMLApp::app()); fcj->exec(); if (fcj->error()) { uError() << "Could not move" << tmpfile.fileName() << "to" << url; KMessageBox::error(0, i18n("There was a problem saving: %1", url.url(QUrl::PreferLocalFile)), i18n("Save Error")); #else if (KIO::NetAccess::synchronousRun(fcj, (QWidget*)UMLApp::app()) == false) { KMessageBox::error(0, i18n("There was a problem saving file: %1", url.pathOrUrl()), i18n("Save Error")); #endif setUrlUntitled(); return false; } } } if (!uploaded) { #if QT_VERSION >= 0x050000 KMessageBox::error(0, i18n("There was a problem uploading: %1", url.url(QUrl::PreferLocalFile)), i18n("Save Error")); #else KMessageBox::error(0, i18n("There was a problem uploading file: %1", url.pathOrUrl()), i18n("Save Error")); #endif setUrlUntitled(); } setModified(false); return uploaded; } /** * Sets up the signals needed by the program for it to work. */ void UMLDoc::setupSignals() { WorkToolBar *tb = UMLApp::app()->workToolBar(); connect(this, SIGNAL(sigDiagramChanged(Uml::DiagramType::Enum)), tb, SLOT(slotCheckToolBar(Uml::DiagramType::Enum))); } /** * Finds a view (diagram) by the ID given to method. * * @param id The ID of the view to search for. * @return Pointer to the view found, or NULL if not found. */ UMLView * UMLDoc::findView(Uml::ID::Type id) { UMLView *v = 0; for (int i = 0; i < Uml::ModelType::N_MODELTYPES; ++i) { v = m_root[i]->findView(id); if (v) { break; } } return v; } /** * Finds a view (diagram) by the type and name given. * * @param type The type of view to find. * @param name The name of the view to find. * @param searchAllScopes Search in all subfolders (default: false.) * @return Pointer to the view found, or NULL if not found. */ UMLView * UMLDoc::findView(Uml::DiagramType::Enum type, const QString &name, bool searchAllScopes /* =false */) { Uml::ModelType::Enum mt = Model_Utils::convert_DT_MT(type); return m_root[mt]->findView(type, name, searchAllScopes); } /** * Used to find a reference to a @ref UMLObject by its ID. * * @param id The @ref UMLObject to find. * @return Pointer to the UMLObject found, or NULL if not found. */ UMLObject* UMLDoc::findObjectById(Uml::ID::Type id) { UMLObject *o = 0; for (int i = 0; i < Uml::ModelType::N_MODELTYPES; ++i) { if (id == m_root[i]->id()) { return m_root[i]; } o = m_root[i]->findObjectById(id); if (o) { return o; } } o = findStereotypeById(id); return o; } /** * Used to find a @ref UMLObject by its type and name. * * @param name The name of the @ref UMLObject to find. * @param type ObjectType of the object to find (optional.) * When the given type is ot_UMLObject the type is * disregarded, i.e. the given name is the only * search criterion. * @param currentObj Object relative to which to search (optional.) * If given then the enclosing scope(s) of this * object are searched before the global scope. * @return Pointer to the UMLObject found, or NULL if not found. */ UMLObject* UMLDoc::findUMLObject(const QString &name, UMLObject::ObjectType type /* = ot_UMLObject */, UMLObject *currentObj /* = 0 */) { UMLObject *o = m_datatypeRoot->findObject(name); if (o) { return o; } for (int i = 0; i < Uml::ModelType::N_MODELTYPES; ++i) { UMLObjectList &list = m_root[i]->containedObjects(); if (list.size() == 0) continue; o = Model_Utils::findUMLObject(list, name, type, currentObj); if (o) { return o; } if ((type == UMLObject::ot_UMLObject || type == UMLObject::ot_Folder) && name == m_root[i]->name()) { return m_root[i]; } } return 0; } /** * Used to find a @ref UMLObject by its type and raw name. * * @param modelType The model type in which to search for the object * @param name The raw name of the @ref UMLObject to find. * @param type ObjectType of the object to find * @return Pointer to the UMLObject found, or NULL if not found. */ UMLObject* UMLDoc::findUMLObjectRaw(Uml::ModelType::Enum modelType, const QString &name, UMLObject::ObjectType type) { return findUMLObjectRaw(rootFolder(modelType), name, type); } /** * Used to find a @ref UMLObject by its type and raw name. * * @param folder The UMLFolder in which to search for the object * @param name The raw name of the @ref UMLObject to find. * @param type ObjectType of the object to find * @return Pointer to the UMLObject found, or NULL if not found. */ UMLObject* UMLDoc::findUMLObjectRaw(UMLFolder *folder, const QString &name, UMLObject::ObjectType type) { if (folder == 0) return 0; UMLObjectList &list = folder->containedObjects(); if (list.size() == 0) return 0; return Model_Utils::findUMLObjectRaw(list, name, type, 0); } +/** + * Used to find a @ref UMLObject by its type and raw name recursivly + * + * @param modelType The model type in which to search for the object + * @param name The raw name of the @ref UMLObject to find. + * @param type ObjectType of the object to find + * @return Pointer to the UMLObject found, or NULL if not found. + */ +UMLObject* UMLDoc::findUMLObjectRecursive(Uml::ModelType::Enum modelType, + const QString &name, + UMLObject::ObjectType type) +{ + return findUMLObjectRecursive(rootFolder(modelType), name, type); +} + +/** + * Used to find a @ref UMLObject by its type and raw name recursivly + * + * @param folder The UMLFolder in which to search for the object + * @param name The raw name of the @ref UMLObject to find. + * @param type ObjectType of the object to find + * @return Pointer to the UMLObject found, or NULL if not found. + */ +UMLObject* UMLDoc::findUMLObjectRecursive(UMLFolder *folder, + const QString &name, + UMLObject::ObjectType type) +{ + if (folder == 0) + return 0; + UMLObjectList &list = folder->containedObjects(); + if (list.size() == 0) + return 0; + return Model_Utils::findUMLObjectRecursive(list, name, type); +} + /** * Used to find a @ref UMLClassifier by its name. * * @param name The name of the @ref UMLObject to find. */ UMLClassifier* UMLDoc::findUMLClassifier(const QString &name) { //this is used only by code generator so we don't need to look at Datatypes UMLObject * obj = findUMLObject(name); return obj->asUMLClassifier(); } /** * Adds a UMLObject thats already created but doesn't change * any ids or signal. Use AddUMLObjectPaste if pasting. * * @param object The object to add. * @return True if the object was actually added. */ bool UMLDoc::addUMLObject(UMLObject* object) { if (object->isUMLStereotype()) { DEBUG(DBG_SRC) << object->name() << ": not adding type " << object->baseTypeStr(); return false; } UMLPackage *pkg = object->umlPackage(); if (pkg == 0) { pkg = currentRoot(); DEBUG(DBG_SRC) << object->name() << ": no parent package set, assuming " << pkg->name(); object->setUMLPackage(pkg); } // FIXME restore stereotype UMLClassifierListItem *c = object->asUMLClassifierListItem(); if (c) { if (!pkg->subordinates().contains(c)) pkg->subordinates().append(c); return true; } return pkg->addObject(object); } /** * Write text to the status bar. * @param text the text to write */ void UMLDoc::writeToStatusBar(const QString &text) { emit sigWriteToStatusBar(text); } /** * Simple removal of an object. * @param object the UMLObject to be removed */ void UMLDoc::slotRemoveUMLObject(UMLObject* object) { //m_objectList.remove(object); UMLPackage *pkg = object->umlPackage(); if (pkg == 0) { uError() << object->name() << ": parent package is not set !"; return; } pkg->removeObject(object); } /** * Returns true if the given name is unique within its scope. * * @param name The name to check. * @return True if name is unique. */ bool UMLDoc::isUnique(const QString &name) { UMLListView *listView = UMLApp::app()->listView(); UMLListViewItem *currentItem = (UMLListViewItem*)listView->currentItem(); UMLListViewItem *parentItem = 0; // check for current item, if its a package, then we do a check on that // otherwise, if current item exists, find its parent and check if thats // a package.. if (currentItem) { // its possible that the current item *is* a package, then just // do check now if (Model_Utils::typeIsContainer(currentItem->type())) { return isUnique (name, (UMLPackage*) currentItem->umlObject()); } parentItem = (UMLListViewItem*)currentItem->parent(); } // item is in a package so do check only in that if (parentItem != 0 && Model_Utils::typeIsContainer(parentItem->type())) { UMLPackage *parentPkg = parentItem->umlObject()->asUMLPackage(); return isUnique(name, parentPkg); } uError() << name << ": Not currently in a package"; /* Check against all objects that _don't_ have a parent package. for (UMLObjectListIt oit(m_objectList); oit.current(); ++oit) { UMLObject *obj = oit.current(); if ((obj->getUMLPackage() == 0) && (obj->getName() == name)) return false; } */ return true; } /** * Returns true if the given name is unique within its scope of given package. * * @param name The name to check. * @param package The UMLPackage in which we have to determine the unique-ness * @return True if name is unique. */ bool UMLDoc::isUnique(const QString &name, UMLPackage *package) { // if a package, then only do check in that if (package) { return (package->findObject(name) == 0); } // Not currently in a package: ERROR uError() << name << " (2): Not currently in a package"; /* Check against all objects that _don't_ have a parent package. for (UMLObjectListIt oit(m_objectList); oit.current(); ++oit) { UMLObject *obj = oit.current(); if ((obj->getUMLPackage() == 0) && (obj->getName() == name)) return false; } */ return true; } /** * Creates a stereotype for the parent object. * @param name the name of the stereotype */ UMLStereotype* UMLDoc::createStereotype(const QString &name) { UMLStereotype *s = new UMLStereotype(name, Uml::ID::fromString(name)); addStereotype(s); return s; } /** * Finds a UMLStereotype by its name. * * @param name The name of the UMLStereotype to find. * @return Pointer to the UMLStereotype found, or NULL if not found. */ UMLStereotype* UMLDoc::findStereotype(const QString &name) { foreach (UMLStereotype *s, m_stereoList) { if (s->name() == name) { return s; } } return 0; } /** * Finds or creates a stereotype for the parent object. * @param name the name of the stereotype * @return the found stereotype object or a just created one */ UMLStereotype* UMLDoc::findOrCreateStereotype(const QString &name) { UMLStereotype *s = findStereotype(name); if (s != 0) { return s; } return createStereotype(name); } /** * Find a UMLStereotype by its unique ID. * @param id the unique ID * @return the found stereotype or NULL */ UMLStereotype * UMLDoc::findStereotypeById(Uml::ID::Type id) { foreach (UMLStereotype *s, m_stereoList) { if (s->id() == id) return s; } return 0; } /** * Add a UMLStereotype to the application. * @param s the stereotype to be added */ void UMLDoc::addStereotype(UMLStereotype *s) { if (m_stereotypesModel->addStereotype(s)) emit sigObjectCreated(s); } /** * Remove a UMLStereotype from the application. * @param s the stereotype to be removed */ void UMLDoc::removeStereotype(UMLStereotype *s) { if (m_stereotypesModel->removeStereotype(s)) emit sigObjectRemoved(s); } /** * Add a stereotype if it doesn't already exist. * Used by code generators, operations and attribute dialog. */ void UMLDoc::addDefaultStereotypes() { CodeGenerator *gen = UMLApp::app()->generator(); if (gen) { gen->createDefaultStereotypes(); } } /** * Returns a list of the stereotypes in this UMLDoc. * * @return List of UML stereotypes. */ const UMLStereotypeList& UMLDoc::stereotypes() const { return m_stereoList; } /** * Removes an association. * * @param assoc Pointer to the UMLAssociation to remove. * @param doSetModified Whether to mark the document as modified (default: true.) */ void UMLDoc::removeAssociation (UMLAssociation * assoc, bool doSetModified /*=true*/) { if (!assoc) { return; } // Remove the UMLAssociation from m_objectList. UMLPackage *pkg = assoc->umlPackage(); if (pkg == 0) { uError() << assoc->name() << ": parent package is not set !"; return; } pkg->removeObject(assoc); if (doSetModified) { // so we will save our document setModified(true); } } /** * Finds an association. * * @param assocType Type of the UMLAssociation to seek. * @param roleAObj Pointer to the role A UMLCanvasObject. * @param roleBObj Pointer to the role B UMLCanvasObject. * @param swap Optional pointer to boolean. * The bool is set to true if the association * matched with swapped roles, else it is set * to false. * @return Pointer to the UMLAssociation found or NULL if not found. */ UMLAssociation * UMLDoc::findAssociation(Uml::AssociationType::Enum assocType, const UMLObject *roleAObj, const UMLObject *roleBObj, bool *swap) { UMLAssociationList assocs = associations(); UMLAssociation *ret = 0; foreach (UMLAssociation* a, assocs) { if (a->getAssocType() != assocType) { continue; } if (a->getObject(Uml::RoleType::A) == roleAObj && a->getObject(Uml::RoleType::B) == roleBObj) { return a; } if (a->getObject(Uml::RoleType::A) == roleBObj && a->getObject(Uml::RoleType::B) == roleAObj) { ret = a; } } if (swap) { *swap = (ret != 0); } return ret; } /** * Creates AND adds an association between two UMLObjects. * Used by refactoring assistant. * NOTE: this method does not check if the association is valid / legal * * @param a The UMLObject "A" for the association (source) * @param b The UMLObject "B" for the association (destination) * @param type The association's type * @return The Association created */ UMLAssociation* UMLDoc::createUMLAssociation(UMLObject *a, UMLObject *b, Uml::AssociationType::Enum type) { bool swap; UMLAssociation *assoc = findAssociation(type, a, b, &swap); if (assoc == 0) { assoc = new UMLAssociation(type, a, b); assoc->setUMLPackage(a->umlPackage()); addAssociation(assoc); } return assoc; } /** * Adds an association. * * @param assoc Pointer to the UMLAssociation to add. */ void UMLDoc::addAssociation(UMLAssociation *assoc) { if (assoc == 0) { return; } // First, check that this association has not already been added. // This may happen when loading old XMI files where all the association // information was taken from the tag. UMLAssociationList assocs = associations(); foreach (UMLAssociation* a, assocs) { // check if its already been added (shouldn't be the case right now // as UMLAssociations only belong to one associationwidget at a time) if (a == assoc) { DEBUG(DBG_SRC) << "duplicate addition attempted"; return; } } // If we get here it's really a new association. // Add the UMLAssociation at the owning UMLPackage. UMLPackage *pkg = assoc->umlPackage(); if (pkg == 0) { uError() << assoc->name() << ": parent package is not set !"; return; } pkg->addObject(assoc); // I don't believe this appropriate, UMLAssociations ARENT UMLWidgets -b.t. // emit sigObjectCreated(o); setModified(true); } /** * Returns a name for the new object, appended with a number * if the default name is taken e.g. class diagram, class * diagram_1 etc. * @param type the diagram type * @return the unique view name */ QString UMLDoc::uniqueViewName(const Uml::DiagramType::Enum type) { QString dname; switch (type) { case Uml::DiagramType::UseCase: dname = i18n("use case diagram"); break; case Uml::DiagramType::Class: dname = i18n("class diagram"); break; case Uml::DiagramType::Sequence: dname = i18n("sequence diagram"); break; case Uml::DiagramType::Collaboration: dname = i18n("communication diagram"); break; case Uml::DiagramType::Object: dname = i18n("object diagram"); break; case Uml::DiagramType::State: dname = i18n("state diagram"); break; case Uml::DiagramType::Activity: dname = i18n("activity diagram"); break; case Uml::DiagramType::Component: dname = i18n("component diagram"); break; case Uml::DiagramType::Deployment: dname = i18n("deployment diagram"); break; case Uml::DiagramType::EntityRelationship: dname = i18n("entity relationship diagram"); break; default: uWarning() << "called with unknown diagram type"; break; } QString name = dname; for (int number = 1; findView(type, name, true); ++number) { name = dname + QLatin1Char('_') + QString::number(number); } return name; } /** * Returns true when loading a document file. * @return the value of the flag */ bool UMLDoc::loading() const { return m_bLoading || !m_bTypesAreResolved; } /** * Sets loading boolean flag to the value given. * @param state value to set */ void UMLDoc::setLoading(bool state /* = true */) { m_bLoading = state; } /** * Returns true when importing file(s). * @return the value of the flag */ bool UMLDoc::importing() const { return m_importing; } /** * Sets importing boolean flag to the value given. * @param state value to set */ void UMLDoc::setImporting(bool state /* = true */) { m_importing = state; } /** * Returns the m_bClosing flag. * @return the value of the flag */ bool UMLDoc::closing() const { return m_bClosing; } /** * Creates the name of the given diagram type. * @param type The type of diagram to create. * @param askForName If true shows a dialog box asking for name, * else uses a default name. * @return name of the new diagram */ QString UMLDoc::createDiagramName(Uml::DiagramType::Enum type, bool askForName /*= true */) { QString defaultName = uniqueViewName(type); QString name = defaultName; while (true) { if (askForName && !Dialog_Utils::askName(i18nc("diagram name", "Name"), i18n("Enter name:"), name)) break; if (name.length() == 0) { KMessageBox::error(0, i18n("That is an invalid name for a diagram."), i18n("Invalid Name")); } else if (findView(type, name)) { KMessageBox::error(0, i18n("A diagram is already using that name."), i18n("Not a Unique Name")); } else { return name; } } // end while return QString(); } /** * Creates a diagram of the given type. * * @param folder the folder in which tp create the diagram. * @param type the type of diagram to create * @param name the name for the diagram to create * @param id optional ID of new diagram * @return pointer to the UMLView of the new diagram */ UMLView* UMLDoc::createDiagram(UMLFolder *folder, Uml::DiagramType::Enum type, const QString& name, Uml::ID::Type id) { DEBUG(DBG_SRC) << "folder=" << folder->name() << " / type=" << Uml::DiagramType::toString(type) << " / name=" << name; if (id == Uml::ID::None) { id = UniqueID::gen(); } if (name.length() > 0) { UMLView* view = new UMLView(folder); view->umlScene()->setOptionState(Settings::optionState()); view->umlScene()->setName(name); view->umlScene()->setType(type); view->umlScene()->setID(id); addView(view); emit sigDiagramCreated(id); setModified(true); UMLApp::app()->enablePrint(true); changeCurrentView(id); return view; } return 0; } /** * Used to rename a document. This method takes care of everything. * You just need to give the ID of the diagram to the method. * * @param id The ID of the diagram to rename. */ void UMLDoc::renameDiagram(Uml::ID::Type id) { UMLView *view = findView(id); Uml::DiagramType::Enum type = view->umlScene()->type(); QString name = view->umlScene()->name(); while (true) { bool ok = Dialog_Utils::askName(i18nc("renaming diagram", "Name"), i18n("Enter name:"), name); if (!ok) { break; } if (name.length() == 0) { KMessageBox::error(0, i18n("That is an invalid name for a diagram."), i18n("Invalid Name")); } else if (!findView(type, name)) { view->umlScene()->setName(name); emit sigDiagramRenamed(id); setModified(true); break; } else { KMessageBox::error(0, i18n("A diagram is already using that name."), i18n("Not a Unique Name")); } } } /** * Used to rename a @ref UMLObject. The @ref UMLObject is to be an * actor, use case or concept. * * @param o The object to rename. */ void UMLDoc::renameUMLObject(UMLObject *o) { QString name = o->name(); while (true) { bool ok = Dialog_Utils::askName(i18nc("renaming uml object", "Name"), i18n("Enter name:"), name); if (!ok) { break; } if (name.length() == 0) { KMessageBox::error(0, i18n("That is an invalid name."), i18n("Invalid Name")); } else if (isUnique(name)) { UMLApp::app()->executeCommand(new Uml::CmdRenameUMLObject(o, name)); setModified(true); break; } else { KMessageBox::error(0, i18n("That name is already being used."), i18n("Not a Unique Name")); } } return; } /** * Used to rename an operation or attribute of a concept. * * @param o The attribute or operation to rename. */ void UMLDoc::renameChildUMLObject(UMLObject *o) { UMLClassifier* p = o->umlParent()->asUMLClassifier(); if (!p) { DEBUG(DBG_SRC) << "Cannot create object, no parent found."; return; } QString name = o->name(); while (true) { bool ok = Dialog_Utils::askName(i18nc("renaming child uml object", "Name"), i18n("Enter name:"), name); if (!ok) { break; } if (name.length() == 0) { KMessageBox::error(0, i18n("That is an invalid name."), i18n("Invalid Name")); } else if (p->findChildObject(name) == 0 || ((o->baseType() == UMLObject::ot_Operation) && KMessageBox::warningYesNo(0, i18n("The name you entered was not unique.\nIs this what you wanted?"), i18n("Name Not Unique"), KGuiItem(i18n("Use Name")), KGuiItem(i18n("Enter New Name"))) == KMessageBox::Yes)) { UMLApp::app()->executeCommand(new Uml::CmdRenameUMLObject(o, name)); setModified(true); break; } else { KMessageBox::error(0, i18n("That name is already being used."), i18n("Not a Unique Name")); } } } /** * Changes the current view (diagram) to the view with the given ID. * * @param id The ID of the view to change to. */ void UMLDoc::changeCurrentView(Uml::ID::Type id) { DEBUG(DBG_SRC) << "id=" << Uml::ID::toString(id); UMLView* view = findView(id); if (view) { UMLScene* scene = view->umlScene(); scene->setIsOpen(true); UMLApp::app()->setCurrentView(view); emit sigDiagramChanged(scene->type()); UMLApp::app()->setDiagramMenuItemsState(true); setModified(true); emit sigCurrentViewChanged(); // when clicking on a tab, the documentation of diagram is upated in docwindow UMLApp::app()->docWindow()->showDocumentation(scene); } else { uWarning() << "New current view was not found with id=" << Uml::ID::toString(id) << "!"; } } /** * Deletes a diagram from the current file. * * Undo command * * @param id The ID of the diagram to delete. */ void UMLDoc::removeDiagram(Uml::ID::Type id) { UMLView* umlView = findView(id); if (!umlView) { uError() << "Request to remove diagram " << Uml::ID::toString(id) << ": Diagram not found!"; return; } UMLScene* umlScene = umlView->umlScene(); if (Dialog_Utils::askDeleteDiagram(umlScene->name())) { UMLApp::app()->executeCommand(new Uml::CmdRemoveDiagram( umlScene->folder(), umlScene->type(), umlScene->name(), id )); } } /** * Deletes a diagram from the current file. * * @param id The ID of the diagram to delete. */ void UMLDoc::removeDiagramCmd(Uml::ID::Type id) { UMLApp::app()->docWindow()->updateDocumentation(true); UMLView* umlview = findView(id); if (!umlview) { uError() << "Request to remove diagram " << Uml::ID::toString(id) << ": Diagram not found!"; return; } removeView(umlview); emit sigDiagramRemoved(id); setModified(true); } /** * Return the currently selected root folder. * This will be an element from the m_root[] array. * @return the currently selected root folder or NULL */ UMLFolder *UMLDoc::currentRoot() { UMLView *currentView = UMLApp::app()->currentView(); if (currentView == 0) { if (m_pCurrentRoot) { return m_pCurrentRoot; } uError() << "m_pCurrentRoot is NULL"; return 0; } UMLFolder *f = currentView->umlScene()->folder(); while (f && f->umlPackage()) { f = f->umlParent()->asUMLFolder(); } return f; } /** * Set the current root folder. * * @param rootType The type of the root folder to set. * The element from m_root[] which is indexed * by this type is selected. */ void UMLDoc::setCurrentRoot(Uml::ModelType::Enum rootType) { m_pCurrentRoot = m_root[rootType]; } /** * Removes an @ref UMLObject from the current file. If this object * is being represented on a diagram it will also delete all those * representations. * * @param umlobject Pointer to the UMLObject to delete. * @param deleteObject Delete the UMLObject instance. */ void UMLDoc::removeUMLObject(UMLObject* umlobject, bool deleteObject) { if (umlobject == 0) { uError() << "called with NULL parameter"; return; } UMLApp::app()->docWindow()->updateDocumentation(true); UMLObject::ObjectType type = umlobject->baseType(); umlobject->setUMLStereotype(0); // triggers possible cleanup of UMLStereotype if (umlobject->asUMLClassifierListItem()) { UMLClassifier* parent = umlobject->umlParent()->asUMLClassifier(); if (parent == 0) { uError() << "parent of umlobject is NULL"; return; } if (type == UMLObject::ot_Operation) { parent->removeOperation(umlobject->asUMLOperation()); if (deleteObject) delete umlobject->asUMLOperation(); } else if (type == UMLObject::ot_EnumLiteral) { UMLEnum *e = parent->asUMLEnum(); e->removeEnumLiteral(umlobject->asUMLEnumLiteral()); } else if (type == UMLObject::ot_EntityAttribute) { UMLEntity *ent = parent->asUMLEntity(); ent->removeEntityAttribute(umlobject->asUMLClassifierListItem()); } else if (type == UMLObject::ot_UniqueConstraint || type == UMLObject::ot_ForeignKeyConstraint || type == UMLObject::ot_CheckConstraint) { UMLEntity* ent = parent->asUMLEntity(); ent->removeConstraint(umlobject->asUMLEntityConstraint()); } else { UMLClassifier* pClass = parent->asUMLClassifier(); if (pClass == 0) { uError() << "parent of umlobject has unexpected type " << parent->baseType(); return; } if (type == UMLObject::ot_Attribute) { pClass->removeAttribute(umlobject->asUMLAttribute()); } else if (type == UMLObject::ot_Template) { pClass->removeTemplate(umlobject->asUMLTemplate()); if (deleteObject) delete umlobject->asUMLTemplate(); } else { uError() << "umlobject has unexpected type " << type; } } } else { if (type == UMLObject::ot_Association) { UMLAssociation *a = umlobject->asUMLAssociation(); removeAssociation(a, false); // don't call setModified here, it's done below emit sigObjectRemoved(umlobject); if (deleteObject) delete a; } else { UMLPackage* pkg = umlobject->umlPackage(); if (pkg) { // Remove associations that this object may participate in. UMLCanvasObject *c = umlobject->asUMLCanvasObject(); if (c) { // In the current implementation, all associations live in the // root folder. UMLPackage* rootPkg = Model_Utils::rootPackage(c); if (rootPkg == 0) { uError() << umlobject->name() << ": root package is not set !"; return; } UMLObjectList &rootObjects = rootPkg->containedObjects(); // Store the associations to remove in a buffer because we // should not remove elements from m_objectList while it is // being iterated over. UMLAssociationList assocsToRemove; foreach (UMLObject *obj, rootObjects) { uIgnoreZeroPointer(obj); if (obj->baseType() == UMLObject::ot_Association) { UMLAssociation *assoc = obj->asUMLAssociation(); if (c->hasAssociation(assoc)) { assocsToRemove.append(assoc); } } } foreach (UMLAssociation *a, assocsToRemove) { removeAssociation(a, false); } } pkg->removeObject(umlobject); emit sigObjectRemoved(umlobject); if (deleteObject) delete umlobject; } else { uError() << umlobject->name() << ": parent package is not set !"; } } } setModified(true); } /** * Signal that a UMLObject has been created. * * @param o The object that has been created. */ void UMLDoc::signalUMLObjectCreated(UMLObject * o) { emit sigObjectCreated(o); /* This is the wrong place to do: setModified(true); Instead, that should be done by the callers when object creation and all its side effects (e.g. new widget in view, new list view item, etc.) is finalized. */ } /** * Set the name of this model. */ void UMLDoc::setName(const QString& name) { m_Name = name; } /** * Return the name of this model. */ QString UMLDoc::name() const { return m_Name; } /** * Set coordinates resolution for current document. * @param resolution document resolution in DPI */ void UMLDoc::setResolution(qreal resolution) { m_resolution = resolution; uDebug() << "screen dpi:" << qApp->desktop()->logicalDpiX() << "file dpi:" << resolution << "scale:" << qApp->desktop()->logicalDpiX() / resolution; } /** * Returns coordinates resolution for current document. * @return document resolution in DPI */ qreal UMLDoc::resolution() const { return m_resolution; } /** * Returns scale factor for recalculation of document coordinates. * @return scale factor */ qreal UMLDoc::dpiScale() const { #ifdef ENABLE_XMIRESOLUTION if (resolution() != 0.0) return (qreal)qApp->desktop()->logicalDpiX() / resolution(); else #endif return 1.0; } /** * Return the m_modelID (currently this a fixed value: * Umbrello supports only a single document.) */ Uml::ID::Type UMLDoc::modelID() const { return m_modelID; } /** * This method is called for saving the given model as a XMI file. * It is virtual and calls the corresponding saveToXMI1() functions * of the derived classes. * * @param file The file to be saved to. */ void UMLDoc::saveToXMI1(QIODevice& file) { QDomDocument doc; QDomProcessingInstruction xmlHeading = doc.createProcessingInstruction(QLatin1String("xml"), QString::fromLatin1("version=\"1.0\" encoding=\"UTF-8\"")); doc.appendChild(xmlHeading); QDomElement root = doc.createElement(QLatin1String("XMI")); root.setAttribute(QLatin1String("xmi.version"), QLatin1String("1.2")); QDateTime now = QDateTime::currentDateTime(); root.setAttribute(QLatin1String("timestamp"), now.toString(Qt::ISODate)); root.setAttribute(QLatin1String("verified"), QLatin1String("false")); root.setAttribute(QLatin1String("xmlns:UML"), QLatin1String("http://schema.omg.org/spec/UML/1.4")); doc.appendChild(root); QDomElement header = doc.createElement(QLatin1String("XMI.header")); QDomElement meta = doc.createElement(QLatin1String("XMI.metamodel")); meta.setAttribute(QLatin1String("xmi.name"), QLatin1String("UML")); meta.setAttribute(QLatin1String("xmi.version"), QLatin1String("1.4")); meta.setAttribute(QLatin1String("href"), QLatin1String("UML.xml")); header.appendChild(meta); /** * bugs.kde.org/56184 comment by M. Alanen 2004-12-19: * " XMI.model requires xmi.version. (or leave the whole XMI.model out, * it's not required) " QDomElement model = doc.createElement("XMI.model"); QFile* qfile = dynamic_cast(&file); if (qfile) { QString modelName = qfile->name(); modelName = modelName.section('/', -1); modelName = modelName.section('.', 0, 0); model.setAttribute("xmi.name", modelName); model.setAttribute("href", qfile->name()); } */ QDomElement documentation = doc.createElement(QLatin1String("XMI.documentation")); // If we consider it useful we might add user and contact details // QDomElement owner = doc.createElement("XMI.owner"); // owner.appendChild(doc.createTextNode("Jens Kruger")); // Add a User // documentation.appendChild(owner); // QDomElement contact = doc.createElement("XMI.contact"); // contact.appendChild(doc.createTextNode("je.krueger@web.de")); // add a contact // documentation.appendChild(contact); QDomElement exporter = doc.createElement(QLatin1String("XMI.exporter")); exporter.appendChild(doc.createTextNode(QLatin1String("umbrello uml modeller http://umbrello.kde.org"))); documentation.appendChild(exporter); QDomElement exporterVersion = doc.createElement(QLatin1String("XMI.exporterVersion")); exporterVersion.appendChild(doc.createTextNode(QLatin1String(XMI_FILE_VERSION))); documentation.appendChild(exporterVersion); // all files are now saved with correct Unicode encoding, we add this // information to the header, so that the file will be loaded correctly QDomElement exporterEncoding = doc.createElement(QLatin1String("XMI.exporterEncoding")); exporterEncoding.appendChild(doc.createTextNode(QLatin1String("UnicodeUTF8"))); documentation.appendChild(exporterEncoding); header.appendChild(documentation); // See comment on above // header.appendChild(model); header.appendChild(meta); root.appendChild(header); QDomElement content = doc.createElement(QLatin1String("XMI.content")); QDomElement contentNS = doc.createElement(QLatin1String("UML:Namespace.contents")); QDomElement objectsElement = doc.createElement(QLatin1String("UML:Model")); objectsElement.setAttribute(QLatin1String("xmi.id"), Uml::ID::toString(m_modelID)); objectsElement.setAttribute(QLatin1String("name"), m_Name); objectsElement.setAttribute(QLatin1String("isSpecification"), QLatin1String("false")); objectsElement.setAttribute(QLatin1String("isAbstract"), QLatin1String("false")); objectsElement.setAttribute(QLatin1String("isRoot"), QLatin1String("false")); objectsElement.setAttribute(QLatin1String("isLeaf"), QLatin1String("false")); QDomElement ownedNS = doc.createElement(QLatin1String("UML:Namespace.ownedElement")); // Save stereotypes and toplevel datatypes first so that upon loading // they are known first. // There is a bug causing duplication of the same stereotype in m_stereoList. // As a workaround, we use a string list to memorize which stereotype has been saved. QStringList stereoNames; foreach (UMLStereotype *s, m_stereoList) { QString stName = s->name(); if (!stereoNames.contains(stName)) { s->saveToXMI1(doc, ownedNS); stereoNames.append(stName); } } for (int i = 0; i < Uml::ModelType::N_MODELTYPES; ++i) { m_root[i]->saveToXMI1(doc, ownedNS); } objectsElement.appendChild(ownedNS); content.appendChild(objectsElement); root.appendChild(content); // Save the XMI extensions: docsettings, diagrams, listview, and codegeneration. QDomElement extensions = doc.createElement(QLatin1String("XMI.extensions")); extensions.setAttribute(QLatin1String("xmi.extender"), QLatin1String("umbrello")); QDomElement docElement = doc.createElement(QLatin1String("docsettings")); Uml::ID::Type viewID = Uml::ID::None; UMLView *currentView = UMLApp::app()->currentView(); if (currentView) { viewID = currentView->umlScene()->ID(); } docElement.setAttribute(QLatin1String("viewid"), Uml::ID::toString(viewID)); docElement.setAttribute(QLatin1String("documentation"), m_Doc); docElement.setAttribute(QLatin1String("uniqueid"), Uml::ID::toString(UniqueID::get())); extensions.appendChild(docElement); // save listview UMLApp::app()->listView()->saveToXMI1(doc, extensions); // save code generator CodeGenerator *codegen = UMLApp::app()->generator(); if (codegen) { QDomElement codeGenElement = doc.createElement(QLatin1String("codegeneration")); codegen->saveToXMI1(doc, codeGenElement); extensions.appendChild(codeGenElement); } root.appendChild(extensions); QTextStream stream(&file); stream.setCodec("UTF-8"); stream << doc.toString(); } /** * Checks the given XMI file if it was saved with correct Unicode * encoding set or not. * * @param file The file to be checked. */ short UMLDoc::encoding(QIODevice & file) { QTextStream stream(&file); stream.setCodec("UTF-8"); QString data = stream.readAll(); QString error; int line; QDomDocument doc; if (!doc.setContent(data, false, &error, &line)) { uWarning() << "Cannot set content: " << error << " Line: " << line; return ENC_UNKNOWN; } // we start at the beginning and go to the point in the header where we can // find out if the file was saved using Unicode QDomNode node = doc.firstChild(); short enc = ENC_UNKNOWN; while (node.isComment() || node.isProcessingInstruction()) { if (node.isProcessingInstruction()) { const QDomProcessingInstruction& pi = node.toProcessingInstruction(); QRegExp rx(QLatin1String("\\bencoding=['\"]([^'\"]+)['\"]")); const int pos = rx.indexIn(pi.data()); if (pos >= 0) { const QString& encData = rx.cap(1); if (encData == QLatin1String("UTF-8")) { enc = ENC_UNICODE; } else if (encData == QLatin1String("windows-1252")) { enc = ENC_WINDOWS; } else { uDebug() << "ProcessingInstruction encoding=" << encData << " is not yet implemented"; enc = ENC_OLD_ENC; } } } node = node.nextSibling(); } QDomElement root = node.toElement(); if (root.isNull()) { uDebug() << "Null element at " << node.nodeName() << " : " << node.nodeValue(); return enc; } // make sure it is an XMI file if (root.tagName() != QLatin1String("XMI") && root.tagName() != QLatin1String("xmi:XMI")) { uDebug() << "Unknown tag at " << root.tagName(); return enc; } if (node.firstChild().isNull()) { uDebug() << "No child at " << node.nodeName() << " : " << node.nodeValue(); return enc; } node = node.firstChild(); QDomElement element = node.toElement(); // check header if (element.isNull()) { uDebug() << "No element at " << node.nodeName() << " : " << node.nodeValue(); return enc; } if (element.tagName() != QLatin1String("XMI.header")) { uDebug() << "Expecting XMI.header at " << element.tagName(); return enc; } QDomNode headerNode = node.firstChild(); while (!headerNode.isNull()) { QDomElement headerElement = headerNode.toElement(); // the information if Unicode was used is now stored in the // XMI.documentation section of the header if (headerElement.isNull() || headerElement.tagName() != QLatin1String("XMI.documentation")) { headerNode = headerNode.nextSibling(); continue; } QDomNode docuNode = headerNode.firstChild(); while (!docuNode.isNull()) { QDomElement docuElement = docuNode.toElement(); // a tag XMI.exporterEncoding was added since version 1.2 to // mark a file as saved with Unicode if (! docuElement.isNull() && docuElement.tagName() == QLatin1String("XMI.exporterEncoding")) { // at the moment this isn't really necessary but maybe // later we will have other encoding standards if (docuElement.text() == QLatin1String("UnicodeUTF8")) { return ENC_UNICODE; // stop here } } docuNode = docuNode.nextSibling(); } break; } return ENC_OLD_ENC; } /** * Load a given XMI model from a file. If the encoding of the file * is already known it can be passed to the function. If this info * isn't given, loadFromXMI1 will check which encoding was used. * * @param file The file to be loaded. * @param encode The encoding used. */ bool UMLDoc::loadFromXMI1(QIODevice & file, short encode) { // old Umbrello versions (version < 1.2) didn't save the XMI in Unicode // this wasn't correct, because non Latin1 chars where lost // to ensure backward compatibility we have to ensure to load the old files // with non Unicode encoding if (encode == ENC_UNKNOWN) { if ((encode = encoding(file)) == ENC_UNKNOWN) { return false; } file.reset(); } QTextStream stream(&file); if (encode == ENC_UNICODE) { stream.setCodec("UTF-8"); } else if (encode == ENC_WINDOWS) { stream.setCodec("windows-1252"); } QString data = stream.readAll(); qApp->processEvents(); // give UI events a chance QString error; int line; QDomDocument doc; if (!doc.setContent(data, false, &error, &line)) { uWarning() << "Cannot set content:" << error << " Line:" << line; return false; } qApp->processEvents(); // give UI events a chance QDomNode node = doc.firstChild(); //Before Umbrello 1.1-rc1 we didn't add a listView(); lv->setTitle(0, m_Name); recognized = true; } if (outerTag != QLatin1String("XMI.content")) { if (!recognized) { DEBUG(DBG_SRC) << "skipping <" << outerTag << ">"; } continue; } bool seen_UMLObjects = false; //process content for (QDomNode child = node.firstChild(); !child.isNull(); child = child.nextSibling()) { if (child.isComment()) { continue; } element = child.toElement(); QString tag = element.tagName(); if (tag == QLatin1String("umlobjects") // for bkwd compat. || tagEq(tag, QLatin1String("Subsystem")) || tagEq(tag, QLatin1String("Project")) // Embarcadero's Describe || tagEq(tag, QLatin1String("Model"))) { if(!loadUMLObjectsFromXMI1(element)) { uWarning() << "failed load on objects"; return false; } m_Name = element.attribute(QLatin1String("name"), i18n("UML Model")); UMLListView *lv = UMLApp::app()->listView(); lv->setTitle(0, m_Name); seen_UMLObjects = true; } else if (tagEq(tag, QLatin1String("Package")) || tagEq(tag, QLatin1String("Class")) || tagEq(tag, QLatin1String("Interface"))) { // These tests are only for foreign XMI files that // are missing the tag (e.g. NSUML) QString stID = element.attribute(QLatin1String("stereotype")); UMLObject *pObject = Object_Factory::makeObjectFromXMI(tag, stID); if (!pObject) { uWarning() << "Unknown type of umlobject to create: " << tag; // We want a best effort, therefore this is handled as a // soft error. continue; } UMLObject::ObjectType ot = pObject->baseType(); // Set the parent root folder. UMLPackage *pkg = 0; if (ot != UMLObject::ot_Stereotype) { if (ot == UMLObject::ot_Datatype) { pkg = m_datatypeRoot; } else { Uml::ModelType::Enum guess = Model_Utils::guessContainer(pObject); if (guess != Uml::ModelType::N_MODELTYPES) { pkg = m_root[guess]; } else { uError() << "Guess is Uml::ModelType::N_MODELTYPES - package not set correctly for " << pObject->name() << " / base type " << pObject->baseTypeStr(); pkg = m_root[Uml::ModelType::Logical]; } } } pObject->setUMLPackage(pkg); bool status = pObject->loadFromXMI1(element); if (!status) { delete pObject; return false; } seen_UMLObjects = true; } else if (tagEq(tag, QLatin1String("TaggedValue"))) { // This tag is produced here, i.e. outside of , // by the Unisys.JCR.1 Rose-to-XMI tool. if (! seen_UMLObjects) { DEBUG(DBG_SRC) << "skipping TaggedValue because not seen_UMLObjects"; continue; } tag = element.attribute(QLatin1String("tag")); if (tag != QLatin1String("documentation")) { continue; } QString modelElement = element.attribute(QLatin1String("modelElement")); if (modelElement.isEmpty()) { DEBUG(DBG_SRC) << "skipping TaggedValue(documentation) because " << "modelElement.isEmpty()"; continue; } UMLObject *o = findObjectById(Uml::ID::fromString(modelElement)); if (o == 0) { DEBUG(DBG_SRC) << "TaggedValue(documentation): cannot find object" << " for modelElement " << modelElement; continue; } QString value = element.attribute(QLatin1String("value")); if (! value.isEmpty()) { o->setDoc(value); } } else { // for backward compatibility loadExtensionsFromXMI1(child); } } } resolveTypes(); loadDiagrams1(); // set a default code generator if no tag seen if (UMLApp::app()->generator() == 0) { UMLApp::app()->setGenerator(UMLApp::app()->defaultLanguage()); } emit sigWriteToStatusBar(i18n("Setting up the document...")); qApp->processEvents(); // give UI events a chance activateAllViews(); UMLView *viewToBeSet = 0; if (m_nViewID != Uml::ID::None) { viewToBeSet = findView(m_nViewID); } if (viewToBeSet) { changeCurrentView(m_nViewID); } else { QString name = createDiagramName(Uml::DiagramType::Class, false); createDiagram(m_root[Uml::ModelType::Logical], Uml::DiagramType::Class, name); m_pCurrentRoot = m_root[Uml::ModelType::Logical]; } emit sigResetStatusbarProgress(); return true; } /** * Type resolution pass. */ void UMLDoc::resolveTypes() { // Resolve the types. // This is done in a separate pass because of possible forward references. if (m_bTypesAreResolved) { return; } writeToStatusBar(i18n("Resolving object references...")); for (int i = 0; i < Uml::ModelType::N_MODELTYPES; ++i) { UMLFolder *obj = m_root[i]; #ifdef VERBOSE_DEBUGGING DEBUG(DBG_SRC) << "UMLDoc: invoking resolveRef() for " << obj->name() << " (id=" << Uml::ID::toString(obj->id()) << ")"; #endif obj->resolveRef(); } m_bTypesAreResolved = true; qApp->processEvents(); // give UI events a chance } /** * Load all diagrams collected from the xmi file. * * Loading diagrams is implemented as additional pass to avoid unresolved * uml objects which are defined later in the xmi file. */ bool UMLDoc::loadDiagrams1() { bool result = true; DiagramsMap::const_iterator i; for (i = m_diagramsToLoad.constBegin(); i != m_diagramsToLoad.constEnd(); i++) { UMLFolder *f = i.key(); foreach(QDomNode node, i.value()) if (!f->loadDiagramsFromXMI1(node)) result = false; } m_diagramsToLoad.clear(); return result; } /** * Add a xml node containing a diagram to the list of diagrams to load. * Helper function for loadDiagrams(). * * @param folder pointer to UMFolder instance the diagrams belongs to * @param node xml document node containing the diagram */ void UMLDoc::addDiagramToLoad(UMLFolder *folder, QDomNode node) { if (m_diagramsToLoad.contains(folder)) m_diagramsToLoad[folder].append(node); else m_diagramsToLoad[folder] = QList() << node; } DiagramsModel *UMLDoc::diagramsModel() { return m_diagramsModel; } ObjectsModel *UMLDoc::objectsModel() { return m_objectsModel; } StereotypesModel *UMLDoc::stereotypesModel() { return m_stereotypesModel; } /** * Ensures the XMI file is a valid UML file. * Currently only checks for metamodel=UML. * * @param headerNode The node */ bool UMLDoc::validateXMI1Header(QDomNode& headerNode) { QDomElement headerElement = headerNode.toElement(); while (!headerNode.isNull()) { /* //Seems older Umbrello files used a different metamodel, so don't validate it for now if(!headerElement.isNull() && headerElement.tagName() == "XMI.metamodel") { String metamodel = headerElement.attribute("xmi.name"); if (metamodel != "UML") { return false; } } */ headerNode = headerNode.nextSibling(); headerElement = headerNode.toElement(); } return true; } /** * Loads all UML objects from XMI into the current UMLDoc. * * @return True if operation successful. */ bool UMLDoc::loadUMLObjectsFromXMI1(QDomElement& element) { /* FIXME need a way to make status bar actually reflect how much of the file has been loaded rather than just counting to 10 (an arbitrary number) emit sigResetStatusbarProgress(); emit sigSetStatusbarProgress(0); emit sigSetStatusbarProgressSteps(10); m_count = 0; */ emit sigWriteToStatusBar(i18n("Loading UML elements...")); // For Umbrello native XMI files, when called from loadFromXMI1() we // get here with Element.tagName() == "UML:Model" from the XMI input: // for (QDomNode node = element.firstChild(); !node.isNull(); node = node.nextSibling()) { if (node.isComment()) { continue; } QDomElement tempElement = node.toElement(); QString type = tempElement.tagName(); if (tagEq(type, QLatin1String("Model"))) { // Handling of Umbrello native XMI files: // We get here from a recursive call to loadUMLObjectsFromXMI() // a few lines below, see // if (tagEq(type, "Namespace.ownedElement") .... // Inside this Namespace.ownedElement envelope there are the // four submodels: // // // // // These are ultimately loaded by UMLFolder::loadFromXMI1() // Furthermore, in Umbrello native XMI format this // Namespace.ownedElement is the container of all stereotypes // (). bool foundUmbrelloRootFolder = false; QString name = tempElement.attribute(QLatin1String("name")); for (int i = 0; i < Uml::ModelType::N_MODELTYPES; ++i) { if (name == m_root[i]->name()) { m_pCurrentRoot = m_root[i]; m_root[i]->loadFromXMI1(tempElement); foundUmbrelloRootFolder = true; break; } } if (foundUmbrelloRootFolder) { continue; } } if (tagEq(type, QLatin1String("Namespace.ownedElement")) || tagEq(type, QLatin1String("Namespace.contents")) || tagEq(type, QLatin1String("Element.ownedElement")) || // Embarcadero's Describe tagEq(type, QLatin1String("Model"))) { //CHECK: Umbrello currently assumes that nested elements // are ownedElements anyway. // Therefore the tag is of no // significance. // The tagEq(type, "Namespace.contents") and tagEq(type, "Model") // tests do not become true for Umbrello native files, only for // some foreign XMI files. if (!loadUMLObjectsFromXMI1(tempElement)) { uWarning() << "failed load on " << type; return false; } continue; } // From here on, it's support for stereotypes, pre 1.5.5 versions, // and foreign files if (Model_Utils::isCommonXMI1Attribute(type)) { continue; } else if (tagEq(type, QLatin1String("packagedElement")) || tagEq(type, QLatin1String("ownedElement"))) { type = tempElement.attribute(QLatin1String("xmi:type")); } if (!tempElement.hasAttribute(QLatin1String("xmi.id")) && !tempElement.hasAttribute(QLatin1String("xmi:id"))) { QString idref = tempElement.attribute(QLatin1String("xmi.idref")); if (! idref.isEmpty()) { DEBUG(DBG_SRC) << "resolution of xmi.idref " << idref << " is not yet implemented"; } else { uError() << "Cannot load " << type << " because xmi.id is missing"; } continue; } QString stID = tempElement.attribute(QLatin1String("stereotype")); UMLObject *pObject = Object_Factory::makeObjectFromXMI(type, stID); if (!pObject) { uWarning() << "Unknown type of umlobject to create: " << type; // We want a best effort, therefore this is handled as a // soft error. continue; } UMLObject::ObjectType ot = pObject->baseType(); // Set the parent root folder. UMLPackage *pkg = 0; if (ot != UMLObject::ot_Stereotype) { if (ot == UMLObject::ot_Datatype) { pkg = m_datatypeRoot; } else { Uml::ModelType::Enum guess = Model_Utils::guessContainer(pObject); if (guess != Uml::ModelType::N_MODELTYPES) { pkg = m_root[guess]; } else { uError() << "Guess is Uml::ModelType::N_MODELTYPES - package not set correctly for " << pObject->name() << " / base type " << pObject->baseTypeStr(); pkg = m_root[Uml::ModelType::Logical]; } } } pObject->setUMLPackage(pkg); bool status = pObject->loadFromXMI1(tempElement); if (!status) { delete pObject; return false; } pkg = pObject->umlPackage(); if (ot == UMLObject::ot_Stereotype) { UMLStereotype *s = pObject->asUMLStereotype(); UMLStereotype *exist = findStereotype(pObject->name()); if (exist) { if (exist->id() == pObject->id()) { delete pObject; } else { DEBUG(DBG_SRC) << "Stereotype " << pObject->name() << "(id=" << Uml::ID::toString(pObject->id()) << ") already exists with id=" << Uml::ID::toString(exist->id()); addStereotype(s); } } else { addStereotype(s); } continue; } if (pkg) { UMLObjectList &objects = pkg->containedObjects(); if (! objects.contains(pObject)) { DEBUG(DBG_SRC) << "CHECK: adding " << pObject->name() << " to " << pkg->name(); pkg->addObject(pObject); } } else if (ot != UMLObject::ot_Stereotype) { uError() << "Package is NULL for " << pObject->name(); return false; } /* FIXME see comment at loadUMLObjectsFromXMI1 emit sigSetStatusbarProgress(++m_count); */ } return true; } /** * Sets m_nViewID. */ void UMLDoc::setMainViewID(Uml::ID::Type viewID) { m_nViewID = viewID; } /** * Loads umbrello specific extensions from XMI to the UMLDoc. * The extension tags are: "docsettings", "diagrams", "listview", * and "codegeneration". */ void UMLDoc::loadExtensionsFromXMI1(QDomNode& node) { QDomElement element = node.toElement(); QString tag = element.tagName(); if (tag == QLatin1String("docsettings")) { QString viewID = element.attribute(QLatin1String("viewid"), QLatin1String("-1")); m_Doc = element.attribute(QLatin1String("documentation")); QString uniqueid = element.attribute(QLatin1String("uniqueid"), QLatin1String("0")); m_nViewID = Uml::ID::fromString(viewID); UniqueID::set(Uml::ID::fromString(uniqueid)); UMLApp::app()->docWindow()->reset(); } else if (tag == QLatin1String("diagrams") || tag == QLatin1String("UISModelElement")) { // For backward compatibility only: // Since version 1.5.5 diagrams are saved as part of the UMLFolder. QDomNode diagramNode = node.firstChild(); if (tag == QLatin1String("UISModelElement")) { // Unisys.IntegratePlus.2 element = diagramNode.toElement(); tag = element.tagName(); if (tag != QLatin1String("uisOwnedDiagram")) { uError() << "unknown child node " << tag; return; } diagramNode = diagramNode.firstChild(); } else { qreal resolution = 0.0; QString res = node.toElement().attribute(QLatin1String("resolution"), QLatin1String("")); if (!res.isEmpty()) { resolution = res.toDouble(); } if (resolution != 0.0) { UMLApp::app()->document()->setResolution(resolution); } else { // see UMLFolder::loadDiagramsFromXMI() UMLApp::app()->document()->setResolution(0.0); } } if (!loadDiagramsFromXMI1(diagramNode)) { uWarning() << "failed load on diagrams"; } } else if (tag == QLatin1String("listview")) { //FIXME: Need to resolveTypes() before loading listview, // else listview items are duplicated. resolveTypes(); if (!UMLApp::app()->listView()->loadFromXMI1(element)) { uWarning() << "failed load on listview"; } } else if (tag == QLatin1String("codegeneration")) { QDomNode cgnode = node.firstChild(); QDomElement cgelement = cgnode.toElement(); while (!cgelement.isNull()) { QString nodeName = cgelement.tagName(); QString lang = cgelement.attribute(QLatin1String("language"), QLatin1String("UNKNOWN")); Uml::ProgrammingLanguage::Enum pl = Uml::ProgrammingLanguage::fromString(lang); CodeGenerator *g = UMLApp::app()->setGenerator(pl); g->loadFromXMI1(cgelement); cgnode = cgnode.nextSibling(); cgelement = cgnode.toElement(); } if (UMLApp::app()->generator() == 0) { UMLApp::app()->setGenerator(UMLApp::app()->defaultLanguage()); } } } /** * Loads all diagrams from XMI into the current UMLDoc. * For backward compatibility only: * Since version 1.5.5 diagrams are saved as part of the UMLFolder. * * @return True if operation successful. */ bool UMLDoc::loadDiagramsFromXMI1(QDomNode & node) { emit sigWriteToStatusBar(i18n("Loading diagrams...")); emit sigResetStatusbarProgress(); emit sigSetStatusbarProgress(0); emit sigSetStatusbarProgressSteps(10); //FIX ME QDomElement element = node.toElement(); if (element.isNull()) { return true; //return ok as it means there is no umlobjects } const Settings::OptionState state = Settings::optionState(); UMLView * pView = 0; int count = 0; while (!element.isNull()) { QString tag = element.tagName(); if (tag == QLatin1String("diagram") || tag == QLatin1String("UISDiagram")) { pView = new UMLView(0); // IMPORTANT: Set OptionState of new UMLView _BEFORE_ // reading the corresponding diagram: // + allow using per-diagram color and line-width settings // + avoid crashes due to uninitialized values for lineWidth pView->umlScene()->setOptionState(state); bool success = false; if (tag == QLatin1String("UISDiagram")) { success = pView->umlScene()->loadUISDiagram(element); } else { success = pView->umlScene()->loadFromXMI1(element); } if (!success) { uWarning() << "failed load on viewdata loadfromXMI"; delete pView; return false; } // Put diagram in default predefined folder. // @todo pass in the parent folder - it might be a user defined one. Uml::ModelType::Enum mt = Model_Utils::convert_DT_MT(pView->umlScene()->type()); pView->umlScene()->setFolder(m_root[mt]); pView->hide(); addView(pView); emit sigSetStatusbarProgress(++count); qApp->processEvents(); // give UI events a chance } node = node.nextSibling(); element = node.toElement(); } return true; } /** * Call to remove all the views (diagrams) in the current file. */ void UMLDoc::removeAllViews() { for (int i = 0; i < Uml::ModelType::N_MODELTYPES; ++i) { m_root[i]->removeAllViews(); } UMLApp::app()->setCurrentView(0); emit sigDiagramChanged(Uml::DiagramType::Undefined); UMLApp::app()->setDiagramMenuItemsState(false); } /** * Call to remove all objects in the current file. */ void UMLDoc::removeAllObjects() { m_root[Uml::ModelType::Logical]->removeObject(m_datatypeRoot); for (int i = 0; i < Uml::ModelType::N_MODELTYPES; ++i) { m_root[i]->removeAllObjects(); } } /** * Returns a list of the packages in this UMLDoc, * * @return List of UMLPackages. */ -UMLPackageList UMLDoc::packages(bool includeNested /* = true */) +UMLPackageList UMLDoc::packages(bool includeNested /* = true */, Uml::ModelType::Enum model) { UMLPackageList packageList; - m_root[Uml::ModelType::Logical]->appendPackages(packageList, includeNested); + m_root[model]->appendPackages(packageList, includeNested); return packageList; } /** * Returns the datatype folder. * * @return Pointer to the predefined folder for datatypes. */ UMLFolder * UMLDoc::datatypeFolder() const { return m_datatypeRoot; } /** * Returns a list of the concepts in this UMLDoc. * * @param includeNested Whether to include the concepts from * nested packages (default: true.) * @return List of UML concepts. */ UMLClassifierList UMLDoc::concepts(bool includeNested /* =true */) { UMLClassifierList conceptList; m_root[Uml::ModelType::Logical]->appendClassifiers(conceptList, includeNested); return conceptList; } /** * Returns a list of the classes and interfaces in this UMLDoc. * * @param includeNested Whether to include the concepts from * nested packages (default: true.) * @return List of UML concepts. */ UMLClassifierList UMLDoc::classesAndInterfaces(bool includeNested /* =true */) { UMLClassifierList conceptList; m_root[Uml::ModelType::Logical]->appendClassesAndInterfaces(conceptList, includeNested); return conceptList; } /** * Returns a list of the entities in this UMLDoc. * * @param includeNested Whether to include the entities from * nested packages (default: true.) * @return List of UML Entities. */ UMLEntityList UMLDoc::entities(bool includeNested /* =true */) { UMLEntityList entityList; m_root[Uml::ModelType::EntityRelationship]->appendEntities(entityList, includeNested); return entityList; } /** * Returns a list of the datatypes in this UMLDoc. * * @return List of datatypes. */ UMLClassifierList UMLDoc::datatypes() { UMLObjectList &objects = m_datatypeRoot->containedObjects(); UMLClassifierList datatypeList; foreach (UMLObject *obj, objects) { uIgnoreZeroPointer(obj); if (obj->isUMLDatatype()) { datatypeList.append(obj->asUMLClassifier()); } } return datatypeList; } /** * Returns a list of the associations in this UMLDoc. * * @return List of UML associations. */ UMLAssociationList UMLDoc::associations() { UMLAssociationList associationList; for (int i = 0; i < Uml::ModelType::N_MODELTYPES; ++i) { UMLAssociationList assocs = m_root[i]->getAssociations(); foreach (UMLAssociation* a, assocs) { associationList.append(a); } } return associationList; } /** * Controls the printing of the program. * * @param pPrinter The printer (object) to use. * @param selectPage The DiagramPrintPage by which diagrams are selected for printing */ void UMLDoc::print(QPrinter * pPrinter, DiagramPrintPage * selectPage) { UMLView * printView = 0; int count = selectPage->printUmlCount(); QPainter painter(pPrinter); for (int i = 0; i < count; ++i) { if (i>0) { pPrinter->newPage(); } QString sID = selectPage->printUmlDiagram(i); Uml::ID::Type id = Uml::ID::fromString(sID); printView = findView(id); if (printView) { printView->umlScene()->print(pPrinter, painter); } printView = 0; } painter.end(); } /** * Return the list of views for this document. * * @return List of UML views. */ UMLViewList UMLDoc::viewIterator() { UMLViewList accumulator; for (int i = 0; i < Uml::ModelType::N_MODELTYPES; ++i) { m_root[i]->appendViews(accumulator, true); } return accumulator; } /** * Sets the modified flag for the document after a modifying * action on the view connected to the document. * * @param modified The value to set the modified flag to. */ void UMLDoc::setModified(bool modified /*=true*/) { if (!m_bLoading) { m_modified = modified; UMLApp::app()->setModified(modified); } } /** * Returns if the document is modified or not. Use this to * determine if your document needs saving by the user on * closing. * * @return True if this UMLDoc is modified. */ bool UMLDoc::isModified() { return m_modified; } /** * Assigns an already created UMLObject a new ID. * If the object is a classifier then the operations/attributes * are also assigned new IDs. * * @param obj Pointer to the UMLObject to add. * @return True if operation successful. */ bool UMLDoc::assignNewIDs(UMLObject* obj) { if (!obj || !m_pChangeLog) { DEBUG(DBG_SRC) << "no obj || Changelog"; return false; } Uml::ID::Type result = assignNewID(obj->id()); obj->setID(result); //If it is a CONCEPT then change the ids of all its operations and attributes if (obj->baseType() == UMLObject::ot_Class) { UMLClassifier *c = obj->asUMLClassifier(); UMLClassifierListItemList attributes = c->getFilteredList(UMLObject::ot_Attribute); foreach (UMLObject* listItem, attributes) { result = assignNewID(listItem->id()); listItem->setID(result); } UMLClassifierListItemList templates = c->getFilteredList(UMLObject::ot_Template); foreach (UMLObject* listItem, templates) { result = assignNewID(listItem->id()); listItem->setID(result); } } if (obj->baseType() == UMLObject::ot_Interface || obj->baseType() == UMLObject::ot_Class) { UMLOperationList operations(((UMLClassifier*)obj)->getOpList()); foreach (UMLObject* listItem, operations) { result = assignNewID(listItem->id()); listItem->setID(result); } } setModified(true); return true; } /** * Return the predefined root folder of the given type. */ UMLFolder *UMLDoc::rootFolder(Uml::ModelType::Enum mt) { if (mt < Uml::ModelType::Logical || mt >= Uml::ModelType::N_MODELTYPES) { uError() << "illegal input value " << Uml::ModelType::toString(mt); return 0; } return m_root[mt]; } /** * Return the corresponding Model_Type if the given object * is one of the root folders. * When the given object is not one of the root folders then * return Uml::ModelType::N_MODELTYPES. */ Uml::ModelType::Enum UMLDoc::rootFolderType(UMLObject *obj) { for (int i = 0; i < Uml::ModelType::N_MODELTYPES; ++i) { const Uml::ModelType::Enum m = Uml::ModelType::fromInt(i); if (obj == m_root[m]) { return m; } } return Uml::ModelType::N_MODELTYPES; } /** * Read property of IDChangeLog* m_pChangeLog. * * @return Pointer to the IDChangeLog object. */ IDChangeLog* UMLDoc::changeLog() { return m_pChangeLog; } /** * Opens a Paste session, deletes the old ChangeLog and * creates an empty one. */ void UMLDoc::beginPaste() { if (m_pChangeLog) { delete m_pChangeLog; m_pChangeLog = 0; } m_pChangeLog = new IDChangeLog; } /** * Closes a paste session, deletes the ChangeLog. */ void UMLDoc::endPaste() { if (m_pChangeLog) { delete m_pChangeLog; m_pChangeLog = 0; } } /** * Assigns a New ID to an Object, and also logs the assignment * to its internal ChangeLog. * * @param oldID The present ID of the object. * @return The new ID assigned to the object. */ Uml::ID::Type UMLDoc::assignNewID(Uml::ID::Type oldID) { Uml::ID::Type result = UniqueID::gen(); if (m_pChangeLog) { m_pChangeLog->addIDChange(oldID, result); } return result; } /** * Returns the documentation for the project. * * @return The documentation text of this UMLDoc. */ QString UMLDoc::documentation() const { return m_Doc; } /** * Sets the documentation for the project. * * @param doc The documentation to set for this UMLDoc. */ void UMLDoc::setDocumentation(const QString &doc) { m_Doc = doc; } /** * Adds an already created UMLView to the document, it gets * assigned a new ID, if its name is already in use then the * function appends a number to it to differentiate it from * the others; this number is incremental so if number 1 is in * use then it tries 2 and then 3 and so on * * @param pView Pointer to the UMLView to add. * @return True if operation successful. */ bool UMLDoc::addUMLView(UMLView * pView) { if (!pView || !m_pChangeLog) { return false; } Uml::ID::Type oldID = pView->umlScene()->ID(); int i = 0; QString viewName = pView->umlScene()->name(); QString name = viewName; while (findView(pView->umlScene()->type(), name) != 0) { name = viewName + QLatin1Char('_') + QString::number(++i); } if (i) { //If name was modified pView->umlScene()->setName(name); } Uml::ID::Type newID = assignNewID(oldID); pView->umlScene()->setID(newID); pView->umlScene()->activateAfterLoad(true); pView->umlScene()->endPartialWidgetPaste(); pView->umlScene()->setOptionState(Settings::optionState()); addView(pView); emit sigDiagramCreated(pView->umlScene()->ID()); setModified(true); return true; } /** * Activate all the diagrams/views after loading so all their * widgets keep their IDs. */ void UMLDoc::activateAllViews() { // store old setting - for restore of last setting bool m_bLoading_old = m_bLoading; m_bLoading = true; //this is to prevent document becoming modified when activating a view for (int i = 0; i < Uml::ModelType::N_MODELTYPES; ++i) { m_root[i]->activateViews(); } m_bLoading = m_bLoading_old; } /** * Sets the default settings to the given settings. * @param optionState settings */ void UMLDoc::settingsChanged(Settings::OptionState &optionState) { for (int i = 0; i < Uml::ModelType::N_MODELTYPES; ++i) { m_root[i]->setViewOptions(optionState); } initSaveTimer(); } /** * Sets up the autosave timer. */ void UMLDoc::initSaveTimer() { if (m_pAutoSaveTimer) { m_pAutoSaveTimer->stop(); disconnect(m_pAutoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); delete m_pAutoSaveTimer; m_pAutoSaveTimer = 0; } Settings::OptionState optionState = Settings::optionState(); if (optionState.generalState.autosave) { m_pAutoSaveTimer = new QTimer(this); connect(m_pAutoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); m_pAutoSaveTimer->setSingleShot(false); m_pAutoSaveTimer->start(optionState.generalState.autosavetime * 60000); } } /** * Called after a specified time to autosave the document. */ void UMLDoc::slotAutoSave() { //Only save if modified. if (!m_modified) { return; } #if QT_VERSION >= 0x050000 QUrl tempUrl = m_doc_url; #else KUrl tempUrl = m_doc_url; #endif if (tempUrl.fileName() == i18n("Untitled")) { #if QT_VERSION >= 0x050000 tempUrl.setScheme(QLatin1String("file")); #endif tempUrl.setPath(QDir::homePath() + i18n("/autosave%1", QLatin1String(".xmi"))); saveDocument(tempUrl); setUrlUntitled(); m_modified = true; UMLApp::app()->setModified(m_modified); } else { // 2004-05-17 Achim Spangler #if QT_VERSION >= 0x050000 QUrl orgDocUrl = m_doc_url; #else KUrl orgDocUrl = m_doc_url; #endif QString orgFileName = m_doc_url.fileName(); // don't overwrite manually saved file with autosave content QString fileName = tempUrl.fileName(); Settings::OptionState optionState = Settings::optionState(); fileName.replace(QLatin1String(".xmi"), optionState.generalState.autosavesuffix); #if QT_VERSION >= 0x050000 tempUrl.setUrl(tempUrl.toString(QUrl::RemoveFilename) + fileName); #else tempUrl.setFileName(fileName); #endif // End Achim Spangler saveDocument(tempUrl); // 2004-05-17 Achim Spangler // re-activate m_modified if autosave is writing to other file // than the main project file->autosave-suffix != ".xmi" if (optionState.generalState.autosavesuffix != QLatin1String(".xmi")) { m_modified = true; UMLApp::app()->setModified(m_modified); } // restore original file name - // UMLDoc::saveDocument() sets doc_url to filename which is given as autosave-filename setUrl(orgDocUrl); UMLApp * pApp = UMLApp::app(); pApp->setCaption(orgFileName, isModified()); // End Achim Spangler } } /** * Signal a view/diagram has been renamed. */ void UMLDoc::signalDiagramRenamed(UMLView* view) { if (view) { Settings::OptionState optionState = Settings::optionState(); if (optionState.generalState.tabdiagrams) { UMLApp::app()->tabWidget()->setTabText(UMLApp::app()->tabWidget()->indexOf(view), view->umlScene()->name()); } emit sigDiagramRenamed(view->umlScene()->ID()); } else { uError() << "Cannot signal diagram renamed - view is NULL!"; } } /** * Calls the active code generator to create its default datatypes. */ void UMLDoc::addDefaultDatatypes() { CodeGenerator *cg = UMLApp::app()->generator(); if (cg == 0) { DEBUG(DBG_SRC) << "CodeGenerator is still NULL"; return; } QStringList entries = cg->defaultDatatypes(); QStringList::Iterator end(entries.end()); for (QStringList::Iterator it = entries.begin(); it != end; ++it) { createDatatype(*it); } UMLApp::app()->listView()->closeDatatypesFolder(); } /** * Add a datatype if it doesn't already exist. * Used by code generators and attribute dialog. */ void UMLDoc::createDatatype(const QString &name) { UMLObjectList &datatypes = m_datatypeRoot->containedObjects(); UMLObject* umlobject = Model_Utils::findUMLObject(datatypes, name, UMLObject::ot_Datatype, m_datatypeRoot); if (!umlobject) { Object_Factory::createUMLObject(UMLObject::ot_Datatype, name, m_datatypeRoot); } } /** * Make a popup menu for the tabs * signalled from tabWidget's contextMenu(). */ void UMLDoc::slotDiagramPopupMenu(QWidget* umlview, const QPoint& point) { UMLView* view = (UMLView*) umlview; UMLListViewItem::ListViewType type = UMLListViewItem::lvt_Unknown; switch (view->umlScene()->type()) { case Uml::DiagramType::Class: type = UMLListViewItem::lvt_Class_Diagram; break; case Uml::DiagramType::UseCase: type = UMLListViewItem::lvt_UseCase_Diagram; break; case Uml::DiagramType::Sequence: type = UMLListViewItem::lvt_Sequence_Diagram; break; case Uml::DiagramType::Collaboration: type = UMLListViewItem::lvt_Collaboration_Diagram; break; case Uml::DiagramType::State: type = UMLListViewItem::lvt_State_Diagram; break; case Uml::DiagramType::Activity: type = UMLListViewItem::lvt_Activity_Diagram; break; case Uml::DiagramType::Component: type = UMLListViewItem::lvt_Component_Diagram; break; case Uml::DiagramType::Deployment: type = UMLListViewItem::lvt_Deployment_Diagram; break; case Uml::DiagramType::EntityRelationship: type = UMLListViewItem::lvt_EntityRelationship_Diagram; break; default: uWarning() << "unknown diagram type " << view->umlScene()->type(); return; }//end switch UMLListViewItem item((UMLListView *)0, QString(), type); UMLListViewPopupMenu popup(UMLApp::app()->mainViewWidget(), &item); QAction *triggered = popup.exec(point); view->umlScene()->slotMenuSelection(triggered); } /** * Function for comparing tags in XMI files. */ bool UMLDoc::tagEq (const QString& inTag, const QString& inPattern) { QString tag = inTag; QString pattern = inPattern; tag.remove(QRegExp(QLatin1String("^\\w+:"))); // remove leading "UML:" or other int patSections = pattern.count(QLatin1Char('.')) + 1; QString tagEnd = tag.section(QLatin1Char('.'), -patSections); return (tagEnd.toLower() == pattern.toLower()); } diff --git a/umbrello/umldoc.h b/umbrello/umldoc.h index b85953639..13422fe0b 100644 --- a/umbrello/umldoc.h +++ b/umbrello/umldoc.h @@ -1,393 +1,401 @@ /*************************************************************************** * 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 * ***************************************************************************/ #ifndef UMLDOC_H #define UMLDOC_H // app includes #include "basictypes.h" #include "optionstate.h" #include "umlobject.h" #include "umlobjectlist.h" #include "umlassociationlist.h" #include "umlclassifierlist.h" #include "umlentitylist.h" #include "umlviewlist.h" #include "umlstereotypelist.h" #include "umlpackagelist.h" // kde includes #if QT_VERSION < 0x050000 #include #endif // qt includes #if QT_VERSION >= 0x050000 #include #endif // system includes #include #define ENC_UNKNOWN 0 #define ENC_UNICODE 1 #define ENC_WINDOWS 2 #define ENC_OLD_ENC 3 // forward declarations class QDomNode; class QDomElement; class QPrinter; class IDChangeLog; class DiagramsModel; class ObjectsModel; class StereotypesModel; class UMLPackage; class UMLFolder; class DiagramPrintPage; /** * UMLDoc provides a document object for a document-view model. * * The UMLDoc class provides a document object that can be used * in conjunction with the classes UMLApp and UMLView to create * a document-view model for standard KDE applications based on * KApplication and KMainWindow. Thereby, the document object * is created by the UMLApp instance and contains the document * structure with the according methods for manipulation of the * document data by UMLView objects. Also, UMLDoc contains the * methods for serialization of the document data from and to * files. * * @author Paul Hensgen * Bugs and comments to umbrello-devel@kde.org or http://bugs.kde.org */ class UMLDoc : public QObject { Q_OBJECT public: UMLDoc(); ~UMLDoc(); void init(); void addView(UMLView *view); void removeView(UMLView *view, bool enforceOneView = true); void setMainViewID(Uml::ID::Type viewID); void changeCurrentView(Uml::ID::Type id); void activateAllViews(); void removeAllViews(); void removeAllObjects(); void setModified(bool modified = true); bool isModified(); bool saveModified(); bool newDocument(); void closeDocument(); #if QT_VERSION >= 0x050000 bool openDocument(const QUrl& url, const char *format = 0); bool saveDocument(const QUrl& url, const char *format = 0); const QUrl& url() const; void setUrl(const QUrl& url); #else bool openDocument(const KUrl& url, const char *format = 0); bool saveDocument(const KUrl& url, const char *format = 0); const KUrl& url() const; void setUrl(const KUrl& url); #endif void setUrlUntitled(); void setupSignals(); bool isUnique(const QString &name); bool isUnique(const QString &name, UMLPackage *package); UMLAssociation* createUMLAssociation(UMLObject *a, UMLObject *b, Uml::AssociationType::Enum type); void addAssociation(UMLAssociation *assoc); void removeAssociation(UMLAssociation *assoc, bool doSetModified = true); UMLAssociation * findAssociation(Uml::AssociationType::Enum assocType, const UMLObject *roleAObj, const UMLObject *roleBObj, bool *swap = 0); QString createDiagramName(Uml::DiagramType::Enum type, bool askForName = true); UMLView* createDiagram(UMLFolder *folder, Uml::DiagramType::Enum type, const QString& name, Uml::ID::Type id = Uml::ID::None); void removeDiagram(Uml::ID::Type id); void removeDiagramCmd(Uml::ID::Type id); void renameDiagram(Uml::ID::Type id); void removeUMLObject(UMLObject* umlobject, bool deleteObject = false); void renameUMLObject(UMLObject *o); void renameChildUMLObject(UMLObject *o); UMLObject* findObjectById(Uml::ID::Type id); UMLObject* findUMLObject(const QString &name, UMLObject::ObjectType type = UMLObject::ot_UMLObject, UMLObject *currentObj = 0); UMLObject* findUMLObjectRaw(Uml::ModelType::Enum, const QString &name, UMLObject::ObjectType type = UMLObject::ot_UMLObject); UMLObject* findUMLObjectRaw(UMLFolder *folder, const QString &name, UMLObject::ObjectType type = UMLObject::ot_UMLObject); + UMLObject* findUMLObjectRecursive(Uml::ModelType::Enum, + const QString &name, + UMLObject::ObjectType type = UMLObject::ot_UMLObject); + + UMLObject* findUMLObjectRecursive(UMLFolder *folder, + const QString &name, + UMLObject::ObjectType type = UMLObject::ot_UMLObject); + UMLClassifier * findUMLClassifier(const QString &name); UMLView * findView(Uml::ID::Type id); UMLView * findView(Uml::DiagramType::Enum type, const QString &name, bool searchAllScopes = false); void setName(const QString& name); QString name() const; void setResolution(qreal resolution); qreal resolution() const; qreal dpiScale() const; Uml::ID::Type modelID() const; static bool tagEq (const QString& tag, const QString& pattern); virtual void saveToXMI1(QIODevice& file); short encoding(QIODevice & file); virtual bool loadFromXMI1(QIODevice& file, short encode = ENC_UNKNOWN); bool validateXMI1Header(QDomNode& headerNode); bool loadUMLObjectsFromXMI1(QDomElement & element); void loadExtensionsFromXMI1(QDomNode & node); bool loadDiagramsFromXMI1(QDomNode & node); void signalDiagramRenamed(UMLView * view); void signalUMLObjectCreated(UMLObject * o); UMLClassifierList concepts(bool includeNested = true); UMLClassifierList classesAndInterfaces(bool includeNested = true); UMLEntityList entities(bool includeNested = true); UMLFolder * datatypeFolder() const; UMLClassifierList datatypes(); UMLAssociationList associations(); - UMLPackageList packages(bool includeNested = true); + UMLPackageList packages(bool includeNested = true, Uml::ModelType::Enum model = Uml::ModelType::Logical); void print(QPrinter * pPrinter, DiagramPrintPage * selectPage); UMLViewList viewIterator(); bool assignNewIDs(UMLObject* obj); bool addUMLObject(UMLObject * object); bool addUMLView(UMLView * pView); UMLFolder *rootFolder(Uml::ModelType::Enum mt); Uml::ModelType::Enum rootFolderType(UMLObject *obj); UMLFolder *currentRoot(); void setCurrentRoot(Uml::ModelType::Enum rootType); virtual IDChangeLog* changeLog(); void beginPaste(); void endPaste(); Uml::ID::Type assignNewID(Uml::ID::Type oldID); void setDocumentation(const QString &doc); QString documentation() const; void settingsChanged(Settings::OptionState &optionState); QString uniqueViewName(const Uml::DiagramType::Enum type); bool loading() const; void setLoading(bool state = true); bool importing() const; void setImporting(bool state = true); bool closing() const; void addDefaultDatatypes(); void createDatatype(const QString &name); UMLStereotype *createStereotype(const QString &name); UMLStereotype *findStereotype(const QString &name); UMLStereotype *findOrCreateStereotype(const QString &name); UMLStereotype *findStereotypeById(Uml::ID::Type id); void addStereotype(UMLStereotype *s); void removeStereotype(UMLStereotype *s); void addDefaultStereotypes(); const UMLStereotypeList& stereotypes() const; void writeToStatusBar(const QString &text); void resolveTypes(); bool loadDiagrams1(); void addDiagramToLoad(UMLFolder *folder, QDomNode node); DiagramsModel *diagramsModel(); StereotypesModel *stereotypesModel(); ObjectsModel *objectsModel(); private: void initSaveTimer(); void createDatatypeFolder(); /** * Array of predefined root folders. */ UMLFolder *m_root[Uml::ModelType::N_MODELTYPES]; /** * Predefined root folder for datatypes, contained in * m_root[Uml::mt_Logical] */ UMLFolder *m_datatypeRoot; /** * The UMLDoc is the sole owner of all stereotypes. * UMLStereotype instances are reference counted. * When an UMLStereotype is no longer referenced anywhere, * its refcount drops to zero. It is then removed from the * m_stereoList and it is physically deleted. */ UMLStereotypeList m_stereoList; QString m_Name; ///< name of this model as stored in the tag Uml::ID::Type m_modelID; ///< xmi.id of this model in the int m_count; ///< auxiliary counter for the progress bar bool m_modified; #if QT_VERSION >= 0x050000 QUrl m_doc_url; #else KUrl m_doc_url; #endif /** * Contains all the UMLObject id changes of paste session. */ IDChangeLog* m_pChangeLog; /** * true if the we're loading a new document */ bool m_bLoading; /** * true if the we're importing */ bool m_importing; /** * Documentation for the project. */ QString m_Doc; /** * Used for autosave */ QTimer * m_pAutoSaveTimer; /** * Auxiliary to processing */ Uml::ID::Type m_nViewID; /** * True when type resolution pass has been executed. */ bool m_bTypesAreResolved; /** * Auxiliary variable for currentRoot(): * m_pCurrentRoot is only used if UMLApp::app()->currentView() * returns 0. */ UMLFolder * m_pCurrentRoot; /** * True while closeDocument() is executing. */ bool m_bClosing; DiagramsModel *m_diagramsModel; ObjectsModel *m_objectsModel; StereotypesModel *m_stereotypesModel; /** * Holds widgets coordinates resolution. * Unit is dpi. */ qreal m_resolution; /** * Holds diagram xml nodes on loading */ typedef QMap> DiagramsMap; DiagramsMap m_diagramsToLoad; public slots: void slotRemoveUMLObject(UMLObject*o); void slotAutoSave(); void slotDiagramPopupMenu(QWidget* umlview, const QPoint& point); signals: void sigDiagramCreated(Uml::ID::Type id); void sigDiagramRemoved(Uml::ID::Type id); void sigDiagramRenamed(Uml::ID::Type t); void sigDiagramChanged(Uml::DiagramType::Enum); void sigObjectCreated(UMLObject *); void sigObjectRemoved(UMLObject *); /** * Reset the status bar. */ void sigResetStatusbarProgress(); /** * Set the total range of the progressbar. * * @param totalSteps Total range of the progressbar (0..totalSteps) */ void sigSetStatusbarProgressSteps(int totalSteps); /** * Set the progress position of the progressbar. * * @param stepPosition The step position to set. */ void sigSetStatusbarProgress(int stepPosition); /** * Write text to the status bar. */ void sigWriteToStatusBar(const QString &text); /** * The diagram being displayed has changed. * UMLApp uses this to keep its menu items state up to date. */ void sigCurrentViewChanged(); }; #endif // UMLDOC_H diff --git a/umbrello/umlscene.cpp b/umbrello/umlscene.cpp index 7eff7232f..d05fe586b 100644 --- a/umbrello/umlscene.cpp +++ b/umbrello/umlscene.cpp @@ -1,4243 +1,4272 @@ /*************************************************************************** * 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 "umlscene.h" // application specific includes #include "activitywidget.h" #include "actorwidget.h" #include "artifactwidget.h" #include "association.h" #include "associationwidget.h" #include "assocrules.h" #include "attribute.h" #include "boxwidget.h" #include "classifier.h" #include "classifierwidget.h" #include "classoptionspage.h" #include "component.h" #include "cmds.h" #include "componentwidget.h" #include "datatype.h" #include "diagram_utils.h" #include "pinportbase.h" #include "datatypewidget.h" #include "debug_utils.h" #include "dialog_utils.h" #include "docwindow.h" #include "entity.h" #include "entitywidget.h" #include "enumwidget.h" #include "floatingtextwidget.h" #include "folder.h" #include "foreignkeyconstraint.h" #include "forkjoinwidget.h" #include "idchangelog.h" #include "import_utils.h" #include "layoutgenerator.h" #include "layoutgrid.h" #include "messagewidget.h" #include "model_utils.h" #include "notewidget.h" #include "object_factory.h" #include "objectnodewidget.h" #include "objectwidget.h" #include "package.h" #include "packagewidget.h" #include "pinwidget.h" #include "portwidget.h" #include "seqlinewidget.h" #include "signalwidget.h" #include "statewidget.h" #include "toolbarstate.h" #include "toolbarstatefactory.h" #include "uml.h" #include "umldoc.h" #include "umldragdata.h" #include "umlfiledialog.h" #include "umllistview.h" #include "umllistviewitem.h" #include "umlobject.h" #include "umlobjectlist.h" #include "umlrole.h" #include "umlscenepopupmenu.h" #include "umlview.h" #include "umlviewimageexporter.h" #include "umlwidget.h" #include "uniqueid.h" #include "widget_factory.h" #include "widget_utils.h" #include "widgetlist_utils.h" //kde include files #if QT_VERSION < 0x050000 #include #include #endif #include #include #include // include files for Qt #include #include #include #include #include #include // system includes #include // for ceil // static members const qreal UMLScene::defaultCanvasSize = 5000; bool UMLScene::m_showDocumentationIndicator = false; using namespace Uml; DEBUG_REGISTER(UMLScene) /** * The class UMLScenePrivate is intended to hold private * members/classes to reduce the size of the public class * and to speed up recompiling. * The migration to this class is not complete yet. */ class UMLScenePrivate { public: UMLScenePrivate(UMLScene *parent) : p(parent) { } /** * Check if there is a corresponding port widget * for all UMLPort instances and add if not. */ void addMissingPorts() { UMLWidgetList ports; UMLWidgetList components; foreach(UMLWidget *w, p->widgetList()) { if (w->isPortWidget()) ports.append(w); else if (w->isComponentWidget()) components.append(w); } foreach(UMLWidget *cw, components) { UMLComponent *c = cw->umlObject()->asUMLComponent(); if (!c) continue; // iterate through related ports for this component widget foreach(UMLObject *o, c->containedObjects()) { UMLPort *up = o->asUMLPort(); if (!up) continue; Uml::ID::Type id = o->id(); bool found = false; foreach(UMLWidget *p, ports) { if (p->id() == id) { found = true; break; } } if (!found) new PortWidget(p, up, cw); } } } /** * Check if port are located equally on the border of a component * and fix position if not. */ void fixPortPositions() { foreach(UMLWidget *w, p->widgetList()) { if (w->isPortWidget()) { QGraphicsItem *g = w->parentItem(); ComponentWidget *c = dynamic_cast(g); Q_ASSERT(c); qreal w2 = w->width()/2; qreal h2 = w->height()/2; if (w->x() <= -w2 || w->y() <= -h2 || w->x() >= c->width() - w2 || w->y() >= c->height() - h2) continue; if (w->x() >= c->width() - 3 * w2) { // right w->setX(c->width() - w2); } else if (w->y() >= c->height() - 3 * h2) { // bottom w->setY(c->height() - h2); } else if (w->x() < 3 * w2) { // left w->setX(-w2); } else if (w->y() < 3 * h2) { // top w->setY(-h2); } else uWarning() << "uncatched widget position of" << w->name(); } } + } + /** + * Check if duplicated floating text labels are in the scene and remove them + */ + void removeDuplicatedFloatingTextInstances() + { + UMLWidgetList labelsWithoutParents; + UMLWidgetList labelsWithParent; + uDebug() << "checking diagram" << p->name() << "id" << Uml::ID::toString(p->ID()); + foreach(UMLWidget *w, p->widgetList()) { + if (!w->isTextWidget()) + continue; + if (w->parentItem()) + labelsWithParent.append(w); + else + labelsWithoutParents.append(w); + } + foreach(UMLWidget *w, labelsWithoutParents) { + foreach(UMLWidget *wp, labelsWithParent) { + if (w->id() == wp->id() && + w->localID() == wp->localID() && + w->name() == wp->name()) { + p->removeWidgetCmd(w); + uDebug() << "removed duplicated text label" << w->name() << "id:" << Uml::ID::toString(w->id()); + break; + } + } + } } UMLScene *p; }; /** * Constructor. */ UMLScene::UMLScene(UMLFolder *parentFolder, UMLView *view) : QGraphicsScene(0, 0, defaultCanvasSize, defaultCanvasSize), m_nLocalID(Uml::ID::None), m_nID(Uml::ID::None), m_Type(Uml::DiagramType::Undefined), m_Name(QString()), m_Documentation(QString()), m_Options(Settings::optionState()), m_bUseSnapToGrid(false), m_bUseSnapComponentSizeToGrid(false), m_isOpen(true), m_nCollaborationId(0), m_bCreateObject(false), m_bDrawSelectedOnly(false), m_bPaste(false), m_bStartedCut(false), m_d(new UMLScenePrivate(this)), m_view(view), m_pFolder(parentFolder), m_pIDChangesLog(0), m_isActivated(false), m_bPopupShowing(false), m_autoIncrementSequence(false) { m_PastePoint = QPointF(0, 0); m_pImageExporter = new UMLViewImageExporter(this); // setup signals connect(UMLApp::app(), SIGNAL(sigCutSuccessful()), this, SLOT(slotCutSuccessful())); // Create the ToolBarState factory. This class is not a singleton, because it // needs a pointer to this object. m_pToolBarStateFactory = new ToolBarStateFactory(); m_pToolBarState = m_pToolBarStateFactory->getState(WorkToolBar::tbb_Arrow, this); m_doc = UMLApp::app()->document(); // // settings for background // setBackgroundBrush(QColor(195, 195, 195)); m_layoutGrid = new LayoutGrid(this); // fix crash caused by Qt stale item issue see https://bugs.kde.org/show_bug.cgi?id=383592 setItemIndexMethod(NoIndex); } /** * Destructor. */ UMLScene::~UMLScene() { delete m_pImageExporter; m_pImageExporter = 0; delete m_pIDChangesLog; m_pIDChangesLog = 0; // before we can delete the QCanvas, all widgets must be explicitly // removed // otherwise the implicit remove of the contained widgets will cause // events which would demand a valid connected QCanvas // ==> this causes umbrello to crash for some - larger?? - projects // first avoid all events, which would cause some update actions // on deletion of each removed widget blockSignals(true); removeAllWidgets(); delete m_pToolBarStateFactory; m_pToolBarStateFactory = 0; delete m_layoutGrid; delete m_d; } /** * Return the UMLFolder in which this diagram lives. */ UMLFolder* UMLScene::folder() const { return m_pFolder; } /** * Set the UMLFolder in which this diagram lives. */ void UMLScene::setFolder(UMLFolder *folder) { m_pFolder = folder; } /** * Returns the active view associated with this scene. */ UMLView* UMLScene::activeView() const { return m_view; } /** * Return the documentation of the diagram. */ QString UMLScene::documentation() const { return m_Documentation; } /** * Set the documentation of the diagram. */ void UMLScene::setDocumentation(const QString &doc) { m_Documentation = doc; } /** * Return the state of the auto increment sequence */ bool UMLScene::autoIncrementSequence() const { return m_autoIncrementSequence; } void UMLScene::setAutoIncrementSequence(bool state) { m_autoIncrementSequence = state; } /** * Return the next auto increment sequence value */ QString UMLScene::autoIncrementSequenceValue() { int sequenceNumber = 0; if (type() == Uml::DiagramType::Sequence) { foreach (MessageWidget* message, messageList()) { bool ok; int value = message->sequenceNumber().toInt(&ok); if (ok && value > sequenceNumber) sequenceNumber = value; } } else if (type() == Uml::DiagramType::Collaboration) { foreach (AssociationWidget* assoc, associationList()) { bool ok; int value = assoc->sequenceNumber().toInt(&ok); if (ok && value > sequenceNumber) sequenceNumber = value; } } return QString::number(sequenceNumber + 1); } /** * Return the name of the diagram. */ QString UMLScene::name() const { return m_Name; } /** * Set the name of the diagram. */ void UMLScene::setName(const QString &name) { m_Name = name; } /** * Returns the type of the diagram. */ DiagramType::Enum UMLScene::type() const { return m_Type; } /** * Set the type of diagram. */ void UMLScene::setType(DiagramType::Enum type) { m_Type = type; } /** * Returns the ID of the diagram. */ Uml::ID::Type UMLScene::ID() const { return m_nID; } /** * Sets the ID of the diagram. */ void UMLScene::setID(Uml::ID::Type id) { m_nID = id; } /** * Returns the position of the diagram. */ QPointF UMLScene::pos() const { return m_Pos; } /** * Sets the position of the diagram. */ void UMLScene::setPos(const QPointF &pos) { m_Pos = pos; } /** * Returns the fill color to use. */ const QColor& UMLScene::fillColor() const { return m_Options.uiState.fillColor; } /** * Set the background color. * * @param color The color to use. */ void UMLScene::setFillColor(const QColor &color) { m_Options.uiState.fillColor = color; emit sigFillColorChanged(ID()); } /** * Returns the line color to use. */ const QColor& UMLScene::lineColor() const { return m_Options.uiState.lineColor; } /** * Sets the line color. * * @param color The color to use. */ void UMLScene::setLineColor(const QColor &color) { m_Options.uiState.lineColor = color; emit sigLineColorChanged(ID()); } /** * Returns the line width to use. */ uint UMLScene::lineWidth() const { return m_Options.uiState.lineWidth; } /** * Sets the line width. * * @param width The width to use. */ void UMLScene::setLineWidth(uint width) { m_Options.uiState.lineWidth = width; emit sigLineWidthChanged(ID()); } /** * Returns the text color to use. */ const QColor& UMLScene::textColor() const { return m_Options.uiState.textColor; } /** * Sets the text color. * * @param color The color to use. */ void UMLScene::setTextColor(const QColor& color) { m_Options.uiState.textColor = color; emit sigTextColorChanged(ID()); } /** * return grid dot color * * @return Color */ const QColor& UMLScene::gridDotColor() const { return m_layoutGrid->gridDotColor(); } /** * set grid dot color * * @param color grid dot color */ void UMLScene::setGridDotColor(const QColor& color) { m_Options.uiState.gridDotColor = color; m_layoutGrid->setGridDotColor(color); } /** * Returns the options being used. */ Settings::OptionState& UMLScene::optionState() { return m_Options; } /** * Sets the options to be used. */ void UMLScene::setOptionState(const Settings::OptionState& options) { m_Options = options; setBackgroundBrush(options.uiState.backgroundColor); setGridDotColor(options.uiState.gridDotColor); } /** * Returns a reference to the association list. */ const AssociationWidgetList UMLScene::associationList() const { AssociationWidgetList result; foreach(QGraphicsItem *item, items()) { AssociationWidget *w = dynamic_cast(item); if (w) result.append(w); } return result; } /** * Returns a reference to the widget list. */ const UMLWidgetList UMLScene::widgetList() const { UMLWidgetList result; foreach(QGraphicsItem *item, items()) { UMLWidget *w = dynamic_cast(item); if (w && !w->isMessageWidget() && !w->isAssociationWidget()) result.append(w); } return result; } void UMLScene::addWidgetCmd(UMLWidget* widget) { Q_ASSERT(0 != widget); addItem(widget); } void UMLScene::addWidgetCmd(AssociationWidget* widget) { Q_ASSERT(0 != widget); addItem(widget); } /** * Returns a reference to the message list. */ const MessageWidgetList UMLScene::messageList() const { MessageWidgetList result; foreach(QGraphicsItem *item, items()) { MessageWidget *w = dynamic_cast(item); if (w) result.append(w); } return result; } /** * Used for creating unique name of collaboration messages. */ int UMLScene::generateCollaborationId() { return ++m_nCollaborationId; } /** * Returns the open state. * @return when true diagram is shown to the user */ bool UMLScene::isOpen() const { return m_isOpen; } /** * Sets the flag 'isOpen'. * @param isOpen flag indicating that the diagram is shown to the user */ void UMLScene::setIsOpen(bool isOpen) { m_isOpen = isOpen; } /** * Contains the implementation for printing functionality. */ void UMLScene::print(QPrinter *pPrinter, QPainter & pPainter) { bool isFooter = optionState().generalState.footerPrinting; // The printer will probably use a different font with different font metrics, // force the widgets to update accordingly on paint forceUpdateWidgetFontMetrics(&pPainter); QRectF source = diagramRect(); QRect paper = pPrinter->paperRect(); QRect page = pPrinter->pageRect(); // use the painter font metrics, not the screen fm! QFontMetrics fm = pPainter.fontMetrics(); int fontHeight = fm.lineSpacing(); if (paper == page) { QSize margin = page.size() * 0.025; page.adjust(margin.width(), margin.height(), -margin.width(), -margin.height()); } if (isFooter) { int margin = 3 + 3 * fontHeight; page.adjust(0, 0, 0, -margin); } getDiagram(pPainter, QRectF(source), QRectF(page)); //draw foot note if (isFooter) { page.adjust(0, 0, 0, fontHeight); QString string = i18n("Diagram: %2 Page %1", 1, name()); QColor textColor(50, 50, 50); pPainter.setPen(textColor); pPainter.drawLine(page.left(), page.bottom() , page.right(), page.bottom()); pPainter.drawText(page.left(), page.bottom() + 3, page.right(), 2*fontHeight, Qt::AlignLeft, string); } // next painting will most probably be to a different device (i.e. the screen) forceUpdateWidgetFontMetrics(0); } /** * Initialize and announce a newly created widget. * Auxiliary to contentsMouseReleaseEvent(). */ void UMLScene::setupNewWidget(UMLWidget *w, bool setPosition) { if (setPosition && (!w->isPinWidget()) && (!w->isPortWidget()) && (!w->isObjectWidget())) { // ObjectWidget's position is handled by the widget w->setX(m_Pos.x()); w->setY(m_Pos.y()); } w->setVisible(true); w->activate(); w->setFontCmd(font()); w->slotFillColorChanged(ID()); w->slotTextColorChanged(ID()); w->slotLineWidthChanged(ID()); resizeSceneToItems(); m_doc->setModified(); if (m_doc->loading()) { // do not emit signals while loading addWidgetCmd(w); // w->activate(); // will be done by UMLDoc::activateAllViews() after loading } else { UMLApp::app()->executeCommand(new CmdCreateWidget(w)); } } /** * Return whether we are currently creating an object. */ bool UMLScene::getCreateObject() const { return m_bCreateObject; } /** * Set whether we are currently creating an object. */ void UMLScene::setCreateObject(bool bCreate) { m_bCreateObject = bCreate; } /** * Overrides the standard operation. */ void UMLScene::showEvent(QShowEvent* /*se*/) { connect(m_doc, SIGNAL(sigObjectCreated(UMLObject*)), this, SLOT(slotObjectCreated(UMLObject*))); connect(this, SIGNAL(sigAssociationRemoved(AssociationWidget*)), UMLApp::app()->docWindow(), SLOT(slotAssociationRemoved(AssociationWidget*))); connect(this, SIGNAL(sigWidgetRemoved(UMLWidget*)), UMLApp::app()->docWindow(), SLOT(slotWidgetRemoved(UMLWidget*))); } /** * Overrides the standard operation. */ void UMLScene::hideEvent(QHideEvent* /*he*/) { disconnect(m_doc, SIGNAL(sigObjectCreated(UMLObject*)), this, SLOT(slotObjectCreated(UMLObject*))); disconnect(this, SIGNAL(sigAssociationRemoved(AssociationWidget*)), UMLApp::app()->docWindow(), SLOT(slotAssociationRemoved(AssociationWidget*))); disconnect(this, SIGNAL(sigWidgetRemoved(UMLWidget*)), UMLApp::app()->docWindow(), SLOT(slotWidgetRemoved(UMLWidget*))); } /** * Changes the current tool to the selected tool. * The current tool is cleaned and the selected tool initialized. */ void UMLScene::slotToolBarChanged(int c) { m_pToolBarState->cleanBeforeChange(); m_pToolBarState = m_pToolBarStateFactory->getState((WorkToolBar::ToolBar_Buttons)c, this); m_pToolBarState->init(); m_bPaste = false; } /** * Slot called when an object is created. * @param o created UML object */ void UMLScene::slotObjectCreated(UMLObject* o) { DEBUG(DBG_SRC) << "scene=" << name() << " / object=" << o->name(); m_bPaste = false; //check to see if we want the message //may be wanted by someone else e.g. list view if (!m_bCreateObject) { return; } UMLWidget* newWidget = Widget_Factory::createWidget(this, o); if (!newWidget) { return; } setupNewWidget(newWidget); m_bCreateObject = false; if (Model_Utils::hasAssociations(o->baseType())) { createAutoAssociations(newWidget); // We need to invoke createAutoAttributeAssociations() // on all other widgets again because the newly created // widget might saturate some latent attribute assocs. createAutoAttributeAssociations2(newWidget); } resizeSceneToItems(); } /** * Slot called when an object is removed. * @param o removed UML object */ void UMLScene::slotObjectRemoved(UMLObject * o) { m_bPaste = false; Uml::ID::Type id = o->id(); foreach(UMLWidget* obj, widgetList()) { if (obj->id() != id) continue; removeWidget(obj); break; } } /** * Override standard method. */ void UMLScene::dragEnterEvent(QGraphicsSceneDragDropEvent *e) { UMLDragData::LvTypeAndID_List tidList; if (!UMLDragData::getClip3TypeAndID(e->mimeData(), tidList)) { DEBUG(DBG_SRC) << "UMLDragData::getClip3TypeAndID returned false"; return; } for(UMLDragData::LvTypeAndID_List::const_iterator it = tidList.begin(); it != tidList.end(); it++) { UMLListViewItem::ListViewType lvtype = (*it)->type; Uml::ID::Type id = (*it)->id; DiagramType::Enum diagramType = type(); UMLObject* temp = 0; //if dragging diagram - might be a drag-to-note if (Model_Utils::typeIsDiagram(lvtype)) { e->accept(); continue; } //can't drag anything onto state/activity diagrams if (diagramType == DiagramType::State || diagramType == DiagramType::Activity) { e->ignore(); continue; } //make sure can find UMLObject if (!(temp = m_doc->findObjectById(id))) { DEBUG(DBG_SRC) << "object " << Uml::ID::toString(id) << " not found"; e->ignore(); continue; } bool bAccept = Model_Utils::typeIsAllowedInDiagram(temp, this); if (bAccept) { e->accept(); } else { e->ignore(); } } } /** * Override standard method. */ void UMLScene::dragMoveEvent(QGraphicsSceneDragDropEvent* e) { e->accept(); } /** * Override standard method. */ void UMLScene::dropEvent(QGraphicsSceneDragDropEvent *e) { UMLDragData::LvTypeAndID_List tidList; if (!UMLDragData::getClip3TypeAndID(e->mimeData(), tidList)) { DEBUG(DBG_SRC) << "UMLDragData::getClip3TypeAndID returned error"; return; } m_Pos = e->scenePos(); for(UMLDragData::LvTypeAndID_List::const_iterator it = tidList.begin(); it != tidList.end(); it++) { UMLListViewItem::ListViewType lvtype = (*it)->type; Uml::ID::Type id = (*it)->id; if (Model_Utils::typeIsDiagram(lvtype)) { bool breakFlag = false; UMLWidget* w = 0; foreach(w, widgetList()) { if (w->isNoteWidget() && w->onWidget(e->scenePos())) { breakFlag = true; break; } } if (breakFlag) { NoteWidget *note = static_cast(w); note->setDiagramLink(id); } continue; } UMLObject* o = m_doc->findObjectById(id); if (!o) { DEBUG(DBG_SRC) << "object id=" << Uml::ID::toString(id) << " not found"; continue; } UMLWidget* newWidget = Widget_Factory::createWidget(this, o); if (!newWidget) { uWarning() << "could not create widget for uml object" << o->name(); continue; } setupNewWidget(newWidget); m_Pos += QPointF(UMLWidget::DefaultMinimumSize.width(), UMLWidget::DefaultMinimumSize.height()); createAutoAssociations(newWidget); createAutoAttributeAssociations2(newWidget); } } /** * Overrides the standard operation. * Calls the same method in the current tool bar state. */ void UMLScene::mouseMoveEvent(QGraphicsSceneMouseEvent* ome) { m_pToolBarState->mouseMove(ome); } /** * Override standard method. * Calls the same method in the current tool bar state. */ void UMLScene::mousePressEvent(QGraphicsSceneMouseEvent* event) { if (event->button() != Qt::LeftButton) { event->ignore(); return; } m_pToolBarState->mousePress(event); //TODO should be managed by widgets when are selected. Right now also has some //problems, such as clicking on a widget, and clicking to move that widget shows //documentation of the diagram instead of keeping the widget documentation. //When should diagram documentation be shown? When clicking on an empty //space in the diagram with arrow tool? UMLWidget* widget = widgetAt(event->scenePos()); if (widget) { DEBUG(DBG_SRC) << "widget = " << widget->name() << " / type = " << widget->baseTypeStr(); UMLApp::app()->docWindow()->showDocumentation(widget); event->accept(); } else { AssociationWidget* association = associationAt(event->scenePos()); if (association) { DEBUG(DBG_SRC) << "association widget = " << association->name() << " / type = " << association->baseTypeStr(); // the following is done in AssociationWidget::setSelected() // UMLApp::app()->docWindow()->showDocumentation(association, true); // event->accept(); } //:TODO: else if (clicking on other elements with documentation) { //:TODO: UMLApp::app()->docWindow()->showDocumentation(umlObject, true); else { // clicking on an empty space in the diagram with arrow tool UMLApp::app()->docWindow()->showDocumentation(this); event->accept(); } } } /** * Override standard method. * Calls the same method in the current tool bar state. */ void UMLScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event) { if (!m_doc->loading()) m_pToolBarState->mouseDoubleClick(event); if (!event->isAccepted()) { // show properties dialog of the scene if (m_view->showPropertiesDialog() == true) { m_doc->setModified(); } event->accept(); } } /** * Overrides the standard operation. * Calls the same method in the current tool bar state. */ void UMLScene::mouseReleaseEvent(QGraphicsSceneMouseEvent* ome) { m_pToolBarState->mouseRelease(ome); } /** * Determine whether on a sequence diagram we have clicked on a line * of an Object. * * @return The widget owning the line which was clicked. * Returns 0 if no line was clicked on. */ ObjectWidget * UMLScene::onWidgetLine(const QPointF &point) const { foreach(UMLWidget* obj, widgetList()) { ObjectWidget *ow = obj->asObjectWidget(); if (ow == 0) continue; SeqLineWidget *pLine = ow->sequentialLine(); if (pLine == 0) { uError() << "SeqLineWidget of " << ow->name() << " (id=" << Uml::ID::toString(ow->localID()) << ") is NULL"; continue; } if (pLine->onWidget(point)) return ow; } return 0; } /** * Determine whether on a sequence diagram we have clicked on * the destruction box of an Object. * * @return The widget owning the destruction box which was clicked. * Returns 0 if no destruction box was clicked on. */ ObjectWidget * UMLScene::onWidgetDestructionBox(const QPointF &point) const { foreach(UMLWidget* obj, widgetList()) { ObjectWidget *ow = obj->asObjectWidget(); if (ow == 0) continue; SeqLineWidget *pLine = ow->sequentialLine(); if (pLine == 0) { uError() << "SeqLineWidget of " << ow->name() << " (id=" << Uml::ID::toString(ow->localID()) << ") is NULL"; continue; } if (pLine->onDestructionBox(point)) return ow; } return 0; } /** * Return pointer to the first selected widget (for multi-selection) */ UMLWidget* UMLScene::getFirstMultiSelectedWidget() const { if (selectedWidgets().size() == 0) return 0; return selectedWidgets().first(); } /** * Tests the given point against all widgets and returns the * widget for which the point is within its bounding rectangle. * In case of multiple matches, returns the smallest widget. * Returns NULL if the point is not inside any widget. * TODO: What about using QGraphicsScene::items(...)? */ UMLWidget* UMLScene::widgetAt(const QPointF& p) { return dynamic_cast(itemAt(p)); } /** * Tests the given point against all associations and returns the * association widget for which the point is on the line. * Returns NULL if the point is not inside any association. * CHECK: This is the same method as in ToolBarState. */ AssociationWidget* UMLScene::associationAt(const QPointF& p) { foreach (AssociationWidget* association, associationList()) { if (association->onAssociation(p)) { return association; } } return 0; } /** * Tests the given point against all associations and returns the * association widget for which the point is on the line. * Returns NULL if the point is not inside any association. */ MessageWidget* UMLScene::messageAt(const QPointF& p) { foreach(MessageWidget *message, messageList()) { if (message->onWidget(p)) { return message; } } return 0; } /** * Sees if a message is relevant to the given widget. If it does delete it. * @param w The widget to check messages against. */ void UMLScene::checkMessages(ObjectWidget * w) { if (type() != DiagramType::Sequence) { return; } foreach(MessageWidget *obj, messageList()) { if (obj->hasObjectWidget(w)) { removeWidgetCmd(obj); } } } /** * Returns whether a widget is already on the diagram. * * @param id The id of the widget to check for. * * @return Returns pointer to the widget if it is on the diagram, NULL if not. */ UMLWidget* UMLScene::widgetOnDiagram(Uml::ID::Type id) { foreach(UMLWidget *obj, widgetList()) { if (!obj) continue; UMLWidget* w = obj->widgetWithID(id); if (w) return w; } foreach(UMLWidget *obj, messageList()) { // CHECK: Should MessageWidget reimplement widgetWithID() ? // If yes then we should use obj->widgetWithID(id) here too. if (id == obj->id()) return obj; } return 0; } /** * Finds a widget with the given ID. * Search both our UMLWidget AND MessageWidget lists. * @param id The ID of the widget to find. * * @return Returns the widget found, returns 0 if no widget found. */ UMLWidget * UMLScene::findWidget(Uml::ID::Type id) { foreach(UMLWidget* obj, widgetList()) { if (!obj) continue; UMLWidget* w = obj->widgetWithID(id); if (w) { return w; } } foreach(UMLWidget* obj, messageList()) { // CHECK: Should MessageWidget reimplement widgetWithID() ? // If yes then we should use obj->widgetWithID(id) here too. if (obj->localID() == id || obj->id() == id) return obj; } return 0; } /** * Finds an association widget with the given ID. * * @param id The ID of the widget to find. * * @return Returns the widget found, returns 0 if no widget found. */ AssociationWidget * UMLScene::findAssocWidget(Uml::ID::Type id) { foreach(AssociationWidget* obj, associationList()) { UMLAssociation* umlassoc = obj->association(); if (umlassoc && umlassoc->id() == id) { return obj; } } return 0; } /** * Finds an association widget with the given widgets and the given role B name. * Considers the following association types: * at_Association, at_UniAssociation, at_Composition, at_Aggregation * This is used for seeking an attribute association. * * @param pWidgetA Pointer to the UMLWidget of role A. * @param pWidgetB Pointer to the UMLWidget of role B. * @param roleNameB Name at the B side of the association (the attribute name) * * @return Returns the widget found, returns 0 if no widget found. */ AssociationWidget * UMLScene::findAssocWidget(UMLWidget *pWidgetA, UMLWidget *pWidgetB, const QString& roleNameB) { foreach(AssociationWidget* assoc, associationList()) { const Uml::AssociationType::Enum testType = assoc->associationType(); if (testType != Uml::AssociationType::Association && testType != Uml::AssociationType::UniAssociation && testType != Uml::AssociationType::Composition && testType != Uml::AssociationType::Aggregation && testType != Uml::AssociationType::Relationship) { continue; } if (pWidgetA->id() == assoc->widgetIDForRole(Uml::RoleType::A) && pWidgetB->id() == assoc->widgetIDForRole(Uml::RoleType::B) && assoc->roleName(Uml::RoleType::B) == roleNameB) { return assoc; } } return 0; } /** * Finds an association widget with the given type and widgets. * * @param at The AssociationType of the widget to find. * @param pWidgetA Pointer to the UMLWidget of role A. * @param pWidgetB Pointer to the UMLWidget of role B. * * @return Returns the widget found, returns 0 if no widget found. */ AssociationWidget * UMLScene::findAssocWidget(AssociationType::Enum at, UMLWidget *pWidgetA, UMLWidget *pWidgetB) { foreach(AssociationWidget* assoc, associationList()) { Uml::AssociationType::Enum testType = assoc->associationType(); if (testType != at) { continue; } if (pWidgetA->id() == assoc->widgetIDForRole(Uml::RoleType::A) && pWidgetB->id() == assoc->widgetIDForRole(Uml::RoleType::B)) { return assoc; } } return 0; } /** * Remove a widget from view (undo command) * * @param o The widget to remove. */ void UMLScene::removeWidget(UMLWidget * o) { UMLApp::app()->executeCommand(new CmdRemoveWidget(o)); } /** * Remove a widget from view. * * @param o The widget to remove. */ void UMLScene::removeWidgetCmd(UMLWidget * o) { if (!o) return; emit sigWidgetRemoved(o); removeAssociations(o); removeOwnedWidgets(o); WidgetBase::WidgetType t = o->baseType(); if (type() == DiagramType::Sequence && t == WidgetBase::wt_Object) { checkMessages(static_cast(o)); } o->cleanup(); o->setSelectedFlag(false); disconnect(this, SIGNAL(sigFillColorChanged(Uml::ID::Type)), o, SLOT(slotFillColorChanged(Uml::ID::Type))); disconnect(this, SIGNAL(sigLineColorChanged(Uml::ID::Type)), o, SLOT(slotLineColorChanged(Uml::ID::Type))); disconnect(this, SIGNAL(sigTextColorChanged(Uml::ID::Type)), o, SLOT(slotTextColorChanged(Uml::ID::Type))); removeItem(o); o->deleteLater(); m_doc->setModified(true); } /** * Remove all widgets that have given widget as owner. * * @param o The owner widget that will be removed. */ void UMLScene::removeOwnedWidgets(UMLWidget* o) { foreach(QGraphicsItem* item, o->childItems()) { UMLWidget* widget = dynamic_cast(item); if ((widget != 0) && (widget->isPinWidget() || widget->isPortWidget())) { removeWidgetCmd(widget); } } } /** * Returns background color */ const QColor& UMLScene::backgroundColor() const { return backgroundBrush().color(); } /** * Returns whether to use the fill/background color */ bool UMLScene::useFillColor() const { return m_Options.uiState.useFillColor; } /** * Sets whether to use the fill/background color */ void UMLScene::setUseFillColor(bool ufc) { m_Options.uiState.useFillColor = ufc; } /** * Gets the smallest area to print. * * @return Returns the smallest area to print. */ QRectF UMLScene::diagramRect() { return itemsBoundingRect(); } /** * Returns a list of selected widgets * @return list of selected widgets based on class UMLWidget * @note This method returns widgets including message widgets, but no association widgets */ UMLWidgetList UMLScene::selectedWidgets() const { QList items = selectedItems(); UMLWidgetList widgets; foreach(QGraphicsItem *item, items) { UMLWidget *w = dynamic_cast(item); if (w) widgets.append(w); } return widgets; } /** * Returns a list of selected association widgets * @return list of selected widgets based on class AssociationWidget */ AssociationWidgetList UMLScene::selectedAssociationWidgets() const { QList items = selectedItems(); AssociationWidgetList widgets; foreach(QGraphicsItem *item, items) { AssociationWidget *w = dynamic_cast(item); if (w) widgets.append(w); } return widgets; } /** * Returns a list of selected message widgets * @return list of selected widgets based on class MessageWidget */ UMLWidgetList UMLScene::selectedMessageWidgets() const { QList items = selectedItems(); UMLWidgetList widgets; foreach(QGraphicsItem *item, items) { MessageWidget *w = dynamic_cast(item); if (w) widgets.append(w); } return widgets; } /** * Clear the selected widgets list. */ void UMLScene::clearSelected() { clearSelection(); //m_doc->enableCutCopy(false); } /** * Move all the selected widgets by a relative X and Y offset. * TODO: Only used in UMLApp::handleCursorKeyReleaseEvent * * @param dX The distance to move horizontally. * @param dY The distance to move vertically. */ void UMLScene::moveSelectedBy(qreal dX, qreal dY) { // DEBUG(DBG_SRC) << "********** m_selectedList count=" << m_selectedList.count(); foreach(UMLWidget *w, selectedWidgets()) { w->moveByLocal(dX, dY); } } /** * Set the useFillColor variable to all selected widgets * * @param useFC The state to set the widget to. */ void UMLScene::selectionUseFillColor(bool useFC) { if (useFC) { UMLApp::app()->beginMacro(i18n("Use fill color")); } else { UMLApp::app()->beginMacro(i18n("No fill color")); } foreach(UMLWidget* widget, selectedWidgets()) { widget->setUseFillColor(useFC); } UMLApp::app()->endMacro(); } /** * Set the font for all the currently selected items. */ void UMLScene::selectionSetFont(const QFont &font) { UMLApp::app()->beginMacro(i18n("Change font")); foreach(UMLWidget* temp, selectedWidgets()) { temp->setFont(font); } UMLApp::app()->endMacro(); } /** * Set the line color for all the currently selected items. */ void UMLScene::selectionSetLineColor(const QColor &color) { UMLApp::app()->beginMacro(i18n("Change line color")); foreach(UMLWidget *temp, selectedWidgets()) { temp->setLineColor(color); } AssociationWidgetList assoclist = selectedAssocs(); foreach(AssociationWidget *aw, assoclist) { aw->setLineColor(color); } UMLApp::app()->endMacro(); } /** * Set the line width for all the currently selected items. */ void UMLScene::selectionSetLineWidth(uint width) { UMLApp::app()->beginMacro(i18n("Change line width")); foreach(UMLWidget* temp, selectedWidgets()) { temp->setLineWidth(width); temp->setUsesDiagramLineWidth(false); } AssociationWidgetList assoclist = selectedAssocs(); foreach(AssociationWidget *aw, assoclist) { aw->setLineWidth(width); aw->setUsesDiagramLineWidth(false); } UMLApp::app()->endMacro(); } /** * Set the fill color for all the currently selected items. */ void UMLScene::selectionSetFillColor(const QColor &color) { UMLApp::app()->beginMacro(i18n("Change fill color")); foreach(UMLWidget* widget, selectedWidgets()) { widget->setFillColor(color); widget->setUsesDiagramFillColor(false); } UMLApp::app()->endMacro(); } /** * Set or unset the visual property (show ..) setting of all selected items. */ void UMLScene::selectionSetVisualProperty(ClassifierWidget::VisualProperty property, bool value) { UMLApp::app()->beginMacro(i18n("Change visual property")); foreach(UMLWidget *temp, selectedWidgets()) { ClassifierWidget *cw = temp->asClassifierWidget(); cw->setVisualProperty(property, value); } UMLApp::app()->endMacro(); } /** * Unselect child widgets when their owner is already selected. */ void UMLScene::unselectChildrenOfSelectedWidgets() { foreach(UMLWidget* widget, selectedWidgets()) { if (widget->isPinWidget() || widget->isPortWidget()) { foreach(UMLWidget* potentialParentWidget, selectedWidgets()) { if (widget->parentItem() == potentialParentWidget) { widget->setSelectedFlag(false); } } } } } /** * Delete the selected widgets list and the widgets in it. */ void UMLScene::deleteSelection() { AssociationWidgetList selectedAssociations = selectedAssociationWidgets(); int selectionCount = selectedWidgets().count() + selectedAssociations.count(); if (selectionCount == 0) return; // check related associations bool hasAssociations = false; foreach(UMLWidget* widget, selectedWidgets()) { if (widget->isTextWidget() && widget->asFloatingTextWidget()->textRole() != Uml::TextRole::Floating) { continue; } if (widget->isMessageWidget() || widget->associationWidgetList().size() > 0) hasAssociations = true; } if (hasAssociations && !Dialog_Utils::askDeleteAssociation()) return; UMLApp::app()->beginMacro(i18n("Delete widgets")); unselectChildrenOfSelectedWidgets(); foreach(UMLWidget* widget, selectedWidgets()) { // Don't delete text widget that are connect to associations as these will // be cleaned up by the associations. if (widget->isTextWidget() && widget->asFloatingTextWidget()->textRole() != Uml::TextRole::Floating) { widget->setSelectedFlag(false); widget->hide(); } else if (widget->isPortWidget()) { UMLObject *o = widget->umlObject(); removeWidget(widget); if (o) UMLApp::app()->executeCommand(new CmdRemoveUMLObject(o)); // message widgets are handled later } else if (!widget->isMessageWidget()){ removeWidget(widget); } } // Delete any selected associations. foreach(AssociationWidget* assocwidget, selectedAssociations) { removeWidgetCmd(assocwidget); } // we also have to remove selected messages from sequence diagrams foreach(UMLWidget* cur_msgWgt, selectedMessageWidgets()) { removeWidget(cur_msgWgt); } //make sure list empty - it should be anyway, just a check. clearSelected(); UMLApp::app()->endMacro(); } /** * resize selected widgets */ void UMLScene::resizeSelection() { int selectionCount = selectedWidgets().count(); if (selectionCount > 1) { UMLApp::app()->beginMacro(i18n("Resize widgets")); } if (selectedCount() == 0) return; foreach(UMLWidget *w, selectedWidgets()) { w->resize(); } m_doc->setModified(); if (selectionCount > 1) { UMLApp::app()->endMacro(); } } /** * Selects all widgets */ void UMLScene::selectAll() { selectWidgets(sceneRect().left(), sceneRect().top(), sceneRect().right(), sceneRect().bottom()); } /** * Returns true if this diagram resides in an externalized folder. * CHECK: It is probably cleaner to move this to the UMLListViewItem. */ bool UMLScene::isSavedInSeparateFile() { if (optionState().generalState.tabdiagrams) { // Umbrello currently does not support external folders // when tabbed diagrams are enabled. return false; } const QString msgPrefix(QLatin1String("UMLScene::isSavedInSeparateFile(") + name() + QLatin1String("): ")); UMLListView *listView = UMLApp::app()->listView(); UMLListViewItem *lvItem = listView->findItem(m_nID); if (lvItem == 0) { uError() << msgPrefix << "listView->findUMLObject(this) returns false"; return false; } UMLListViewItem *parentItem = dynamic_cast(lvItem->parent()); if (parentItem == 0) { uError() << msgPrefix << "parent item in listview is not a UMLListViewItem (?)"; return false; } const UMLListViewItem::ListViewType lvt = parentItem->type(); if (! Model_Utils::typeIsFolder(lvt)) return false; UMLFolder *modelFolder = parentItem->umlObject()->asUMLFolder(); if (modelFolder == 0) { uError() << msgPrefix << "parent model object is not a UMLFolder (?)"; return false; } QString folderFile = modelFolder->folderFile(); return !folderFile.isEmpty(); } UMLSceneItemList UMLScene::collisions(const QPointF &p, int delta) { QPointF a = p-QPointF(delta, delta); QPointF b = p+QPointF(delta, delta); QList list = items(QRectF(a, b)); return list; } /** * Calls setSelected on the given UMLWidget and enters * it into the m_selectedList while making sure it is * there only once. */ void UMLScene::makeSelected(UMLWidget* uw) { if (uw) { uw->setSelected(true); } } /** * Selects all the widgets of the given association widget. */ void UMLScene::selectWidgetsOfAssoc(AssociationWidget * a) { if (a) { a->setSelected(true); //select the two widgets makeSelected(a->widgetForRole(Uml::RoleType::A)); makeSelected(a->widgetForRole(Uml::RoleType::B)); //select all the text makeSelected(a->multiplicityWidget(Uml::RoleType::A)); makeSelected(a->multiplicityWidget(Uml::RoleType::B)); makeSelected(a->roleWidget(Uml::RoleType::A)); makeSelected(a->roleWidget(Uml::RoleType::B)); makeSelected(a->changeabilityWidget(Uml::RoleType::A)); makeSelected(a->changeabilityWidget(Uml::RoleType::B)); } } /** * Selects all the widgets within an internally kept rectangle. */ void UMLScene::selectWidgets(qreal px, qreal py, qreal qx, qreal qy) { clearSelected(); QRectF rect; if (px <= qx) { rect.setLeft(px); rect.setRight(qx); } else { rect.setLeft(qx); rect.setRight(px); } if (py <= qy) { rect.setTop(py); rect.setBottom(qy); } else { rect.setTop(qy); rect.setBottom(py); } // Select UMLWidgets that fall within the selection rectangle foreach(UMLWidget* temp, widgetList()) { uIgnoreZeroPointer(temp); selectWidget(temp, &rect); } // Select messages that fall within the selection rectangle foreach(MessageWidget* temp, messageList()) { selectWidget(temp->asUMLWidget(), &rect); } // Select associations of selected widgets selectAssociations(true); // Automatically select all messages if two object widgets are selected foreach(MessageWidget *w, messageList()) { if (w->objectWidget(Uml::RoleType::A) && w->objectWidget(Uml::RoleType::B) && w->objectWidget(Uml::RoleType::A)->isSelected() && w->objectWidget(Uml::RoleType::B)->isSelected()) { makeSelected(w); } } } /** * Select a single widget * * If QRectF* rect is provided, the selection is only made if the widget is * visible within the rectangle. */ void UMLScene::selectWidget(UMLWidget* widget, QRectF* rect) { if (rect == 0) { makeSelected(widget); return; } int x = widget->x(); int y = widget->y(); int w = widget->width(); int h = widget->height(); QRectF rect2(x, y, w, h); //see if any part of widget is in the rectangle if (!rect->intersects(rect2)) { return; } //if it is text that is part of an association then select the association //and the objects that are connected to it. if (widget->isTextWidget()) { FloatingTextWidget *ft = widget->asFloatingTextWidget(); Uml::TextRole::Enum t = ft->textRole(); LinkWidget *lw = ft->link(); MessageWidget * mw = dynamic_cast(lw); if (mw) { makeSelected(mw); } else if (t != Uml::TextRole::Floating) { AssociationWidget * a = dynamic_cast(lw); if (a) selectWidgetsOfAssoc(a); } } else if (widget->isMessageWidget()) { MessageWidget *mw = widget->asMessageWidget(); makeSelected(mw); } if (widget->isVisible()) { makeSelected(widget); } } /** * Selects all the widgets from a list. */ void UMLScene::selectWidgets(UMLWidgetList &widgets) { foreach (UMLWidget* widget, widgets) makeSelected(widget); } /** * Returns the PNG picture of the paste operation. * @param diagram the class to store PNG picture of the paste operation. * @param rect the area of the diagram to copy */ void UMLScene::getDiagram(QPixmap &diagram, const QRectF &rect) { DEBUG(DBG_SRC) << "rect=" << rect << ", pixmap=" << diagram.rect(); QPainter painter(&diagram); painter.fillRect(0, 0, rect.width(), rect.height(), Qt::white); getDiagram(painter, rect); } /** * Paint diagram to the paint device * @param painter the QPainter to which the diagram is painted * @param source the area of the diagram to copy * @param target the rect where to paint into */ void UMLScene::getDiagram(QPainter &painter, const QRectF &source, const QRectF &target) { DEBUG(DBG_SRC) << "painter=" << painter.window() << ", source=" << source << ", target=" << target; //TODO unselecting and selecting later doesn't work now as the selection is //cleared in UMLSceneImageExporter. Check if the anything else than the //following is needed and, if it works, remove the clearSelected in //UMLSceneImageExporter and UMLSceneImageExporterModel UMLWidgetList selected = selectedWidgets(); foreach(UMLWidget* widget, selected) { widget->setSelected(false); } AssociationWidgetList selectedAssociationsList = selectedAssocs(); foreach(AssociationWidget* association, selectedAssociationsList) { association->setSelected(false); } // we don't want to get the grid bool showSnapGrid = isSnapGridVisible(); setSnapGridVisible(false); const int sourceMargin = 1; QRectF alignedSource(source); alignedSource.adjust(-sourceMargin, -sourceMargin, sourceMargin, sourceMargin); uDebug() << "TODO: Check if this render method is identical to cavnas()->drawArea()"; // [PORT] render(&painter, target, alignedSource, Qt::KeepAspectRatio); setSnapGridVisible(showSnapGrid); //select again foreach(UMLWidget* widget, selected) { widget->setSelected(true); } foreach(AssociationWidget* association, selectedAssociationsList) { association->setSelected(true); } } /** * Returns the imageExporter used to export the view. * * @return The imageExporter used to export the view. */ UMLViewImageExporter* UMLScene::getImageExporter() { return m_pImageExporter; } /** * makes this view the active view by asking the document to show us */ void UMLScene::slotActivate() { m_doc->changeCurrentView(ID()); } /** * Activate all the objects and associations after a load from the clipboard */ void UMLScene::activate() { //Activate Regular widgets then activate messages foreach(UMLWidget* obj, widgetList()) { uIgnoreZeroPointer(obj); //If this UMLWidget is already activated or is a MessageWidget then skip it if (obj->isActivated() || obj->isMessageWidget()) { continue; } if (obj->activate()) { obj->setVisible(true); } else { removeItem(obj); delete obj; } }//end foreach //Activate Message widgets foreach(UMLWidget* obj, messageList()) { //If this MessageWidget is already activated then skip it if (obj->isActivated()) continue; obj->activate(m_doc->changeLog()); obj->setVisible(true); }//end foreach // Activate all association widgets foreach(AssociationWidget* aw, associationList()) { if (aw->activate()) { if (m_PastePoint.x() != 0) { int x = m_PastePoint.x() - m_Pos.x(); int y = m_PastePoint.y() - m_Pos.y(); aw->moveEntireAssoc(x, y); } } else { removeWidgetCmd(aw); delete aw; } } } /** * Return the amount of widgets selected. * * @param filterText When true, do NOT count floating text widgets that * belong to other widgets (i.e. only count TextRole::Floating.) * Default: Count all widgets. * @return Number of widgets selected. */ int UMLScene::selectedCount(bool filterText) const { if (!filterText) return selectedWidgets().count(); int counter = 0; foreach(UMLWidget* temp, selectedWidgets()) { if (temp->isTextWidget()) { const FloatingTextWidget *ft = static_cast(temp); if (ft->textRole() == TextRole::Floating) counter++; } else { counter++; } } return counter; } /** * Fills the List with all the selected widgets from the diagram * The list can be filled with all the selected widgets, or be filtered to prevent * text widgets other than tr_Floating to be append. * * @param filterText Don't append the text, unless their role is tr_Floating * @return The UMLWidgetList to fill. */ UMLWidgetList UMLScene::selectedWidgetsExt(bool filterText /*= true*/) { UMLWidgetList widgetList; foreach(UMLWidget* widgt, selectedWidgets()) { if (filterText && widgt->isTextWidget()) { FloatingTextWidget *ft = widgt->asFloatingTextWidget(); if (ft->textRole() == Uml::TextRole::Floating) widgetList.append(widgt); } else { widgetList.append(widgt); } } return widgetList; } /** * Returns a list with all the selected associations from the diagram */ AssociationWidgetList UMLScene::selectedAssocs() { AssociationWidgetList assocWidgetList; foreach(AssociationWidget* assocwidget, associationList()) { if (assocwidget->isSelected()) assocWidgetList.append(assocwidget); } return assocWidgetList; } /** * Adds a floating text widget to the view */ void UMLScene::addFloatingTextWidget(FloatingTextWidget* pWidget) { int wX = pWidget->x(); int wY = pWidget->y(); bool xIsOutOfRange = (wX < sceneRect().left() || wX > sceneRect().right()); bool yIsOutOfRange = (wY < sceneRect().top() || wY > sceneRect().bottom()); if (xIsOutOfRange || yIsOutOfRange) { QString name = pWidget->name(); if (name.isEmpty()) { FloatingTextWidget *ft = pWidget->asFloatingTextWidget(); if (ft) name = ft->displayText(); } DEBUG(DBG_SRC) << name << " type=" << pWidget->baseTypeStr() << ": position (" << wX << "," << wY << ") is out of range"; if (xIsOutOfRange) { pWidget->setX(0); wX = 0; } if (yIsOutOfRange) { pWidget->setY(0); wY = 0; } } addWidgetCmd(pWidget); } /** * Adds an association to the view from the given data. * Use this method when pasting. */ bool UMLScene::addAssociation(AssociationWidget* pAssoc, bool isPasteOperation) { if (!pAssoc) { return false; } const Uml::AssociationType::Enum assocType = pAssoc->associationType(); if (isPasteOperation) { IDChangeLog * log = m_doc->changeLog(); if (!log) { return false; } Uml::ID::Type ida = Uml::ID::None, idb = Uml::ID::None; if (type() == DiagramType::Collaboration || type() == DiagramType::Sequence) { //check local log first ida = m_pIDChangesLog->findNewID(pAssoc->widgetIDForRole(Uml::RoleType::A)); idb = m_pIDChangesLog->findNewID(pAssoc->widgetIDForRole(Uml::RoleType::B)); //if either is still not found and assoc type is anchor //we are probably linking to a notewidet - else an error if (ida == Uml::ID::None && assocType == Uml::AssociationType::Anchor) ida = log->findNewID(pAssoc->widgetIDForRole(Uml::RoleType::A)); if (idb == Uml::ID::None && assocType == Uml::AssociationType::Anchor) idb = log->findNewID(pAssoc->widgetIDForRole(Uml::RoleType::B)); } else { Uml::ID::Type oldIdA = pAssoc->widgetIDForRole(Uml::RoleType::A); Uml::ID::Type oldIdB = pAssoc->widgetIDForRole(Uml::RoleType::B); ida = log->findNewID(oldIdA); if (ida == Uml::ID::None) { // happens after a cut if (oldIdA == Uml::ID::None) { return false; } ida = oldIdA; } idb = log->findNewID(oldIdB); if (idb == Uml::ID::None) { // happens after a cut if (oldIdB == Uml::ID::None) { return false; } idb = oldIdB; } } if (ida == Uml::ID::None || idb == Uml::ID::None) { return false; } // cant do this anymore.. may cause problem for pasting // pAssoc->setWidgetID(ida, A); // pAssoc->setWidgetID(idb, B); pAssoc->setWidgetForRole(findWidget(ida), Uml::RoleType::A); pAssoc->setWidgetForRole(findWidget(idb), Uml::RoleType::B); } UMLWidget * pWidgetA = findWidget(pAssoc->widgetIDForRole(Uml::RoleType::A)); UMLWidget * pWidgetB = findWidget(pAssoc->widgetIDForRole(Uml::RoleType::B)); //make sure valid widget ids if (!pWidgetA || !pWidgetB) { return false; } //make sure there isn't already the same assoc foreach(AssociationWidget* assocwidget, associationList()) { if (*pAssoc == *assocwidget) // this is nuts. Paste operation wants to know if 'true' // for duplicate, but loadFromXMI1 needs 'false' value return (isPasteOperation ? true : false); } addWidgetCmd(pAssoc); FloatingTextWidget *ft[5] = { pAssoc->nameWidget(), pAssoc->roleWidget(Uml::RoleType::A), pAssoc->roleWidget(Uml::RoleType::B), pAssoc->multiplicityWidget(Uml::RoleType::A), pAssoc->multiplicityWidget(Uml::RoleType::B) }; for (int i = 0; i < 5; i++) { FloatingTextWidget *flotxt = ft[i]; if (flotxt) { flotxt->updateGeometry(); addFloatingTextWidget(flotxt); } } return true; } /** * Activate the view after a load a new file */ void UMLScene::activateAfterLoad(bool bUseLog) { if (m_isActivated) { return; } if (bUseLog) { beginPartialWidgetPaste(); } //now activate them all activate(); if (bUseLog) { endPartialWidgetPaste(); } m_view->centerOn(0, 0); m_isActivated = true; } void UMLScene::beginPartialWidgetPaste() { delete m_pIDChangesLog; m_pIDChangesLog = 0; m_pIDChangesLog = new IDChangeLog(); m_bPaste = true; } void UMLScene::endPartialWidgetPaste() { delete m_pIDChangesLog; m_pIDChangesLog = 0; m_bPaste = false; } /** * Removes a AssociationWidget from a diagram * Physically deletes the AssociationWidget passed in. * * @param pAssoc Pointer to the AssociationWidget. */ void UMLScene::removeWidgetCmd(AssociationWidget* pAssoc) { if (!pAssoc) return; emit sigAssociationRemoved(pAssoc); pAssoc->cleanup(); removeItem(pAssoc); pAssoc->deleteLater(); m_doc->setModified(); } /** * Removes an AssociationWidget from the association list * and removes the corresponding UMLAssociation from the current UMLDoc. */ void UMLScene::removeAssocInViewAndDoc(AssociationWidget* a) { // For umbrello 1.2, UMLAssociations can only be removed in two ways: // 1. Right click on the assocwidget in the view and select Delete // 2. Go to the Class Properties page, select Associations, right click // on the association and select Delete if (!a) return; if (a->associationType() == Uml::AssociationType::Containment) { UMLObject *objToBeMoved = a->widgetForRole(Uml::RoleType::B)->umlObject(); if (objToBeMoved != 0) { UMLListView *lv = UMLApp::app()->listView(); lv->moveObject(objToBeMoved->id(), Model_Utils::convert_OT_LVT(objToBeMoved), lv->theLogicalView()); // UMLListView::moveObject() will delete the containment // AssociationWidget via UMLScene::updateContainment(). } else { DEBUG(DBG_SRC) << "removeAssocInViewAndDoc(containment): " << "objB is NULL"; } } else { // Remove assoc in doc. m_doc->removeAssociation(a->association()); // Remove assoc in view. removeWidgetCmd(a); } } /** * Removes all the associations related to Widget. * * @param widget Pointer to the widget to remove. */ void UMLScene::removeAssociations(UMLWidget* widget) { foreach(AssociationWidget* assocwidget, associationList()) { if (assocwidget->containsAsEndpoint(widget)) { removeWidgetCmd(assocwidget); } } } /** * Sets each association as selected if the widgets it associates are selected * * @param bSelect True to select, false for unselect */ void UMLScene::selectAssociations(bool bSelect) { foreach(AssociationWidget* assocwidget, associationList()) { UMLWidget *widA = assocwidget->widgetForRole(Uml::RoleType::A); UMLWidget *widB = assocwidget->widgetForRole(Uml::RoleType::B); if (bSelect && widA && widA->isSelected() && widB && widB->isSelected()) { assocwidget->setSelected(true); } else { assocwidget->setSelected(false); } } } /** * Fills Associations with all the associations that includes a widget related to object */ void UMLScene::getWidgetAssocs(UMLObject* Obj, AssociationWidgetList & Associations) { if (! Obj) return; foreach(AssociationWidget* assocwidget, associationList()) { if (assocwidget->widgetForRole(Uml::RoleType::A)->umlObject() == Obj || assocwidget->widgetForRole(Uml::RoleType::B)->umlObject() == Obj) Associations.append(assocwidget); } } /** * Removes All the associations of the diagram */ void UMLScene::removeAllAssociations() { //Remove All association widgets foreach(AssociationWidget* assocwidget, associationList()) { removeWidgetCmd(assocwidget); } } /** * Removes All the widgets of the diagram */ void UMLScene::removeAllWidgets() { // Remove widgets. foreach(UMLWidget* temp, widgetList()) { uIgnoreZeroPointer(temp); // I had to take this condition back in, else umbrello // crashes on exit. Still to be analyzed. --okellogg if (!(temp->isTextWidget() && temp->asFloatingTextWidget()->textRole() != TextRole::Floating)) { removeWidgetCmd(temp); } } } /** * Refreshes containment association, i.e. removes possible old * containment and adds new containment association if applicable. * * @param self Pointer to the contained object for which * the association to the containing object is * recomputed. */ void UMLScene::updateContainment(UMLCanvasObject *self) { if (self == 0) return; // See if the object has a widget representation in this view. // While we're at it, also see if the new parent has a widget here. UMLWidget *selfWidget = 0, *newParentWidget = 0; UMLPackage *newParent = self->umlPackage(); foreach(UMLWidget* w, widgetList()) { UMLObject *o = w->umlObject(); if (o == self) selfWidget = w; else if (newParent != 0 && o == newParent) newParentWidget = w; } if (selfWidget == 0) return; // Remove possibly obsoleted containment association. foreach(AssociationWidget* a, associationList()) { if (a->associationType() != Uml::AssociationType::Containment) continue; // Container is at role A, containee at B. // We only look at association for which we are B. UMLWidget *wB = a->widgetForRole(Uml::RoleType::B); UMLObject *roleBObj = wB->umlObject(); if (roleBObj != self) continue; UMLWidget *wA = a->widgetForRole(Uml::RoleType::A); UMLObject *roleAObj = wA->umlObject(); if (roleAObj == newParent) { // Wow, all done. Great! return; } removeWidgetCmd(a); // It's okay to break out because there can only be a single // containing object. break; } if (newParentWidget == 0) return; // Create the new containment association. AssociationWidget *a = AssociationWidget::create (this, newParentWidget, Uml::AssociationType::Containment, selfWidget); addWidgetCmd(a); } /** * Creates automatically any Associations that the given @ref UMLWidget * may have on any diagram. This method is used when you just add the UMLWidget * to a diagram. */ void UMLScene::createAutoAssociations(UMLWidget * widget) { if (widget == 0 || (m_Type != Uml::DiagramType::Class && m_Type != Uml::DiagramType::Object && m_Type != Uml::DiagramType::Component && m_Type != Uml::DiagramType::Deployment && m_Type != Uml::DiagramType::EntityRelationship)) return; // Recipe: // If this widget has an underlying UMLCanvasObject then // for each of the UMLCanvasObject's UMLAssociations // if umlassoc's "other" role has a widget representation on this view then // if the AssocWidget does not already exist then // if the assoc type is permitted in the current diagram type then // create the AssocWidget // end if // end if // end if // end loop // Do createAutoAttributeAssociations() // if this object is capable of containing nested objects then // for each of the object's containedObjects // if the containedObject has a widget representation on this view then // if the containedWidget is not physically located inside this widget // create the containment AssocWidget // end if // end if // end loop // end if // if the UMLCanvasObject has a parentPackage then // if the parentPackage has a widget representation on this view then // create the containment AssocWidget // end if // end if // end if UMLObject *tmpUmlObj = widget->umlObject(); if (tmpUmlObj == 0) return; UMLCanvasObject *umlObj = tmpUmlObj->asUMLCanvasObject(); if (umlObj == 0) return; const UMLAssociationList& umlAssocs = umlObj->getAssociations(); Uml::ID::Type myID = umlObj->id(); foreach(UMLAssociation* assoc, umlAssocs) { UMLCanvasObject *other = 0; UMLObject *roleAObj = assoc->getObject(Uml::RoleType::A); if (roleAObj == 0) { DEBUG(DBG_SRC) << "roleA object is NULL at UMLAssoc " << Uml::ID::toString(assoc->id()); continue; } UMLObject *roleBObj = assoc->getObject(Uml::RoleType::B); if (roleBObj == 0) { DEBUG(DBG_SRC) << "roleB object is NULL at UMLAssoc " << Uml::ID::toString(assoc->id()); continue; } if (roleAObj->id() == myID) { other = roleBObj->asUMLCanvasObject(); } else if (roleBObj->id() == myID) { other = roleAObj->asUMLCanvasObject(); } else { DEBUG(DBG_SRC) << "Cannot find own object " << Uml::ID::toString(myID) << " in UMLAssoc " << Uml::ID::toString(assoc->id()); continue; } // Now that we have determined the "other" UMLObject, seek it in // this view's UMLWidgets. if (!other) { continue; } Uml::ID::Type otherID = other->id(); bool breakFlag = false; UMLWidget* pOtherWidget = 0; foreach(pOtherWidget, widgetList()) { if (pOtherWidget->id() == otherID) { breakFlag = true; break; } } if (!breakFlag) continue; // Both objects are represented in this view: // Assign widget roles as indicated by the UMLAssociation. UMLWidget *widgetA, *widgetB; if (myID == roleAObj->id()) { widgetA = widget; widgetB = pOtherWidget; } else { widgetA = pOtherWidget; widgetB = widget; } // Check that the assocwidget does not already exist. Uml::AssociationType::Enum assocType = assoc->getAssocType(); AssociationWidget * assocwidget = findAssocWidget(assocType, widgetA, widgetB); if (assocwidget) { assocwidget->calculateEndingPoints(); // recompute assoc lines continue; } // Check that the assoc is allowed. if (!AssocRules::allowAssociation(assocType, widgetA, widgetB)) { DEBUG(DBG_SRC) << "not transferring assoc " << "of type " << assocType; continue; } // Create the AssociationWidget. assocwidget = AssociationWidget::create(this); assocwidget->setWidgetForRole(widgetA, Uml::RoleType::A); assocwidget->setWidgetForRole(widgetB, Uml::RoleType::B); assocwidget->setAssociationType(assocType); assocwidget->setUMLObject(assoc); // Call calculateEndingPoints() before setting the FloatingTexts // because their positions are computed according to the // assocwidget line positions. assocwidget->calculateEndingPoints(); assocwidget->syncToModel(); assocwidget->setActivated(true); if (! addAssociation(assocwidget)) delete assocwidget; } createAutoAttributeAssociations(widget); if (m_Type == Uml::DiagramType::EntityRelationship) { createAutoConstraintAssociations(widget); } // if this object is capable of containing nested objects then UMLObject::ObjectType t = umlObj->baseType(); if (t == UMLObject::ot_Package || t == UMLObject::ot_Class || t == UMLObject::ot_Interface || t == UMLObject::ot_Component) { // for each of the object's containedObjects UMLPackage *umlPkg = umlObj->asUMLPackage(); UMLObjectList lst = umlPkg->containedObjects(); foreach(UMLObject* obj, lst) { uIgnoreZeroPointer(obj); // if the containedObject has a widget representation on this view then Uml::ID::Type id = obj->id(); foreach(UMLWidget *w, widgetList()) { uIgnoreZeroPointer(w); if (w->id() != id) continue; // if the containedWidget is not physically located inside this widget if (widget->rect().contains(w->rect())) continue; // create the containment AssocWidget AssociationWidget *a = AssociationWidget::create(this, widget, Uml::AssociationType::Containment, w); a->calculateEndingPoints(); a->setActivated(true); if (! addAssociation(a)) delete a; } } } // if the UMLCanvasObject has a parentPackage then UMLPackage *parent = umlObj->umlPackage(); if (parent == 0) return; // if the parentPackage has a widget representation on this view then Uml::ID::Type pkgID = parent->id(); bool breakFlag = false; UMLWidget* pWidget = 0; foreach(pWidget, widgetList()) { uIgnoreZeroPointer(pWidget); if (pWidget->id() == pkgID) { breakFlag = true; break; } } if (!breakFlag || pWidget->rect().contains(widget->rect())) return; // create the containment AssocWidget AssociationWidget *a = AssociationWidget::create(this, pWidget, Uml::AssociationType::Containment, widget); if (! addAssociation(a)) delete a; } /** * If the m_Type of the given widget is WidgetBase::wt_Class then * iterate through the class' attributes and create an * association to each attribute type widget that is present * on the current diagram. */ void UMLScene::createAutoAttributeAssociations(UMLWidget *widget) { if (widget == 0 || m_Type != Uml::DiagramType::Class || !m_Options.classState.showAttribAssocs) return; // Pseudocode: // if the underlying model object is really a UMLClassifier then // for each of the UMLClassifier's UMLAttributes // if the attribute type has a widget representation on this view then // if the AssocWidget does not already exist then // if the current diagram type permits compositions then // create a composition AssocWidget // end if // end if // end if // if the attribute type is a Datatype then // if the Datatype is a reference (pointer) type then // if the referenced type has a widget representation on this view then // if the AssocWidget does not already exist then // if the current diagram type permits aggregations then // create an aggregation AssocWidget from the ClassifierWidget to the // widget of the referenced type // end if // end if // end if // end if // end if // end loop // end if // // Implementation: UMLObject *tmpUmlObj = widget->umlObject(); if (tmpUmlObj == 0) return; // if the underlying model object is really a UMLClassifier then if (tmpUmlObj->isUMLDatatype()) { UMLDatatype *dt = tmpUmlObj->asUMLDatatype(); while (dt && dt->originType() != 0) { tmpUmlObj = dt->originType(); if (!tmpUmlObj->isUMLDatatype()) break; dt = tmpUmlObj->asUMLDatatype(); } } if (tmpUmlObj->baseType() != UMLObject::ot_Class) return; UMLClassifier * klass = tmpUmlObj->asUMLClassifier(); // for each of the UMLClassifier's UMLAttributes UMLAttributeList attrList = klass->getAttributeList(); foreach(UMLAttribute* attr, attrList) { createAutoAttributeAssociation(attr->getType(), attr, widget); /* * The following code from attachment 19935 of http://bugs.kde.org/140669 * creates Aggregation/Composition to the template parameters. * The current solution uses Dependency instead, see handling of template * instantiation at Import_Utils::createUMLObject(). UMLClassifierList templateList = attr->getTemplateParams(); for (UMLClassifierListIt it(templateList); it.current(); ++it) { createAutoAttributeAssociation(it, attr, widget); } */ } } /** * Create an association with the attribute attr associated with the UMLWidget * widget if the UMLClassifier type is present on the current diagram. */ void UMLScene::createAutoAttributeAssociation(UMLClassifier *type, UMLAttribute *attr, UMLWidget *widget /*, UMLClassifier * klass*/) { if (type == 0) { // DEBUG(DBG_SRC) << klass->getName() << ": type is NULL for " // << "attribute " << attr->getName(); return; } Uml::AssociationType::Enum assocType = Uml::AssociationType::Composition; UMLWidget *w = findWidget(type->id()); // if the attribute type has a widget representation on this view if (w) { AssociationWidget *a = findAssocWidget(widget, w, attr->name()); if (a == 0 && // if the current diagram type permits compositions AssocRules::allowAssociation(assocType, widget, w)) { // Create a composition AssocWidget, or, if the attribute type is // stereotyped <>, create a UniAssociation widget. if (type->stereotype() == QLatin1String("CORBAInterface")) assocType = Uml::AssociationType::UniAssociation; a = AssociationWidget::create(this, widget, assocType, w, attr); a->setVisibility(attr->visibility(), Uml::RoleType::B); /* if (assocType == Uml::AssociationType::Aggregation || assocType == Uml::AssociationType::UniAssociation) a->setMulti("0..1", Uml::RoleType::B); */ a->setRoleName(attr->name(), Uml::RoleType::B); a->setActivated(true); if (! addAssociation(a)) delete a; } } // if the attribute type is a Datatype then if (type->isUMLDatatype()) { UMLDatatype *dt = type->asUMLDatatype(); // if the Datatype is a reference (pointer) type if (dt && dt->isReference()) { //Uml::AssociationType::Enum assocType = Uml::AssociationType::Composition; UMLClassifier *c = dt->originType(); UMLWidget *w = c ? findWidget(c->id()) : 0; // if the referenced type has a widget representation on this view if (w) { AssociationWidget *a = findAssocWidget(widget, w, attr->name()); if (a == 0 && // if the current diagram type permits aggregations AssocRules::allowAssociation(Uml::AssociationType::Aggregation, widget, w)) { // create an aggregation AssocWidget from the ClassifierWidget // to the widget of the referenced type a = AssociationWidget::create (this, widget, Uml::AssociationType::Aggregation, w, attr); a->setVisibility(attr->visibility(), Uml::RoleType::B); //a->setChangeability(true, Uml::RoleType::B); a->setMultiplicity(QLatin1String("0..1"), Uml::RoleType::B); a->setRoleName(attr->name(), Uml::RoleType::B); a->setActivated(true); if (! addAssociation(a)) delete a; } } } } } void UMLScene::createAutoConstraintAssociations(UMLWidget *widget) { if (widget == 0 || m_Type != Uml::DiagramType::EntityRelationship) return; // Pseudocode: // if the underlying model object is really a UMLEntity then // for each of the UMLEntity's UMLForeignKeyConstraint's // if the attribute type has a widget representation on this view then // if the AssocWidget does not already exist then // if the current diagram type permits relationships then // create a relationship AssocWidget // end if // end if // end if UMLObject *tmpUmlObj = widget->umlObject(); if (tmpUmlObj == 0) return; // check if the underlying model object is really a UMLEntity UMLCanvasObject *umlObj = tmpUmlObj->asUMLCanvasObject(); if (umlObj == 0) return; // finished checking whether this widget has a UMLCanvas Object if (tmpUmlObj->baseType() != UMLObject::ot_Entity) return; UMLEntity *entity = tmpUmlObj->asUMLEntity(); // for each of the UMLEntity's UMLForeignKeyConstraints UMLClassifierListItemList constrList = entity->getFilteredList(UMLObject::ot_ForeignKeyConstraint); foreach(UMLClassifierListItem* cli, constrList) { UMLEntityConstraint *eConstr = cli->asUMLEntityConstraint(); UMLForeignKeyConstraint* fkc = eConstr->asUMLForeignKeyConstraint(); if (fkc == 0) { return; } UMLEntity* refEntity = fkc->getReferencedEntity(); if (refEntity == 0) { return; } createAutoConstraintAssociation(refEntity, fkc, widget); } } void UMLScene::createAutoConstraintAssociation(UMLEntity* refEntity, UMLForeignKeyConstraint* fkConstraint, UMLWidget* widget) { if (refEntity == 0) { return; } Uml::AssociationType::Enum assocType = Uml::AssociationType::Relationship; UMLWidget *w = findWidget(refEntity->id()); AssociationWidget *aw = 0; if (w) { aw = findAssocWidget(w, widget, fkConstraint->name()); if (aw == 0 && // if the current diagram type permits relationships AssocRules::allowAssociation(assocType, w, widget)) { // for foreign key contstraint, we need to create the association type Uml::AssociationType::Relationship. // The referenced entity is the "1" part (Role A) and the entity holding the relationship is the "many" part. (Role B) AssociationWidget *a = AssociationWidget::create(this, w, assocType, widget); a->setUMLObject(fkConstraint); //a->setVisibility(attr->getVisibility(), Uml::RoleType::B); a->setRoleName(fkConstraint->name(), Uml::RoleType::B); a->setActivated(true); if (! addAssociation(a)) delete a; } } } void UMLScene::createAutoAttributeAssociations2(UMLWidget *widget) { foreach(UMLWidget* w, widgetList()) { uIgnoreZeroPointer(w); if (w != widget) { createAutoAttributeAssociations(w); if (widget->umlObject() && widget->umlObject()->baseType() == UMLObject::ot_Entity) createAutoConstraintAssociations(w); } } } /** * Find the maximum bounding rectangle of FloatingTextWidget widgets. * Auxiliary to copyAsImage(). * * @param ft Pointer to the FloatingTextWidget widget to consider. * @param px X coordinate of lower left corner. This value will be * updated if the X coordinate of the lower left corner * of ft is smaller than the px value passed in. * @param py Y coordinate of lower left corner. This value will be * updated if the Y coordinate of the lower left corner * of ft is smaller than the py value passed in. * @param qx X coordinate of upper right corner. This value will be * updated if the X coordinate of the upper right corner * of ft is larger than the qx value passed in. * @param qy Y coordinate of upper right corner. This value will be * updated if the Y coordinate of the upper right corner * of ft is larger than the qy value passed in. */ void UMLScene::findMaxBoundingRectangle(const FloatingTextWidget* ft, qreal& px, qreal& py, qreal& qx, qreal& qy) { if (ft == 0 || !ft->isVisible()) return; qreal x = ft->x(); qreal y = ft->y(); qreal x1 = x + ft->width() - 1; qreal y1 = y + ft->height() - 1; if (px == -1 || x < px) px = x; if (py == -1 || y < py) py = y; if (qx == -1 || x1 > qx) qx = x1; if (qy == -1 || y1 > qy) qy = y1; } /** * Returns the PNG picture of the paste operation. */ void UMLScene::copyAsImage(QPixmap*& pix) { //get the smallest rect holding the diagram QRectF rect = diagramRect(); QPixmap diagram(rect.width(), rect.height()); //only draw what is selected m_bDrawSelectedOnly = true; selectAssociations(true); getDiagram(diagram, rect); //now get the selection cut qreal px = -1, py = -1, qx = -1, qy = -1; //first get the smallest rect holding the widgets foreach(UMLWidget* temp, selectedWidgets()) { qreal x = temp->x(); qreal y = temp->y(); qreal x1 = x + temp->width() - 1; qreal y1 = y + temp->height() - 1; if (px == -1 || x < px) { px = x; } if (py == -1 || y < py) { py = y; } if (qx == -1 || x1 > qx) { qx = x1; } if (qy == -1 || y1 > qy) { qy = y1; } } //also take into account any text lines in assocs or messages //get each type of associations //This needs to be reimplemented to increase the rectangle //if a part of any association is not included foreach(AssociationWidget *a, associationList()) { if (! a->isSelected()) continue; const FloatingTextWidget* multiA = a->multiplicityWidget(Uml::RoleType::A); const FloatingTextWidget* multiB = a->multiplicityWidget(Uml::RoleType::B); const FloatingTextWidget* roleA = a->roleWidget(Uml::RoleType::A); const FloatingTextWidget* roleB = a->roleWidget(Uml::RoleType::B); const FloatingTextWidget* changeA = a->changeabilityWidget(Uml::RoleType::A); const FloatingTextWidget* changeB = a->changeabilityWidget(Uml::RoleType::B); findMaxBoundingRectangle(multiA, px, py, qx, qy); findMaxBoundingRectangle(multiB, px, py, qx, qy); findMaxBoundingRectangle(roleA, px, py, qx, qy); findMaxBoundingRectangle(roleB, px, py, qx, qy); findMaxBoundingRectangle(changeA, px, py, qx, qy); findMaxBoundingRectangle(changeB, px, py, qx, qy); }//end foreach QRectF imageRect; //area with respect to diagramRect() //i.e. all widgets on the scene. Was previously with //respect to whole scene imageRect.setLeft(px - rect.left()); imageRect.setTop(py - rect.top()); imageRect.setRight(qx - rect.left()); imageRect.setBottom(qy - rect.top()); pix = new QPixmap(imageRect.width(), imageRect.height()); QPainter output(pix); output.drawPixmap(QPoint(0, 0), diagram, imageRect); m_bDrawSelectedOnly = false; } /** * Reset the toolbar. */ void UMLScene::resetToolbar() { emit sigResetToolBar(); } /** * Event handler for context menu events. */ void UMLScene::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { QGraphicsScene::contextMenuEvent(event); if (!event->isAccepted()) { setPos(event->scenePos()); UMLScenePopupMenu popup(m_view, this); QAction *triggered = popup.exec(event->screenPos()); slotMenuSelection(triggered); event->accept(); } } /** * Returns the status on whether in a paste state. * * @return Returns the status on whether in a paste state. */ bool UMLScene::getPaste() const { return m_bPaste; } /** * Sets the status on whether in a paste state. */ void UMLScene::setPaste(bool paste) { m_bPaste = paste; } /** * When a menu selection has been made on the menu * that this view created, this method gets called. */ void UMLScene::slotMenuSelection(QAction* action) { ListPopupMenu::MenuType sel = ListPopupMenu::typeFromAction(action); switch (sel) { case ListPopupMenu::mt_Undo: UMLApp::app()->undo(); break; case ListPopupMenu::mt_Redo: UMLApp::app()->redo(); break; case ListPopupMenu::mt_Clear: clearDiagram(); break; case ListPopupMenu::mt_Export_Image: m_pImageExporter->exportView(); break; case ListPopupMenu::mt_Apply_Layout: case ListPopupMenu::mt_Apply_Layout1: case ListPopupMenu::mt_Apply_Layout2: case ListPopupMenu::mt_Apply_Layout3: case ListPopupMenu::mt_Apply_Layout4: case ListPopupMenu::mt_Apply_Layout5: case ListPopupMenu::mt_Apply_Layout6: case ListPopupMenu::mt_Apply_Layout7: case ListPopupMenu::mt_Apply_Layout8: case ListPopupMenu::mt_Apply_Layout9: { QVariant value = ListPopupMenu::dataFromAction(ListPopupMenu::dt_ApplyLayout, action); applyLayout(value.toString()); } break; case ListPopupMenu::mt_FloatText: { FloatingTextWidget* ft = new FloatingTextWidget(this); ft->showChangeTextDialog(); //if no text entered delete if (!FloatingTextWidget::isTextValid(ft->text())) { delete ft; } else { ft->setID(UniqueID::gen()); setupNewWidget(ft); } } break; case ListPopupMenu::mt_UseCase: m_bCreateObject = true; Object_Factory::createUMLObject(UMLObject::ot_UseCase); break; case ListPopupMenu::mt_Actor: m_bCreateObject = true; Object_Factory::createUMLObject(UMLObject::ot_Actor); break; case ListPopupMenu::mt_Class: case ListPopupMenu::mt_Object: m_bCreateObject = true; Object_Factory::createUMLObject(UMLObject::ot_Class); break; case ListPopupMenu::mt_Package: m_bCreateObject = true; Object_Factory::createUMLObject(UMLObject::ot_Package); break; case ListPopupMenu::mt_Subsystem: { m_bCreateObject = true; UMLPackage *component = UMLApp::app()->document()->rootFolder(Uml::ModelType::Component); QString name = Model_Utils::uniqObjectName(UMLObject::ot_Package, component, i18n("new_subsystem")); if (Dialog_Utils::askName(i18n("Enter Subsystem Name"), i18n("Enter the name of the new subsystem"), name)) { UMLObject *package = Object_Factory::createUMLObject(UMLObject::ot_Package, name, component); if (package) package->setStereotypeCmd(QLatin1String("subsystem")); } break; } case ListPopupMenu::mt_Component: m_bCreateObject = true; Object_Factory::createUMLObject(UMLObject::ot_Component); break; case ListPopupMenu::mt_Node: m_bCreateObject = true; Object_Factory::createUMLObject(UMLObject::ot_Node); break; case ListPopupMenu::mt_Artifact: m_bCreateObject = true; Object_Factory::createUMLObject(UMLObject::ot_Artifact); break; case ListPopupMenu::mt_Interface: m_bCreateObject = true; Object_Factory::createUMLObject(UMLObject::ot_Interface); break; case ListPopupMenu::mt_Enum: m_bCreateObject = true; Object_Factory::createUMLObject(UMLObject::ot_Enum); break; case ListPopupMenu::mt_Entity: m_bCreateObject = true; Object_Factory::createUMLObject(UMLObject::ot_Entity); break; case ListPopupMenu::mt_Category: m_bCreateObject = true; Object_Factory::createUMLObject(UMLObject::ot_Category); break; case ListPopupMenu::mt_Datatype: m_bCreateObject = true; Object_Factory::createUMLObject(UMLObject::ot_Datatype); break; case ListPopupMenu::mt_Instance: m_bCreateObject = true; Object_Factory::createUMLObject(UMLObject::ot_Instance); break; case ListPopupMenu::mt_Note: { m_bCreateObject = true; UMLWidget* widget = new NoteWidget(this); addItem(widget); widget->setPos(pos()); widget->setSize(100, 40); widget->showPropertiesDialog(); QSizeF size = widget->minimumSize(); widget->setSize(size); break; } case ListPopupMenu::mt_Cut: //FIXME make this work for diagram's right click menu if (selectedWidgets().count() && UMLApp::app()->editCutCopy(true)) { deleteSelection(); m_doc->setModified(true); } break; case ListPopupMenu::mt_Copy: //FIXME make this work for diagram's right click menu selectedWidgets().count() && UMLApp::app()->editCutCopy(true); break; case ListPopupMenu::mt_Paste: m_PastePoint = m_Pos; m_Pos.setX(2000); m_Pos.setY(2000); UMLApp::app()->slotEditPaste(); m_PastePoint.setX(0); m_PastePoint.setY(0); break; case ListPopupMenu::mt_Initial_State: { StateWidget* state = new StateWidget(this, StateWidget::Initial); setupNewWidget(state); } break; case ListPopupMenu::mt_End_State: { StateWidget* state = new StateWidget(this, StateWidget::End); setupNewWidget(state); } break; case ListPopupMenu::mt_Junction: { StateWidget* state = new StateWidget(this, StateWidget::Junction); setupNewWidget(state); } break; case ListPopupMenu::mt_DeepHistory: { StateWidget* state = new StateWidget(this, StateWidget::DeepHistory); setupNewWidget(state); } break; case ListPopupMenu::mt_ShallowHistory: { StateWidget* state = new StateWidget(this, StateWidget::ShallowHistory); setupNewWidget(state); } break; case ListPopupMenu::mt_Choice: { StateWidget* state = new StateWidget(this, StateWidget::Choice); setupNewWidget(state); } break; case ListPopupMenu::mt_StateFork: { StateWidget* state = new StateWidget(this, StateWidget::Fork); setupNewWidget(state); } break; case ListPopupMenu::mt_StateJoin: { StateWidget* state = new StateWidget(this, StateWidget::Join); setupNewWidget(state); } break; case ListPopupMenu::mt_State: { QString name = i18n("new state"); bool ok = Dialog_Utils::askName(i18n("Enter State Name"), i18n("Enter the name of the new state:"), name); if (ok) { StateWidget* state = new StateWidget(this); state->setName(name); setupNewWidget(state); } } break; case ListPopupMenu::mt_Initial_Activity: { ActivityWidget* activity = new ActivityWidget(this, ActivityWidget::Initial); setupNewWidget(activity); } break; case ListPopupMenu::mt_End_Activity: { ActivityWidget* activity = new ActivityWidget(this, ActivityWidget::End); setupNewWidget(activity); } break; case ListPopupMenu::mt_Branch: { ActivityWidget* activity = new ActivityWidget(this, ActivityWidget::Branch); setupNewWidget(activity); } break; case ListPopupMenu::mt_Activity: { QString name = i18n("new activity"); bool ok = Dialog_Utils::askName(i18n("Enter Activity Name"), i18n("Enter the name of the new activity:"), name); if (ok) { ActivityWidget* activity = new ActivityWidget(this, ActivityWidget::Normal); activity->setName(name); setupNewWidget(activity); } } break; case ListPopupMenu::mt_SnapToGrid: toggleSnapToGrid(); m_doc->setModified(); break; case ListPopupMenu::mt_SnapComponentSizeToGrid: toggleSnapComponentSizeToGrid(); m_doc->setModified(); break; case ListPopupMenu::mt_ShowSnapGrid: toggleShowGrid(); m_doc->setModified(); break; case ListPopupMenu::mt_ShowDocumentationIndicator: setShowDocumentationIndicator(!isShowDocumentationIndicator()); update(); break; case ListPopupMenu::mt_Properties: if (m_view->showPropertiesDialog() == true) m_doc->setModified(); break; case ListPopupMenu::mt_Delete: m_doc->removeDiagram(ID()); break; case ListPopupMenu::mt_Rename: { QString newName = name(); bool ok = Dialog_Utils::askName(i18n("Enter Diagram Name"), i18n("Enter the new name of the diagram:"), newName); if (ok) { setName(newName); m_doc->signalDiagramRenamed(activeView()); } } break; case ListPopupMenu::mt_Import_from_File: { QPointer dialog = new UMLFileDialog(QUrl(), QString(), UMLApp::app()); dialog->exec(); QUrl url = dialog->selectedUrl(); if (!url.isEmpty()) if (!Diagram_Utils::importGraph(url.toLocalFile(), this)) UMLApp::app()->slotStatusMsg(i18n("Failed to import from file.")); break; } default: uWarning() << "unknown ListPopupMenu::MenuType " << ListPopupMenu::toString(sel); break; } } /** * Connects to the signal that @ref UMLApp emits when a cut operation * is successful. * If the view or a child started the operation the flag m_bStartedCut will * be set and we can carry out any operation that is needed, like deleting the selected * widgets for the cut operation. */ void UMLScene::slotCutSuccessful() { if (m_bStartedCut) { deleteSelection(); m_bStartedCut = false; } } /** * Called by menu when to show the instance of the view. */ void UMLScene::slotShowView() { m_doc->changeCurrentView(ID()); } /** * Returns the offset point at which to place the paste from clipboard. * Just add the amount to your co-ords. * Only call this straight after the event, the value won't stay valid. * Should only be called by Assoc widgets at the moment. no one else needs it. */ QPointF UMLScene::getPastePoint() { QPointF point = m_PastePoint; point.setX(point.x() - m_Pos.x()); point.setY(point.y() - m_Pos.y()); return point; } /** * Reset the paste point. */ void UMLScene::resetPastePoint() { m_PastePoint = m_Pos; } /** * Called by the view or any of its children when they start a cut * operation. */ void UMLScene::setStartedCut() { m_bStartedCut = true; } /** * Returns the font to use */ QFont UMLScene::font() const { return m_Options.uiState.font; } /** * Sets the font for the view and optionally all the widgets on the view. */ void UMLScene::setFont(QFont font, bool changeAllWidgets /* = false */) { m_Options.uiState.font = font; if (!changeAllWidgets) return; foreach(UMLWidget* w, widgetList()) { uIgnoreZeroPointer(w); w->setFont(font); } } /** * Sets some options for all the @ref ClassifierWidget on the view. */ void UMLScene::setClassWidgetOptions(ClassOptionsPage * page) { foreach(UMLWidget* pWidget, widgetList()) { uIgnoreZeroPointer(pWidget); WidgetBase::WidgetType wt = pWidget->baseType(); if (wt == WidgetBase::wt_Class || wt == WidgetBase::wt_Interface) { page->setWidget(static_cast(pWidget)); page->apply(); } } } /** * Returns the type of the selected widget or widgets. * * If multiple widgets of different types are selected. WidgetType::UMLWidget * is returned. */ WidgetBase::WidgetType UMLScene::getUniqueSelectionType() { if (selectedWidgets().isEmpty()) { return WidgetBase::wt_UMLWidget; } // Get the first item and its base type UMLWidget * pTemp = (UMLWidget *) selectedWidgets().first(); WidgetBase::WidgetType tmpType = pTemp->baseType(); // Check all selected items, if they have the same BaseType foreach(pTemp, selectedWidgets()) { if (pTemp->baseType() != tmpType) { return WidgetBase::wt_UMLWidget; } } return tmpType; } /** * Asks for confirmation and clears everything on the diagram. * Called from menus. */ void UMLScene::clearDiagram() { if (Dialog_Utils::askDeleteDiagram()) { removeAllWidgets(); } } /** * Apply an automatic layout. */ void UMLScene::applyLayout(const QString &variant) { DEBUG(DBG_SRC) << "layout = " << variant; LayoutGenerator r; r.generate(this, variant); r.apply(this); UMLApp::app()->slotZoomFit(); } /** * Changes snap to grid boolean. * Called from menus. */ void UMLScene::toggleSnapToGrid() { setSnapToGrid(!snapToGrid()); } /** * Changes snap to grid for component size boolean. * Called from menus. */ void UMLScene::toggleSnapComponentSizeToGrid() { setSnapComponentSizeToGrid(!snapComponentSizeToGrid()); } /** * Changes show grid boolean. * Called from menus. */ void UMLScene::toggleShowGrid() { setSnapGridVisible(!isSnapGridVisible()); } /** * Return whether to use snap to grid. */ bool UMLScene::snapToGrid() const { return m_bUseSnapToGrid; } /** * Sets whether to snap to grid. */ void UMLScene::setSnapToGrid(bool bSnap) { m_bUseSnapToGrid = bSnap; emit sigSnapToGridToggled(snapToGrid()); } /** * Return whether to use snap to grid for component size. */ bool UMLScene::snapComponentSizeToGrid() const { return m_bUseSnapComponentSizeToGrid; } /** * Sets whether to snap to grid for component size. */ void UMLScene::setSnapComponentSizeToGrid(bool bSnap) { m_bUseSnapComponentSizeToGrid = bSnap; updateComponentSizes(); emit sigSnapComponentSizeToGridToggled(snapComponentSizeToGrid()); } /** * Returns the x grid size. */ int UMLScene::snapX() const { return m_layoutGrid->gridSpacingX(); } /** * Returns the y grid size. */ int UMLScene::snapY() const { return m_layoutGrid->gridSpacingY(); } /** * Sets the grid size in x and y. */ void UMLScene::setSnapSpacing(int x, int y) { m_layoutGrid->setGridSpacing(x, y); } /** * Returns the input coordinate with possible grid-snap applied. */ qreal UMLScene::snappedX(qreal _x) { if (snapToGrid()) { int x = (int)_x; int gridX = snapX(); int modX = x % gridX; x -= modX; if (modX >= gridX / 2) x += gridX; return x; } else return _x; } /** * Returns the input coordinate with possible grid-snap applied. */ qreal UMLScene::snappedY(qreal _y) { if (snapToGrid()) { int y = (int)_y; int gridY = snapY(); int modY = y % gridY; y -= modY; if (modY >= gridY / 2) y += gridY; return y; } else return _y; } /** * Returns whether to show snap grid or not. */ bool UMLScene::isSnapGridVisible() const { return m_layoutGrid->isVisible(); } /** * Sets whether to show snap grid. */ void UMLScene::setSnapGridVisible(bool bShow) { m_layoutGrid->setVisible(bShow); emit sigShowGridToggled(bShow); } /** * Returns whether to show documentation indicator. */ bool UMLScene::isShowDocumentationIndicator() const { return m_showDocumentationIndicator; } /** * sets whether to show documentation indicator. */ void UMLScene::setShowDocumentationIndicator(bool bShow) { m_showDocumentationIndicator = bShow; } /** * Returns whether to show operation signatures. */ bool UMLScene::showOpSig() const { return m_Options.classState.showOpSig; } /** * Sets whether to show operation signatures. */ void UMLScene::setShowOpSig(bool bShowOpSig) { m_Options.classState.showOpSig = bShowOpSig; } /** * Changes the zoom to the currently set level (now loaded from file) * Called from UMLApp::slotUpdateViews() */ void UMLScene::fileLoaded() { m_view->setZoom(m_view->zoom()); resizeSceneToItems(); } /** * Sets the size of the scene to just fit on all the items */ void UMLScene::resizeSceneToItems() { // let QGraphicsScene handle scene size by itself setSceneRect(QRectF()); } /** * Updates the size of all components in this view. */ void UMLScene::updateComponentSizes() { // update sizes of all components foreach(UMLWidget *obj, widgetList()) { uIgnoreZeroPointer(obj); obj->updateGeometry(); } } /** * Force the widget font metrics to be updated next time * the widgets are drawn. * This is necessary because the widget size might depend on the * font metrics and the font metrics might change for different * QPainter, i.e. font metrics for Display font and Printer font are * usually different. * Call this when you change the QPainter. */ void UMLScene::forceUpdateWidgetFontMetrics(QPainter * painter) { foreach(UMLWidget *obj, widgetList()) { uIgnoreZeroPointer(obj); obj->forceUpdateFontMetrics(painter); } } /** * Overrides standard method from QGraphicsScene drawing the background. */ void UMLScene::drawBackground(QPainter *painter, const QRectF &rect) { QGraphicsScene::drawBackground(painter, rect); m_layoutGrid->paint(painter, rect); } /** * Creates the "diagram" tag and fills it with the contents of the diagram. */ void UMLScene::saveToXMI1(QDomDocument & qDoc, QDomElement & qElement) { resizeSceneToItems(); QDomElement viewElement = qDoc.createElement(QLatin1String("diagram")); viewElement.setAttribute(QLatin1String("xmi.id"), Uml::ID::toString(m_nID)); viewElement.setAttribute(QLatin1String("name"), name()); viewElement.setAttribute(QLatin1String("type"), m_Type); viewElement.setAttribute(QLatin1String("documentation"), m_Documentation); //option state m_Options.saveToXMI1(viewElement); //misc viewElement.setAttribute(QLatin1String("localid"), Uml::ID::toString(m_nLocalID)); viewElement.setAttribute(QLatin1String("showgrid"), m_layoutGrid->isVisible()); viewElement.setAttribute(QLatin1String("snapgrid"), m_bUseSnapToGrid); viewElement.setAttribute(QLatin1String("snapcsgrid"), m_bUseSnapComponentSizeToGrid); viewElement.setAttribute(QLatin1String("snapx"), m_layoutGrid->gridSpacingX()); viewElement.setAttribute(QLatin1String("snapy"), m_layoutGrid->gridSpacingY()); // FIXME: move to UMLView viewElement.setAttribute(QLatin1String("zoom"), activeView()->zoom()); viewElement.setAttribute(QLatin1String("canvasheight"), QString::number(height())); viewElement.setAttribute(QLatin1String("canvaswidth"), QString::number(width())); viewElement.setAttribute(QLatin1String("isopen"), isOpen()); if (type() == Uml::DiagramType::Sequence || type() == Uml::DiagramType::Collaboration) viewElement.setAttribute(QLatin1String("autoincrementsequence"), autoIncrementSequence()); //now save all the widgets QDomElement widgetElement = qDoc.createElement(QLatin1String("widgets")); foreach(UMLWidget *widget, widgetList()) { uIgnoreZeroPointer(widget); // do not save floating text widgets having a parent widget; they are saved as part of the parent if (widget->isTextWidget() && widget->parentItem()) continue; // Having an exception is bad I know, but gotta work with // system we are given. // We DON'T want to record any text widgets which are belonging // to associations as they are recorded later in the "associations" // section when each owning association is dumped. -b.t. if ((!widget->isTextWidget() && !widget->isFloatingDashLineWidget()) || (widget->asFloatingTextWidget() && widget->asFloatingTextWidget()->link() == 0)) widget->saveToXMI1(qDoc, widgetElement); } viewElement.appendChild(widgetElement); //now save the message widgets QDomElement messageElement = qDoc.createElement(QLatin1String("messages")); foreach(UMLWidget* widget, messageList()) { widget->saveToXMI1(qDoc, messageElement); } viewElement.appendChild(messageElement); //now save the associations QDomElement assocElement = qDoc.createElement(QLatin1String("associations")); if (associationList().count()) { // We guard against (associationList().count() == 0) because // this code could be reached as follows: // ^ UMLScene::saveToXMI1() // ^ UMLDoc::saveToXMI1() // ^ UMLDoc::addToUndoStack() // ^ UMLDoc::setModified() // ^ UMLDoc::createDiagram() // ^ UMLDoc::newDocument() // ^ UMLApp::newDocument() // ^ main() // AssociationWidget * assoc = 0; foreach(assoc, associationList()) { assoc->saveToXMI1(qDoc, assocElement); } } viewElement.appendChild(assocElement); qElement.appendChild(viewElement); } /** * Loads the "diagram" tag. */ bool UMLScene::loadFromXMI1(QDomElement & qElement) { QString id = qElement.attribute(QLatin1String("xmi.id"), QLatin1String("-1")); m_nID = Uml::ID::fromString(id); if (m_nID == Uml::ID::None) return false; setName(qElement.attribute(QLatin1String("name"))); QString type = qElement.attribute(QLatin1String("type"), QLatin1String("0")); m_Documentation = qElement.attribute(QLatin1String("documentation")); QString localid = qElement.attribute(QLatin1String("localid"), QLatin1String("0")); // option state m_Options.loadFromXMI1(qElement); setBackgroundBrush(m_Options.uiState.backgroundColor); setGridDotColor(m_Options.uiState.gridDotColor); //misc QString showgrid = qElement.attribute(QLatin1String("showgrid"), QLatin1String("0")); m_layoutGrid->setVisible((bool)showgrid.toInt()); QString snapgrid = qElement.attribute(QLatin1String("snapgrid"), QLatin1String("0")); m_bUseSnapToGrid = (bool)snapgrid.toInt(); QString snapcsgrid = qElement.attribute(QLatin1String("snapcsgrid"), QLatin1String("0")); m_bUseSnapComponentSizeToGrid = (bool)snapcsgrid.toInt(); QString snapx = qElement.attribute(QLatin1String("snapx"), QLatin1String("10")); QString snapy = qElement.attribute(QLatin1String("snapy"), QLatin1String("10")); m_layoutGrid->setGridSpacing(snapx.toInt(), snapy.toInt()); QString zoom = qElement.attribute(QLatin1String("zoom"), QLatin1String("100")); activeView()->setZoom(zoom.toInt()); resizeSceneToItems(); QString isOpen = qElement.attribute(QLatin1String("isopen"), QLatin1String("1")); m_isOpen = (bool)isOpen.toInt(); int nType = type.toInt(); if (nType == -1 || nType >= 400) { // Pre 1.5.5 numeric values // Values of "type" were changed in 1.5.5 to merge with Settings::Diagram switch (nType) { case 400: m_Type = Uml::DiagramType::UseCase; break; case 401: m_Type = Uml::DiagramType::Collaboration; break; case 402: m_Type = Uml::DiagramType::Class; break; case 403: m_Type = Uml::DiagramType::Sequence; break; case 404: m_Type = Uml::DiagramType::State; break; case 405: m_Type = Uml::DiagramType::Activity; break; case 406: m_Type = Uml::DiagramType::Component; break; case 407: m_Type = Uml::DiagramType::Deployment; break; case 408: m_Type = Uml::DiagramType::EntityRelationship; break; case 409: m_Type = Uml::DiagramType::Object; break; default: m_Type = Uml::DiagramType::Undefined; break; } } else { m_Type = Uml::DiagramType::fromInt(nType); } m_nLocalID = Uml::ID::fromString(localid); if (m_Type == Uml::DiagramType::Sequence || m_Type == Uml::DiagramType::Collaboration) { QString autoIncrementSequence = qElement.attribute(QLatin1String("autoincrementsequence"), QLatin1String("0")); m_autoIncrementSequence = (bool)autoIncrementSequence.toInt(); } QDomNode node = qElement.firstChild(); bool widgetsLoaded = false, messagesLoaded = false, associationsLoaded = false; while (!node.isNull()) { QDomElement element = node.toElement(); if (!element.isNull()) { if (element.tagName() == QLatin1String("widgets")) widgetsLoaded = loadWidgetsFromXMI(element); else if (element.tagName() == QLatin1String("messages")) messagesLoaded = loadMessagesFromXMI(element); else if (element.tagName() == QLatin1String("associations")) associationsLoaded = loadAssociationsFromXMI(element); } node = node.nextSibling(); } if (!widgetsLoaded) { uWarning() << "failed UMLScene load on widgets"; return false; } if (!messagesLoaded) { uWarning() << "failed UMLScene load on messages"; return false; } if (!associationsLoaded) { uWarning() << "failed UMLScene load on associations"; return false; } if (this->type() == Uml::DiagramType::Component) { m_d->addMissingPorts(); m_d->fixPortPositions(); } + m_d->removeDuplicatedFloatingTextInstances(); return true; } bool UMLScene::loadWidgetsFromXMI(QDomElement & qElement) { UMLWidget* widget = 0; QDomNode node = qElement.firstChild(); QDomElement widgetElement = node.toElement(); while (!widgetElement.isNull()) { widget = loadWidgetFromXMI(widgetElement); if (widget) { addWidgetCmd(widget); widget->clipSize(); // In the interest of best-effort loading, in case of a // (widget == 0) we still go on. // The individual widget's loadFromXMI1 method should // already have generated an error message to tell the // user that something went wrong. } node = widgetElement.nextSibling(); widgetElement = node.toElement(); } return true; } /** * Loads a "widget" element from XMI, used by loadFromXMI1() and the clipboard. */ UMLWidget* UMLScene::loadWidgetFromXMI(QDomElement& widgetElement) { if (!m_doc) { uWarning() << "m_doc is NULL"; return 0L; } QString tag = widgetElement.tagName(); QString idstr = widgetElement.attribute(QLatin1String("xmi.id"), QLatin1String("-1")); UMLWidget* widget = Widget_Factory::makeWidgetFromXMI(tag, idstr, this); if (widget == 0) return 0; if (!widget->loadFromXMI1(widgetElement)) { widget->cleanup(); delete widget; return 0; } return widget; } bool UMLScene::loadMessagesFromXMI(QDomElement & qElement) { MessageWidget * message = 0; QDomNode node = qElement.firstChild(); QDomElement messageElement = node.toElement(); while (!messageElement.isNull()) { QString tag = messageElement.tagName(); DEBUG(DBG_SRC) << "tag = " << tag; if (tag == QLatin1String("messagewidget") || tag == QLatin1String("UML:MessageWidget")) { // for bkwd compatibility message = new MessageWidget(this, SequenceMessage::Asynchronous, Uml::ID::Reserved); if (!message->loadFromXMI1(messageElement)) { delete message; return false; } addWidgetCmd(message); FloatingTextWidget *ft = message->floatingTextWidget(); if (!ft && message->sequenceMessageType() != SequenceMessage::Creation) DEBUG(DBG_SRC) << "floating text is NULL for message " << Uml::ID::toString(message->id()); } node = messageElement.nextSibling(); messageElement = node.toElement(); } return true; } bool UMLScene::loadAssociationsFromXMI(QDomElement & qElement) { QDomNode node = qElement.firstChild(); QDomElement assocElement = node.toElement(); int countr = 0; while (!assocElement.isNull()) { QString tag = assocElement.tagName(); if (tag == QLatin1String("assocwidget") || tag == QLatin1String("UML:AssocWidget")) { // for bkwd compatibility countr++; AssociationWidget *assoc = AssociationWidget::create(this); if (!assoc->loadFromXMI1(assocElement)) { uError() << "could not loadFromXMI1 association widget:" << assoc << ", bad XMI file? Deleting from UMLScene."; delete assoc; /* return false; Returning false here is a little harsh when the rest of the diagram might load okay. */ } else { assoc->clipSize(); if (!addAssociation(assoc, false)) { uError() << "Could not addAssociation(" << assoc << ") to UMLScene, deleting."; delete assoc; //return false; // soften error.. may not be that bad } } } node = assocElement.nextSibling(); assocElement = node.toElement(); } return true; } /** * Add an object to the application, and update the view. */ void UMLScene::addObject(UMLObject *object) { m_bCreateObject = true; if (m_doc->addUMLObject(object)) m_doc->signalUMLObjectCreated(object); // m_bCreateObject is reset by slotObjectCreated() else m_bCreateObject = false; } bool UMLScene::loadUisDiagramPresentation(QDomElement & qElement) { for (QDomNode node = qElement.firstChild(); !node.isNull(); node = node.nextSibling()) { QDomElement elem = node.toElement(); QString tag = elem.tagName(); if (! UMLDoc::tagEq(tag, QLatin1String("Presentation"))) { uError() << "ignoring unknown UisDiagramPresentation tag " << tag; continue; } QDomNode n = elem.firstChild(); QDomElement e = n.toElement(); QString idStr; int x = 0, y = 0, w = 0, h = 0; while (!e.isNull()) { tag = e.tagName(); DEBUG(DBG_SRC) << "Presentation: tag = " << tag; if (UMLDoc::tagEq(tag, QLatin1String("Presentation.geometry"))) { QDomNode gnode = e.firstChild(); QDomElement gelem = gnode.toElement(); QString csv = gelem.text(); QStringList dim = csv.split(QLatin1Char(',')); x = dim[0].toInt(); y = dim[1].toInt(); w = dim[2].toInt(); h = dim[3].toInt(); } else if (UMLDoc::tagEq(tag, QLatin1String("Presentation.style"))) { // TBD } else if (UMLDoc::tagEq(tag, QLatin1String("Presentation.model"))) { QDomNode mnode = e.firstChild(); QDomElement melem = mnode.toElement(); idStr = melem.attribute(QLatin1String("xmi.idref")); } else { DEBUG(DBG_SRC) << "ignoring tag " << tag; } n = n.nextSibling(); e = n.toElement(); } Uml::ID::Type id = Uml::ID::fromString(idStr); UMLObject *o = m_doc->findObjectById(id); if (o == 0) { uError() << "Cannot find object for id " << idStr; } else { UMLObject::ObjectType ot = o->baseType(); DEBUG(DBG_SRC) << "Create widget for model object of type " << UMLObject::toString(ot); UMLWidget *widget = 0; switch (ot) { case UMLObject::ot_Class: widget = new ClassifierWidget(this, o->asUMLClassifier()); break; case UMLObject::ot_Association: { UMLAssociation *umla = o->asUMLAssociation(); Uml::AssociationType::Enum at = umla->getAssocType(); UMLObject* objA = umla->getObject(Uml::RoleType::A); UMLObject* objB = umla->getObject(Uml::RoleType::B); if (objA == 0 || objB == 0) { uError() << "intern err 1"; return false; } UMLWidget *wA = findWidget(objA->id()); UMLWidget *wB = findWidget(objB->id()); if (wA != 0 && wB != 0) { AssociationWidget *aw = AssociationWidget::create(this, wA, at, wB, umla); aw->syncToModel(); addWidgetCmd(aw); } else { uError() << "cannot create assocwidget from (" ; //<< wA << ", " << wB << ")"; } break; } case UMLObject::ot_Role: { //UMLRole *robj = o->asUMLRole(); //UMLAssociation *umla = robj->getParentAssociation(); // @todo properly display role names. // For now, in order to get the role names displayed // simply delete the participating diagram objects // and drag them from the list view to the diagram. break; } default: uError() << "Cannot create widget of type " << ot; } if (widget) { DEBUG(DBG_SRC) << "Widget: x=" << x << ", y=" << y << ", w=" << w << ", h=" << h; widget->setX(x); widget->setY(y); widget->setSize(w, h); addWidgetCmd(widget); } } } return true; } /** * Loads the "UISDiagram" tag of Unisys.IntegratePlus.2 generated files. */ bool UMLScene::loadUISDiagram(QDomElement & qElement) { QString idStr = qElement.attribute(QLatin1String("xmi.id")); if (idStr.isEmpty()) return false; m_nID = Uml::ID::fromString(idStr); UMLListViewItem *ulvi = 0; for (QDomNode node = qElement.firstChild(); !node.isNull(); node = node.nextSibling()) { if (node.isComment()) continue; QDomElement elem = node.toElement(); QString tag = elem.tagName(); if (tag == QLatin1String("uisDiagramName")) { setName(elem.text()); if (ulvi) ulvi->setText(name()); } else if (tag == QLatin1String("uisDiagramStyle")) { QString diagramStyle = elem.text(); if (diagramStyle != QLatin1String("ClassDiagram")) { uError() << "diagram style " << diagramStyle << " is not yet implemented"; continue; } m_doc->setMainViewID(m_nID); m_Type = Uml::DiagramType::Class; UMLListView *lv = UMLApp::app()->listView(); ulvi = new UMLListViewItem(lv->theLogicalView(), name(), UMLListViewItem::lvt_Class_Diagram, m_nID); } else if (tag == QLatin1String("uisDiagramPresentation")) { loadUisDiagramPresentation(elem); } else if (tag != QLatin1String("uisToolName")) { DEBUG(DBG_SRC) << "ignoring tag " << tag; } } return true; } /** * Left Alignment */ void UMLScene::alignLeft() { UMLWidgetList widgetList = selectedWidgetsExt(); if (widgetList.isEmpty()) return; qreal smallestX = WidgetList_Utils::getSmallestX(widgetList); foreach(UMLWidget *widget, widgetList) { widget->setX(smallestX); widget->adjustAssocs(widget->x(), widget->y()); } //TODO: Push stored cmds to stack. } /** * Right Alignment */ void UMLScene::alignRight() { UMLWidgetList widgetList = selectedWidgetsExt(); if (widgetList.isEmpty()) return; qreal biggestX = WidgetList_Utils::getBiggestX(widgetList); foreach(UMLWidget *widget, widgetList) { widget->setX(biggestX - widget->width()); widget->adjustAssocs(widget->x(), widget->y()); } //TODO: Push stored cmds to stack. } /** * Top Alignment */ void UMLScene::alignTop() { UMLWidgetList widgetList = selectedWidgetsExt(); if (widgetList.isEmpty()) return; qreal smallestY = WidgetList_Utils::getSmallestY(widgetList); foreach(UMLWidget *widget, widgetList) { widget->setY(smallestY); widget->adjustAssocs(widget->x(), widget->y()); } //TODO: Push stored cmds to stack. } /** * Bottom Alignment */ void UMLScene::alignBottom() { UMLWidgetList widgetList = selectedWidgetsExt(); if (widgetList.isEmpty()) return; qreal biggestY = WidgetList_Utils::getBiggestY(widgetList); foreach(UMLWidget *widget, widgetList) { widget->setY(biggestY - widget->height()); widget->adjustAssocs(widget->x(), widget->y()); } //TODO: Push stored cmds to stack. } /** * Vertical Middle Alignment */ void UMLScene::alignVerticalMiddle() { UMLWidgetList widgetList = selectedWidgetsExt(); if (widgetList.isEmpty()) return; qreal smallestY = WidgetList_Utils::getSmallestY(widgetList); qreal biggestY = WidgetList_Utils::getBiggestY(widgetList); qreal middle = int((biggestY - smallestY) / 2) + smallestY; foreach(UMLWidget *widget, widgetList) { widget->setY(middle - widget->height() / 2); widget->adjustAssocs(widget->x(), widget->y()); } AssociationWidgetList assocList = selectedAssocs(); if (!assocList.isEmpty()) { foreach (AssociationWidget *widget, assocList) { widget->setYEntireAssoc(middle); } } //TODO: Push stored cmds to stack. } /** * Horizontal Middle Alignment */ void UMLScene::alignHorizontalMiddle() { UMLWidgetList widgetList = selectedWidgetsExt(); if (widgetList.isEmpty()) return; qreal smallestX = WidgetList_Utils::getSmallestX(widgetList); qreal biggestX = WidgetList_Utils::getBiggestX(widgetList); qreal middle = int((biggestX - smallestX) / 2) + smallestX; foreach(UMLWidget *widget, widgetList) { widget->setX(middle - widget->width() / 2); widget->adjustAssocs(widget->x(), widget->y()); } AssociationWidgetList assocList = selectedAssocs(); if (!assocList.isEmpty()) { foreach (AssociationWidget *widget, assocList) { widget->setXEntireAssoc(middle); } } //TODO: Push stored cmds to stack. } /** * Vertical Distribute Alignment */ void UMLScene::alignVerticalDistribute() { UMLWidgetList widgetList = selectedWidgetsExt(); if (widgetList.isEmpty()) return; qreal smallestY = WidgetList_Utils::getSmallestY(widgetList); qreal biggestY = WidgetList_Utils::getBiggestY(widgetList); qreal heightsSum = WidgetList_Utils::getHeightsSum(widgetList); qreal distance = int(((biggestY - smallestY) - heightsSum) / (widgetList.count() - 1.0) + 0.5); qSort(widgetList.begin(), widgetList.end(), Widget_Utils::hasSmallerY); int i = 1; UMLWidget* widgetPrev = 0; foreach(UMLWidget *widget, widgetList) { if (i == 1) { widgetPrev = widget; } else { widget->setY(widgetPrev->y() + widgetPrev->height() + distance); widget->adjustAssocs(widget->x(), widget->y()); widgetPrev = widget; } i++; } //TODO: Push stored cmds to stack. } /** * Horizontal Distribute Alignment */ void UMLScene::alignHorizontalDistribute() { UMLWidgetList widgetList = selectedWidgetsExt(); if (widgetList.isEmpty()) return; qreal smallestX = WidgetList_Utils::getSmallestX(widgetList); qreal biggestX = WidgetList_Utils::getBiggestX(widgetList); qreal widthsSum = WidgetList_Utils::getWidthsSum(widgetList); qreal distance = int(((biggestX - smallestX) - widthsSum) / (widgetList.count() - 1.0) + 0.5); qSort(widgetList.begin(), widgetList.end(), Widget_Utils::hasSmallerX); int i = 1; UMLWidget* widgetPrev = 0; foreach(UMLWidget *widget, widgetList) { if (i == 1) { widgetPrev = widget; } else { widget->setX(widgetPrev->x() + widgetPrev->width() + distance); widget->adjustAssocs(widget->x(), widget->y()); widgetPrev = widget; } i++; } //TODO: Push stored cmds to stack. } /** * Overloading operator for debugging output. */ QDebug operator<<(QDebug dbg, UMLScene *item) { dbg.nospace() << "UMLScene: " << item->name() << " / type=" << DiagramType::toString(item->type()) << " / id=" << Uml::ID::toString(item->ID()) << " / isOpen=" << item->isOpen(); return dbg.space(); } diff --git a/umbrello/umlwidgets/associationwidget.cpp b/umbrello/umlwidgets/associationwidget.cpp index e4a06b7df..189fafb65 100644 --- a/umbrello/umlwidgets/associationwidget.cpp +++ b/umbrello/umlwidgets/associationwidget.cpp @@ -1,4393 +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(); + if (m_role[RoleType::B].umlWidget && m_role[RoleType::B].umlWidget->changesShape()) + m_role[RoleType::B].umlWidget->updateGeometry(false); }//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(); + if (m_role[RoleType::A].umlWidget && m_role[RoleType::A].umlWidget->changesShape()) + m_role[RoleType::A].umlWidget->updateGeometry(false); }//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/childwidgetplacementport.cpp b/umbrello/umlwidgets/childwidgetplacementport.cpp index b0802f68f..d9bea01d8 100644 --- a/umbrello/umlwidgets/childwidgetplacementport.cpp +++ b/umbrello/umlwidgets/childwidgetplacementport.cpp @@ -1,268 +1,271 @@ /*************************************************************************** * 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() { m_connectedSide = TopLeft; setPos(minX(), minY()); } 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/classifierwidget.cpp b/umbrello/umlwidgets/classifierwidget.cpp index d9aae4da4..3ca61de09 100644 --- a/umbrello/umlwidgets/classifierwidget.cpp +++ b/umbrello/umlwidgets/classifierwidget.cpp @@ -1,1497 +1,1521 @@ /*************************************************************************** * 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(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); } +/** + * Show a properties dialog for a ClassifierWidget + */ +bool ClassifierWidget::showPropertiesDialog() +{ + if (UMLWidget::showPropertiesDialog()) { + if (isInterfaceWidget() && visualProperty(DrawAsCircle)) + m_pInterfaceName->setText(name()); + return true; + } + return false; +} + +/** + * Overriding the method from WidgetBase because we need to do + * something extra in case this ClassifierWidget represents + * an interface widget used in component diagrams. + */ +void ClassifierWidget::setUMLObject(UMLObject *obj) +{ + WidgetBase::setUMLObject(obj); + if (isInterfaceWidget() && visualProperty(DrawAsCircle)) + m_pInterfaceName->setText(obj->name()); +} diff --git a/umbrello/umlwidgets/classifierwidget.h b/umbrello/umlwidgets/classifierwidget.h index da91b959b..cd6f48ab3 100644 --- a/umbrello/umlwidgets/classifierwidget.h +++ b/umbrello/umlwidgets/classifierwidget.h @@ -1,145 +1,148 @@ /*************************************************************************** * 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 CLASSIFIERWIDGET_H #define CLASSIFIERWIDGET_H #include "basictypes.h" #include "umlobject.h" #include "umlwidget.h" class AssociationWidget; class FloatingTextWidget; class QPainter; class UMLClassifier; /** * @short Common implementation for class widget and interface widget * * @author Oliver Kellogg * @author Gopala Krishna * * @see UMLWidget * Bugs and comments to umbrello-devel@kde.org or http://bugs.kde.org */ class ClassifierWidget : public UMLWidget { Q_OBJECT Q_ENUMS(VisualProperty) public: /** * This enumeration lists the visual properties that can be easily * set, reset and toggled and all these operate on an integer * which stores all the flag status. */ enum VisualProperty { ShowStereotype = 0x1, ShowOperations = 0x2, ShowPublicOnly = 0x4, ShowVisibility = 0x8, ShowPackage = 0x10, ShowAttributes = 0x20, DrawAsCircle = 0x40, ShowOperationSignature = 0x60, ///< only in setter ShowAttributeSignature = 0x80, ///< only in setter DrawAsPackage = 0x100, ShowDocumentation = 0x200, }; Q_DECLARE_FLAGS(VisualProperties, VisualProperty) ClassifierWidget(UMLScene * scene, UMLClassifier * o); ClassifierWidget(UMLScene * scene, UMLPackage * o); virtual ~ClassifierWidget(); UMLClassifier *classifier() const; VisualProperties visualProperties() const; void setVisualProperties(VisualProperties properties); bool visualProperty(VisualProperty property) const; void setVisualProperty(VisualProperty property, bool enable = true); void setVisualPropertyCmd(VisualProperty property, bool enable = true); void toggleVisualProperty(VisualProperty property); int displayedAttributes() const; int displayedOperations() const; Uml::SignatureType::Enum attributeSignature() const; void setAttributeSignature(Uml::SignatureType::Enum sig); Uml::SignatureType::Enum operationSignature() const; void setOperationSignature(Uml::SignatureType::Enum sig); void setShowAttSigs(bool _show); void toggleShowAttSigs(); bool getDrawAsCircle() const; void setDrawAsCircle(bool drawAsCircle); void toggleDrawAsCircle(); void changeToClass(); void changeToInterface(); void changeToPackage(); AssociationWidget *classAssociationWidget() const; void setClassAssociationWidget(AssociationWidget *assocwidget); // virtual void adjustAssociations(int x, int y); UMLWidget* onWidget(const QPointF& p); UMLWidget* widgetWithID(Uml::ID::Type id); virtual void setDocumentation(const QString& doc); QSizeF calculateSize(bool withExtensions = true) const; virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); virtual QPainterPath shape() const; virtual void saveToXMI1(QDomDocument & qDoc, QDomElement & qElement); virtual bool loadFromXMI1(QDomElement & qElement); + virtual bool showPropertiesDialog(); + void setUMLObject(UMLObject *obj); + public Q_SLOTS: virtual void slotMenuSelection(QAction* action); private Q_SLOTS: void slotShowAttributes(bool state); void slotShowOperations(bool state); private: void updateSignatureTypes(); QSize calculateTemplatesBoxSize() const; QSizeF minimumSize() const; void drawAsCircle(QPainter *p, const QStyleOptionGraphicsItem *option); QSize calculateAsCircleSize() const; void drawAsPackage(QPainter *painter, const QStyleOptionGraphicsItem *option); QSize calculateAsPackageSize() const; int displayedMembers(UMLObject::ObjectType ot) const; void drawMembers(QPainter *painter, UMLObject::ObjectType ot, Uml::SignatureType::Enum sigType, int x, int y, int fontHeight); static const int CIRCLE_SIZE; ///< size of circle when interface is rendered as such static const int SOCKET_INCREMENT; ///< augmentation of circle for socket (required interface) VisualProperties m_visualProperties; Uml::SignatureType::Enum m_attributeSignature; ///< Loaded/saved item. Uml::SignatureType::Enum m_operationSignature; ///< Loaded/saved item. AssociationWidget *m_pAssocWidget; ///< related AssociationWidget in case this classifier acts as an association class QPointer m_pInterfaceName; ///< Separate widget for name in case of interface drawn as circle }; Q_DECLARE_OPERATORS_FOR_FLAGS(ClassifierWidget::VisualProperties) #endif diff --git a/umbrello/umlwidgets/floatingtextwidget.cpp b/umbrello/umlwidgets/floatingtextwidget.cpp index 993272fcd..88b24211e 100644 --- a/umbrello/umlwidgets/floatingtextwidget.cpp +++ b/umbrello/umlwidgets/floatingtextwidget.cpp @@ -1,788 +1,792 @@ /*************************************************************************** * 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 "floatingtextwidget.h" // local includes #include "association.h" #include "associationwidget.h" #include "associationpropertiesdialog.h" #include "classifier.h" #include "cmds.h" #include "debug_utils.h" #include "dialog_utils.h" #include "linkwidget.h" #include "classifierwidget.h" #include "listpopupmenu.h" #include "messagewidget.h" #include "model_utils.h" #include "object_factory.h" #include "operation.h" #include "selectoperationdialog.h" #include "uml.h" #include "umldoc.h" #include "umlview.h" // kde includes #if QT_VERSION < 0x050000 #include #endif #include // qt includes #if QT_VERSION >= 0x050000 #include #endif #include #include #include #include DEBUG_REGISTER_DISABLED(FloatingTextWidget) /** * Constructs a FloatingTextWidget instance. * * @param scene The parent of this FloatingTextWidget. * @param role The role this FloatingTextWidget will take up. * @param text The main text to display. * @param id The ID to assign (-1 will prompt a new ID.) */ FloatingTextWidget::FloatingTextWidget(UMLScene * scene, Uml::TextRole::Enum role, const QString& text, Uml::ID::Type id) : UMLWidget(scene, WidgetBase::wt_Text, id), m_linkWidget(0), m_preText(QString()), m_postText(QString()), m_textRole(role), m_unconstrainedPositionX(0), m_unconstrainedPositionY(0), m_movementDirectionX(0), m_movementDirectionY(0) { m_Text = text; m_resizable = false; setZValue(10); //make sure always on top. } /** * Destructor. */ FloatingTextWidget::~FloatingTextWidget() { } /** * Use to get the _main body_ of text (e.g. prepended and appended * text is omitted) as currently displayed by the widget. * * @return The main text currently being displayed by the widget. */ QString FloatingTextWidget::text() const { // test to make sure not just the ":" between the seq number and // the actual message widget // hmm. this section looks like it could have been avoided by // using pre-, post- text instead of storing in the main body of // the text -b.t. if (m_textRole == Uml::TextRole::Seq_Message || m_textRole == Uml::TextRole::Seq_Message_Self || m_textRole == Uml::TextRole::Coll_Message || m_textRole == Uml::TextRole::Coll_Message_Self) { if (m_Text.length() <= 1 || m_Text == QLatin1String(": ")) return QString(); } return m_Text; } /** * Set the main body of text to display. * * @param t The text to display. */ void FloatingTextWidget::setText(const QString &t) { if (m_textRole == Uml::TextRole::Seq_Message || m_textRole == Uml::TextRole::Seq_Message_Self) { QString op; if (m_linkWidget) op = m_linkWidget->lwOperationText(); if (op.length() > 0) { if (!m_scene->showOpSig()) op.replace(QRegExp(QLatin1String("\\(.*\\)")), QLatin1String("()")); m_Text = op; } else m_Text = t; } else { m_Text = t; } QSizeF s = minimumSize(); setSize(s.width(), s.height()); updateGeometry(); update(); } /** * Set some text to be prepended to the main body of text. * @param t The text to prepend to main body which is displayed. */ void FloatingTextWidget::setPreText(const QString &t) { m_preText = t; updateGeometry(); update(); } /** * Set some text to be appended to the main body of text. * @param t The text to append to main body which is displayed. */ void FloatingTextWidget::setPostText(const QString &t) { m_postText = t; updateGeometry(); update(); } /** * Use to get the total text (prepended + main body + appended) * currently displayed by the widget. * * @return The text currently being displayed by the widget. */ QString FloatingTextWidget::displayText() const { QString displayText; if (!m_SequenceNumber.isEmpty()) displayText = m_SequenceNumber + QLatin1String(": ") + m_Text; else displayText = m_Text; displayText.prepend(m_preText); displayText.append(m_postText); return displayText; } /** * Return state if no pre, post and main text is empty * @return true if widget contains no text */ bool FloatingTextWidget::isEmpty() { return m_Text.isEmpty() && m_preText.isEmpty() && m_postText.isEmpty(); } /** * Overrides method from UMLWidget. */ QSizeF FloatingTextWidget::minimumSize() const { const QFontMetrics &fm = getFontMetrics(FT_NORMAL); int h = fm.lineSpacing(); int w = fm.width(displayText()); return QSizeF(w + 8, h + 4); // give a small margin } /** * Method used by setText: its called by cmdsetTxt, Don't use it! * * @param t The text to display. */ void FloatingTextWidget::setTextcmd(const QString &t) { UMLApp::app()->executeCommand(new Uml::CmdSetTxt(this, t)); } /** * Displays a dialog box to change the text. */ void FloatingTextWidget::showChangeTextDialog() { QString newText = text(); bool ok = Dialog_Utils::askName(i18n("Change Text"), i18n("Enter new text:"), newText); if (ok && newText != text() && isTextValid(newText)) { setText(newText); setVisible(!text().isEmpty()); updateGeometry(); update(); } if (!isTextValid(newText)) hide(); } /** * Shows an operation dialog box. * * @param enableAutoIncrement Enable auto increment checkbox */ bool FloatingTextWidget::showOperationDialog(bool enableAutoIncrement) { if (!m_linkWidget) { uError() << "m_linkWidget is NULL"; return false; } if (!m_linkWidget->lwClassifier()) { uError() << "m_linkWidget->lwClassifier() returns a NULL classifier"; return false; } bool result = false; QPointer selectDialog = new SelectOperationDialog(m_scene->activeView(), m_linkWidget->lwClassifier(), m_linkWidget, enableAutoIncrement); if (selectDialog->exec()) { selectDialog->apply(); setMessageText(); result = true; } delete selectDialog; return result; } /** * Show the properties for a FloatingTextWidget. * Depending on the role of the floating text wiget, the options dialog * for the floating text widget, the rename dialog for floating text or * the options dialog for the link widget are shown. */ bool FloatingTextWidget::showPropertiesDialog() { - if (m_textRole == Uml::TextRole::Coll_Message || m_textRole == Uml::TextRole::Coll_Message_Self || + UMLWidget *p = dynamic_cast(parentItem()); + if (p && p->isInterfaceWidget()) { + if (p->showPropertiesDialog()) + setText(p->name()); + } else if (m_textRole == Uml::TextRole::Coll_Message || m_textRole == Uml::TextRole::Coll_Message_Self || m_textRole == Uml::TextRole::Seq_Message || m_textRole == Uml::TextRole::Seq_Message_Self) { return showOperationDialog(false); } else if (m_textRole == Uml::TextRole::Floating) { // double clicking on a text line opens the dialog to change the text return handleRename(); } else if (m_linkWidget) { return m_linkWidget->showPropertiesDialog(); } return false; } /** * Use to get the pre-text which is prepended to the main body of * text to be displayed. * * @return The pre-text currently displayed by the widget. */ QString FloatingTextWidget::preText() const { return m_preText; } /** * Use to get the post-text which is appended to the main body of * text to be displayed. * * @return The post-text currently displayed by the widget. */ QString FloatingTextWidget::postText() const { return m_postText; } /** * Activate the FloatingTextWidget after the saved data has been loaded * * @param ChangeLog Pointer to the IDChangeLog. * @return true for success */ bool FloatingTextWidget::activate(IDChangeLog* ChangeLog /*= 0 */) { if (! UMLWidget::activate(ChangeLog)) return false; update(); return true; } /** * Set the LinkWidget that this FloatingTextWidget is related to. * * @param l The related LinkWidget. */ void FloatingTextWidget::setLink(LinkWidget * l) { m_linkWidget = l; } /** * Returns the LinkWidget this floating text is related to. * * @return The LinkWidget this floating text is related to. */ LinkWidget * FloatingTextWidget::link() const { return m_linkWidget; } /** * Sets the role type of this FloatingTextWidget. * * @param role The TextRole::Enum of this FloatingTextWidget. */ void FloatingTextWidget::setTextRole(Uml::TextRole::Enum role) { m_textRole = role; } /** * Return the role of the text widget * @return The TextRole::Enum of this FloatingTextWidget. */ Uml::TextRole::Enum FloatingTextWidget::textRole() const { return m_textRole; } /** * Handle the ListPopupMenu::mt_Rename case of the slotMenuSelection. * Given an own method because it requires rather lengthy code. */ bool FloatingTextWidget::handleRename() { QString t; if (m_textRole == Uml::TextRole::RoleAName || m_textRole == Uml::TextRole::RoleBName) { t = i18n("Enter role name:"); } else if (m_textRole == Uml::TextRole::MultiA || m_textRole == Uml::TextRole::MultiB) { t = i18n("Enter multiplicity:"); /* // NO! shouldn't be allowed } else if (m_textRole == Uml::TextRole::ChangeA || m_textRole == Uml::TextRole::ChangeB) { t = i18n("Enter changeability"); */ } else if (m_textRole == Uml::TextRole::Name) { t = i18n("Enter association name:"); } else if (m_textRole == Uml::TextRole::Floating) { t = i18n("Enter new text:"); } else { t = i18n("ERROR"); } QString newText = text(); bool ok = Dialog_Utils::askName(i18n("Rename"), t, newText); if (!ok || newText == text()) { return false; } UMLApp::app()->executeCommand(new Uml::CmdHandleRename(this, newText)); return true; } /** * Changes the text of linked widget. * @param newText the new text */ void FloatingTextWidget::changeName(const QString& newText) { if (m_linkWidget && !isTextValid(newText)) { AssociationWidget *assoc = dynamic_cast(m_linkWidget); if (assoc) { switch (m_textRole) { case Uml::TextRole::MultiA: assoc->setMultiplicity(QString(), Uml::RoleType::A); break; case Uml::TextRole::MultiB: assoc->setMultiplicity(QString(), Uml::RoleType::B); break; case Uml::TextRole::RoleAName: assoc->setRoleName(QString(), Uml::RoleType::A); break; case Uml::TextRole::RoleBName: assoc->setRoleName(QString(), Uml::RoleType::B); break; case Uml::TextRole::ChangeA: assoc->setChangeability(Uml::Changeability::Changeable, Uml::RoleType::A); break; case Uml::TextRole::ChangeB: assoc->setChangeability(Uml::Changeability::Changeable, Uml::RoleType::B); break; default: assoc->setName(QString()); break; } } else { MessageWidget *msg = dynamic_cast(m_linkWidget); if (msg) { msg->setName(QString()); m_scene->removeWidget(this); } } return; } if (m_linkWidget && m_textRole != Uml::TextRole::Seq_Message && m_textRole != Uml::TextRole::Seq_Message_Self) { m_linkWidget->setText(this, newText); } else { setText(newText); UMLApp::app()->document()->setModified(true); } setVisible(true); updateGeometry(); update(); } /** * Write property of QString m_SequenceNumber. */ void FloatingTextWidget::setSequenceNumber(const QString &sequenceNumber) { m_SequenceNumber = sequenceNumber; } /** * Read property of QString m_SequenceNumber. */ QString FloatingTextWidget::sequenceNumber() const { return m_SequenceNumber; } /** * For a text to be valid it must be non-empty, i.e. have a length * larger than zero, and have at least one non whitespace character. * * @param text The string to analyze. * @return True if the given text is valid. */ bool FloatingTextWidget::isTextValid(const QString &text) { int length = text.length(); if(length < 1) return false; for(int i=0;iconstrainTextPos(newX, newY, width(), height(), textRole()); } return QPointF(newX, newY); } /** * Overridden from UMLWidget. * Moves the widget to a new position using the difference between the * current position and the new position. * If the floating text widget is part of a sequence message, and the * message widget is selected, it does nothing: the message widget will * update the text position when it's moved. * In any other case, the floating text widget constrains its move using * constrainPosition. When the position of the floating text is constrained, * it's kept at that position until it can be moved to another valid * position (m_unconstrainedPositionX/Y and m_movementDirectionX/Y are * used for that). * Moreover, if is part of a sequence message (and the message widget * isn't selected), it updates the position of the message widget. * @see constrainPosition * * @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 FloatingTextWidget::moveWidgetBy(qreal diffX, qreal diffY) { if (textRole() == Uml::TextRole::Seq_Message_Self) return; if (textRole() == Uml::TextRole::Seq_Message && ((MessageWidget*)link())->isSelected()) { return; } m_unconstrainedPositionX += diffX; m_unconstrainedPositionY += diffY; QPointF constrainedPosition = constrainPosition(diffX, diffY); qreal newX = constrainedPosition.x(); qreal newY = constrainedPosition.y(); if (!m_movementDirectionX) { if (m_unconstrainedPositionX != constrainedPosition.x()) { m_movementDirectionX = (diffX > 0)? 1: -1; } } else if ((m_movementDirectionX < 0 && m_unconstrainedPositionX > x()) || (m_movementDirectionX > 0 && m_unconstrainedPositionX < x()) ) { newX = m_unconstrainedPositionX; m_movementDirectionX = 0; } if (!m_movementDirectionY) { if (m_unconstrainedPositionY != constrainedPosition.y()) { m_movementDirectionY = (diffY > 0)? 1: -1; } } else if ((m_movementDirectionY < 0 && m_unconstrainedPositionY > y()) || (m_movementDirectionY > 0 && m_unconstrainedPositionY < y()) ) { newY = m_unconstrainedPositionY; m_movementDirectionY = 0; } setX(newX); setY(newY); if (link()) { link()->calculateNameTextSegment(); if (textRole() == Uml::TextRole::Seq_Message) { MessageWidget* messageWidget = (MessageWidget*)link(); messageWidget->setY(newY + height()); } } } /** * Overridden from UMLWidget. * Modifies the value of the diffX and diffY variables used to move the * widgets. * The values are constrained using constrainPosition. * @see constrainPosition * * @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 FloatingTextWidget::constrainMovementForAllWidgets(qreal &diffX, qreal &diffY) { QPointF constrainedPosition = constrainPosition(diffX, diffY); diffX = constrainedPosition.x() - x(); diffY = constrainedPosition.y() - y(); } /** * Override method from UMLWidget in order to additionally check widget parentage. * * @param p Point to be checked. * * @return 'this' if UMLWidget::onWidget(p) returns non 0; * else NULL. */ UMLWidget* FloatingTextWidget::onWidget(const QPointF &p) { WidgetBase *pw = dynamic_cast(parentItem()); if (pw == 0) { return WidgetBase::onWidget(p); } const WidgetBase::WidgetType t = pw->baseType(); bool isWidgetChild = false; if (t == WidgetBase::wt_Interface) { ClassifierWidget *cw = static_cast(pw); isWidgetChild = cw->getDrawAsCircle(); } else if (t == wt_Association || t == WidgetBase::wt_Port || t == WidgetBase::wt_Pin) { isWidgetChild = true; } if (!isWidgetChild) { return WidgetBase::onWidget(p); } const qreal w = width(); const qreal h = height(); const qreal left = pw->x() + x(); const qreal right = left + w; const qreal top = pw->y() + y(); const qreal bottom = top + h; // uDebug() << "child_of_pw; 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() << "floatingtext: " << m_Text; return this; } return 0; } /** * Overrides default method */ void FloatingTextWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); int w = width(); int h = height(); painter->setFont(UMLWidget::font()); painter->setPen(textColor()); painter->drawText(0, 0, w, h, Qt::AlignCenter, displayText()); UMLWidget::paint(painter, option, widget); } /** * Loads the "floatingtext" XMI element. */ bool FloatingTextWidget::loadFromXMI1(QDomElement & qElement) { if(!UMLWidget::loadFromXMI1(qElement)) return false; m_unconstrainedPositionX = x(); m_unconstrainedPositionY = y(); QString role = qElement.attribute(QLatin1String("role")); if(!role.isEmpty()) m_textRole = Uml::TextRole::fromInt(role.toInt()); m_preText = qElement.attribute(QLatin1String("pretext")); m_postText = qElement.attribute(QLatin1String("posttext")); setText(qElement.attribute(QLatin1String("text"))); // use setText for geometry update // If all texts are empty then this is a useless widget. // In that case we return false. // CAVEAT: The caller should not interpret the false return value // as an indication of failure since previous umbrello versions // saved lots of these empty FloatingTexts. bool usefulWidget = !isEmpty(); return usefulWidget; } /** * Reimplemented from UMLWidget::saveToXMI1 to save the widget * data into XMI 'floatingtext' element. */ void FloatingTextWidget::saveToXMI1(QDomDocument & qDoc, QDomElement & qElement) { if (isEmpty()) return; QDomElement textElement = qDoc.createElement(QLatin1String("floatingtext")); UMLWidget::saveToXMI1(qDoc, textElement); textElement.setAttribute(QLatin1String("text"), m_Text); textElement.setAttribute(QLatin1String("pretext"), m_preText); textElement.setAttribute(QLatin1String("posttext"), m_postText); /* No need to save these - the messagewidget already did it. m_Operation = qElement.attribute("operation"); m_SeqNum = qElement.attribute("seqnum"); */ textElement.setAttribute(QLatin1String("role"), m_textRole); qElement.appendChild(textElement); } /** * Called when a menu selection has been made. * * @param action The action that has been selected. */ void FloatingTextWidget::slotMenuSelection(QAction* action) { ListPopupMenu::MenuType sel = ListPopupMenu::typeFromAction(action); switch(sel) { case ListPopupMenu::mt_Properties: showPropertiesDialog(); break; case ListPopupMenu::mt_Delete: hide(); // This is only a workaround; if set then m_linkWidget should // really delete this FloatingTextWidget. update(); m_scene->removeWidget(this); break; case ListPopupMenu::mt_New_Operation: // needed by AssociationWidget case ListPopupMenu::mt_Operation: { if (m_linkWidget == 0) { DEBUG(DBG_SRC) << "mt_Operation: m_linkWidget is NULL"; return; } UMLClassifier* c = m_linkWidget->operationOwner(); if (c == 0) { QString opText = text(); bool ok = Dialog_Utils::askName(i18nc("operation name", "Name"), i18n("Enter operation name:"), opText); if (ok) m_linkWidget->setCustomOpText(opText); return; } UMLClassifierListItem* umlObj = Object_Factory::createChildObject(c, UMLObject::ot_Operation); if (umlObj) { UMLOperation* newOperation = umlObj->asUMLOperation(); m_linkWidget->setOperation(newOperation); } } break; case ListPopupMenu::mt_Select_Operation: showOperationDialog(false); break; case ListPopupMenu::mt_Rename: handleRename(); 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 if(m_textRole == Uml::TextRole::Floating || m_textRole == Uml::TextRole::Seq_Message) { setFont(fnt); } else if (m_linkWidget) { m_linkWidget->lwSetFont(fnt); } } } break; case ListPopupMenu::mt_Reset_Label_Positions: if (m_linkWidget) m_linkWidget->resetTextPositions(); break; default: UMLWidget::slotMenuSelection(action); break; }//end switch } /** * Sets the text for this label if it is acting as a sequence diagram * message or a collaboration diagram message. */ void FloatingTextWidget::setMessageText() { if (m_linkWidget) { m_linkWidget->setMessageText(this); QSizeF s = minimumSize(); setSize(s.width(), s.height()); } setVisible(!text().isEmpty()); } diff --git a/umbrello/umlwidgets/messagewidget.cpp b/umbrello/umlwidgets/messagewidget.cpp index df3be6b8c..2d8a4fadd 100644 --- a/umbrello/umlwidgets/messagewidget.cpp +++ b/umbrello/umlwidgets/messagewidget.cpp @@ -1,1430 +1,1431 @@ /*************************************************************************** * 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 * ***************************************************************************/ // onw header #include "messagewidget.h" //app includes #include "classifier.h" #include "debug_utils.h" #include "dialog_utils.h" #include "docwindow.h" #include "floatingtextwidget.h" #include "listpopupmenu.h" #include "objectwidget.h" #include "operation.h" #include "uml.h" #include "umldoc.h" #include "messagewidgetpropertiesdialog.h" #include "umlview.h" #include "uniqueid.h" #include "idchangelog.h" //qt includes #include #include #include #include //kde includes #include DEBUG_REGISTER_DISABLED(MessageWidget) static const int circleWidth = 10; /** * Constructs a MessageWidget. * * This method is used for creation, synchronous and synchronous message types. * * @param scene The parent to this class. * @param a The role A widget for this message. * @param b The role B widget for this message. * @param y The vertical position to display this message. * @param sequenceMessageType Whether synchronous or asynchronous * @param id A unique id used for deleting this object cleanly. * The default (-1) will prompt generation of a new ID. */ MessageWidget::MessageWidget(UMLScene * scene, ObjectWidget* a, ObjectWidget* b, int y, Uml::SequenceMessage::Enum sequenceMessageType, Uml::ID::Type id /* = Uml::id_None */) : UMLWidget(scene, WidgetBase::wt_Message, id) { init(); m_pOw[Uml::RoleType::A] = a; m_pOw[Uml::RoleType::B] = b; m_sequenceMessageType = sequenceMessageType; if (m_sequenceMessageType == Uml::SequenceMessage::Creation) { y -= m_pOw[Uml::RoleType::B]->height() / 2; m_pOw[Uml::RoleType::B]->setY(y); } updateResizability(); calculateWidget(); y = y < getMinY() ? getMinY() : y; - y = y > getMaxY() ? getMaxY() : y; + if (y > b->getEndLineY()) + b->setEndLine(y); setY(y); this->activate(); } /** * Constructs a MessageWidget. * * @param scene The parent to this class. * @param seqMsgType The Uml::SequenceMessage::Enum of this message widget * @param id The ID to assign (-1 will prompt a new ID.) */ MessageWidget::MessageWidget(UMLScene * scene, Uml::SequenceMessage::Enum seqMsgType, Uml::ID::Type id) : UMLWidget(scene, WidgetBase::wt_Message, id) { init(); m_sequenceMessageType = seqMsgType; } /** * Constructs a Lost or Found MessageWidget. * * @param scene The parent to this class. * @param a The role A widget for this message. * @param xclick The horizontal position clicked by the user * @param yclick The vertical position clicked by the user * @param sequenceMessageType Whether lost or found * @param id The ID to assign (-1 will prompt a new ID.) */ MessageWidget::MessageWidget(UMLScene * scene, ObjectWidget* a, int xclick, int yclick, Uml::SequenceMessage::Enum sequenceMessageType, Uml::ID::Type id /*= Uml::id_None*/) : UMLWidget(scene, WidgetBase::wt_Message, id) { init(); m_pOw[Uml::RoleType::A] = a; m_pOw[Uml::RoleType::B] = a; m_sequenceMessageType = sequenceMessageType; m_xclicked = xclick; m_yclicked = yclick; updateResizability(); calculateWidget(); yclick = yclick < getMinY() ? getMinY() : yclick; yclick = yclick > getMaxY() ? getMaxY() : yclick; setY(yclick); m_yclicked = yclick; this->activate(); } /** * Initializes key variables of the class. */ void MessageWidget::init() { m_xclicked = -1; m_yclicked = -1; m_ignoreSnapToGrid = true; m_ignoreSnapComponentSizeToGrid = true; m_pOw[Uml::RoleType::A] = m_pOw[Uml::RoleType::B] = 0; m_pFText = 0; } /** * Standard destructor. */ MessageWidget::~MessageWidget() { } /** * Sets the y-coordinate. * Reimplemented from UMLWidget. * * @param y The y-coordinate to be set. */ void MessageWidget::setY(qreal y) { if (y < getMinY()) { DEBUG(DBG_SRC) << "got out of bounds y position, check the reason" << this->y() << getMinY(); return; } UMLWidget::setY(y); if (m_sequenceMessageType == Uml::SequenceMessage::Creation) { const qreal objWidgetHalfHeight = m_pOw[Uml::RoleType::B]->height() / 2; m_pOw[Uml::RoleType::B]->setY(y - objWidgetHalfHeight); } if (m_pFText && !UMLApp::app()->document()->loading()) { setTextPosition(); emit sigMessageMoved(); } } /** * Update the UMLWidget::m_resizable flag according to the * charactersitics of this message. */ void MessageWidget::updateResizability() { if (m_sequenceMessageType == Uml::SequenceMessage::Synchronous) UMLWidget::m_resizable = true; else UMLWidget::m_resizable = false; } /** * Overridden from UMLWidget. * Returns the cursor to be shown when resizing the widget. * The cursor shown is KCursor::sizeVerCursor(). * * @return The cursor to be shown when resizing the widget. */ QCursor MessageWidget::resizeCursor() const { return Qt::SizeVerCursor; } /** * Overridden from UMLWidget. * Resizes the height of the message widget and emits the message moved signal. * Message widgets can only be resized vertically, so width isn't modified. * * @param newW The new width for the widget (isn't used). * @param newH The new height for the widget. */ void MessageWidget::resizeWidget(qreal newW, qreal newH) { if (sequenceMessageType() == Uml::SequenceMessage::Creation) setSize(width(), newH); else { qreal x1 = m_pOw[Uml::RoleType::A]->x(); qreal x2 = getxclicked(); qreal diffX = 0; if (x1 < x2) { diffX = x2 + (newW - width()); } else { diffX = x2 - (newW - width()); } if (diffX <= 0 ) diffX = 10; setxclicked (diffX); setSize(newW, newH); calculateWidget(); } emit sigMessageMoved(); } /** * Constrains the vertical position of the message widget so it doesn't go * upper than the bottom side of the lower object. * The height of the floating text widget in the message is taken in account * if there is any and isn't empty. * * @param diffY The difference between current Y position and new Y position. * @return The new Y position, constrained. */ qreal MessageWidget::constrainPositionY(qreal diffY) { qreal newY = y() + diffY; qreal minY = getMinY(); if (m_pFText && !m_pFText->displayText().isEmpty()) { minY += m_pFText->height(); } if (newY < minY) { newY = minY; } return newY; } /** * Overridden from UMLWidget. * Moves the widget to a new position using the difference between the * current position and the new position. X position is ignored, and widget * is only moved along Y axis. If message goes upper than the object, it's * kept at this position until it should be lowered again (the unconstrained * Y position is saved to know when it's the time to lower it again). * If the message is a creation message, the object created is also moved to * the new vertical position. * @see constrainPositionY * * @param diffX The difference between current X position and new X position * (isn't used). * @param diffY The difference between current Y position and new Y position. */ void MessageWidget::moveWidgetBy(qreal diffX, qreal diffY) { Q_UNUSED(diffX); qreal newY = constrainPositionY(diffY); setY(newY); } /** * Overridden from UMLWidget. * Modifies the value of the diffX and diffY variables used to move the widgets. * All the widgets are constrained to be moved only in Y axis (diffX is set to 0). * @see constrainPositionY * * @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 MessageWidget::constrainMovementForAllWidgets(qreal &diffX, qreal &diffY) { diffX = 0; diffY = constrainPositionY(diffY) - y(); } /** * Reimplemented from UMLWidget and calls other paint...() methods * depending on the message type. */ void MessageWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); if(!m_pOw[Uml::RoleType::A] || !m_pOw[Uml::RoleType::B]) { return; } setPenFromSettings(painter); if (m_sequenceMessageType == Uml::SequenceMessage::Synchronous) { paintSynchronous(painter, option); } else if (m_sequenceMessageType == Uml::SequenceMessage::Asynchronous) { paintAsynchronous(painter, option); } else if (m_sequenceMessageType == Uml::SequenceMessage::Creation) { paintCreation(painter, option); } else if (m_sequenceMessageType == Uml::SequenceMessage::Lost) { paintLost(painter, option); } else if (m_sequenceMessageType == Uml::SequenceMessage::Found) { paintFound(painter, option); } else { uWarning() << "Unknown message type"; } } /** * Draw a solid (triangular) arrowhead pointing in the given direction. * The direction can be either Qt::LeftArrow or Qt::RightArrow. */ void MessageWidget::paintSolidArrowhead(QPainter *p, int x, int y, Qt::ArrowType direction) { int arrowheadExtentX = 4; if (direction == Qt::RightArrow) { arrowheadExtentX = -arrowheadExtentX; } QPolygon points; points.putPoints(0, 3, x, y, x + arrowheadExtentX, y - 3, x + arrowheadExtentX, y + 3); p->setBrush(QBrush(p->pen().color())); p->drawPolygon(points); } /** * Draw an arrow pointing in the given direction. * The arrow head is not solid, i.e. it is made up of two lines * like so: ---> * The direction can be either Qt::LeftArrow or Qt::RightArrow. */ void MessageWidget::paintArrow(QPainter *p, int x, int y, int w, Qt::ArrowType direction, bool useDottedLine /* = false */) { if (w > 3) { int arrowheadStartX = x; int arrowheadExtentX = 4; if (direction == Qt::RightArrow) { arrowheadStartX += w; arrowheadExtentX = -arrowheadExtentX; } // draw upper half of arrowhead p->drawLine(arrowheadStartX, y, arrowheadStartX + arrowheadExtentX, y - 3); // draw lower half of arrowhead p->drawLine(arrowheadStartX, y, arrowheadStartX + arrowheadExtentX, y + 3); } // draw arrow line if (useDottedLine) { QPen pen = p->pen(); pen.setStyle(Qt::DotLine); p->setPen(pen); } p->drawLine(x, y, x + w, y); } /** * Draws the calling arrow with filled in arrowhead, the * timeline box and the returning arrow with a dashed line and * stick arrowhead. */ void MessageWidget::paintSynchronous(QPainter *painter, const QStyleOptionGraphicsItem *option) { int x1 = m_pOw[Uml::RoleType::A]->x(); int x2 = m_pOw[Uml::RoleType::B]->x(); int w = width() - 1; int h = height(); int offsetX = 0; int offsetY = 0; bool messageOverlaps = m_pOw[Uml::RoleType::A]->messageOverlap(y(), this); const int boxWidth = 17; const int wr = w < boxWidth ? w : boxWidth; const int arrowWidth = 4; if (UMLWidget::useFillColor()) painter->setBrush(UMLWidget::fillColor()); else painter->setBrush(m_scene->backgroundColor()); if(isSelf()) { painter->fillRect(offsetX, offsetY, wr, h, QBrush(Qt::white)); //box painter->drawRect(offsetX, offsetY, wr, h); //box offsetX += wr; w -= wr; offsetY += 3; const int lowerLineY = offsetY + h - 6; // draw upper line segment (leaving the life line) painter->drawLine(offsetX, offsetY, offsetX + w, offsetY); // draw line segment parallel to (and at the right of) the life line painter->drawLine(offsetX + w, offsetY, offsetX + w, lowerLineY); // draw lower line segment (back to the life line) paintArrow(painter, offsetX, lowerLineY, w, Qt::LeftArrow); offsetX -= wr; offsetY -= 3; } else if(x1 < x2) { if (messageOverlaps) { offsetX += 8; w -= 8; } QPen pen = painter->pen(); int startX = offsetX + w - wr + 1; painter->fillRect(startX, offsetY, wr, h, QBrush(Qt::white)); //box painter->drawRect(startX, offsetY, wr, h); //box painter->drawLine(offsetX, offsetY + arrowWidth, startX, offsetY + arrowWidth); //arrow line if (w > boxWidth + arrowWidth) paintSolidArrowhead(painter, startX - 1, offsetY + arrowWidth, Qt::RightArrow); paintArrow(painter, offsetX, offsetY + h - arrowWidth + 1, w - wr + 1, Qt::LeftArrow, true); // return arrow if (messageOverlaps) { offsetX -= 8; //reset for drawSelected() } } else { if (messageOverlaps) { w -=8; } QPen pen = painter->pen(); painter->fillRect(offsetX, offsetY, wr, h, QBrush(Qt::white)); //box painter->drawRect(offsetX, offsetY, wr, h); //box painter->drawLine(offsetX + wr + 1, offsetY + arrowWidth, offsetX + w, offsetY + arrowWidth); //arrow line if (w > boxWidth + arrowWidth) paintSolidArrowhead(painter, offsetX + wr, offsetY + arrowWidth, Qt::LeftArrow); paintArrow(painter, offsetX + wr + 1, offsetY + h - arrowWidth + 1, w - wr - 1, Qt::RightArrow, true); // return arrow } UMLWidget::paint(painter, option); } /** * Draws a solid arrow line and a stick arrow head. */ void MessageWidget::paintAsynchronous(QPainter *painter, const QStyleOptionGraphicsItem *option) { int x1 = m_pOw[Uml::RoleType::A]->x(); int x2 = m_pOw[Uml::RoleType::B]->x(); int w = width() - 1; int h = height() - 1; int offsetX = 0; int offsetY = 0; bool messageOverlapsA = m_pOw[Uml::RoleType::A]->messageOverlap(y(), this); //bool messageOverlapsB = m_pOw[Uml::RoleType::B]->messageOverlap(y(), this); if(isSelf()) { if (messageOverlapsA) { offsetX += 7; w -= 7; } const int lowerLineY = offsetY + h - 3; // draw upper line segment (leaving the life line) painter->drawLine(offsetX, offsetY, offsetX + w, offsetY); // draw line segment parallel to (and at the right of) the life line painter->drawLine(offsetX + w, offsetY, offsetX + w, lowerLineY); // draw lower line segment (back to the life line) paintArrow(painter, offsetX, lowerLineY, w, Qt::LeftArrow); if (messageOverlapsA) { offsetX -= 7; //reset for drawSelected() } } else if(x1 < x2) { if (messageOverlapsA) { offsetX += 7; w -= 7; } paintArrow(painter, offsetX, offsetY + 4, w, Qt::RightArrow); if (messageOverlapsA) { offsetX -= 7; } } else { if (messageOverlapsA) { w -= 7; } paintArrow(painter, offsetX, offsetY + 4, w, Qt::LeftArrow); } UMLWidget::paint(painter, option); } /** * Draws a solid arrow line and a stick arrow head to the * edge of the target object widget instead of to the * sequence line. */ void MessageWidget::paintCreation(QPainter *painter, const QStyleOptionGraphicsItem *option) { int x1 = m_pOw[Uml::RoleType::A]->x(); int x2 = m_pOw[Uml::RoleType::B]->x(); int w = width(); //int h = height() - 1; int offsetX = 0; int offsetY = 0; bool messageOverlapsA = m_pOw[Uml::RoleType::A]->messageOverlap(y(), this); //bool messageOverlapsB = m_pOw[Uml::RoleType::B]->messageOverlap(y(), this); const int lineY = offsetY + 4; if (x1 < x2) { if (messageOverlapsA) { offsetX += 7; w -= 7; } paintArrow(painter, offsetX, lineY, w, Qt::RightArrow); if (messageOverlapsA) { offsetX -= 7; } } else { if (messageOverlapsA) { w -= 7; } paintArrow(painter, offsetX, lineY, w, Qt::LeftArrow); } UMLWidget::paint(painter, option); } /** * Draws a solid arrow line and a stick arrow head * and a circle */ void MessageWidget::paintLost(QPainter *painter, const QStyleOptionGraphicsItem *option) { int x1 = m_pOw[Uml::RoleType::A]->centerX(); int x2 = m_xclicked; int w = width(); int h = height(); int offsetX = 0; int offsetY = 0; bool messageOverlapsA = m_pOw[Uml::RoleType::A]->messageOverlap(y(), this); //bool messageOverlapsB = m_pOw[Uml::RoleType::B]->messageOverlap(y(), this); if(x1 < x2) { if (messageOverlapsA) { offsetX += 7; w -= 7; } setPenFromSettings(painter); painter->setBrush(WidgetBase::lineColor()); painter->drawEllipse(offsetX + w - h, offsetY, h, h); paintArrow(painter, offsetX, offsetY + h/2, w - h, Qt::RightArrow); if (messageOverlapsA) { offsetX -= 7; } } else { setPenFromSettings(painter); painter->setBrush(WidgetBase::lineColor()); painter->drawEllipse(offsetX, offsetY, h, h); paintArrow(painter, offsetX + h, offsetY + h/2, w - h, Qt::LeftArrow); } UMLWidget::paint(painter, option); } /** * Draws a circle and a solid arrow line and a stick arrow head. */ void MessageWidget::paintFound(QPainter *painter, const QStyleOptionGraphicsItem *option) { int x1 = m_pOw[Uml::RoleType::A]->centerX(); int x2 = m_xclicked; int w = width(); int h = height(); int offsetX = 0; int offsetY = 0; bool messageOverlapsA = m_pOw[Uml::RoleType::A]->messageOverlap(y(), this); //bool messageOverlapsB = m_pOw[Uml::RoleType::B]->messageOverlap(y(), this); if(x1 < x2) { if (messageOverlapsA) { offsetX += 7; w -= 7; } setPenFromSettings(painter); painter->setBrush(WidgetBase::lineColor()); painter->drawEllipse(offsetX + w - h, offsetY, h, h); paintArrow(painter, offsetX, offsetY + h/2, w, Qt::LeftArrow); if (messageOverlapsA) { offsetX -= 7; } } else { if (messageOverlapsA) { w -= 7; } setPenFromSettings(painter); painter->setBrush(WidgetBase::lineColor()); painter->drawEllipse(offsetX, offsetY, h, h); paintArrow(painter, offsetX, offsetY + h/2, w, Qt::RightArrow); } UMLWidget::paint(painter, option); } /** * Overrides operation from UMLWidget. * * @param p Point to be checked. * * @return 'this' if the point is on a part of the MessageWidget. * NB In case of a synchronous message, the empty space * between call line and return line does not count, i.e. if * the point is located in that space the function returns NULL. */ UMLWidget* MessageWidget::onWidget(const QPointF& p) { if (m_sequenceMessageType != Uml::SequenceMessage::Synchronous) { return UMLWidget::onWidget(p); } // Synchronous message: // Consists of top arrow (call) and bottom arrow (return.) if (p.x() < x() || p.x() > x() + width()) return 0; const int tolerance = 5; // pixels const int pY = p.y(); const int topArrowY = y() + 3; const int bottomArrowY = y() + height() - 3; if (pY < topArrowY - tolerance || pY > bottomArrowY + tolerance) return 0; if (height() <= 2 * tolerance) return this; if (pY > topArrowY + tolerance && pY < bottomArrowY - tolerance) return 0; return this; } /** * Sets the text position relative to the sequence message. */ void MessageWidget::setTextPosition() { if (m_pFText == 0) { DEBUG(DBG_SRC) << "m_pFText is NULL"; return; } if (m_pFText->displayText().isEmpty()) { return; } m_pFText->updateGeometry(); int ftX = constrainX(m_pFText->x(), m_pFText->width(), m_pFText->textRole()); int ftY = y() - m_pFText->height(); m_pFText->setX(ftX); m_pFText->setY(ftY); } /** * Returns the textX arg with constraints applied. * Auxiliary to setTextPosition() and constrainTextPos(). */ int MessageWidget::constrainX(int textX, int textWidth, Uml::TextRole::Enum tr) { int result = textX; const int minTextX = x() + 5; if (textX < minTextX || tr == Uml::TextRole::Seq_Message_Self) { result = minTextX; } else { ObjectWidget *objectAtRight = 0; if (m_pOw[Uml::RoleType::B]->x() > m_pOw[Uml::RoleType::A]->x()) objectAtRight = m_pOw[Uml::RoleType::B]; else objectAtRight = m_pOw[Uml::RoleType::A]; const int objRight_seqLineX = objectAtRight->centerX(); const int maxTextX = objRight_seqLineX - textWidth - 5; if (maxTextX <= minTextX) result = minTextX; else if (textX > maxTextX) result = maxTextX; } return result; } /** * Constrains the FloatingTextWidget X and Y values supplied. * Overrides 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::TextRole::Enum of the text */ void MessageWidget::constrainTextPos(qreal &textX, qreal &textY, qreal textWidth, qreal textHeight, Uml::TextRole::Enum tr) { textX = constrainX(textX, textWidth, tr); // Constrain Y. const qreal minTextY = getMinY(); const qreal maxTextY = getMaxY() - textHeight - 5; if (textY < minTextY) textY = minTextY; else if (textY > maxTextY) textY = maxTextY; // setY(textY + textHeight); // NB: side effect } /** * Shortcut for calling m_pFText->setLink() followed by * this->setTextPosition(). */ void MessageWidget::setLinkAndTextPos() { if (m_pFText) { m_pFText->setLink(this); setTextPosition(); } } void MessageWidget::resizeEvent(QResizeEvent* /*re*/) { } /** * Calculate the geometry of the widget. */ void MessageWidget::calculateWidget() { setMessageText(m_pFText); calculateDimensions(); setVisible(true); } void MessageWidget::slotWidgetMoved(Uml::ID::Type id) { const Uml::ID::Type idA = m_pOw[Uml::RoleType::A]->localID(); const Uml::ID::Type idB = m_pOw[Uml::RoleType::B]->localID(); if (idA != id && idB != id) { DEBUG(DBG_SRC) << "id=" << Uml::ID::toString(id) << ": ignoring for idA=" << Uml::ID::toString(idA) << ", idB=" << Uml::ID::toString(idB); return; } qreal y = this->y(); if (y < getMinY()) y = getMinY(); if (y > getMaxY()) y = getMaxY(); setPos(x(), y); calculateWidget(); if(!m_pFText) return; if (m_scene->selectedCount(true) > 1) return; setTextPosition(); } /** * Check to see if the given ObjectWidget is involved in the message. * * @param w The ObjectWidget to check for. * @return true - if is contained, false - not contained. */ bool MessageWidget::hasObjectWidget(ObjectWidget * w) { if(m_pOw[Uml::RoleType::A] == w || m_pOw[Uml::RoleType::B] == w) return true; else return false; } /** * This method determines whether the message is for "Self" for * an ObjectWidget. * * @retval True If both ObjectWidgets for this widget exists and * are same. */ bool MessageWidget::isSelf() const { return (m_pOw[Uml::RoleType::A] && m_pOw[Uml::RoleType::B] && m_pOw[Uml::RoleType::A] == m_pOw[Uml::RoleType::B]); } void MessageWidget::slotMenuSelection(QAction* action) { ListPopupMenu::MenuType sel = ListPopupMenu::typeFromAction(action); if (sel == ListPopupMenu::mt_Delete) { if (Dialog_Utils::askDeleteAssociation()) { // This will clean up this widget and the text widget: m_scene->removeWidget(this); } } else { UMLWidget::slotMenuSelection(action); } } /** * Activates a MessageWidget. Connects its m_pOw[] pointers * to UMLObjects and also send signals about its FloatingTextWidget. */ bool MessageWidget::activate(IDChangeLog * /*Log = 0*/) { m_scene->resetPastePoint(); // UMLWidget::activate(Log); CHECK: I don't think we need this ? if (m_pOw[Uml::RoleType::A] == 0) { UMLWidget *pWA = m_scene->findWidget(m_widgetAId); if (pWA == 0) { DEBUG(DBG_SRC) << "role A object " << Uml::ID::toString(m_widgetAId) << " not found"; return false; } m_pOw[Uml::RoleType::A] = pWA->asObjectWidget(); if (m_pOw[Uml::RoleType::A] == 0) { DEBUG(DBG_SRC) << "role A widget " << Uml::ID::toString(m_widgetAId) << " is not an ObjectWidget"; return false; } } if (m_pOw[Uml::RoleType::B] == 0) { UMLWidget *pWB = m_scene->findWidget(m_widgetBId); if (pWB == 0) { DEBUG(DBG_SRC) << "role B object " << Uml::ID::toString(m_widgetBId) << " not found"; return false; } m_pOw[Uml::RoleType::B] = pWB->asObjectWidget(); if (m_pOw[Uml::RoleType::B] == 0) { DEBUG(DBG_SRC) << "role B widget " << Uml::ID::toString(m_widgetBId) << " is not an ObjectWidget"; return false; } } updateResizability(); UMLClassifier *c = m_pOw[Uml::RoleType::B]->umlObject()->asUMLClassifier(); UMLOperation *op = 0; if (c && !m_CustomOp.isEmpty()) { Uml::ID::Type opId = Uml::ID::fromString(m_CustomOp); op = c->findChildObjectById(opId, true)->asUMLOperation(); if (op) { // If the UMLOperation is set, m_CustomOp isn't used anyway. // Just setting it empty for the sake of sanity. m_CustomOp.clear(); } } if(!m_pFText) { Uml::TextRole::Enum tr = Uml::TextRole::Seq_Message; if (isSelf()) tr = Uml::TextRole::Seq_Message_Self; m_pFText = new FloatingTextWidget(m_scene, tr, operationText(m_scene)); m_scene->addFloatingTextWidget(m_pFText); m_pFText->setFontCmd(UMLWidget::font()); } if (op) setOperation(op); // This requires a valid m_pFText. setLinkAndTextPos(); m_pFText->setText(QString()); m_pFText->setActivated(); QString messageText = m_pFText->text(); m_pFText->setVisible(messageText.length() > 1); connect(m_pOw[Uml::RoleType::A], SIGNAL(sigWidgetMoved(Uml::ID::Type)), this, SLOT(slotWidgetMoved(Uml::ID::Type))); connect(m_pOw[Uml::RoleType::B], SIGNAL(sigWidgetMoved(Uml::ID::Type)), this, SLOT(slotWidgetMoved(Uml::ID::Type))); connect(this, SIGNAL(sigMessageMoved()), m_pOw[Uml::RoleType::A], SLOT(slotMessageMoved())); connect(this, SIGNAL(sigMessageMoved()), m_pOw[Uml::RoleType::B], SLOT(slotMessageMoved())); m_pOw[Uml::RoleType::A]->messageAdded(this); if (!isSelf()) m_pOw[Uml::RoleType::B]->messageAdded(this); // Calculate the size and position of the message widget calculateDimensions(); // Position the floating text accordingly setTextPosition(); emit sigMessageMoved(); return true; } /** * Resolve references of this message so they reference the correct * new object widgets after paste. */ void MessageWidget::resolveObjectWidget(IDChangeLog* log) { m_widgetAId = log->findNewID(m_widgetAId); m_widgetBId = log->findNewID(m_widgetBId); } /** * Overrides operation from LinkWidget. * Required by FloatingTextWidget. * * @param ft The text widget which to update. */ void MessageWidget::setMessageText(FloatingTextWidget *ft) { if (ft == 0) return; ft->setSequenceNumber(m_SequenceNumber); ft->setText(operationText(m_scene)); setTextPosition(); } /** * Overrides operation from LinkWidget. * Required by FloatingTextWidget. * * @param ft The text widget which to update. * @param newText The new text to set. */ void MessageWidget::setText(FloatingTextWidget *ft, const QString &newText) { ft->setText(newText); UMLApp::app()->document()->setModified(true); } /** * Overrides operation from LinkWidget. * Required by FloatingTextWidget. * * @param op The new operation string to set. */ void MessageWidget::setOperationText(const QString &op) { m_CustomOp = op; ///FIXME m_pOperation } /** * Implements operation from LinkWidget. * Required by FloatingTextWidget. */ void MessageWidget::lwSetFont (QFont font) { UMLWidget::setFont(font); } /** * Overrides operation from LinkWidget. * Required by FloatingTextWidget. * @todo Move to LinkWidget. */ UMLClassifier *MessageWidget::operationOwner() { UMLObject *pObject = m_pOw[Uml::RoleType::B]->umlObject(); if (pObject == 0) return 0; UMLClassifier *c = pObject->asUMLClassifier(); return c; } /** * Implements operation from LinkWidget. * Motivated by FloatingTextWidget. */ UMLOperation *MessageWidget::operation() { return m_umlObject->asUMLOperation(); } /** * Implements operation from LinkWidget. * Motivated by FloatingTextWidget. */ void MessageWidget::setOperation(UMLOperation *op) { if (m_umlObject && m_pFText) disconnect(m_umlObject, SIGNAL(modified()), m_pFText, SLOT(setMessageText())); m_umlObject = op; if (m_umlObject && m_pFText) { connect(m_umlObject, SIGNAL(modified()), m_pFText, SLOT(setMessageText())); m_pFText->setMessageText(); } } /** * Overrides operation from LinkWidget. * Required by FloatingTextWidget. */ QString MessageWidget::customOpText() { return m_CustomOp; } /** * Overrides operation from LinkWidget. * Required by FloatingTextWidget. */ void MessageWidget::setCustomOpText(const QString &opText) { m_CustomOp = opText; m_pFText->setMessageText(); } /** * Overrides operation from LinkWidget. * Required by FloatingTextWidget. */ QString MessageWidget::lwOperationText() { UMLOperation *pOperation = operation(); if (pOperation != 0) { return pOperation->toString(Uml::SignatureType::SigNoVis); } else { return customOpText(); } } /** * Overrides operation from LinkWidget. * Required by FloatingTextWidget. */ UMLClassifier *MessageWidget::lwClassifier() { UMLObject *o = m_pOw[Uml::RoleType::B]->umlObject(); UMLClassifier *c = o->asUMLClassifier(); return c; } /** * Calculates the size of the widget by calling * calculateDimensionsSynchronous(), * calculateDimensionsAsynchronous(), or * calculateDimensionsCreation() */ void MessageWidget::calculateDimensions() { if (m_sequenceMessageType == Uml::SequenceMessage::Synchronous) { calculateDimensionsSynchronous(); } else if (m_sequenceMessageType == Uml::SequenceMessage::Asynchronous) { calculateDimensionsAsynchronous(); } else if (m_sequenceMessageType == Uml::SequenceMessage::Creation) { calculateDimensionsCreation(); } else if (m_sequenceMessageType == Uml::SequenceMessage::Lost) { calculateDimensionsLost(); } else if (m_sequenceMessageType == Uml::SequenceMessage::Found) { calculateDimensionsFound(); } else { uWarning() << "Unknown message type"; } if (! UMLApp::app()->document()->loading()) { adjustAssocs(x(), y()); // adjust assoc lines } } /** * Calculates and sets the size of the widget for a synchronous message. */ void MessageWidget::calculateDimensionsSynchronous() { int x = 0; int x1 = m_pOw[Uml::RoleType::A]->centerX(); int x2 = m_pOw[Uml::RoleType::B]->centerX(); int widgetWidth = 0; if(isSelf()) { widgetWidth = 50; x = x1 - 2; } else if(x1 < x2) { x = x1; widgetWidth = x2 - x1 + 8; } else { x = x2 - 8; widgetWidth = x1 - x2 + 8; } QSizeF minSize = minimumSize(); int widgetHeight = 0; if (height() < minSize.height()) { widgetHeight = minSize.height(); } else { widgetHeight = height(); } setX(x); setSize(widgetWidth, widgetHeight); } /** * Calculates and sets the size of the widget for an asynchronous message. */ void MessageWidget::calculateDimensionsAsynchronous() { int x = 0; int x1 = m_pOw[Uml::RoleType::A]->centerX(); int x2 = m_pOw[Uml::RoleType::B]->centerX(); int widgetWidth = 0; if(isSelf()) { widgetWidth = 50; x = x1; } else if(x1 < x2) { x = x1; widgetWidth = x2 - x1; } else { x = x2; widgetWidth = x1 - x2; } x += 1; widgetWidth -= 2; QSizeF minSize = minimumSize(); int widgetHeight = 0; if (height() < minSize.height()) { widgetHeight = minSize.height(); } else { widgetHeight = height(); } setX(x); setSize(widgetWidth, widgetHeight); } /** * Calculates and sets the size of the widget for a creation message. */ void MessageWidget::calculateDimensionsCreation() { int x = 0; int x1 = m_pOw[Uml::RoleType::A]->centerX(); int x2 = m_pOw[Uml::RoleType::B]->x(); int w2 = m_pOw[Uml::RoleType::B]->width(); if (x1 > x2) x2 += w2; int widgetWidth = 0; if (x1 < x2) { x = x1; widgetWidth = x2 - x1; } else { x = x2; widgetWidth = x1 - x2; } x += 1; widgetWidth -= 2; int widgetHeight = minimumSize().height(); setPos(x, m_pOw[Uml::RoleType::B]->y() + m_pOw[Uml::RoleType::B]->height() / 2); setSize(widgetWidth, widgetHeight); } /** * Calculates and sets the size of the widget for a lost message. */ void MessageWidget::calculateDimensionsLost() { int x = 0; int x1 = m_pOw[Uml::RoleType::A]->centerX(); int x2 = m_xclicked; int widgetWidth = 0; if(x1 < x2) { x = x1; widgetWidth = x2 - x1 + circleWidth/2; } else { x = x2 - circleWidth/2; widgetWidth = x1 - x2 + circleWidth/2; } int widgetHeight = minimumSize().height(); setX(x); setSize(widgetWidth, widgetHeight); } /** * Calculates and sets the size of the widget for a found message. */ void MessageWidget::calculateDimensionsFound() { int x = 0; int x1 = m_pOw[Uml::RoleType::A]->centerX(); int x2 = m_xclicked; int widgetWidth = 0; if(x1 < x2) { x = x1; widgetWidth = x2 - x1 + circleWidth/2; } else { x = x2 - circleWidth/2; widgetWidth = x1 - x2 + circleWidth/2; } int widgetHeight = minimumSize().height(); setX(x); setSize(widgetWidth, widgetHeight); } /** * Used to cleanup any other widget it may need to delete. */ void MessageWidget::cleanup() { if (m_pOw[Uml::RoleType::A]) { disconnect(this, SIGNAL(sigMessageMoved()), m_pOw[Uml::RoleType::A], SLOT(slotMessageMoved())); m_pOw[Uml::RoleType::A]->messageRemoved(this); } if (m_pOw[Uml::RoleType::B]) { disconnect(this, SIGNAL(sigMessageMoved()), m_pOw[Uml::RoleType::B], SLOT(slotMessageMoved())); m_pOw[Uml::RoleType::B]->messageRemoved(this); } UMLWidget::cleanup(); if (m_pFText) { m_scene->removeWidgetCmd(m_pFText); m_pFText = 0; } } /** * Sets the state of whether the widget is selected. * * @param _select True if the widget is selected. */ void MessageWidget::setSelected(bool _select) { UMLWidget::setSelected(_select); if(!m_pFText || m_pFText->displayText().isEmpty()) return; if(isSelected() && m_pFText->isSelected()) return; if(!isSelected() && !m_pFText->isSelected()) return; m_pFText->setSelected(isSelected()); } /** * Returns the minimum height this widget should be set at on * a sequence diagrams. Takes into account the widget positions * it is related to. */ int MessageWidget::getMinY() { if (!m_pOw[Uml::RoleType::A] || !m_pOw[Uml::RoleType::B]) { return 0; } if (m_sequenceMessageType == Uml::SequenceMessage::Creation) { return m_pOw[Uml::RoleType::A]->y() + m_pOw[Uml::RoleType::A]->height(); } int heightA = m_pOw[Uml::RoleType::A]->y() + m_pOw[Uml::RoleType::A]->height(); int heightB = m_pOw[Uml::RoleType::B]->y() + m_pOw[Uml::RoleType::B]->height(); int height = heightA; if(heightA < heightB) { height = heightB; } return height; } /** * Returns the maximum height this widget should be set at on * a sequence diagrams. Takes into account the widget positions * it is related to. */ int MessageWidget::getMaxY() { if(!m_pOw[Uml::RoleType::A] || !m_pOw[Uml::RoleType::B]) { return 0; } int heightA = (int)((ObjectWidget*)m_pOw[Uml::RoleType::A])->getEndLineY(); int heightB = (int)((ObjectWidget*)m_pOw[Uml::RoleType::B])->getEndLineY(); int height = heightA; if(heightA > heightB) { height = heightB; } return (height - this->height()); } /** * Overrides method from UMLWidget. */ QSizeF MessageWidget::minimumSize() const { if (m_sequenceMessageType == Uml::SequenceMessage::Synchronous) { return QSizeF(width(), 20); } else if (m_sequenceMessageType == Uml::SequenceMessage::Asynchronous) { return isSelf() ? QSizeF(width(), 20) : QSizeF(width(), 8); } else if (m_sequenceMessageType == Uml::SequenceMessage::Creation) { return QSizeF(width(), 8); } else if (m_sequenceMessageType == Uml::SequenceMessage::Lost) { return QSizeF(width(), 10); } else if (m_sequenceMessageType == Uml::SequenceMessage::Found) { return QSizeF(width(), 10); } else { uWarning() << "Unknown message type"; } return QSize(width(), height()); } /** * Sets the related widget on the given side. * * @param ow The ObjectWidget we are related to. * @param role The Uml::RoleType::Enum to be set for the ObjectWidget */ void MessageWidget::setObjectWidget(ObjectWidget * ow, Uml::RoleType::Enum role) { m_pOw[role] = ow; updateResizability(); } /** * Returns the related widget on the given side. * * @return The ObjectWidget we are related to. */ ObjectWidget* MessageWidget::objectWidget(Uml::RoleType::Enum role) { return m_pOw[role]; } /** * Set the xclicked */ void MessageWidget::setxclicked(int xclick) { m_xclicked = xclick; } /** * Set the yclicked */ void MessageWidget::setyclicked(int yclick) { m_yclicked = yclick; } /** * Show a properties dialog for an ObjectWidget. */ bool MessageWidget::showPropertiesDialog() { if (!lwClassifier()) { uError() << "lwClassifier() returns a NULL classifier"; return false; } bool result = false; UMLApp::app()->docWindow()->updateDocumentation(false); QPointer dlg = new MessageWidgetPropertiesDialog(0, this); if (dlg->exec()) { m_pFText->setMessageText(); UMLApp::app()->docWindow()->showDocumentation(this, true); UMLApp::app()->document()->setModified(true); result = true; } delete dlg; return result; } /** * Saves to the "messagewidget" XMI element. */ void MessageWidget::saveToXMI1(QDomDocument & qDoc, QDomElement & qElement) { QDomElement messageElement = qDoc.createElement(QLatin1String("messagewidget")); UMLWidget::saveToXMI1(qDoc, messageElement); LinkWidget::saveToXMI1(qDoc, messageElement); if (m_pOw[Uml::RoleType::A]) messageElement.setAttribute(QLatin1String("widgetaid"), Uml::ID::toString(m_pOw[Uml::RoleType::A]->localID())); if (m_pOw[Uml::RoleType::B]) messageElement.setAttribute(QLatin1String("widgetbid"), Uml::ID::toString(m_pOw[Uml::RoleType::B]->localID())); UMLOperation *pOperation = operation(); if (pOperation) messageElement.setAttribute(QLatin1String("operation"), Uml::ID::toString(pOperation->id())); else messageElement.setAttribute(QLatin1String("operation"), m_CustomOp); messageElement.setAttribute(QLatin1String("sequencemessagetype"), m_sequenceMessageType); if (m_sequenceMessageType == Uml::SequenceMessage::Lost || m_sequenceMessageType == Uml::SequenceMessage::Found) { messageElement.setAttribute(QLatin1String("xclicked"), m_xclicked); messageElement.setAttribute(QLatin1String("yclicked"), m_yclicked); } // save the corresponding message text if (m_pFText && !m_pFText->text().isEmpty()) { messageElement.setAttribute(QLatin1String("textid"), Uml::ID::toString(m_pFText->id())); m_pFText->saveToXMI1(qDoc, messageElement); } qElement.appendChild(messageElement); } /** * Loads from the "messagewidget" XMI element. */ bool MessageWidget::loadFromXMI1(QDomElement& qElement) { if (!UMLWidget::loadFromXMI1(qElement)) { return false; } if (!LinkWidget::loadFromXMI1(qElement)) { return false; } QString textid = qElement.attribute(QLatin1String("textid"), QLatin1String("-1")); QString widgetaid = qElement.attribute(QLatin1String("widgetaid"), QLatin1String("-1")); QString widgetbid = qElement.attribute(QLatin1String("widgetbid"), QLatin1String("-1")); m_CustomOp = qElement.attribute(QLatin1String("operation")); QString sequenceMessageType = qElement.attribute(QLatin1String("sequencemessagetype"), QLatin1String("1001")); m_sequenceMessageType = Uml::SequenceMessage::fromInt(sequenceMessageType.toInt()); if (m_sequenceMessageType == Uml::SequenceMessage::Lost || m_sequenceMessageType == Uml::SequenceMessage::Found) { m_xclicked = qElement.attribute(QLatin1String("xclicked"), QLatin1String("-1")).toInt(); m_yclicked = qElement.attribute(QLatin1String("yclicked"), QLatin1String("-1")).toInt(); } m_widgetAId = Uml::ID::fromString(widgetaid); m_widgetBId = Uml::ID::fromString(widgetbid); m_textId = Uml::ID::fromString(textid); Uml::TextRole::Enum tr = Uml::TextRole::Seq_Message; if (m_widgetAId == m_widgetBId) tr = Uml::TextRole::Seq_Message_Self; //now load child elements QDomNode node = qElement.firstChild(); QDomElement element = node.toElement(); if (!element.isNull()) { QString tag = element.tagName(); if (tag == QLatin1String("floatingtext") || tag == QLatin1String("UML::FloatingTextWidget")) { m_pFText = new FloatingTextWidget(m_scene, tr, operationText(m_scene), m_textId); m_scene->addFloatingTextWidget(m_pFText); if(! m_pFText->loadFromXMI1(element)) { // Most likely cause: The FloatingTextWidget is empty. delete m_pFText; m_pFText = 0; } else m_pFText->setSequenceNumber(m_SequenceNumber); } else { uError() << "unknown tag " << tag; } } return true; } diff --git a/umbrello/umlwidgets/umlwidget.cpp b/umbrello/umlwidgets/umlwidget.cpp index 4d189a37c..4eadf7481 100644 --- a/umbrello/umlwidgets/umlwidget.cpp +++ b/umbrello/umlwidgets/umlwidget.cpp @@ -1,2022 +1,2026 @@ /*************************************************************************** * 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 "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); + umlWidget->setParentItem(this); 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); } 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) { // 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. + * + * @param withAssocs true - update associations too */ -void UMLWidget::updateGeometry() +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(); - adjustAssocs(size.width()-oldW, size.height()-oldH); + 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 */ 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/umlwidget.h b/umbrello/umlwidgets/umlwidget.h index 2d746ccc7..dc20cca26 100644 --- a/umbrello/umlwidgets/umlwidget.h +++ b/umbrello/umlwidgets/umlwidget.h @@ -1,348 +1,348 @@ /*************************************************************************** * 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 * ***************************************************************************/ #ifndef UMLWIDGET_H #define UMLWIDGET_H #include "associationwidgetlist.h" #include "basictypes.h" #include "optionstate.h" #include "umlwidgetlist.h" #include "widgetbase.h" #include #include class IDChangeLog; class UMLDoc; class UMLObject; class UMLScene; class QPainter; class QFontMetrics; /** * This is the base class for nearly all graphical widgets. * * @short The base class for graphical UML objects. * @author Paul Hensgen * Bugs and comments to umbrello-devel@kde.org or http://bugs.kde.org */ class UMLWidget : public WidgetBase { Q_OBJECT public: friend class ToolBarStateArrow; // for calling the mouse*Event handlers static const QSizeF DefaultMinimumSize; static const QSizeF DefaultMaximumSize; static const int defaultMargin; static const int selectionMarkerSize; static const int resizeMarkerLineCount; explicit UMLWidget(UMLScene *scene, WidgetType type = wt_UMLWidget, UMLObject *o = 0); explicit UMLWidget(UMLScene *scene, WidgetType type = wt_UMLWidget, Uml::ID::Type id = Uml::ID::None); virtual ~UMLWidget(); // Copy constructor - not implemented. // UMLWidget(const UMLWidget& other); UMLWidget& operator=(const UMLWidget& other); bool operator==(const UMLWidget& other) const; void setLocalID(Uml::ID::Type id); Uml::ID::Type localID() const; virtual UMLWidget* widgetWithID(Uml::ID::Type id); virtual QSizeF minimumSize() const; void setMinimumSize(const QSizeF &size); virtual QSizeF maximumSize(); void setMaximumSize(const QSizeF &size); virtual void setUseFillColor(bool fc); void setUseFillColorCmd(bool fc); virtual void setTextColor(const QColor &color); void setTextColorCmd(const QColor &color); virtual void setLineColor(const QColor &color); virtual void setLineColorCmd(const QColor &color); virtual void setLineWidth(uint width); void setLineWidthCmd(uint width); virtual void setFillColor(const QColor &color); void setFillColorCmd(const QColor &color); void setSelectedFlag(bool _select); virtual void setSelected(bool _select); void setScene(UMLScene *scene); virtual bool activate(IDChangeLog* ChangeLog = 0); void setPenFromSettings(QPainter &p); void setPenFromSettings(QPainter *p); virtual void setFont(const QFont &font); void setFontCmd(const QFont &font); /** * Returns whether we triggered the update of position movement. * If so, you probably don't want to move it. * * @return The moving state. */ bool getStartMove() const { return m_startMove; } virtual void setX(qreal x); virtual void setY(qreal y); /** * Returns the height of widget. */ qreal height() const { return rect().height(); } /** * Returns the width of the widget. */ qreal width() const { return rect().width(); } void setSize(qreal width, qreal height); void setSize(const QSizeF& size); virtual void resizeWidget(qreal newW, qreal newH); virtual void notifyParentResize(); bool getIgnoreSnapToGrid() const; void setIgnoreSnapToGrid(bool to); void moveByLocal(qreal dx, qreal dy); void removeAssoc(AssociationWidget* pAssoc); void addAssoc(AssociationWidget* pAssoc); AssociationWidgetList &associationWidgetList() const; /** * Read property of bool m_isInstance */ bool isInstance() const { return m_isInstance; } /** * Write property of bool m_isInstance */ void setIsInstance(bool isInstance) { m_isInstance = isInstance; } /** * Write property of m_instanceName */ void setInstanceName(const QString &instanceName) { m_instanceName = instanceName; } /** * Read property of m_instanceName */ QString instanceName() const { return m_instanceName; } bool showStereotype() const; virtual void setShowStereotype(bool flag); virtual bool showPropertiesDialog(); virtual void adjustAssocs(qreal dx, qreal dy); virtual void adjustUnselectedAssocs(qreal dx, qreal dy); bool isActivated() const; void setActivated(bool active = true); virtual void cleanup(); static bool widgetHasUMLObject(WidgetBase::WidgetType type); - void updateGeometry(); + void updateGeometry(bool withAssocs = true); void clipSize(); void forceUpdateFontMetrics(QPainter *painter); void forceUpdateFontMetrics(QFont &font, QPainter *painter); virtual bool loadFromXMI1(QDomElement &qElement); virtual void saveToXMI1(QDomDocument &qDoc, QDomElement &qElement); QPointF startMovePosition() const; void setStartMovePosition(const QPointF &position); QSizeF startResizeSize() const; virtual QSizeF calculateSize(bool withExtensions = true) const; void resize(); bool fixedAspectRatio() const { return m_fixedAspectRatio; } void setFixedAspectRatio(bool state) { m_fixedAspectRatio = state; } typedef enum { FT_NORMAL = 0, FT_BOLD = 1, FT_ITALIC = 2, FT_UNDERLINE = 3, FT_BOLD_ITALIC = 4, FT_BOLD_UNDERLINE = 5, FT_ITALIC_UNDERLINE = 6, FT_BOLD_ITALIC_UNDERLINE = 7, FT_INVALID = 8 } FontType; virtual void setDefaultFontMetrics(QFont &font, UMLWidget::FontType fontType); virtual void setDefaultFontMetrics(QFont &font, UMLWidget::FontType fontType, QPainter &painter); QFontMetrics &getFontMetrics(UMLWidget::FontType fontType) const; void setFontMetrics(UMLWidget::FontType fontType, QFontMetrics fm); void setupFontType(QFont &font, UMLWidget::FontType fontType); virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); public Q_SLOTS: virtual void updateWidget(); virtual void slotMenuSelection(QAction* action); virtual void slotWidgetMoved(Uml::ID::Type id); virtual void slotFillColorChanged(Uml::ID::Type viewID); virtual void slotLineColorChanged(Uml::ID::Type viewID); virtual void slotTextColorChanged(Uml::ID::Type viewID); virtual void slotLineWidthChanged(Uml::ID::Type viewID); void slotSnapToGrid(); signals: /** * Emit when the widget moves its' position. * @param id The id of the object behind the widget. */ void sigWidgetMoved(Uml::ID::Type id); protected: virtual void contextMenuEvent(QGraphicsSceneContextMenuEvent* event); virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); virtual void mousePressEvent(QGraphicsSceneMouseEvent *event); virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event); virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); virtual void moveEvent(QGraphicsSceneMouseEvent *event); virtual void moveWidgetBy(qreal diffX, qreal diffY); virtual void constrainMovementForAllWidgets(qreal &diffX, qreal &diffY); virtual void constrain(qreal& width, qreal& height); virtual bool isInResizeArea(QGraphicsSceneMouseEvent *me); virtual QCursor resizeCursor() const; void selectSingle(QGraphicsSceneMouseEvent *me); void selectMultiple(QGraphicsSceneMouseEvent *me); void deselect(QGraphicsSceneMouseEvent *me); // void resetSelection(); void setSelectionBounds(); void resize(QGraphicsSceneMouseEvent *me); bool wasSizeChanged(); bool wasPositionChanged(); virtual void toForeground(); void addConnectedWidget(UMLWidget *widget, Uml::AssociationType::Enum type = Uml::AssociationType::Association); void addWidget(UMLWidget *widget, bool showProperties = true); ///////////////// Data Loaded/Saved ///////////////////////////////// QString m_instanceName; ///< instance name (used if on a deployment diagram) bool m_isInstance; ///< holds whether this widget is a component instance (i.e. on a deployment diagram) bool m_showStereotype; ///< should the stereotype be displayed ///////////////// End of Data Loaded/Saved ////////////////////////// Uml::ID::Type m_nLocalID; bool m_startMove; QPointF m_startMovePostion; QSizeF m_startResizeSize; int m_nPosX; UMLDoc *m_doc; ///< shortcut for UMLApp::app()->getDocument() bool m_resizable; QFontMetrics *m_pFontMetrics[FT_INVALID]; QSizeF m_minimumSize; QSizeF m_maximumSize; /// true if the activate function has been called for this class instance bool m_activated; /** * Change Widget Behaviour */ bool m_ignoreSnapToGrid; bool m_ignoreSnapComponentSizeToGrid; bool m_fixedAspectRatio; /// The text in the status bar when the cursor was pressed. QString m_oldStatusBarMsg; /// The X/Y offset from the position of the cursor when it was pressed to the /// upper left corner of the widget. QPointF m_pressOffset; /// The X/Y position the widget had when the movement started. QPointF m_oldPos; /// The width/height the widget had when the resize started. qreal m_oldW, m_oldH; /// If shift or control button were pressed in mouse press event. bool m_shiftPressed; /** * If cursor was in move/resize area when left button was pressed (and no * other widgets were selected). */ bool m_inMoveArea, m_inResizeArea; /** * If the widget was selected/moved/resized in the press and release cycle. * Moved/resized is true if the widget was moved/resized even if the final * position/size is the same as the starting one. */ bool m_moved, m_resized; private: void init(); /// A list of AssociationWidgets between the UMLWidget and other UMLWidgets in the diagram mutable AssociationWidgetList m_Assocs; }; #endif