diff --git a/umbrello/codeimport/pythonimport.cpp b/umbrello/codeimport/pythonimport.cpp index 813bc62c2..de62814be 100644 --- a/umbrello/codeimport/pythonimport.cpp +++ b/umbrello/codeimport/pythonimport.cpp @@ -1,400 +1,400 @@ /*************************************************************************** * 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) 2006-2014 * * Umbrello UML Modeller Authors * ***************************************************************************/ // own header #include "pythonimport.h" // app includes #include "attribute.h" #include "classifier.h" #include "codeimpthread.h" #include "debug_utils.h" #include "enum.h" #include "import_utils.h" #include "operation.h" #include "package.h" #include "uml.h" #include "umldoc.h" #include "umlpackagelist.h" // qt includes #include /** * Constructor. */ PythonImport::PythonImport(CodeImpThread* thread) : NativeImportBase(QLatin1String("#"), thread) { setMultiLineComment(QLatin1String("\"\"\""), QLatin1String("\"\"\"")); initVars(); } /** * Destructor. */ PythonImport::~PythonImport() { } /** * Reimplement operation from NativeImportBase. */ void PythonImport::initVars() { m_srcIndentIndex = 0; m_srcIndent[m_srcIndentIndex] = 0; m_braceWasOpened = false; m_isStatic = false; } /** * Reimplement operation from NativeImportBase. * In addition to handling multiline comments, this method transforms * changes in leading indentation into braces (opening brace for increase * in indentation, closing brace for decrease in indentation) in m_source. * Removal of Python's indentation sensitivity simplifies subsequent * processing using Umbrello's native import framework. * @param line the line to preprocess * @return success status of operation */ bool PythonImport::preprocess(QString& line) { if (NativeImportBase::preprocess(line)) return true; // Handle single line comment int pos = line.indexOf(m_singleLineCommentIntro); if (pos != -1) { QString cmnt = line.mid(pos); m_source.append(cmnt); m_srcIndex++; if (pos == 0) return true; line = line.left(pos); line.remove(QRegExp(QLatin1String("\\s+$"))); } // Transform changes in indentation into braces a la C++/Java/Perl/... pos = line.indexOf(QRegExp(QLatin1String("\\S"))); if (pos == -1) return true; bool isContinuation = false; int leadingWhite = line.left(pos).count(QRegExp(QLatin1String("\\s"))); if (leadingWhite > m_srcIndent[m_srcIndentIndex]) { if (m_srcIndex == 0) { uError() << "internal error"; return true; } if (m_braceWasOpened) { m_srcIndent[++m_srcIndentIndex] = leadingWhite; m_braceWasOpened = false; } else { isContinuation = true; } } else { while (m_srcIndentIndex > 0 && leadingWhite < m_srcIndent[m_srcIndentIndex]) { m_srcIndentIndex--; m_source.append(QLatin1String("}")); m_srcIndex++; } } if (m_braceWasOpened && m_srcIndentIndex == 0) { m_source.append(QLatin1String("}")); m_srcIndex++; } if (line.endsWith(QLatin1Char(':'))) { line.replace(QRegExp(QLatin1String(":$")), QLatin1String("{")); m_braceWasOpened = true; } else { m_braceWasOpened = false; } if (!isContinuation && !m_braceWasOpened) line += QLatin1Char(';'); return false; // The input was not completely consumed by preprocessing. } /** * Implement abstract operation from NativeImportBase. * @param word whitespace delimited item */ void PythonImport::fillSource(const QString& word) { QString lexeme; const uint len = word.length(); for (uint i = 0; i < len; ++i) { const QChar& c = word[i]; if (c.isLetterOrNumber() || c == QLatin1Char('_') || c == QLatin1Char('.')) { lexeme += c; } else { if (!lexeme.isEmpty()) { m_source.append(lexeme); m_srcIndex++; lexeme.clear(); } m_source.append(QString(c)); m_srcIndex++; } } if (!lexeme.isEmpty()) { m_source.append(lexeme); m_srcIndex++; } } /** * Return an amount of spaces that corresponds to @param level * @return spaces of indentation */ QString PythonImport::indentation(int level) { QString spaces; for (int i = 0; i < level; ++i) { spaces += QLatin1String(" "); } return spaces; } /** * Skip ahead to outermost closing brace. * @return body contents skipped */ QString PythonImport::skipBody() { /* During input preprocessing, changes in indentation were replaced by braces, and a semicolon was appended to each line ending. In order to return the body, we try to reconstruct the original Python syntax by reverting those changes. */ QString body; if (m_source[m_srcIndex] != QLatin1String("{")) skipStmt(QLatin1String("{")); bool firstTokenAfterNewline = true; int braceNesting = 0; QString token; while (!(token = advance()).isNull()) { if (token == QLatin1String("}")) { if (braceNesting <= 0) break; braceNesting--; body += QLatin1Char('\n'); firstTokenAfterNewline = true; } else if (token == QLatin1String("{")) { braceNesting++; body += QLatin1String(":\n"); firstTokenAfterNewline = true; } else if (token == QLatin1String(";")) { body += QLatin1Char('\n'); firstTokenAfterNewline = true; } else { if (firstTokenAfterNewline) { body += indentation(braceNesting); firstTokenAfterNewline = false; } else if (body.contains(QRegExp(QLatin1String("\\w$"))) && token.contains(QRegExp(QLatin1String("^\\w")))) { body += QLatin1Char(' '); } body += token; } } return body; } /** - * Parse assignments in the form '=' + * Parse assignments in the form \ '=' \ * Instance variables are identified by a prefixed 'self.'. * @return success status of parsing */ bool PythonImport::parseAssignmentStmt(const QString keyword) { QString variable = keyword; advance(); QString value = advance(); if (value == QLatin1String("-")) value.append(advance()); bool isStatic = true; if (variable.startsWith(QLatin1String("self."))) { variable.remove(0,5); isStatic = false; } Uml::Visibility::Enum visibility = Uml::Visibility::Public; if (variable.startsWith(QLatin1String("__"))) { visibility = Uml::Visibility::Private; variable.remove(0, 2); } else if (variable.startsWith(QLatin1String("_"))) { visibility = Uml::Visibility::Protected; variable.remove(0, 1); } QString type; if (value == QLatin1String("[")) { if (lookAhead() == QLatin1String("]")) { advance(); type = QLatin1String("list"); value = QLatin1String(""); } } else if (value == QLatin1String("{")) { if (lookAhead() == QLatin1String("}")) { advance(); type = QLatin1String("dict"); value = QLatin1String(""); } } else if (value.startsWith(QLatin1String("\""))) { type = QLatin1String("string"); } else if (value == QLatin1String("True") || value == QLatin1String("False")) { type = QLatin1String("bool"); } else if (value.contains(QRegExp(QLatin1String("-?\\d+\\.\\d*")))) { type = QLatin1String("float"); } else if (value.contains(QRegExp(QLatin1String("-?\\d+")))) { type = QLatin1String("int"); } else if (!value.isEmpty()) { type = QLatin1String("object"); } UMLObject* o = Import_Utils::insertAttribute(m_klass, visibility, variable, type, m_comment, false); UMLAttribute* a = o->asUMLAttribute(); a->setInitialValue(value); a->setStatic(isStatic); return true; } /** * Implement abstract operation from NativeImportBase. * @return success status of operation */ bool PythonImport::parseStmt() { const int srcLength = m_source.count(); QString keyword = m_source[m_srcIndex]; if (keyword == QLatin1String("class")) { const QString& name = advance(); UMLObject *ns = Import_Utils::createUMLObject(UMLObject::ot_Class, name, currentScope(), m_comment); pushScope(m_klass = ns->asUMLClassifier()); m_comment.clear(); if (advance() == QLatin1String("(")) { while (m_srcIndex < srcLength - 1 && advance() != QLatin1String(")")) { const QString& baseName = m_source[m_srcIndex]; Import_Utils::createGeneralization(m_klass, baseName); if (advance() != QLatin1String(",")) break; } } if (m_source[m_srcIndex] != QLatin1String("{")) { skipStmt(QLatin1String("{")); } log(QLatin1String("class ") + name); return true; } if (keyword == QLatin1String("@")) { const QString& annotation = m_source[++m_srcIndex]; uDebug() << "annotation:" << annotation; if (annotation == QLatin1String("staticmethod")) m_isStatic = true; return true; } if (keyword == QLatin1String("def")) { if (m_klass == 0) { // skip functions outside of a class skipBody(); return true; } if (!m_klass->hasDoc() && !m_comment.isEmpty()) { m_klass->setDoc(m_comment); m_comment = QString(); } QString name = advance(); Uml::Visibility::Enum visibility = Uml::Visibility::Public; if (name.startsWith(QLatin1String("__"))) { name = name.mid(2); visibility = Uml::Visibility::Private; } else if (name.startsWith(QLatin1String("_"))) { name = name.mid(1); visibility = Uml::Visibility::Protected; } UMLOperation *op = Import_Utils::makeOperation(m_klass, name); if (advance() != QLatin1String("(")) { uError() << "importPython def " << name << ": expecting \"(\""; skipBody(); return true; } bool firstParam = true; while (m_srcIndex < srcLength && advance() != QLatin1String(")")) { const QString& parName = m_source[m_srcIndex]; if (firstParam) { if (parName.compare(QLatin1String("self"), Qt::CaseInsensitive) != 0) { m_isStatic = true; Import_Utils::addMethodParameter(op, QLatin1String("string"), parName); } firstParam = false; } else { /*UMLAttribute *att =*/ Import_Utils::addMethodParameter(op, QLatin1String("string"), parName); } if (advance() != QLatin1String(",")) break; } Import_Utils::insertMethod(m_klass, op, visibility, QLatin1String("string"), m_isStatic, false /*isAbstract*/, false /*isFriend*/, false /*isConstructor*/, false, m_comment); m_isStatic = false; int srcIndex = m_srcIndex; op->setSourceCode(skipBody()); if (!op->hasDoc() && !m_comment.isEmpty()) { op->setDoc(m_comment); m_comment = QString(); } // parse instance variables from __init__ method if (name == QLatin1String("__init__")) { int indexSave = m_srcIndex; m_srcIndex = srcIndex; advance(); keyword = advance(); while (m_srcIndex < indexSave) { if (lookAhead() == QLatin1String("=")) { parseAssignmentStmt(keyword); // skip ; inserted by lexer if (lookAhead() == QLatin1String(";")) { advance(); keyword = advance(); } } else { skipStmt(QLatin1String(";")); keyword = advance(); } } m_srcIndex = indexSave; } log(QLatin1String("def ") + name); return true; } // parse class variables if (m_klass && lookAhead() == QLatin1String("=")) { bool result = parseAssignmentStmt(keyword); log(QLatin1String("class attribute ") + keyword); return result; } if (keyword == QLatin1String("}")) { if (scopeIndex()) { m_klass = popScope()->asUMLClassifier(); } else uError() << "parsing: too many }"; return true; } return false; // @todo parsing of attributes } diff --git a/umbrello/codeimport/sqlimport.cpp b/umbrello/codeimport/sqlimport.cpp index 11c6931d5..5c92f9ece 100644 --- a/umbrello/codeimport/sqlimport.cpp +++ b/umbrello/codeimport/sqlimport.cpp @@ -1,1022 +1,1022 @@ /* Copyright 2015 Ralf Habacker 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // own header #include "sqlimport.h" // app includes #include "association.h" #include "attribute.h" #include "checkconstraint.h" #include "classifier.h" #include "debug_utils.h" #include "enum.h" #include "folder.h" #include "import_utils.h" #include "operation.h" #include "package.h" #include "uml.h" #include "entity.h" #include "entityattribute.h" #include "foreignkeyconstraint.h" #include "uniqueconstraint.h" #include "umldoc.h" #include "umlpackagelist.h" // kde includes #if QT_VERSION < 0x050000 #include #endif // qt includes #include #include #if QT_VERSION >= 0x050000 #include #endif #include #include DEBUG_REGISTER(SQLImport) /** * Constructor. * * @param thread thread in which the code import runs */ SQLImport::SQLImport(CodeImpThread* thread) : NativeImportBase(QLatin1String("--"), thread) { setMultiLineComment(QLatin1String("/*"), QLatin1String("*/")); } /** * Destructor. */ SQLImport::~SQLImport() { } /** * Implement abstract operation from NativeImportBase. */ void SQLImport::fillSource(const QString& word) { QString lexeme; const uint len = word.length(); for (uint i = 0; i < len; ++i) { QChar c = word[i]; if (c.isLetterOrNumber() || c == QLatin1Char('_')) { lexeme += c; } else { if (!lexeme.isEmpty()) { m_source.append(lexeme); lexeme.clear(); } m_source.append(QString(c)); } } if (!lexeme.isEmpty()) m_source.append(lexeme); } /** * Strip qoutes from identifier. * * @param token string with current token * @return stripped string */ QString &stripQuotes(QString &token) { if (token.contains(QLatin1String("\""))) token.replace(QLatin1String("\""), QLatin1String("")); else if (token.contains(QLatin1String("`"))) token.replace(QLatin1String("`"), QLatin1String("")); else if (token.contains(QLatin1String("'"))) token.replace(QLatin1String("'"), QLatin1String("")); return token; } /** * Parse identifier. * * @param token string with current token * @return parsed identifier */ QString SQLImport::parseIdentifier(QString &token) { QString value; if (token == QLatin1String("`")) { // mysql value = advance(); token = advance(); } else value = token; token = advance(); if (token == QLatin1String(".")) { // FIXME we do not support packages yet #if 0 value += token; value += advance(); token = advance(); #else value = advance(); token = advance(); #endif } return stripQuotes(value); } /** * Parse identifier list. * * @param token string with current token * @return string list with identifiers */ QStringList SQLImport::parseIdentifierList(QString &token) { QStringList values; if (token.toLower() == QLatin1String("(")) { for (token = advance(); token != QLatin1String(")");) { if (token == QLatin1String(",")) { token = advance(); continue; } QString value = parseIdentifier(token); values.append(value); } token = advance(); } else { ;// error; } return values; } /** * Parse field type. * * @param token string with current token * @return string list containing field type (index 0), size/count (index 1) and optional values (index > 2) */ QStringList SQLImport::parseFieldType(QString &token) { QString type = token; QString typeLength; // handle type extensions token = advance(); // schema.type if (token == QLatin1String(".")) { type += token; type += advance(); token = advance(); if (token.toLower() == QLatin1String("precision")) { type += token; token = advance(); } } if (type.toLower() == QLatin1String("enum")) { QStringList values = parseIdentifierList(token); return QStringList() << type << QString() << values; } if (token.toLower() == QLatin1String("varying")) { type += QLatin1String(" ") + token; token = advance(); // '(' } // (number) | (number,number) if (token.toLower() == QLatin1String("(")) { typeLength = advance(); // value token = advance(); if (token == QLatin1String(",")) { typeLength += token; typeLength += advance(); token = advance(); } token = advance(); } else if (token.toLower() == QLatin1String("precision")) { type += QLatin1String(" ") + token; token = advance(); } if (token == QLatin1String("[")) { token = advance(); if (token == QLatin1String("]")) { type += QLatin1String("[]"); token = advance(); } } else if (token.toLower().startsWith(QLatin1String("with"))) { type += QLatin1String(" ") + token; token = advance(); type += QLatin1String(" ") + token; token = advance(); type += QLatin1String(" ") + token; token = advance(); } else if (token.toLower() == QLatin1String("unsigned")) { // mysql token = advance(); } return QStringList() << type << typeLength; } /** * Parse default expression. * * The expression could be in the form - * (expression):: + * (expression)\::\ * function(expression) * * @param token string with current token * @return string with default expression */ QString SQLImport::parseDefaultExpression(QString &token) { QString defaultValue; if (token == QLatin1String("(")) { int index = m_srcIndex; skipToClosing(QLatin1Char('(')); token = advance(); for (int i = index; i < m_srcIndex; i++) defaultValue += m_source[i]; } else { defaultValue += token; token = advance(); } if (token == (QLatin1String(":"))) { defaultValue += token; token = advance(); if (token == (QLatin1String(":"))) { defaultValue += token; token = advance(); defaultValue += parseFieldType(token).first(); } } if (token == QLatin1String("(")) { int index = m_srcIndex; skipToClosing(QLatin1Char('(')); token = advance(); for (int i = index; i < m_srcIndex; i++) defaultValue += m_source[i]; } return defaultValue; } /** * Parse column constraint. * * pgsql: * [ CONSTRAINT constraint_name ] * { NOT NULL | * NULL | * CHECK ( expression ) | * COLLATE collation | * DEFAULT default_expr | * UNIQUE index_parameters | * PRIMARY KEY index_parameters | * REFERENCES reftable [ ( refcolumn ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] * [ ON DELETE action ] [ ON UPDATE action ] } * [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] * * mysql: * [ PRIMARY KEY index_parameters | * KEY key_name ( fields ) * CHARACTER SET charset_name | * COLLATE collation ] * * @param token string with current token * @return column constraints */ SQLImport::ColumnConstraints SQLImport::parseColumnConstraints(QString &token) { ColumnConstraints constraints; int index = m_srcIndex; while(token != QLatin1String(",") && token != QLatin1String(")") && token.toLower() != QLatin1String("COMMENT")) { if (token.toLower() == QLatin1String("character")) { // mysql token = advance(); // set if (token.toLower() == QLatin1String("set")) { constraints.characterSet = advance(); // token = advance(); } else { m_srcIndex--; // take back token = m_source[m_srcIndex]; } } if (token.toLower() == QLatin1String("collate")) { // mysql constraints.collate = advance(); token = advance(); } // [ CONSTRAINT constraint_name ] if (token.toLower() == QLatin1String("constraint")) { constraints.constraintName = advance(); token = advance(); } // NOT NULL if (token.toLower() == QLatin1String("not")) { token = advance(); if (token.toLower() == QLatin1String("null")) { constraints.notNullConstraint = true; token = advance(); } } // NULL if (token.toLower() == QLatin1String("null")) { constraints.notNullConstraint = false; token = advance(); } // CHECK ( expression ) if (token.toLower() == QLatin1String("check")) { skipStmt(QLatin1String(")")); token = advance(); } // DEFAULT default_expr if (token.toLower() == QLatin1String("default")) { token = advance(); constraints.defaultValue = parseDefaultExpression(token); } // UNIQUE index_parameters if (token.toLower() == QLatin1String("unique")) { constraints.uniqueKey = true; token = advance(); // WITH ( storage_parameter [= value] [, ... ] ) if (token.toLower() == QLatin1String("with")) { skipStmt(QLatin1String(")")); token = advance(); } // USING INDEX TABLESPACE tablespace if (token.toLower() == QLatin1String("using")) { token = advance(); token = advance(); token = advance(); token = advance(); } } // PRIMARY KEY index_parameters if (token.toLower() == QLatin1String("primary")) { token = advance(); if (token.toLower() == QLatin1String("key")) { constraints.primaryKey = true; token = advance(); // WITH ( storage_parameter [= value] [, ... ] ) if (token.toLower() == QLatin1String("with")) { skipStmt(QLatin1String(")")); token = advance(); } // USING INDEX TABLESPACE tablespace if (token.toLower() == QLatin1String("using")) { token = advance(); token = advance(); token = advance(); token = advance(); } } } // REFERENCES reftable [ ( refcolumn ) ] if (token.toLower() == QLatin1String("references")) { token = advance(); token = advance(); if (token == QLatin1String("(")) { skipStmt(QLatin1String(")")); token = advance(); } // [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] if (token.toLower() == QLatin1String("match")) { token = advance(); token = advance(); } // [ ON DELETE action ] if (token.toLower() == QLatin1String("on")) { token = advance(); token = advance(); token = advance(); } // [ ON UPDATE action ] if (token.toLower() == QLatin1String("on")) { token = advance(); token = advance(); token = advance(); } } // [ DEFERRABLE | NOT DEFERRABLE ] if (token.toLower() == QLatin1String("deferrable")) { token = advance(); } else if (token.toLower() == QLatin1String("not")) { token = advance(); token = advance(); } // [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] if (token.toLower() == QLatin1String("initially")) { token = advance(); token = advance(); } if (token.toLower() == QLatin1String("auto_increment")) { // mysql constraints.autoIncrement = true; token = advance(); } if (index == m_srcIndex) { log(m_parsedFiles.first(), QLatin1String("could not parse column constraint '") + token + QLatin1String("'")); token = advance(); index = m_srcIndex; } } return constraints; } /** * Parse table constraint. * * pgsql: * * [ CONSTRAINT constraint_name ] * { CHECK ( expression ) | * UNIQUE ( column_name [, ... ] ) index_parameters | * PRIMARY KEY ( column_name [, ... ] ) index_parameters | * EXCLUDE [ USING index_method ] ( exclude_element WITH operator [, ... ] ) index_parameters [ WHERE ( predicate ) ] | * FOREIGN KEY ( column_name [, ... ] ) REFERENCES reftable [ ( refcolumn [, ... ] ) ] * [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] [ ON DELETE action ] [ ON UPDATE action ] } * [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] * * mysql: * PRIMARY KEY (`uid`, `pid`) | * KEY `t3ver_oid` (`t3ver_oid`,`t3ver_wsid`) * UNIQUE KEY `entry_identifier` (`entry_namespace`,`entry_key`) * * @param token string with current token * @return table constraints */ SQLImport::TableConstraints SQLImport::parseTableConstraints(QString &token) { TableConstraints constraints; if (token.toLower() == QLatin1String("constraint")) { constraints.constraintName = advance(); token = advance(); } // CHECK ( expression ) if (token.toLower() == QLatin1String("check")) { token = advance(); if (token == QLatin1String("(")) { int index = m_srcIndex; skipToClosing(QLatin1Char('(')); token = advance(); constraints.checkConstraint = true; for (int i = index; i < m_srcIndex; i++) constraints.checkExpression += m_source[i]; } } // PRIMARY KEY (`uid`, `pid`), if (token.toLower() == QLatin1String("primary")) { token = advance(); // key token = advance(); // ( constraints.primaryKey = true; constraints.primaryKeyFields = parseIdentifierList(token); } // UNIQUE KEY `entry_identifier` (`entry_namespace`,`entry_key`) else if (token.toLower() == QLatin1String("unique")) { token = advance(); token = advance(); constraints.uniqueKeys = true; constraints.uniqueKeyName = parseIdentifier(token); constraints.uniqueKeysFields = parseIdentifierList(token); } // KEY `t3ver_oid` (`t3ver_oid`,`t3ver_wsid`) // mysql else if (token.toLower() == QLatin1String("key")) { if (m_source[m_srcIndex+4] == QLatin1String("(") ) { token = advance(); constraints.uniqueKeys = true; constraints.uniqueKeyName = parseIdentifier(token); constraints.uniqueKeysFields = parseIdentifierList(token); } } return constraints; } /** * Parse table create definition. * * @param token string with current token * @param entity entity to save the definition into * @return true on success * @return false on error */ bool SQLImport::parseCreateDefinition(QString &token, UMLEntity *entity) { if (token != QLatin1String("(")) { skipStmt(QLatin1String(";")); return false; } while (m_source.count() > m_srcIndex) { token = advance(); if (token == QLatin1String(")")) { break; } TableConstraints tableConstraints = parseTableConstraints(token); if (tableConstraints.primaryKey) { if (!addPrimaryKey(entity, tableConstraints.constraintName, tableConstraints.primaryKeyFields)) { ; // log error } } if (tableConstraints.uniqueKeys) { if (!addUniqueConstraint(entity, tableConstraints.uniqueKeyName, tableConstraints.uniqueKeysFields)) { ; // log error } } if (tableConstraints.checkConstraint) { if (entity) { QString name; if (!tableConstraints.constraintName.isEmpty()) name = tableConstraints.constraintName; else name = entity->name() + QLatin1String("_check"); UMLCheckConstraint *cc = new UMLCheckConstraint(entity, name); cc->setCheckCondition(tableConstraints.checkExpression); entity->addConstraint(cc); } else { uError() << "Could not add check constraint '" << tableConstraints.constraintName << "' because of zero entity."; } } if (token == QLatin1String(",")) continue; else if (token == QLatin1String(")")) break; // handle field name QString fieldName = parseIdentifier(token); // handle field type QStringList fieldType = parseFieldType(token); SQLImport::ColumnConstraints constraints = parseColumnConstraints(token); DEBUG(DBG_SRC) << "field" << fieldName << fieldType.at(0); if (entity && !fieldName.isEmpty()) { UMLObject *type = addDatatype(fieldType); UMLEntityAttribute *a = new UMLEntityAttribute(0, fieldName, Uml::ID::None, Uml::Visibility::Public, type); if (constraints.primaryKey) a->setIndexType(UMLEntityAttribute::Primary); a->setNull(!constraints.notNullConstraint); // convert index to value if present, see https://dev.mysql.com/doc/refman/8.0/en/enum.html if (UMLApp::app()->activeLanguage() == Uml::ProgrammingLanguage::MySQL && type->isUMLEnum()) { bool ok; int index = constraints.defaultValue.toInt(&ok); if (!ok) // string (not checked if valid) or empty a->setInitialValue(constraints.defaultValue); else if (index > 0) { index--; // 0 is empty UMLEnum *_enum = type->asUMLEnum(); UMLClassifierListItemList enumLiterals = _enum->getFilteredList(UMLObject::ot_EnumLiteral); if (index < enumLiterals.size()) a->setInitialValue(enumLiterals.at(index)->name()); } } else { a->setInitialValue(constraints.defaultValue); } a->setValues(fieldType.at(1)); a->setAutoIncrement(constraints.autoIncrement); if (constraints.primaryKey) { UMLUniqueConstraint *pkey = new UMLUniqueConstraint(a, a->name() + QLatin1String("_pkey")); entity->setAsPrimaryKey(pkey); } else if (constraints.uniqueKey) { UMLUniqueConstraint *uc = new UMLUniqueConstraint(a, a->name() + QLatin1String("_unique")); entity->addConstraint(uc); } QStringList attributes; if (!constraints.characterSet.isEmpty()) attributes.append(QLatin1String("CHARACTER SET ") + constraints.characterSet); if (!constraints.collate.isEmpty()) attributes.append(QLatin1String("COLLATE ") + constraints.collate); if (attributes.size() > 0) a->setAttributes(attributes.join(QLatin1String(" "))); entity->addEntityAttribute(a); } else if (!entity) { uError() << "Could not add field '" << fieldName << "' because of zero entity."; } if (token == QLatin1String(",")) continue; else if (token == QLatin1String(")")) break; } token = advance(); return true; } /** * Parse create table statement. * * @param token string with current token * @return true on success * @return false on error */ bool SQLImport::parseCreateTable(QString &token) { bool returnValue = true; QString tableName = parseIdentifier(token); DEBUG(DBG_SRC) << "parsing create table" << tableName; UMLFolder *folder = UMLApp::app()->document()->rootFolder(Uml::ModelType::EntityRelationship); UMLObject *o = Import_Utils::createUMLObject(UMLObject::ot_Entity, tableName, folder, m_comment); UMLEntity *entity = o->asUMLEntity(); m_comment.clear(); if (token.toLower() == QLatin1String("as")) { skipStmt(QLatin1String(";")); return false; } else if (token == QLatin1String("(")) { parseCreateDefinition(token, entity); } else { skipStmt(QLatin1String(";")); return false; } if (token.toLower() == QLatin1String("inherits")) { token = advance(); // ( const QString &baseTable = advance(); token = advance(); // ) UMLObject *b = Import_Utils::createUMLObject(UMLObject::ot_Entity, baseTable, folder, m_comment); UMLAssociation *a = new UMLAssociation(Uml::AssociationType::Generalization, o, b); if (entity) entity->addAssocToConcepts(a); else { uError() << "Could not add generalization '" << baseTable << "' because of zero entity."; returnValue = false; } } skipStmt(QLatin1String(";")); return returnValue; } /** * Parse alter table statement. * * @param token string with current token * @return true on success * @return false on error */ bool SQLImport::parseAlterTable(QString &token) { if (token.toLower() == QLatin1String("only")) token = advance(); QString tableName = token; token = advance(); if (token == QLatin1String(".")) { tableName += token; token = advance(); if (token.contains(QLatin1String("\""))) token.replace(QLatin1String("\""), QLatin1String("")); tableName += token; token = advance(); } if (token.toLower() == QLatin1String("add")) { token = advance(); if (token.toLower() == QLatin1String("constraint")) { const QString &constraintName = advance(); token = advance(); UMLFolder *folder = UMLApp::app()->document()->rootFolder(Uml::ModelType::EntityRelationship); UMLObject *o = UMLApp::app()->document()->findUMLObject(tableName, UMLObject::ot_Entity, folder); if (token.toLower() == QLatin1String("primary")) { token = advance(); // key token = advance(); const QStringList &fieldNames = parseIdentifierList(token); if (!o) { // report error } UMLEntity *entity = o->asUMLEntity(); if (!addPrimaryKey(entity, constraintName, fieldNames)) { ; // reporter error } } else if (token.toLower() == QLatin1String("unique")) { token = advance(); const QStringList &fieldNames = parseIdentifierList(token); if (!o) { // report error } UMLEntity *entity = o->asUMLEntity(); if (!addUniqueConstraint(entity, constraintName, fieldNames)) { ; // report error } } // FOREIGN KEY () REFERENCES () else if (token.toLower() == QLatin1String("foreign")) { token = advance(); // key token = advance(); const QStringList &localFieldNames = parseIdentifierList(token); token = advance(); // references const QString &referencedTableName = parseIdentifier(token); const QStringList &referencedFieldNames = parseIdentifierList(token); // ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED; // use parseColumnConstraint() if (token.toLower() == QLatin1String("on")) { token = advance(); token = advance(); // delete/update if (token.toLower() == QLatin1String("cascade")) token = advance(); } else if (token.toLower() == QLatin1String("match")) { token = advance(); token = advance(); // full } if (!o) { // report error } UMLEntity *entity = o->asUMLEntity(); if (!addForeignConstraint(entity, constraintName, localFieldNames, referencedTableName, referencedFieldNames)) { ; // report error } } } } else skipStmt(QLatin1String(";")); return true; } /** * Implement abstract operation from NativeImportBase. */ bool SQLImport::parseStmt() { const QString& keyword = m_source[m_srcIndex]; if (keyword.toLower() == QLatin1String("set")) { skipStmt(QLatin1String(";")); return true; } // CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] else if (keyword.toLower() == QLatin1String("create")) { QString type = advance(); // [ GLOBAL | LOCAL ] if (type.toLower() == QLatin1String("global")) type = advance(); else if (type.toLower() == QLatin1String("local")) type = advance(); // [ { TEMPORARY | TEMP } | UNLOGGED ] if (type.toLower() == QLatin1String("temp")) type = advance(); else if (type.toLower() == QLatin1String("temporary")) type = advance(); if (type.toLower() == QLatin1String("unlogged")) type = advance(); // TABLE if (type.toLower() == QLatin1String("table")) { QString token = advance(); // [ IF NOT EXISTS ] if (token.toLower() == QLatin1String("if")) { token = advance(); token = advance(); token = advance(); } return parseCreateTable(token); } else if (m_source[m_srcIndex] != QLatin1String(";")) { skipStmt(QLatin1String(";")); return true; } } else if (keyword.toLower() == QLatin1String("alter")) { QString type = advance(); if (type.toLower() == QLatin1String("table")) { QString token = advance(); return parseAlterTable(token); } else if (m_source[m_srcIndex] != QLatin1String(";")) { skipStmt(QLatin1String(";")); return true; } } return true; } /** * Implement virtual method * @return string with next token */ QString SQLImport::advance() { QString token = NativeImportBase::advance(); DEBUG(DBG_SRC) << m_srcIndex << token; return token; } UMLObject *SQLImport::addDatatype(const QStringList &type) { UMLObject *datatype = 0; UMLPackage *parent = UMLApp::app()->document()->datatypeFolder(); if (type.at(0).toLower() == QLatin1String("enum")) { QString name = Model_Utils::uniqObjectName(UMLObject::ot_Enum, parent, type.at(0)); datatype = Import_Utils::createUMLObject(UMLObject::ot_Enum, name, parent); UMLEnum *enumType = datatype->asUMLEnum(); if (enumType == 0) enumType = Import_Utils::remapUMLEnum(datatype, currentScope()); if (enumType) { for (int i = 2; i < type.size(); i++) { Import_Utils::addEnumLiteral(enumType, type.at(i)); } } else { uError() << "Invalid dynamic cast to UMLEnum from datatype."; } } else { datatype = Import_Utils::createUMLObject(UMLObject::ot_Datatype, type.at(0), parent); } return datatype; } bool SQLImport::addPrimaryKey(UMLEntity *entity, const QString &_name, const QStringList &fields) { if (!entity) { uError() << "Could not add primary key '" << _name << "' because of zero entity."; return false; } QString name; if (_name.isEmpty()) name = entity->name() + QLatin1String("_pkey"); else name = _name; foreach(UMLObject *a, entity->getFilteredList(UMLObject::ot_EntityConstraint)) { if (a->name() == name) return false; } UMLUniqueConstraint *pkey = new UMLUniqueConstraint(entity, name); foreach(const QString &field, fields) { foreach(UMLEntityAttribute *a, entity->getEntityAttributes()) { if (a->name() == field) pkey->addEntityAttribute(a); } } // update list view item to see 'P' bool state = UMLApp::app()->document()->loading(); UMLApp::app()->document()->setLoading(false); bool result = entity->setAsPrimaryKey(pkey); UMLApp::app()->document()->setLoading(state); return result; } /** * Add UML object for unique constraint. * * @param entity entity object * @param _name unique constraint name * @param fields field list * @return true on success * @return false on error */ bool SQLImport::addUniqueConstraint(UMLEntity *entity, const QString &_name, const QStringList &fields) { if (!entity) { uError() << "Could not add unique constraint '" << _name << "' because of zero entity."; return false; } QString name; if (_name.isEmpty()) name = entity->name() + QLatin1String("_unique"); else name = _name; foreach(UMLObject *a, entity->getFilteredList(UMLObject::ot_EntityConstraint)) { if (a->name() == name) return false; } UMLUniqueConstraint *uc = new UMLUniqueConstraint(entity, name); foreach(const QString &field, fields) { foreach(UMLEntityAttribute *a, entity->getEntityAttributes()) { if (a->name() == field) uc->addEntityAttribute(a); } } return entity->addConstraint(uc); } /** * Add UML object foreign constraint. * * @param entityA entity object the foreign constraint belongs * @param _name name of foreign constraint * @param fieldNames list of field names * @param referencedTable referenced table name * @param referencedFields list of referenced field names * @return true on success * @return false on error */ bool SQLImport::addForeignConstraint(UMLEntity *entityA, const QString &_name, const QStringList &fieldNames, const QString &referencedTable, const QStringList &referencedFields) { if (!entityA) { uError() << "Could not add foreign constraint '" << _name << "' because of zero entity."; return false; } QString name; if (_name.isEmpty()) name = entityA->name() + QLatin1String("_foreign"); else name = _name; foreach(UMLObject *a, entityA->getFilteredList(UMLObject::ot_EntityConstraint)) { if (a->name() == name) return false; } UMLFolder *root = UMLApp::app()->document()->rootFolder(Uml::ModelType::EntityRelationship); UMLObject *o = UMLApp::app()->document()->findUMLObject(referencedTable, UMLObject::ot_Entity, root); UMLEntity *entityB = o->asUMLEntity(); if (!entityB) return false; UMLForeignKeyConstraint *fc = new UMLForeignKeyConstraint(entityA, name); if (fieldNames.size() != referencedFields.size()) { return false; } fc->setReferencedEntity(entityB); for(int i = 0; i < fieldNames.size(); i++) { const QString &fieldA = fieldNames.at(i); const QString &fieldB = referencedFields.at(i); UMLEntityAttribute *aA = 0; UMLEntityAttribute *aB = 0; foreach(UMLEntityAttribute *a, entityA->getEntityAttributes()) { if (a->name() == fieldA) { aA = a; break; } } foreach(UMLEntityAttribute *a, entityB->getEntityAttributes()) { if (a->name() == fieldB) { aB = a; break; } } if (!aA || !aB) return false; fc->addEntityAttributePair(aA, aB); } return entityA->addConstraint(fc); } diff --git a/umbrello/codeimpwizard/codeimpthread.cpp b/umbrello/codeimpwizard/codeimpthread.cpp index 45f220b0c..7c0e5afef 100644 --- a/umbrello/codeimpwizard/codeimpthread.cpp +++ b/umbrello/codeimpwizard/codeimpthread.cpp @@ -1,117 +1,117 @@ /* Copyright 2011 Andi Fischer 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "codeimpthread.h" // app includes #include "classimport.h" // kde includes #include #include /** * Constructor. * @param file File to import for which the thread shall be spawned */ CodeImpThread::CodeImpThread(QFileInfo file, QObject* parent) : QObject(parent), m_file(file) { connect(this, SIGNAL(askQuestion(QString,int)), this, SLOT(questionAsked(QString,int))); } /** * Destructor. */ CodeImpThread::~CodeImpThread() { } /** * Thread run method. */ void CodeImpThread::run() { ClassImport *classImporter = ClassImport::createImporterByFileExt(m_file.fileName(), this); QString fileName = m_file.absoluteFilePath(); if (classImporter) { emit messageToLog(m_file.fileName(), QLatin1String("start import...")); emit messageToWiz(m_file.fileName(), QLatin1String("started")); emit messageToApp(i18n("Importing file: %1", fileName)); // FIXME: ClassImport still uses umldoc->writeToStatusBar for log writing if (!classImporter->importFile(fileName)) { emit messageToApp(i18nc("show failed on status bar", "Failed.")); emit messageToWiz(m_file.fileName(), QString()); emit messageToLog(m_file.fileName(), QLatin1String("...import failed")); emit finished(false); } else { emit messageToApp(i18nc("show Ready on status bar", "Ready.")); emit messageToWiz(m_file.fileName(), QLatin1String("finished")); emit messageToLog(m_file.fileName(), QLatin1String("...import finished")); emit finished(true); } delete classImporter; } else { emit messageToWiz(m_file.fileName(), QLatin1String("aborted")); emit messageToApp(i18n("No code importer for file: %1", fileName)); emit aborted(); } } /** * Emit a signal to the main gui thread to show a question box. * @param question the text of the question - * @return the code of the answer button @ref KMessageBox::ButtonCode + * @return the code of the answer button KMessageBox::ButtonCode */ int CodeImpThread::emitAskQuestion(const QString& question) { int buttonCode = 0; //QMutexLocker locker(&m_mutex); emit askQuestion(question, buttonCode); //m_waitCondition.wait(&m_mutex); return buttonCode; } /** * Emit a signal to the main gui thread to write a log text to the log widget. * @param file the file that is in work * @param text the text which has to be added to the log widget */ void CodeImpThread::emitMessageToLog(const QString& file, const QString& text) { emit messageToLog(file, text); } /** * Slot for signal askQuestion. * @param question the question to ask * @param answer the pressed answer button code @ref KMessageBox::ButtonCode */ void CodeImpThread::questionAsked(const QString& question, int& answer) { //QMutexLocker locker(&m_mutex); answer = KMessageBox::questionYesNo(0, question, QLatin1String("Question code import:")); // @todo i18n //m_waitCondition.wakeOne(); } diff --git a/umbrello/diagram_utils.cpp b/umbrello/diagram_utils.cpp index 601b26e59..f923b6467 100644 --- a/umbrello/diagram_utils.cpp +++ b/umbrello/diagram_utils.cpp @@ -1,515 +1,517 @@ /*************************************************************************** * 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) 2017 * * Umbrello UML Modeller Authors * ***************************************************************************/ // own header #include "diagram_utils.h" // app includes #include "associationwidget.h" #include "association.h" #include "debug_utils.h" #include "import_utils.h" #include "messagewidget.h" #include "object_factory.h" #include "objectwidget.h" #include "uml.h" #include "umldoc.h" #include "umlobject.h" #include "umlscene.h" #include "widget_factory.h" //// qt includes #include #include #include #include DEBUG_REGISTER_DISABLED(Diagram_Utils) #undef DBG_SRC #define DBG_SRC QLatin1String("Diagram_Utils") namespace Diagram_Utils { /** * Detect sequence line format * @param lines * @return */ SequenceLineFormat detectSequenceLineFormat(const QStringList &lines) { QStringList l = lines; while(l.size() > 0) { QStringList cols = l.takeFirst().split(QRegExp(QLatin1String("\\s+")),QString::SkipEmptyParts); if (cols.size() < 1) continue; if (cols[0] == QLatin1String("#")) { continue; } /* * #0 0x000000000050d0b0 in Import_Utils::importStackTrace(QString const&, UMLScene*) (fileName=..., scene=scene@entry=0x12bd0f0) */ if (cols.size() > 2 && cols[0].startsWith(QLatin1String("#"))) return GDB; /* * 6 Driver::ParseHelper::ParseHelper driver.cpp 299 0x634c44 */ else if (cols[cols.size()-1].startsWith(QLatin1String("0x"))) return QtCreatorGDB; /* * FloatingTextWidget::setPreText */ else if (cols[cols.size()-1].contains(QLatin1String("::"))) return Simple; else return Invalid; } return Invalid; } /** * Helper function to parse sequence line. The supported formats are: * * @param s string to parse * @param sequence return of sequence number * @param package return of package * @param method return of method * @return true line could be parsed and return variable has been filled * @return false line could not be parsed, no return variable has been filled */ bool parseSequenceLine(const QString &s, QString &sequence, QString &package, QString &method, QString &error) { QString identifier; QString module; QStringList cols = s.split(QRegExp(QLatin1String("\\s+")),QString::SkipEmptyParts); if (cols.size() < 1) { error = QLatin1String("could not parse"); return false; } // skip comments if (cols[0] == QLatin1String("#")) { return false; } /* gdb * #0 0x000000000050d0b0 in Import_Utils::importStackTrace(QString const&, UMLScene*) (fileName=..., scene=scene@entry=0x12bd0f0) * #25 0x00007fffefe1670c in g_main_context_iteration () from /usr/lib64/libglib-2.0.so.0 * #0 Import_Utils::importStackTrace (fileName=..., scene=scene@entry=0x137c000) at /home/ralf.habacker/src/umbrello-3/umbrello/codeimport/import_utils.cpp:715 */ if (cols.size() > 2 && cols[0].startsWith(QLatin1String("#"))) { QString file; sequence = cols.takeFirst(); if (cols[cols.size()-2] == QLatin1String("at")) { file = cols.takeLast(); cols.takeLast(); } else if (cols[cols.size()-2] == QLatin1String("from")) { module = cols.takeLast(); cols.takeLast(); } if (cols[1] == QLatin1String("in")) { cols.takeFirst(); // remove address cols.takeFirst(); // in } identifier = cols.join(QLatin1String(" ")); if (identifier.contains(QLatin1String("::"))) { QStringList b = identifier.split( QLatin1String("::")); // TODO handle multiple '::' package = b.takeFirst(); method = b.join(QLatin1String("::")); } else { method = identifier; package = module; } return true; } /** * Qtcreator/gdb + * @verbatim * 6 Driver::ParseHelper::ParseHelper driver.cpp 299 0x634c44 * 31 g_main_context_dispatch /usr/lib64/libglib-2.0.so.0 0x7fffefe16316 * ignoring * ... 0x7ffff41152d9 * 13 ?? 0x7ffff41152d9 + * @endverbatim */ else if (cols[cols.size()-1].startsWith(QLatin1String("0x"))) { if (cols[0] == QLatin1String("...") || cols[1] == QLatin1String("??")) return false; sequence = cols.takeFirst(); cols.takeLast(); // remove address QString line, file; if (cols.size() == 2) { module = cols.takeLast(); identifier = cols.join(QLatin1String(" ")); } else if (cols.size() > 2) { line = cols.takeLast(); file = cols.takeLast(); identifier = cols.join(QLatin1String(" ")); } if (identifier.contains(QLatin1String("::"))) { QStringList b = identifier.split( QLatin1String("::")); method = b.takeLast(); package = b.join(QLatin1String("::")); } else { method = identifier; package = module; } if (package.isEmpty() && !file.isEmpty()) package = file; if (!method.endsWith(QLatin1String(")"))) method.append(QLatin1String("()")); return true; } else if (cols[cols.size()-1].contains(QLatin1String("::"))) { QStringList b = s.split( QLatin1String("::")); method = b.takeLast(); package = b.join(QLatin1String("::")); return true; } error = QLatin1String("unsupported line format"); return false; } /** * Import sequence diagram entries from a string list. * * @param lines String list with sequences * @param scene The diagram to import the sequences into. * @param fileName The filename the sequences are imported from * @return true Import was successful. * @return false Import failed. */ bool importSequences(const QStringList &lines, UMLScene *scene, const QString &fileName) { // object widget cache map QMap objectsMap; // create "client widget" UMLDoc *umldoc = UMLApp::app()->document(); QString name(QLatin1String("client")); UMLObject *left = umldoc->findUMLObject(name, UMLObject::ot_Class); if (!left ) { left = new UMLObject(0, name); left->setBaseType(UMLObject::ot_Class); } ObjectWidget *leftWidget = (ObjectWidget *)Widget_Factory::createWidget(scene, left); leftWidget->activate(); // required to be savable scene->addWidgetCmd(leftWidget); objectsMap[name] = leftWidget; ObjectWidget *rightWidget = 0; ObjectWidget *mostRightWidget = leftWidget; MessageWidget *messageWidget = 0; // for further processing MessageWidgetList messages; QStringList l; SequenceLineFormat format = detectSequenceLineFormat(lines); if (format == GDB || format == QtCreatorGDB) foreach(const QString &s, lines) l.push_front(s); else l = lines; // for each line int index = 1; foreach(const QString &line, l) { QString stackframe, package, method, error; if (!parseSequenceLine(line, stackframe, package, method, error)) { if (!error.isEmpty()) { QString item = QString::fromLatin1("%1:%2:%3: %4: %5") .arg(fileName).arg(index) .arg(1).arg(line).arg(error); UMLApp::app()->logWindow()->addItem(item); } continue; } bool createObject = false; if (package.contains(method)) createObject = true; if (package.isEmpty()) package = QLatin1String("unknown"); // get or create right object widget if (objectsMap.contains(package)) { rightWidget = objectsMap[package]; } else { UMLObject *right = umldoc->findUMLObject(package, UMLObject::ot_Class); if (!right) right = umldoc->findUMLObject(package, UMLObject::ot_Package); if (!right) { right = Object_Factory::createUMLObject(UMLObject::ot_Class, package); } rightWidget = (ObjectWidget *)Widget_Factory::createWidget(scene, right); rightWidget->setX(mostRightWidget->x() + mostRightWidget->width() + 10); rightWidget->activate(); objectsMap[package] = rightWidget; scene->addWidgetCmd(rightWidget); mostRightWidget = rightWidget; } // create message int y = 10 + (messageWidget ? messageWidget->y() + messageWidget->height() : 20); messageWidget = new MessageWidget(scene, leftWidget, rightWidget, y, createObject ? Uml::SequenceMessage::Creation : Uml::SequenceMessage::Synchronous); messageWidget->setCustomOpText(method); messageWidget->setSequenceNumber(QString::number(index++)); messageWidget->calculateWidget(); messageWidget->activate(); messageWidget->setY(y); // to make it savable scene->addWidgetCmd(messageWidget); messages.insert(0, messageWidget); leftWidget = rightWidget; } if (messages.isEmpty()) return false; // adjust vertical position foreach(MessageWidget *w, messages) { w->setY(w->y() + 20); } // adjust heights starting from the last message MessageWidget *previous = messages.takeFirst(); foreach(MessageWidget *w, messages) { w->setSize(w->width(), previous->y() - w->y() + previous->height() + 5); // adjust vertical line length of object widgets w->objectWidget(Uml::RoleType::A)->slotMessageMoved(); w->objectWidget(Uml::RoleType::B)->slotMessageMoved(); previous = w; } return true; } /** * Import sequence diagram entries from a string list. * * @param lines String list with sequences * @param scene The diagram to import the sequences into. * @return true Import was successful. * @return false Import failed. */ bool importGraph(const QStringList &lines, UMLScene *scene, const QString &fileName) { if (scene->type() == Uml::DiagramType::Sequence) return importSequences(lines, scene); else if (scene->type() != Uml::DiagramType::Class) return false; UMLDoc *umldoc = UMLApp::app()->document(); UMLWidget *lastWidget = 0; UMLClassifier *c = 0; QString methodIdentifier(QLatin1String("()")); QMap> widgetList; int lineNumber = 0; // for each line foreach(const QString &line, lines) { lineNumber++; if (line.trimmed().isEmpty() || line.startsWith(QLatin1Char('#')) || line.startsWith(QLatin1String("//"))) continue; QStringList l = line.split(QLatin1String(" ")); if (l.size() == 1) { UMLObject *o = umldoc->findUMLObject(l[0], UMLObject::ot_Class); if (!o) o = Object_Factory::createUMLObject(UMLObject::ot_Class, l[0]); c = o->asUMLClassifier(); // TODO: avoid multiple inserts UMLWidget *w = Widget_Factory::createWidget(scene, o); if (lastWidget) w->setX(lastWidget->x() + lastWidget->width() + 10); scene->setupNewWidget(w, false); scene->createAutoAssociations(w); scene->createAutoAttributeAssociations2(w); widgetList[l[0]] = w; lastWidget = w; } else if (l.size() == 3 && l[1].startsWith(QLatin1Char('-'))) { // associations UMLObject *o1 = umldoc->findUMLObject(l[0], UMLObject::ot_Class); if (!o1) o1 = Object_Factory::createUMLObject(UMLObject::ot_Class, l[0]); UMLObject *o2 = umldoc->findUMLObject(l[2], UMLObject::ot_Class); if (!o2) o2 = Object_Factory::createUMLObject(UMLObject::ot_Class, l[2]); bool swapObjects = false; Uml::AssociationType::Enum type = Uml::AssociationType::Unknown; UMLAssociation *assoc = 0; bool newAssoc = false; if (l[1] == QLatin1String("---")) { type = Uml::AssociationType::Association; } else if (l[1] == QLatin1String("-->")) { type = Uml::AssociationType::UniAssociation; } else if (l[1] == QLatin1String("-<>")) { type = Uml::AssociationType::Aggregation; swapObjects = true; } else if (l[1] == QLatin1String("--*")) { type = Uml::AssociationType::Composition; swapObjects = true; } else if (l[1] == QLatin1String("-|>")) { type = Uml::AssociationType::Generalization; } QPointer w1 = 0; QPointer w2 = 0; bool error = false; if (swapObjects) { w1 = widgetList[l[2]]; w2 = widgetList[l[0]]; if (w1 && w2) { if (!assoc) { assoc = umldoc->findAssociation(type, o1, o2); if (!assoc) { assoc = new UMLAssociation(type, o1, o2); newAssoc = true; } } } else error = true; } else { w1 = widgetList[l[0]]; w2 = widgetList[l[2]]; if (w1 && w2) { if (!assoc) { assoc = umldoc->findAssociation(type, o2, o1); if (!assoc) { assoc = new UMLAssociation(type, o2, o1); newAssoc = true; } } } else error = true; } if (!error) { if (newAssoc) { assoc->setUMLPackage(umldoc->rootFolder(Uml::ModelType::Logical)); umldoc->addAssociation(assoc); } AssociationWidget* aw = AssociationWidget::create(scene, w1, type, w2, assoc); scene->addAssociation(aw); } else { if (assoc) delete assoc; QString item = QString::fromLatin1("%1:%2:%3: %4: %5") .arg(fileName).arg(lineNumber) .arg(1).arg(line).arg(QLatin1String("error:could not add association")); UMLApp::app()->logWindow()->addItem(item); } } else if (l[0].isEmpty() && c && l.size() == 2) { QString name = l.last(); if (name.contains(methodIdentifier)) { name.remove(methodIdentifier); UMLOperation *m = Import_Utils::makeOperation(c, name); Import_Utils::insertMethod(c, m, Uml::Visibility::Public, QLatin1String("void"), false, false); } else { Import_Utils::insertAttribute(c, Uml::Visibility::Public, name, QLatin1String("int")); } } else if (l[0].isEmpty() && c && l.size() >= 3) { QString name = l.takeLast(); l.takeFirst(); QString v = l.first().toLower(); Uml::Visibility::Enum visibility = Uml::Visibility::fromString(v, true); if (visibility == Uml::Visibility::Unknown) visibility = Uml::Visibility::Public; else l.takeFirst(); QString type = l.join(QLatin1String(" ")); if (name.contains(methodIdentifier)) { name.remove(methodIdentifier); UMLOperation *m = Import_Utils::makeOperation(c, name); Import_Utils::insertMethod(c, m, visibility, type, false, false); } else { Import_Utils::insertAttribute(c, visibility, name, type); } } else { QString item = QString::fromLatin1("%1:%2:%3: %4: %5") .arg(fileName).arg(lineNumber) .arg(1).arg(line).arg(QLatin1String("syntax error")); UMLApp::app()->logWindow()->addItem(item); } } return true; } /** * Import graph entries from clipboard * * @param mimeData instance of mime data to import from * @param scene The diagram to import the graph into. * @return true Import successful. * @return false Import failed. */ bool importGraph(const QMimeData* mimeData, UMLScene *scene) { QString requestedFormat = QLatin1String("text/plain"); if (!mimeData->hasFormat(requestedFormat)) return false; QByteArray payload = mimeData->data(requestedFormat); if (!payload.size()) { return false; } QString data = QString::fromUtf8(payload); QStringList lines = data.split(QLatin1String("\n")); UMLDoc *doc = UMLApp::app()->document(); doc->beginPaste(); bool result = false; if (scene->type() == Uml::DiagramType::Sequence) result = importSequences(lines, scene); else result = importGraph(lines, scene); doc->endPaste(); return result; } /** * Import graph entries from file * * @param fileName filename to import the graph from. * @param scene The diagram to import the graph into. * @return true Import successful. * @return false Import failed. */ bool importGraph(const QString &fileName, UMLScene *scene) { QFile file(fileName); if(!file.open(QIODevice::ReadOnly)) return false; QStringList lines; QTextStream in(&file); in.setCodec("UTF-8"); while (!in.atEnd()) { lines.append(in.readLine()); } return importGraph(lines, scene, fileName); } } // end namespace Diagram_Utils diff --git a/umbrello/dialogs/dontaskagain.h b/umbrello/dialogs/dontaskagain.h index 24ce38748..3886eb51d 100644 --- a/umbrello/dialogs/dontaskagain.h +++ b/umbrello/dialogs/dontaskagain.h @@ -1,116 +1,116 @@ /*************************************************************************** * 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) 2018 * * Umbrello UML Modeller Authors * ***************************************************************************/ #ifndef DONTASKAGAIN_H #define DONTASKAGAIN_H // Qt includes #include #include #include // KDE includes #include class QVBoxLayout; /** * The DontAskAgainItem class holds a 'dont ask again' item, * see @ref DontAskAgainHandler for details. * * @author Ralf Habacker */ class DontAskAgainItem { public: DontAskAgainItem(const QString &name); virtual ~DontAskAgainItem(); QString &name(); virtual QString text() const = 0; bool isAll(); bool isEnabled(); void setEnabled(bool state = true); protected: QString m_name; }; #define DefineDontAskAgainItem(name,key,_text) \ class DontAskAgainItem##name : public DontAskAgainItem \ { \ public: \ DontAskAgainItem##name() : DontAskAgainItem(QLatin1String(key)) {} \ virtual QString text() const { return _text; } \ } name; /** * The DontAskAgainWidget provides a graphical user interface * to change 'dont ask again' settings and could be embedded * in dialogs or dialog pages. * * After adding the widget to a dialog call @ref apply() to apply * changed values and call @ref setDefaults() to reset changes to * default values, which is all messages enabled. * * The widget is designed as group box with embedded check boxes. * * @author Ralf Habacker */ class DontAskAgainWidget : public QWidget { Q_OBJECT public: DontAskAgainWidget(QList &items, QWidget *parent = 0); bool apply(); void setDefaults(); protected: void addItem(DontAskAgainItem *item); QVBoxLayout *m_layout; QList &m_items; protected slots: void slotToggled(bool state); }; /** * Dialogs provided by namespace KMessageBox support a feature to hide dialogs on user request * by specifying the parameter dontAskAgainName, which adds a checkbox named "Don't ask again" * to the related dialog. * * What is currently missing in KMessageBox namespace and therefore provided by class * DontAskAgainHandler, is a widget to reenable or disable dialogs using the "Don't ask again" * support in an application setting dialog. * * To use this support call macro @ref DefineDontAskAgainItem and add a call to method - * @ref DontAskAgainItem::name as parameter @ref dontAskAgainName to related class KMessageBox + * DontAskAgainItem::name as parameter @ref dontAskAgainName to related class KMessageBox * methods. See the following example: * * \code{.cpp} * DefineDontAskAgainItem(aDontAskAgainItem, "delete-diagram", i18n("Enable 'delete diagram' related messages")); * ... * return KMessageBox::warningContinueCancel(..., aDontAskAgainItem.name()) * \endcode * * To add the mentioned widget to a settings dialog call @ref createWidget() and * add the result to a dialog layout. * * @author Ralf Habacker */ class DontAskAgainHandler { public: void addItem(DontAskAgainItem *item); static DontAskAgainHandler &instance(); DontAskAgainWidget *createWidget(); protected: QList m_items; }; #endif // DONTASKAGAIN_H diff --git a/umbrello/dialogs/messagewidgetpropertiesdialog.cpp b/umbrello/dialogs/messagewidgetpropertiesdialog.cpp index c14aca874..7c3b8fb91 100644 --- a/umbrello/dialogs/messagewidgetpropertiesdialog.cpp +++ b/umbrello/dialogs/messagewidgetpropertiesdialog.cpp @@ -1,61 +1,60 @@ /*************************************************************************** * 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) 2018 * * Umbrello UML Modeller Authors * ***************************************************************************/ // own header #include "messagewidgetpropertiesdialog.h" // local includes #include "messagewidget.h" #include "uml.h" /** * Sets up an Message Widget Properties Dialog. * @param parent The parent of the dialog * @param widget The Message Widget to display properties of. - * @param pageNum The page to show first. */ MessageWidgetPropertiesDialog::MessageWidgetPropertiesDialog (QWidget *parent, MessageWidget * widget) : MultiPageDialogBase(parent), m_widget(widget) { setupPages(); connect(this, SIGNAL(okClicked()), this, SLOT(slotOk())); connect(this, SIGNAL(applyClicked()), this, SLOT(slotApply())); } /** * Standard destructor. */ MessageWidgetPropertiesDialog::~MessageWidgetPropertiesDialog() { } void MessageWidgetPropertiesDialog::slotOk() { slotApply(); accept(); } void MessageWidgetPropertiesDialog::slotApply() { MultiPageDialogBase::apply(); if (m_widget) { applyFontPage(m_widget); } } void MessageWidgetPropertiesDialog::setupPages() { setupGeneralPage(m_widget); setupStylePage(m_widget); setupFontPage(m_widget); } diff --git a/umbrello/dialogs/multipagedialogbase.cpp b/umbrello/dialogs/multipagedialogbase.cpp index 56bb290d3..fa2d8efcf 100644 --- a/umbrello/dialogs/multipagedialogbase.cpp +++ b/umbrello/dialogs/multipagedialogbase.cpp @@ -1,528 +1,528 @@ /*************************************************************************** * 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) 2012-2014 * * Umbrello UML Modeller Authors * ***************************************************************************/ // own header #include "multipagedialogbase.h" // local includes #include "associationgeneralpage.h" #include "associationrolepage.h" #include "associationwidget.h" #include "debug_utils.h" #include "icon_utils.h" #include "notepage.h" #include "messagewidget.h" #include "uml.h" #include "selectoperationpage.h" #include "umlwidget.h" #include "umlwidgetstylepage.h" #include #if QT_VERSION >=0x050000 #include #endif #include #include #include #include // qt includes #include #include #include #include #include #include #include DEBUG_REGISTER(MultiPageDialogBase) /** * Constructor */ #if QT_VERSION >= 0x050000 MultiPageDialogBase::MultiPageDialogBase(QWidget *parent, bool withDefaultButton) : QWidget(parent), m_pAssocGeneralPage(0), m_notePage(0), m_operationGeneralPage(0), m_pRolePage(0), m_fontChooser(0), m_pStylePage(0), m_pageItem(0), m_pageDialog(0), m_pageWidget(0), m_useDialog(!parent || strcmp(parent->metaObject()->className(),"PropertiesWindow") != 0), m_isModified(false) { if (m_useDialog) { m_pageDialog = new KPageDialog(parent); m_pageDialog->setModal(true); m_pageDialog->setFaceType(KPageDialog::List); m_pageDialog->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel | QDialogButtonBox::Help); QDialogButtonBox * dlgButtonBox = m_pageDialog->findChild(QLatin1String("buttonbox")); if (withDefaultButton) { QPushButton *defaultButton = new QPushButton(i18n("Default")); m_pageDialog->addActionButton(defaultButton); connect(defaultButton, SIGNAL(clicked()), this, SLOT(slotDefaultClicked())); } connect(dlgButtonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(slotButtonClicked(QAbstractButton*))); } else { m_pageWidget = new KPageWidget(this); m_pageWidget->setFaceType(KPageView::Tree); } } #else MultiPageDialogBase::MultiPageDialogBase(QWidget *parent, bool withDefaultButton) : QWidget(parent), m_pAssocGeneralPage(0), m_notePage(0), m_operationGeneralPage(0), m_pRolePage(0), m_fontChooser(0), m_pStylePage(0), m_pageItem(0), m_pageDialog(0), m_pageWidget(0), m_useDialog(!parent || strcmp(parent->metaObject()->className(),"PropertiesWindow") != 0), m_isModified(false) { if (m_useDialog) { m_pageDialog = new KPageDialog(parent); KDialog::ButtonCodes buttons = KDialog::Ok | KDialog::Apply | KDialog::Cancel | KDialog::Help; if (withDefaultButton) buttons |= KDialog::Default; m_pageDialog->setButtons(buttons); m_pageDialog->setDefaultButton(KDialog::Ok); m_pageDialog->showButtonSeparator(true); m_pageDialog->setFaceType(KPageDialog::List); m_pageDialog->setModal(true); m_pageDialog->setHelp(QString::fromLatin1("umbrello/index.html"), QString()); connect(m_pageDialog, SIGNAL(okClicked()), this, SLOT(slotOkClicked())); connect(m_pageDialog, SIGNAL(applyClicked()), this, SLOT(slotApplyClicked())); connect(m_pageDialog, SIGNAL(defaultClicked()), this, SLOT(slotDefaultClicked())); } else { m_pageWidget = new KPageWidget(this); m_pageWidget->setFaceType(KPageView::Tree); } } #endif MultiPageDialogBase::~MultiPageDialogBase() { delete m_pageDialog; delete m_pageWidget; } void MultiPageDialogBase::slotEnableButtonOk(bool state) { #if QT_VERSION >= 0x050000 m_pageDialog->button(QDialogButtonBox::Ok)->setEnabled(state); m_pageDialog->button(QDialogButtonBox::Apply)->setEnabled(state); #else m_pageDialog->button(KDialog::Ok)->setEnabled(state); m_pageDialog->button(KDialog::Apply)->setEnabled(state); #endif } /** * Apply all used pages */ void MultiPageDialogBase::apply() { if (m_pAssocGeneralPage) m_pAssocGeneralPage->apply(); if (m_notePage) m_notePage->apply(); if (m_operationGeneralPage) m_operationGeneralPage->apply(); if (m_pRolePage) { applyAssociationRolePage(); } if (m_pStylePage) { applyStylePage(); } //TODO include applying font settings data } void MultiPageDialogBase::setCaption(const QString &caption) { #if QT_VERSION >= 0x050000 if (m_pageDialog) m_pageDialog->setWindowTitle(caption); #else if (m_pageDialog) m_pageDialog->setCaption(caption); #endif } void MultiPageDialogBase::accept() { if (m_pageDialog) m_pageDialog->accept(); } void MultiPageDialogBase::reject() { if (m_pageDialog) m_pageDialog->reject(); } KPageWidgetItem *MultiPageDialogBase::currentPage() { if (m_pageDialog) return m_pageDialog->currentPage(); else return m_pageWidget->currentPage(); } void MultiPageDialogBase::addPage(KPageWidgetItem *page) { if (m_pageDialog) m_pageDialog->addPage(page); else m_pageWidget->addPage(page); } /** * Set current page. * * @param page the page to set */ void MultiPageDialogBase::setCurrentPage(KPageWidgetItem *page) { if (m_pageDialog) m_pageDialog->setCurrentPage(page); else m_pageWidget->setCurrentPage(page); } int MultiPageDialogBase::spacingHint() { #if QT_VERSION >= 0x050000 return 0; // FIXME KF5 was QDialog::spacingHint(); #else return KDialog::spacingHint(); #endif } int MultiPageDialogBase::exec() { if (m_pageDialog) return m_pageDialog->exec(); else { return 0; } } /** * Return state if any data has been changed in the dialog. * * @return true data has been changed */ bool MultiPageDialogBase::isModified() { return m_isModified; } /** * Handle click on ok button. */ void MultiPageDialogBase::slotOkClicked() { emit okClicked(); } /** * Handle click on apply button. */ void MultiPageDialogBase::slotApplyClicked() { emit applyClicked(); } /** * Handle click on default button, if enabled in constructor. */ void MultiPageDialogBase::slotDefaultClicked() { emit defaultClicked(); } /** * Launch khelpcenter. */ void MultiPageDialogBase::slotHelpClicked() { DEBUG(DBG_SRC) << "HELP clicked...directly handled"; #if QT_VERSION >= 0x050000 KHelpClient::invokeHelp(QLatin1String("help:/umbrello/index.html"), QLatin1String("umbrello")); #else QUrl url = QUrl(QLatin1String("help:/umbrello/index.html")); QDesktopServices::openUrl(url); #endif } #if QT_VERSION >= 0x050000 /** * Button clicked event handler for the dialog button box. * @param button the button which was clicked */ void MultiPageDialogBase::slotButtonClicked(QAbstractButton *button) { if (button == (QAbstractButton*)m_pageDialog->button(QDialogButtonBox::Apply)) { DEBUG(DBG_SRC) << "APPLY clicked..."; slotApplyClicked(); } else if (button == (QAbstractButton*)m_pageDialog->button(QDialogButtonBox::Ok)) { DEBUG(DBG_SRC) << "OK clicked..."; slotOkClicked(); } else if (button == (QAbstractButton*)m_pageDialog->button(QDialogButtonBox::Cancel)) { DEBUG(DBG_SRC) << "CANCEL clicked..."; } else if (button == (QAbstractButton*)m_pageDialog->button(QDialogButtonBox::Help)) { DEBUG(DBG_SRC) << "HELP clicked..."; slotHelpClicked(); } else { DEBUG(DBG_SRC) << "Button clicked with unhandled role."; } } #endif /** * Handle key press event. * * @param event key press event */ void MultiPageDialogBase::keyPressEvent(QKeyEvent *event) { // Set modified state if any text has been typed in if (event->key() >= Qt::Key_Space && event->key() < Qt::Key_Multi_key) m_isModified = true; QWidget::keyPressEvent(event); } /** * Create a property page * @param name The Text displayed in the page list * @param header The Text displayed above the page * @param icon The icon to display in the page list * @return Pointer to created frame */ QFrame* MultiPageDialogBase::createPage(const QString& name, const QString& header, Icon_Utils::IconType icon) { QFrame* page = new QFrame(); m_pageItem = new KPageWidgetItem(page, name); if (!m_pageWidget) { m_pageItem->setHeader(header); #if QT_VERSION >= 0x050000 m_pageItem->setIcon(QIcon(Icon_Utils::DesktopIcon(icon))); #else m_pageItem->setIcon(KIcon(Icon_Utils::DesktopIcon(icon))); #endif } else m_pageItem->setHeader(QString()); addPage(m_pageItem); //page->setMinimumSize(310, 330); return page; } /** * create new page using a dedicated widget * @param name The Text displayed in the page list * @param header The Text displayed above the page * @param icon The icon to display in the page list * @param widget Widget to display in the page * @return page widget item instance */ KPageWidgetItem *MultiPageDialogBase::createPage(const QString& name, const QString& header, Icon_Utils::IconType icon, QWidget *widget) { QFrame* page = createPage(name, header, icon); QHBoxLayout * topLayout = new QHBoxLayout(page); widget->setParent(page); topLayout->addWidget(widget); return m_pageItem; } /** * Sets up the general settings page. * @param widget The widget to load the initial data from */ void MultiPageDialogBase::setupGeneralPage(AssociationWidget *widget) { QFrame *page = createPage(i18nc("general settings", "General"), i18n("General Settings"), Icon_Utils::it_Properties_General); QHBoxLayout *layout = new QHBoxLayout(page); m_pAssocGeneralPage = new AssociationGeneralPage (page, widget); layout->addWidget(m_pAssocGeneralPage); } /** * Sets up the general page for operations * @param widget The widget to load the initial data from */ KPageWidgetItem *MultiPageDialogBase::setupGeneralPage(MessageWidget *widget) { m_operationGeneralPage = new SelectOperationPage(widget->umlScene()->activeView(), widget->lwClassifier(), widget); connect(m_operationGeneralPage, SIGNAL(enableButtonOk(bool)), this, SLOT(slotEnableButtonOk(bool))); return createPage(i18nc("general settings", "General"), i18n("General Settings"), Icon_Utils::it_Properties_General, m_operationGeneralPage); } /** * Sets up the general settings page. * @param widget The widget to load the initial data from */ void MultiPageDialogBase::setupGeneralPage(NoteWidget *widget) { QFrame *page = createPage(i18nc("general settings", "General"), i18n("General Settings"), Icon_Utils::it_Properties_General); QHBoxLayout *layout = new QHBoxLayout(page); m_notePage = new NotePage (page, widget); layout->addWidget(m_notePage); } /** * Sets up the font selection page. * @param font The font to load the initial data from */ KPageWidgetItem *MultiPageDialogBase::setupFontPage(const QFont &font) { m_fontChooser = new KFontChooser(0, KFontChooser::NoDisplayFlags, QStringList(), 0); m_fontChooser->enableColumn(KFontChooser::StyleList, false); m_fontChooser->setFont(font); return createPage(i18n("Font"), i18n("Font Settings"), Icon_Utils::it_Properties_Font, m_fontChooser); } /** * Sets up the font selection page. * @param widget The widget to load the initial data from */ KPageWidgetItem *MultiPageDialogBase::setupFontPage(UMLWidget *widget) { return setupFontPage(widget->font()); } /** * Sets up the font selection page. * @param widget The widget to load the initial data from */ KPageWidgetItem *MultiPageDialogBase::setupFontPage(AssociationWidget *widget) { return setupFontPage(widget->font()); } /** * Set the font page to show the font from the given widget * @param widget */ void MultiPageDialogBase::resetFontPage(QWidget *widget) { Q_ASSERT(m_fontChooser); m_fontChooser->setFont(widget->font()); } /** * updates the font page data * @param widget Widget to save the font data into */ void MultiPageDialogBase::applyFontPage(AssociationWidget *widget) { Q_ASSERT(m_fontChooser); widget->lwSetFont(m_fontChooser->font()); } void MultiPageDialogBase::applyFontPage(Settings::OptionState *state) { Q_ASSERT(m_fontChooser); state->uiState.font = m_fontChooser->font(); } /** * updates the font page data - * @param widget Widget to save the font data into + * @param scene Scene to save the font data into */ void MultiPageDialogBase::applyFontPage(UMLScene *scene) { Q_ASSERT(m_fontChooser); scene->setFont(m_fontChooser->font(), true); } /** * updates the font page data * @param widget Widget to save the font data into */ void MultiPageDialogBase::applyFontPage(UMLWidget *widget) { Q_ASSERT(m_fontChooser); widget->setFont(m_fontChooser->font()); } /** * Sets up the style page. * @param widget The widget to load the initial data from */ KPageWidgetItem *MultiPageDialogBase::setupStylePage(WidgetBase *widget) { m_pStylePage = new UMLWidgetStylePage(0, widget); return createPage(i18nc("widget style page", "Style"), i18n("Widget Style"), Icon_Utils::it_Properties_Color, m_pStylePage); } /** * Sets up the style page. * @param widget The widget to load the initial data from */ KPageWidgetItem *MultiPageDialogBase::setupStylePage(AssociationWidget *widget) { m_pStylePage = new UMLWidgetStylePage(0, widget); return createPage(i18nc("style page name", "Style"), i18n("Role Style"), Icon_Utils::it_Properties_Color, m_pStylePage); } /** * Updates the style page. */ void MultiPageDialogBase::applyStylePage() { Q_ASSERT(m_pStylePage); m_pStylePage->apply(); } /** * Sets up the role settings page. * @param widget The widget to load the initial data from */ KPageWidgetItem *MultiPageDialogBase::setupAssociationRolePage(AssociationWidget *widget) { m_pRolePage = new AssociationRolePage(0, widget); return createPage(i18nc("role page name", "Roles"), i18n("Role Settings"), Icon_Utils::it_Properties_Roles, m_pRolePage); } /** * Save all used pages */ void MultiPageDialogBase::applyAssociationRolePage() { Q_ASSERT(m_pRolePage); m_pRolePage->apply(); } diff --git a/umbrello/dialogs/pages/associationgeneralpage.cpp b/umbrello/dialogs/pages/associationgeneralpage.cpp index 660ce286c..7eca66f2b 100644 --- a/umbrello/dialogs/pages/associationgeneralpage.cpp +++ b/umbrello/dialogs/pages/associationgeneralpage.cpp @@ -1,231 +1,230 @@ /*************************************************************************** * 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 * ***************************************************************************/ // own header #include "associationgeneralpage.h" // local includes #include "associationwidget.h" #include "assocrules.h" #include "debug_utils.h" #include "dialog_utils.h" #include "documentationwidget.h" #include "objectwidget.h" #include "umldoc.h" #include "umlobject.h" #include "association.h" // kde includes #include #include #include #include #include // qt includes #include #include #include #include #include #include #include /** * Sets up the AssociationGeneralPage. * - * @param d The UMLDoc which controls controls object creation. * @param parent The parent to the AssociationGeneralPage. * @param assoc The AssociationWidget to display the properties of. */ AssociationGeneralPage::AssociationGeneralPage (QWidget *parent, AssociationWidget *assoc) : DialogPageBase(parent), m_pAssocNameL(0), m_pAssocNameLE(0), m_pAssocNameComB(0), m_pStereoChkB(0), m_pTypeCB(0), m_pAssociationWidget(assoc), m_pWidget(0) { constructWidget(); } /** * Standard destructor. */ AssociationGeneralPage::~AssociationGeneralPage() { } /** * Construct all the widgets for this dialog. */ void AssociationGeneralPage::constructWidget() { // general configuration of the GUI int margin = fontMetrics().height(); setMinimumSize(310, 330); QVBoxLayout * topLayout = new QVBoxLayout(this); topLayout->setSpacing(6); // group boxes for name+type, documentation properties QGroupBox *nameAndTypeGB = new QGroupBox(this); nameAndTypeGB->setTitle(i18n("Properties")); topLayout->addWidget(nameAndTypeGB); m_pNameAndTypeLayout = new QGridLayout(nameAndTypeGB); m_pNameAndTypeLayout->setSpacing(6); m_pNameAndTypeLayout->setMargin(margin); // Association name m_pAssocNameL = new QLabel(i18nc("name of association widget", "Name:")); m_pNameAndTypeLayout->addWidget(m_pAssocNameL, 0, 0); m_pAssocNameLE = new KLineEdit(m_pAssociationWidget->name()); m_pAssocNameComB = new KComboBox(true, nameAndTypeGB); #if QT_VERSION < 0x050000 m_pAssocNameComB->setCompletionMode(KGlobalSettings::CompletionPopup); #endif m_pAssocNameComB->setDuplicatesEnabled(false); // only allow one of each type in box QWidget *nameInputWidget = m_pAssocNameLE; UMLAssociation *umlAssoc = m_pAssociationWidget->association(); if (umlAssoc && umlAssoc->umlStereotype()) { m_pAssocNameLE->hide(); Dialog_Utils::insertStereotypesSorted(m_pAssocNameComB, umlAssoc->stereotype()); nameInputWidget = m_pAssocNameComB; } else { m_pAssocNameComB->hide(); } m_pNameAndTypeLayout->addWidget(nameInputWidget, 0, 1); nameInputWidget->setFocus(); m_pAssocNameL->setBuddy(nameInputWidget); if (umlAssoc) { // stereotype checkbox m_pStereoChkB = new QCheckBox(i18n("Stereotype"), nameAndTypeGB); m_pStereoChkB->setChecked(umlAssoc->umlStereotype() != 0); connect(m_pStereoChkB, SIGNAL(stateChanged(int)), this, SLOT(slotStereoCheckboxChanged(int))); m_pNameAndTypeLayout->addWidget(m_pStereoChkB, 0, 2); } // type Uml::AssociationType::Enum currentType = m_pAssociationWidget->associationType(); QString currentTypeAsString = Uml::AssociationType::toStringI18n(currentType); QLabel *pTypeL = new QLabel(i18n("Type:"), nameAndTypeGB); m_pNameAndTypeLayout->addWidget(pTypeL, 1, 0); // Here is a list of all the supported choices for changing // association types. m_AssocTypes.clear(); m_AssocTypes << currentType; uDebug() << "current type = " << Uml::AssociationType::toString(currentType); // dynamically load all allowed associations for (int i = Uml::AssociationType::Generalization; i < Uml::AssociationType::Reserved; ++i) { // we don't need to check for current type Uml::AssociationType::Enum assocType = Uml::AssociationType::fromInt(i); if (assocType == currentType) continue; // UMLScene based checks if (m_pAssociationWidget->umlScene()->type() == Uml::DiagramType::Collaboration && !(assocType == Uml::AssociationType::Coll_Message_Asynchronous || assocType == Uml::AssociationType::Coll_Message_Synchronous || assocType == Uml::AssociationType::Anchor)) continue; if (AssocRules::allowAssociation(assocType, m_pAssociationWidget->widgetForRole(Uml::RoleType::A), m_pAssociationWidget->widgetForRole(Uml::RoleType::B))) { m_AssocTypes << assocType; uDebug() << "to type list = " << Uml::AssociationType::toString(assocType); } } bool found = false; m_AssocTypeStrings.clear(); for (int i = 0; i < m_AssocTypes.size(); ++i) { if (m_AssocTypes[i] == currentType) { found = true; } m_AssocTypeStrings << Uml::AssociationType::toStringI18n(m_AssocTypes[i]); } if (!found) { m_AssocTypes.clear(); m_AssocTypes << currentType; m_AssocTypeStrings.clear(); m_AssocTypeStrings << currentTypeAsString; } m_pTypeCB = new KComboBox(nameAndTypeGB); pTypeL->setBuddy(m_pTypeCB); m_pTypeCB->addItems(m_AssocTypeStrings); m_pTypeCB->setCompletedItems(m_AssocTypeStrings); m_pTypeCB->setDuplicatesEnabled(false); // only allow one of each type in box #if QT_VERSION < 0x050000 m_pTypeCB->setCompletionMode(KGlobalSettings::CompletionPopup); #endif m_pNameAndTypeLayout->addWidget(m_pTypeCB, 1, 1); // documentation m_docWidget = new DocumentationWidget(m_pAssociationWidget, this); topLayout->addWidget(m_docWidget); } void AssociationGeneralPage::slotStereoCheckboxChanged(int state) { QWidget *nameInputWidget = 0; if (state) { m_pAssocNameLE->hide(); m_pNameAndTypeLayout->removeWidget(m_pAssocNameLE); UMLAssociation *umlAssoc = m_pAssociationWidget->association(); Dialog_Utils::insertStereotypesSorted(m_pAssocNameComB, umlAssoc->stereotype()); nameInputWidget = m_pAssocNameComB; } else { m_pAssocNameComB->hide(); m_pNameAndTypeLayout->removeWidget(m_pAssocNameComB); nameInputWidget = m_pAssocNameLE; } m_pNameAndTypeLayout->addWidget(nameInputWidget, 0, 1); nameInputWidget->show(); nameInputWidget->setFocus(); m_pAssocNameL->setBuddy(nameInputWidget); } /** * Will move information from the dialog into the object. * Call when the ok or apply button is pressed. */ void AssociationGeneralPage::apply() { if (m_pAssociationWidget) { int comboBoxItem = m_pTypeCB->currentIndex(); Uml::AssociationType::Enum newType = m_AssocTypes[comboBoxItem]; m_pAssociationWidget->setAssociationType(newType); m_docWidget->apply(); if (m_pStereoChkB && m_pStereoChkB->isChecked()) { QString stereo = m_pAssocNameComB->currentText(); // keep the order m_pAssociationWidget->setName(QLatin1String("")); m_pAssociationWidget->setStereotype(stereo); } else { // keep the order m_pAssociationWidget->setStereotype(QLatin1String("")); m_pAssociationWidget->setName(m_pAssocNameLE->text()); } } } diff --git a/umbrello/dialogs/pages/associationrolepage.cpp b/umbrello/dialogs/pages/associationrolepage.cpp index bd33d5bf6..3e1a2f524 100644 --- a/umbrello/dialogs/pages/associationrolepage.cpp +++ b/umbrello/dialogs/pages/associationrolepage.cpp @@ -1,277 +1,276 @@ /*************************************************************************** * 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 * ***************************************************************************/ // own header #include "associationrolepage.h" // local includes #include "associationwidget.h" #include "dialog_utils.h" #include "objectwidget.h" #include "umldoc.h" #include "umlobject.h" #include "visibilityenumwidget.h" // kde includes #include #include #include #include #include // qt includes #include #include #include #include #include #include /** * Sets up the AssociationRolePage. - * @param d The UMLDoc which controls controls object creation. * @param parent The parent to the AssociationRolePage. * @param assoc The AssociationWidget to display the properties of. */ AssociationRolePage::AssociationRolePage (QWidget *parent, AssociationWidget *assoc) : DialogPageBase(parent), m_pRoleALE(0), m_pRoleBLE(0), m_pMultiACB(0), m_pMultiBCB(0), m_pAssociationWidget(assoc), m_pWidget(0) { constructWidget(); } /** * Standard destructor. */ AssociationRolePage::~AssociationRolePage() { } void AssociationRolePage::constructWidget() { // underlying roles and objects QString nameA = m_pAssociationWidget->roleName(Uml::RoleType::A); QString nameB = m_pAssociationWidget->roleName(Uml::RoleType::B); QString titleA = i18n("Role A Properties"); QString titleB = i18n("Role B Properties"); QString widgetNameA = m_pAssociationWidget->widgetForRole(Uml::RoleType::A)->name(); QString widgetNameB = m_pAssociationWidget->widgetForRole(Uml::RoleType::B)->name(); if(!widgetNameA.isEmpty()) titleA.append(QLatin1String(" (") + widgetNameA + QLatin1Char(')')); if(!widgetNameB.isEmpty()) titleB.append(QLatin1String(" (") + widgetNameB + QLatin1Char(')')); // general configuration of the GUI int margin = fontMetrics().height(); QGridLayout * mainLayout = new QGridLayout(this); mainLayout->setSpacing(6); // group boxes for role, documentation properties QGroupBox *propsAGB = new QGroupBox(this); QGroupBox *propsBGB = new QGroupBox(this); QGroupBox *changeABG = new QGroupBox(i18n("Role A Changeability"), this); QGroupBox *changeBBG = new QGroupBox(i18n("Role B Changeability"), this); QGroupBox *docAGB = new QGroupBox(this); QGroupBox *docBGB = new QGroupBox(this); propsAGB->setTitle(titleA); propsBGB->setTitle(titleB); docAGB->setTitle(i18n("Documentation")); docBGB->setTitle(i18n("Documentation")); QGridLayout * propsALayout = new QGridLayout(propsAGB); propsALayout->setSpacing(6); propsALayout->setMargin(margin); QGridLayout * propsBLayout = new QGridLayout(propsBGB); propsBLayout->setSpacing(6); propsBLayout->setMargin(margin); QStringList multiplicities; multiplicities << QString() << QLatin1String("1") << QLatin1String("*") << QLatin1String("1..*") << QLatin1String("0..1"); // Properties // // Rolename A QLabel *pRoleAL = 0; Dialog_Utils::makeLabeledEditField(propsALayout, 0, pRoleAL, i18n("Rolename:"), m_pRoleALE, nameA); // Multi A QLabel *pMultiAL = 0; pMultiAL = new QLabel(i18n("Multiplicity:"), propsAGB); m_pMultiACB = new KComboBox(propsAGB); m_pMultiACB->addItems(multiplicities); m_pMultiACB->setDuplicatesEnabled(false); m_pMultiACB->setEditable(true); QString multiA = m_pAssociationWidget->multiplicity(Uml::RoleType::A); if (!multiA.isEmpty()) m_pMultiACB->setEditText(multiA); propsALayout->addWidget(pMultiAL, 1, 0); propsALayout->addWidget(m_pMultiACB, 1, 1); m_visibilityWidgetA = new VisibilityEnumWidget(m_pAssociationWidget, Uml::RoleType::A, this); mainLayout->addWidget(m_visibilityWidgetA, 1, 0); // Changeability A QHBoxLayout * changeALayout = new QHBoxLayout(changeABG); changeALayout->setMargin(margin); m_ChangeableARB = new QRadioButton(i18nc("changeability for A is changeable", "Changeable"), changeABG); changeALayout->addWidget(m_ChangeableARB); m_FrozenARB = new QRadioButton(i18nc("changeability for A is frozen", "Frozen"), changeABG); changeALayout->addWidget(m_FrozenARB); m_AddOnlyARB = new QRadioButton(i18nc("changeability for A is add only", "Add only"), changeABG); changeALayout->addWidget(m_AddOnlyARB); switch (m_pAssociationWidget->changeability(Uml::RoleType::A)) { case Uml::Changeability::Changeable: m_ChangeableARB->setChecked(true); break; case Uml::Changeability::Frozen: m_FrozenARB->setChecked(true); break; default: m_AddOnlyARB->setChecked(true); break; } // Rolename B QLabel * pRoleBL = 0; Dialog_Utils::makeLabeledEditField(propsBLayout, 0, pRoleBL, i18n("Rolename:"), m_pRoleBLE, nameB); // Multi B QLabel *pMultiBL = 0; pMultiBL = new QLabel(i18n("Multiplicity:"), propsBGB); m_pMultiBCB = new KComboBox(propsBGB); m_pMultiBCB->addItems(multiplicities); m_pMultiBCB->setDuplicatesEnabled(false); m_pMultiBCB->setEditable(true); QString multiB = m_pAssociationWidget->multiplicity(Uml::RoleType::B); if (!multiB.isEmpty()) m_pMultiBCB->setEditText(multiB); propsBLayout->addWidget(pMultiBL, 1, 0); propsBLayout->addWidget(m_pMultiBCB, 1, 1); m_visibilityWidgetB = new VisibilityEnumWidget(m_pAssociationWidget, Uml::RoleType::B, this); mainLayout->addWidget(m_visibilityWidgetB, 1, 1); // Changeability B QHBoxLayout * changeBLayout = new QHBoxLayout(changeBBG); changeBLayout->setMargin(margin); m_ChangeableBRB = new QRadioButton(i18nc("changeability for B is changeable", "Changeable"), changeBBG); changeBLayout->addWidget(m_ChangeableBRB); m_FrozenBRB = new QRadioButton(i18nc("changeability for B is frozen", "Frozen"), changeBBG); changeBLayout->addWidget(m_FrozenBRB); m_AddOnlyBRB = new QRadioButton(i18nc("changeability for B is add only", "Add only"), changeBBG); changeBLayout->addWidget(m_AddOnlyBRB); switch (m_pAssociationWidget->changeability(Uml::RoleType::B)) { case Uml::Changeability::Changeable: m_ChangeableBRB->setChecked(true); break; case Uml::Changeability::Frozen: m_FrozenBRB->setChecked(true); break; default: m_AddOnlyBRB->setChecked(true); break; } // Documentation // // Document A QHBoxLayout * docALayout = new QHBoxLayout(docAGB); docALayout->setMargin(margin); m_docA = new KTextEdit(docAGB); docALayout->addWidget(m_docA); m_docA-> setText(m_pAssociationWidget->roleDocumentation(Uml::RoleType::A)); // m_docA->setText("<>"); // m_docA->setEnabled(false); m_docA->setLineWrapMode(QTextEdit::WidgetWidth); // Document B QHBoxLayout * docBLayout = new QHBoxLayout(docBGB); docBLayout->setMargin(margin); m_docB = new KTextEdit(docBGB); docBLayout->addWidget(m_docB); m_docB->setText(m_pAssociationWidget->roleDocumentation(Uml::RoleType::B)); // m_docB->setEnabled(false); m_docB->setLineWrapMode(QTextEdit::WidgetWidth); // add group boxes to main layout mainLayout->addWidget(propsAGB, 0, 0); mainLayout->addWidget(changeABG, 2, 0); mainLayout->addWidget(docAGB, 3, 0); mainLayout->addWidget(propsBGB, 0, 1); mainLayout->addWidget(changeBBG, 2, 1); mainLayout->addWidget(docBGB, 3, 1); } /** * Will move information from the dialog into the object. * Call when the ok or apply button is pressed. */ void AssociationRolePage::apply() { if (m_pAssociationWidget) { // set props m_pAssociationWidget->setRoleName(m_pRoleALE->text(), Uml::RoleType::A); m_pAssociationWidget->setRoleName(m_pRoleBLE->text(), Uml::RoleType::B); m_pAssociationWidget->setMultiplicity(m_pMultiACB->currentText(), Uml::RoleType::A); m_pAssociationWidget->setMultiplicity(m_pMultiBCB->currentText(), Uml::RoleType::B); m_visibilityWidgetA->apply(); m_visibilityWidgetB->apply(); if (m_FrozenARB->isChecked()) m_pAssociationWidget->setChangeability(Uml::Changeability::Frozen, Uml::RoleType::A); else if (m_AddOnlyARB->isChecked()) m_pAssociationWidget->setChangeability(Uml::Changeability::AddOnly, Uml::RoleType::A); else m_pAssociationWidget->setChangeability(Uml::Changeability::Changeable, Uml::RoleType::A); if (m_FrozenBRB->isChecked()) m_pAssociationWidget->setChangeability(Uml::Changeability::Frozen, Uml::RoleType::B); else if (m_AddOnlyBRB->isChecked()) m_pAssociationWidget->setChangeability(Uml::Changeability::AddOnly, Uml::RoleType::B); else m_pAssociationWidget->setChangeability(Uml::Changeability::Changeable, Uml::RoleType::B); m_pAssociationWidget->setRoleDocumentation(m_docA->toPlainText(), Uml::RoleType::A); m_pAssociationWidget->setRoleDocumentation(m_docB->toPlainText(), Uml::RoleType::B); } //end if m_pAssociationWidget } diff --git a/umbrello/dialogs/pages/uioptionspage.cpp b/umbrello/dialogs/pages/uioptionspage.cpp index 047935e7f..aca4f1049 100644 --- a/umbrello/dialogs/pages/uioptionspage.cpp +++ b/umbrello/dialogs/pages/uioptionspage.cpp @@ -1,252 +1,251 @@ /*************************************************************************** * 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 "uioptionspage.h" //// local includes #include "optionstate.h" #include "umbrellosettings.h" //// kde includes #include #include #include //// qt includes #include #include #include #include #include #include //TODO don't do that, but it's better than hardcoded in the functions body #define FILL_COLOR QColor(255, 255, 192) #define LINK_COLOR Qt::red #define TEXT_COLOR Qt::black /** * Constructor - observe and modify an OptionState structure * * @param pParent Parent widget * @param options Settings to read from/save into - * @param isDiagram Flag if object is for display diagram class options */ UIOptionsPage::UIOptionsPage(QWidget* pParent, Settings::OptionState *options) : DialogPageBase(pParent), m_options(options) { setupPage(); } /** * Destructor */ UIOptionsPage::~UIOptionsPage() { } /** * Creates the page with the correct options for the class/interface */ void UIOptionsPage::setupPage() { QVBoxLayout* uiPageLayout = new QVBoxLayout(this); QGroupBox *box = new QGroupBox(i18nc("General options", "General"), this); QGridLayout * otherLayout = new QGridLayout(box); //otherLayout->setSpacing(spacingHint()); otherLayout->setMargin(fontMetrics().height()); uiPageLayout->addWidget(box); m_rightToLeftUI = new QCheckBox(i18n("Right to left user interface"), box); m_rightToLeftUI->setChecked(UmbrelloSettings::rightToLeftUI()); otherLayout->addWidget(m_rightToLeftUI, 0, 0); m_colorGB = new QGroupBox(i18nc("color group box", "Color"), this); QGridLayout * colorLayout = new QGridLayout(m_colorGB); //colorLayout->setSpacing(spacingHint()); colorLayout->setMargin(fontMetrics().height()); uiPageLayout->addWidget(m_colorGB); uiPageLayout->addItem(new QSpacerItem(0, 20, QSizePolicy::Minimum, QSizePolicy::Expanding)); m_textColorCB = new QCheckBox(i18n("Custom text color"), m_colorGB); colorLayout->addWidget(m_textColorCB, 0, 0); m_textColorB = new KColorButton(m_options->uiState.textColor, m_colorGB); //m_lineColorB->setObjectName(m_colorGB); colorLayout->addWidget(m_textColorB, 0, 1); m_lineColorCB = new QCheckBox(i18n("Custom line color"), m_colorGB); colorLayout->addWidget(m_lineColorCB, 1, 0); m_lineColorB = new KColorButton(m_options->uiState.lineColor, m_colorGB); //m_lineColorB->setObjectName(m_colorGB); colorLayout->addWidget(m_lineColorB, 1, 1); // m_lineDefaultB = new QPushButton(i18n("D&efault Color"), m_colorGB); // colorLayout->addWidget(m_lineDefaultB, 0, 2); m_fillColorCB = new QCheckBox(i18n("Custom fill color"), m_colorGB); colorLayout->addWidget(m_fillColorCB, 2, 0); m_fillColorB = new KColorButton(m_options->uiState.fillColor, m_colorGB); colorLayout->addWidget(m_fillColorB, 2, 1); m_gridColorCB = new QCheckBox(i18n("Custom grid color"), m_colorGB); colorLayout->addWidget(m_gridColorCB, 3, 0); m_gridColorB = new KColorButton(m_options->uiState.gridDotColor, m_colorGB); colorLayout->addWidget(m_gridColorB, 3, 1); m_bgColorCB = new QCheckBox(i18n("Custom background color"), m_colorGB); colorLayout->addWidget(m_bgColorCB, 4, 0); m_bgColorB = new KColorButton(m_options->uiState.backgroundColor, m_colorGB); colorLayout->addWidget(m_bgColorB, 4, 1); m_lineWidthCB = new QCheckBox(i18n("Custom line width"), m_colorGB); colorLayout->addWidget(m_lineWidthCB, 5, 0); #if QT_VERSION >= 0x050000 m_lineWidthB = new QSpinBox(m_colorGB); m_lineWidthB->setMinimum(0); m_lineWidthB->setMaximum(10); m_lineWidthB->setSingleStep(1); m_lineWidthB->setValue(m_options->uiState.lineWidth); #else m_lineWidthB = new KIntSpinBox(0, 10, 1, m_options->uiState.lineWidth, m_colorGB); #endif colorLayout->addWidget(m_lineWidthB, 5, 1); m_useFillColorCB = new QCheckBox(i18n("&Use fill color"), m_colorGB); //colorLayout->setRowStretch(3, 2); colorLayout->addWidget(m_useFillColorCB, 6, 0); m_useFillColorCB->setChecked(m_options->uiState.useFillColor); //connect button signals up connect(m_textColorCB, SIGNAL(toggled(bool)), this, SLOT(slotTextCBChecked(bool))); connect(m_lineColorCB, SIGNAL(toggled(bool)), this, SLOT(slotLineCBChecked(bool))); connect(m_fillColorCB, SIGNAL(toggled(bool)), this, SLOT(slotFillCBChecked(bool))); connect(m_gridColorCB, SIGNAL(toggled(bool)), this, SLOT(slotGridCBChecked(bool))); connect(m_bgColorCB, SIGNAL(toggled(bool)), this, SLOT(slotBgCBChecked(bool))); connect(m_lineWidthCB, SIGNAL(toggled(bool)), this, SLOT(slotLineWidthCBChecked(bool))); // initial setup slotTextCBChecked(false); slotLineCBChecked(false); slotFillCBChecked(false); slotGridCBChecked(false); slotBgCBChecked(false); slotLineWidthCBChecked(false); } void UIOptionsPage::setDefaults() { m_useFillColorCB->setChecked(true); m_textColorCB->setChecked(false); m_lineColorCB->setChecked(false); m_fillColorCB->setChecked(false); m_gridColorCB->setChecked(false); m_bgColorCB->setChecked(false); m_lineWidthCB->setChecked(false); slotTextCBChecked(false); slotLineCBChecked(false); slotFillCBChecked(false); slotGridCBChecked(false); slotBgCBChecked(false); slotLineWidthCBChecked(false); m_rightToLeftUI->setChecked(false); } /** * apply changes */ void UIOptionsPage::apply() { m_options->uiState.useFillColor = m_useFillColorCB->isChecked(); m_options->uiState.fillColor = m_fillColorB->color(); m_options->uiState.textColor = m_textColorB->color(); m_options->uiState.lineColor = m_lineColorB->color(); m_options->uiState.lineWidth = m_lineWidthB->value(); m_options->uiState.backgroundColor = m_bgColorB->color(); m_options->uiState.gridDotColor = m_gridColorB->color(); UmbrelloSettings::setRightToLeftUI(m_rightToLeftUI->isChecked()); qApp->setLayoutDirection(UmbrelloSettings::rightToLeftUI() ? Qt::RightToLeft : Qt::LeftToRight); } void UIOptionsPage::slotTextCBChecked(bool value) { if (value == false) { m_textColorB->setColor(TEXT_COLOR); m_textColorB->setDisabled(true); } else { m_textColorB->setDisabled(false); } } void UIOptionsPage::slotLineCBChecked(bool value) { if (value == false) { m_lineColorB->setColor(LINK_COLOR); m_lineColorB->setDisabled(true); } else { m_lineColorB->setDisabled(false); } } void UIOptionsPage::slotFillCBChecked(bool value) { if (value == false) { m_fillColorB->setColor(FILL_COLOR); m_fillColorB->setDisabled(true); } else { m_fillColorB->setDisabled(false); } } void UIOptionsPage::slotGridCBChecked(bool value) { if (value == false) { QPalette palette; m_gridColorB->setColor(palette.alternateBase().color()); m_gridColorB->setDisabled(true); } else { m_gridColorB->setDisabled(false); } } void UIOptionsPage::slotBgCBChecked(bool value) { if (value == false) { QPalette palette; m_bgColorB->setColor(palette.base().color()); m_bgColorB->setDisabled(true); } else { m_bgColorB->setDisabled(false); } } void UIOptionsPage::slotLineWidthCBChecked(bool value) { if (value == false) { m_lineWidthB->setValue(0); m_lineWidthB->setDisabled(true); } else { m_lineWidthB->setDisabled(false); } } diff --git a/umbrello/dialogs/widgets/defaultvaluewidget.cpp b/umbrello/dialogs/widgets/defaultvaluewidget.cpp index a4f04d3d6..84c0ee590 100644 --- a/umbrello/dialogs/widgets/defaultvaluewidget.cpp +++ b/umbrello/dialogs/widgets/defaultvaluewidget.cpp @@ -1,191 +1,191 @@ /*************************************************************************** * 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) 2018 * * Umbrello UML Modeller Authors * ***************************************************************************/ #include "defaultvaluewidget.h" #include "enum.h" #include "datatype.h" #include "uml.h" #include "umldoc.h" #include #include #include #include #include class DefaultValueWidget::Private { public: DefaultValueWidget *p; QPointertype; QPointer label; QPointer listWidget; QPointer lineWidget; QString initialValue; Private(DefaultValueWidget *parent, UMLObject *_type, const QString &_value) : p(parent), type(_type), initialValue(_value) { QHBoxLayout *layout = new QHBoxLayout; layout->setContentsMargins(0,0,0,0); label = new QLabel(i18n("&Default value:"), p); layout->addWidget(label); listWidget = new QListWidget(p); layout->addWidget(listWidget, 2); lineWidget = new KLineEdit(p); layout->addWidget(lineWidget, 2); label->setBuddy(listWidget); p->setLayout(layout); p->setFocusProxy(listWidget); setupWidget(); } /** * fill list widget with items */ void setupWidget() { if (type && type->isUMLEnum()) { listWidget->clear(); listWidget->addItem(new QListWidgetItem); UMLEnum *e = type->asUMLEnum(); UMLClassifierListItemList enumLiterals = e->getFilteredList(UMLObject::ot_EnumLiteral); foreach (UMLClassifierListItem* enumLiteral, enumLiterals) { QListWidgetItem *item = new QListWidgetItem(enumLiteral->name()); listWidget->addItem(item); } QList currentItem = listWidget->findItems(initialValue, Qt::MatchExactly); if (currentItem.size() > 0) listWidget->setCurrentItem(currentItem.at(0)); } else { lineWidget->setText(initialValue); } setVisible(true); } void setVisible(bool state) { label->setVisible(state); if (type && type->isUMLEnum()) { listWidget->setVisible(state); lineWidget->setVisible(false); } else { listWidget->setVisible(false); lineWidget->setVisible(state); } } void setValue(const QString &value) { initialValue = value; } QString value() const { if (type && type->isUMLEnum()) { return listWidget && listWidget->currentItem() ? listWidget->currentItem()->text() : QString(); } else { return lineWidget->text(); } } }; DefaultValueWidget::DefaultValueWidget(UMLObject *type, const QString &value, QWidget *parent) : QWidget(parent), m_d(new Private(this, type, value)) { } DefaultValueWidget::~DefaultValueWidget() { delete m_d; } /** * Update widget with new data type * * @param type type to set widget from */ void DefaultValueWidget::setType(UMLObject *type) { if (m_d->type == type) return; m_d->type = type; m_d->setupWidget(); } /** * Update widget with data type from a text string * * The method searches for an uml object with the specified * name. If an object was found, the display of the selectable * options depends on the type. * - * @param type type as text to set the widget from + * @param _type type as text to set the widget from */ void DefaultValueWidget::setType(const QString &_type) { UMLObject *type = UMLApp::app()->document()->findUMLObject(_type, UMLObject::ot_UMLObject); m_d->type = type; m_d->setupWidget(); } /** * 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 DefaultValueWidget::addToLayout(QGridLayout *layout, int row) { layout->addWidget(m_d->label, row, 0); layout->addWidget(m_d->listWidget, row, 1); layout->addWidget(m_d->lineWidget, row, 1); } /** * return current text either from list box or from edit field * @return current text */ QString DefaultValueWidget::value() const { return m_d->value(); } /** * Reimplemented from QWidget to control widgets visible state * in case @ref addToLayout() has been called * * @param event show event */ void DefaultValueWidget::showEvent(QShowEvent *event) { m_d->setVisible(true); QWidget::showEvent(event); } /** * Reimplemented from QWidget to control widgets visible state * in case @ref addToLayout() has been called * * @param event hide event */ void DefaultValueWidget::hideEvent(QHideEvent *event) { m_d->setVisible(false); QWidget::hideEvent(event); } diff --git a/umbrello/menus/associationwidgetpopupmenu.cpp b/umbrello/menus/associationwidgetpopupmenu.cpp index 174864298..1bf84b941 100644 --- a/umbrello/menus/associationwidgetpopupmenu.cpp +++ b/umbrello/menus/associationwidgetpopupmenu.cpp @@ -1,116 +1,115 @@ /*************************************************************************** * 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) 2018 * * Umbrello UML Modeller Authors * ***************************************************************************/ #include "associationwidgetpopupmenu.h" // app includes #include "assocrules.h" #include "associationline.h" #include "associationwidget.h" #include "debug_utils.h" // kde includes #include enum class LocalTriggerType { AnchorSelected, AssociationSelected, CollaborationMessage, AttributeAssociation, FullAssociation }; /** * Constructs the popup menu for a scene widget. * * @param parent The parent to ListPopupMenu. - * @param object The WidgetBase to represent a menu for. - * @param multi True if multiple items are selected. - * @param uniqueType The type of widget shared by all selected widgets + * @param type The type of widget shared by all selected widgets. + * @param widget The AssociationWidget to represent a menu for. */ AssociationWidgetPopupMenu::AssociationWidgetPopupMenu(QWidget *parent, Uml::AssociationType::Enum type, AssociationWidget *widget) : ListPopupMenu(parent) { LocalTriggerType triggerType = LocalTriggerType::AssociationSelected; if (type == Uml::AssociationType::Anchor) { triggerType = LocalTriggerType::AnchorSelected; } else if (widget->isCollaboration()) { triggerType = LocalTriggerType::CollaborationMessage; } else if (!widget->association()) { triggerType = LocalTriggerType::AttributeAssociation; } else if (AssocRules::allowRole(type)) { triggerType = LocalTriggerType::FullAssociation; } switch (triggerType) { case LocalTriggerType::AnchorSelected: insert(mt_Delete, Icon_Utils::SmallIcon(Icon_Utils::it_Delete), i18n("Delete Anchor")); break; case LocalTriggerType::AssociationSelected: case LocalTriggerType::AttributeAssociation: case LocalTriggerType::FullAssociation: case LocalTriggerType::CollaborationMessage: if (widget->isPointAddable()) insert(mt_Add_Point, Icon_Utils::SmallIcon(Icon_Utils::it_Add_Point), i18n("Add Point")); if (widget->isPointRemovable()) insert(mt_Delete_Point, Icon_Utils::SmallIcon(Icon_Utils::it_Delete_Point), i18n("Delete Point")); addSeparator(); insertSubMenuLayout(widget->associationLine()); addSeparator(); insert(mt_Delete); default: break; } switch(triggerType) { case LocalTriggerType::AttributeAssociation: case LocalTriggerType::FullAssociation: insert(mt_Rename_Name, i18n("Change Association Name...")); insert(mt_Rename_RoleAName, i18n("Change Role A Name...")); insert(mt_Rename_RoleBName, i18n("Change Role B Name...")); insert(mt_Change_Font); insert(mt_Reset_Label_Positions); break; case LocalTriggerType::CollaborationMessage: insert(mt_Change_Font); insert(mt_New_Operation); insert(mt_Select_Operation, i18n("Select Operation...")); break; default: break; } insert(mt_Line_Color); insert(mt_Properties); setActionChecked(mt_AutoResize, widget->autoResize()); setupActionsData(); } /** * Inserts a sub menu for association layouts. */ void AssociationWidgetPopupMenu::insertSubMenuLayout(AssociationLine *associationLine) { KMenu* layout = newMenu(i18nc("Layout menu", "Layout"), this); insert(mt_LayoutPolyline, layout, i18n("Polyline"), true); insert(mt_LayoutDirect, layout, i18n("Direct"), true); insert(mt_LayoutSpline, layout, i18n("Spline"), true); insert(mt_LayoutOrthogonal, layout, i18n("Orthogonal"), true); switch(associationLine->layout()) { case AssociationLine::Direct: m_actions[mt_LayoutDirect]->setChecked(true); break; case AssociationLine::Orthogonal: m_actions[mt_LayoutOrthogonal]->setChecked(true); break; case AssociationLine::Spline: m_actions[mt_LayoutSpline]->setChecked(true); break; case AssociationLine::Polyline: default: m_actions[mt_LayoutPolyline]->setChecked(true); break; } addMenu(layout); } diff --git a/umbrello/menus/listpopupmenu.cpp b/umbrello/menus/listpopupmenu.cpp index 6a810608e..8b4d6ab9e 100644 --- a/umbrello/menus/listpopupmenu.cpp +++ b/umbrello/menus/listpopupmenu.cpp @@ -1,675 +1,675 @@ /*************************************************************************** * 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 "listpopupmenu.h" // app includes #include "activitywidget.h" #include "associationline.h" #include "associationwidget.h" #include "category.h" #include "classifier.h" #include "classifierwidget.h" #include "combinedfragmentwidget.h" #include "debug_utils.h" #include "floatingtextwidget.h" #include "folder.h" #include "forkjoinwidget.h" #include "layoutgenerator.h" #include "model_utils.h" #include "objectnodewidget.h" #include "objectwidget.h" #include "notewidget.h" #include "pinportbase.h" #include "preconditionwidget.h" #include "signalwidget.h" #include "statewidget.h" #include "uml.h" #include "umldoc.h" #include "umlscene.h" #include "umlview.h" #include "umllistview.h" #include "umllistviewitem.h" #include "widget_utils.h" #include "widgetbase.h" // kde includes #include #include DEBUG_REGISTER_DISABLED(ListPopupMenu) static const bool CHECKABLE = true; // uncomment to see not handled switch cases //#define CHECK_SWITCH class DebugMenu { public: DebugMenu(ListPopupMenu::MenuType _m) : m(_m) {} DebugMenu(const QString & _m) : menu(_m) {} ListPopupMenu::MenuType m; QString menu; }; class ListPopupMenuPrivate { public: QList debugActions; ~ListPopupMenuPrivate() { debugActions.clear(); } }; #define DEBUG_AddAction(m) d->debugActions.append(DebugMenu(m)) #define DEBUG_StartMenu(m) d->debugActions.append(DebugMenu(m->title() + QLatin1String(" - start"))) #define DEBUG_EndMenu(m) d->debugActions.append(DebugMenu(m->title() + QLatin1String(" - end"))) /** * Constructs the popup menu * * @param parent The parent to ListPopupMenu. */ ListPopupMenu::ListPopupMenu(QWidget *parent) : KMenu(parent), d(new ListPopupMenuPrivate) { } /** * Standard destructor. */ ListPopupMenu::~ListPopupMenu() { foreach (QAction* action, m_actions) { delete action; } m_actions.clear(); delete d; } KMenu *ListPopupMenu::newMenu(const QString &title, QWidget *widget) { KMenu *menu = new KMenu(title, widget); DEBUG_StartMenu(menu); return menu; } void ListPopupMenu::addMenu(KMenu *menu) { KMenu::addMenu(menu); DEBUG_EndMenu(menu); } /** * Shortcut for the frequently used addAction() calls. * * @param m The MenuType for which to insert a menu item. */ void ListPopupMenu::insert(MenuType m) { insert(m, this); } /** * Shortcut for the frequently used addAction() calls. * * @param m The MenuType for which to insert a menu item. * @param menu The KMenu for which to insert a menu item. * @param s The entry to be inserted from the action collection */ void ListPopupMenu::insertAction(const MenuType m, KMenu *menu, const QString &s) { QAction* action = UMLApp::app()->actionCollection()->action(s); insert(m, menu, action->icon(), action->text()); } /** * Shortcut for the frequently used addAction() calls. * * @param m The MenuType for which to insert a menu item. * @param menu The KMenu for which to insert a menu item. */ void ListPopupMenu::insert(const MenuType m, KMenu* menu) { DEBUG_AddAction(m); Q_ASSERT(menu != 0); switch (m) { case mt_New_Activity: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_State_Activity), i18n("Activity...")); break; case mt_EnumLiteral: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Enum_Literal), i18n("Enum Literal...")); break; case mt_EntityAttribute: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Entity_Attribute), i18n("Entity Attribute...")); break; case mt_PrimaryKeyConstraint: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Constraint_PrimaryKey), i18n("Primary Key Constraint...")); break; case mt_UniqueConstraint: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Constraint_Unique), i18n("Unique Constraint...")); break; case mt_ForeignKeyConstraint: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Constraint_ForeignKey), i18n("Foreign Key Constraint...")); break; case mt_CheckConstraint: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Constraint_Check), i18n("Check Constraint...")); break; case mt_Attribute: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Public_Attribute), i18n("Attribute")); break; case mt_Operation: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Public_Method), i18n("Operation")); break; case mt_Template: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Template_Class), i18n("Template")); break; case mt_Subsystem: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Subsystem), i18n("Subsystem")); break; case mt_Component: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Component), i18n("Component")); break; case mt_Port: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Port), i18n("Port")); break; case mt_Artifact: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Artifact), i18n("Artifact")); break; case mt_Component_Diagram: insertAction(mt_Component_Diagram, menu, QLatin1String("new_component_diagram")); break; case mt_Node: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Node), i18n("Node")); break; case mt_Deployment_Diagram: insertAction(mt_Deployment_Diagram, menu, QLatin1String("new_deployment_diagram")); break; case mt_Deployment_Folder: case mt_Component_Folder: case mt_UseCase_Folder: case mt_EntityRelationship_Folder: m_actions[m] = menu->addAction(Icon_Utils::BarIcon(Icon_Utils::it_Folder), i18n("Folder")); break; case mt_Entity: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Entity), i18n("Entity")); break; case mt_EntityRelationship_Diagram: insertAction(mt_EntityRelationship_Diagram, menu, QLatin1String("new_entityrelationship_diagram")); break; case mt_Category: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Category), i18n("Category")); break; case mt_Actor: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Actor), i18n("Actor")); break; case mt_UseCase: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_UseCase), i18n("Use Case")); break; case mt_UseCase_Diagram: insertAction(mt_UseCase_Diagram, menu, QLatin1String("new_use_case_diagram")); break; case mt_FloatText: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Text), i18n("Text Line...")); break; case mt_Note: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Note), i18n("Note...")); break; case mt_Properties: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Properties), i18n("Properties")); break; case mt_Rename: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Rename), i18n("Rename...")); break; case mt_Rename_Object: insert(m, menu, i18n("Rename Object...")); break; case mt_Resize: insert(m, menu, i18n("Resize")); break; case mt_Show: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Show), i18n("Show")); break; case mt_Delete: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Delete), i18n("Delete")); break; case mt_Cut: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Cut), i18n("Cut")); break; case mt_Copy: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Copy), i18n("Copy")); break; case mt_Paste: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Paste), i18n("Paste")); break; case mt_Change_Font: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Change_Font), i18n("Change Font...")); break; case mt_Line_Color: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Color_Line), i18n("Line Color...")); break; case mt_Expand_All: m_actions[m] = menu->addAction(i18n("Expand All")); break; case mt_Collapse_All: m_actions[m] = menu->addAction(i18n("Collapse All")); break; case mt_Clone: m_actions[m] = menu->addAction(Icon_Utils::BarIcon(Icon_Utils::it_Duplicate), i18nc("duplicate action", "Duplicate")); break; case mt_Externalize_Folder: m_actions[m] = menu->addAction(i18n("Externalize Folder...")); break; case mt_Internalize_Folder: m_actions[m] = menu->addAction(i18n("Internalize Folder")); break; case mt_Import_Class: m_actions[m] = menu->addAction(Icon_Utils::BarIcon(Icon_Utils::it_Import_File), i18n("Import File(s)...")); break; case mt_Import_Project: m_actions[m] = menu->addAction(Icon_Utils::BarIcon(Icon_Utils::it_Import_Project), i18n("Import from Directory...")); break; case mt_Reset_Label_Positions: m_actions[m] = menu->addAction(i18n("Reset Label Positions")); break; case mt_New_Parameter: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Parameter_New), i18n("New Parameter...")); break; case mt_New_Operation: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Operation_Public_New), i18n("New Operation...")); break; case mt_New_Attribute: case mt_New_InstanceAttribute: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Attribute_New), i18n("New Attribute...")); break; case mt_New_Template: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Template_New), i18n("New Template...")); break; case mt_New_EnumLiteral: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Literal_New), i18n("New Literal...")); break; case mt_New_EntityAttribute: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Entity_Attribute_New), i18n("New Entity Attribute...")); break; case mt_Export_Image: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Export_Picture), i18n("Export as Picture...")); break; case mt_InstanceAttribute: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Attribute_New), i18n("New Attribute...")); break; case mt_Remove: m_actions[m] = menu->addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Remove), i18n("Remove")); break; default: uWarning() << "called on unimplemented MenuType " << toString(m); break; } } /** * Shortcut for the frequently used addAction() calls. * * @param m The MenuType for which to insert a menu item. * @param icon The icon for this action. * @param text The text for this action. */ void ListPopupMenu::insert(const MenuType m, const QIcon & icon, const QString & text) { DEBUG_AddAction(m); m_actions[m] = addAction(icon, text); } /** * Shortcut for the frequently used addAction() calls. * * @param m The MenuType for which to insert a menu item. * @param text The text for this action. * @param checkable Sets the action to checkable. */ void ListPopupMenu::insert(const MenuType m, const QString & text, const bool checkable) { insert(m, this, text, checkable); } /** * Shortcut for the frequently used addAction() calls. * * @param m The MenuType for which to insert a menu item. * @param menu The KMenu for which to insert a menu item. * @param icon The icon for this action. * @param text The text for this action. */ void ListPopupMenu::insert(const MenuType m, KMenu* menu, const QIcon & icon, const QString & text) { DEBUG_AddAction(m); m_actions[m] = menu->addAction(icon, text); } /** * Shortcut for the frequently used addAction() calls. * * @param m The MenuType for which to insert a menu item. * @param menu The KMenu for which to insert a menu item. * @param text The text for this action. * @param checkable Sets the action to checkable. */ void ListPopupMenu::insert(const MenuType m, KMenu* menu, const QString & text, const bool checkable) { DEBUG_AddAction(m); m_actions[m] = menu->addAction(text); if (checkable) { QAction* action = getAction(m); if (action) action->setCheckable(checkable); } } /** * Shortcut for inserting standard model items (Class, Interface, * Datatype, Enum, Package) as well as diagram choices. * * @param folderAndDiagrams Set this true if folders and diagram * types shall be included as choices. * @param packages Set this true if packages * shall be included as choices. */ void ListPopupMenu::insertContainerItems(bool folderAndDiagrams, bool packages) { KMenu* menu = newMenu(i18nc("new container menu", "New"), this); menu->setIcon(Icon_Utils::SmallIcon(Icon_Utils::it_New)); insertContainerItems(menu, folderAndDiagrams, packages); addMenu(menu); } /** * Shortcut for inserting standard model items (Class, Interface, * Datatype, Enum, Package) as well as diagram choices. * * @param menu Menu to add the menu entries * @param folderAndDiagrams Set this true if folders and diagram * types shall be included as choices. * @param packages Set this true if packages * shall be included as choices. */ void ListPopupMenu::insertContainerItems(KMenu* menu, bool folderAndDiagrams, bool packages) { if (folderAndDiagrams) insert(mt_Logical_Folder, menu, Icon_Utils::BarIcon(Icon_Utils::it_Folder), i18n("Folder")); insert(mt_Class, menu, Icon_Utils::SmallIcon(Icon_Utils::it_Class), i18nc("new class menu item", "Class")); insert(mt_Interface, menu, Icon_Utils::SmallIcon(Icon_Utils::it_Interface), i18n("Interface")); insert(mt_Datatype, menu, Icon_Utils::SmallIcon(Icon_Utils::it_Datatype), i18n("Datatype")); insert(mt_Enum, menu, Icon_Utils::SmallIcon(Icon_Utils::it_Enum), i18n("Enum")); if (packages) insert(mt_Package, menu, Icon_Utils::SmallIcon(Icon_Utils::it_Package), i18n("Package")); if (folderAndDiagrams) { insertAction(mt_Class_Diagram, menu, QLatin1String("new_class_diagram")); insertAction(mt_Sequence_Diagram, menu, QLatin1String("new_sequence_diagram")); insertAction(mt_Collaboration_Diagram, menu, QLatin1String("new_collaboration_diagram")); insertAction(mt_State_Diagram, menu, QLatin1String("new_state_diagram")); insertAction(mt_Activity_Diagram, menu, QLatin1String("new_activity_diagram")); } } /** * Inserts a menu item for an association related text * (such as name, role, multiplicity etc.) * * @param label The menu text. * @param mt The menu type. */ void ListPopupMenu::insertAssociationTextItem(const QString &label, MenuType mt) { insert(mt, label); insert(mt_Change_Font); insert(mt_Reset_Label_Positions); insert(mt_Properties); } /** * Convenience method to extract the ListPopupMenu type from an action. * @param action the action which was called * @return menu type enum value */ ListPopupMenu::MenuType ListPopupMenu::typeFromAction(QAction *action) { ListPopupMenu *menu = ListPopupMenu::menuFromAction(action); if (menu) { return menu->getMenuType(action); } else { uError() << "Action's data field does not contain ListPopupMenu pointer!"; return mt_Undefined; } } /** * Utility: Convert a MenuType value to an ObjectType value. */ UMLObject::ObjectType ListPopupMenu::convert_MT_OT(MenuType mt) { UMLObject::ObjectType type = UMLObject::ot_UMLObject; switch (mt) { case mt_UseCase: type = UMLObject::ot_UseCase; break; case mt_Actor: type = UMLObject::ot_Actor; break; case mt_Class: type = UMLObject::ot_Class; break; case mt_Datatype: type = UMLObject::ot_Datatype; break; case mt_Attribute: type = UMLObject::ot_Attribute; break; case mt_Interface: type = UMLObject::ot_Interface; break; case mt_Template: type = UMLObject::ot_Template; break; case mt_Enum: type = UMLObject::ot_Enum; break; case mt_EnumLiteral: type = UMLObject::ot_EnumLiteral; break; case mt_EntityAttribute: type = UMLObject::ot_EntityAttribute; break; case mt_Operation: type = UMLObject::ot_Operation; break; case mt_Category: type = UMLObject::ot_Category; break; case mt_InstanceAttribute: type = UMLObject::ot_InstanceAttribute; break; default: break; } return type; } /** * Returns the data from the given action to the given key. */ QVariant ListPopupMenu::dataFromAction(DataType key, QAction* action) { QVariant data = action->data(); QMap map = data.toMap(); return map[ListPopupMenu::toString(key)]; } /** * Convenience method to extract the ListPopupMenu pointer stored in QAction * objects belonging to ListPopupMenu. */ ListPopupMenu* ListPopupMenu::menuFromAction(QAction *action) { if (action) { QVariant value = dataFromAction(dt_MenuPointer, action); if (value.canConvert()) { return qvariant_cast(value); } } return 0; } /** * Create the 'new' menu * @return menu instance */ KMenu *ListPopupMenu::makeNewMenu() { KMenu *menu = newMenu(i18nc("new sub menu", "New"), this); menu->setIcon(Icon_Utils::SmallIcon(Icon_Utils::it_New)); return menu; } /** * Creates a popup menu for a single category Object * @param category The UMLCategory for which the category menu is created */ void ListPopupMenu::insertSubMenuCategoryType(UMLCategory* category) { KMenu* menu = newMenu(i18nc("category type sub menu", "Category Type"), this); insert(mt_DisjointSpecialisation, menu, i18n("Disjoint(Specialisation)"), CHECKABLE); insert(mt_OverlappingSpecialisation, menu, i18n("Overlapping(Specialisation)"), CHECKABLE); insert(mt_Union, menu, i18n("Union"), CHECKABLE); setActionChecked(mt_DisjointSpecialisation, category->getType()==UMLCategory::ct_Disjoint_Specialisation); setActionChecked(mt_OverlappingSpecialisation, category->getType()==UMLCategory::ct_Overlapping_Specialisation); setActionChecked(mt_Union, category->getType()==UMLCategory::ct_Union); addMenu(menu); } /** * Get the action from the menu type as index. */ QAction* ListPopupMenu::getAction(MenuType idx) { return m_actions.value(idx, 0); } // /** // * Get the MenuType from the action. // */ // ListPopupMenu::MenuType ListPopupMenu::getMenuType(KAction* action) // { // return m_actions.key(action); // } /** * Get the MenuType from the action. */ ListPopupMenu::MenuType ListPopupMenu::getMenuType(QAction* action) { QList keyList = m_actions.keys(action); if (keyList.empty() || /* all key-value pairs are unique*/ keyList.count() > 1) { return mt_Undefined; } else { // we return the first (only) value return keyList.first(); } } /** * Checks the action item. * * @param idx The MenuType for which to check the menu item. * @param value The value. */ void ListPopupMenu::setActionChecked(MenuType idx, bool value) { QAction* action = getAction(idx); if (action && action->isCheckable()) { action->setChecked(value); } else { DEBUG(DBG_SRC) << "called on unknown MenuType " << toString(idx); } } /** * Enables the action item. * * @param idx The MenuType for which to enable the menu item. * @param value The value. */ void ListPopupMenu::setActionEnabled(MenuType idx, bool value) { QAction* action = getAction(idx); if (action) { action->setEnabled(value); } else { DEBUG(DBG_SRC) << "called on unknown MenuType " << toString(idx); } } /** * Sets up actions added to the ListPopupMenu to have their data field set to * pointer to this ListPopupMenu object, so that this menu pointer can be * retrieved in UMLWidget::slotMenuSelection * * @note This might seem like an ugly hack, but this is the only solution which * helped in avoiding storage of ListPopupMenu pointer in each UMLWidget. */ void ListPopupMenu::setupActionsData() { foreach (QAction *action, m_actions) { QMap map = action->data().toMap(); map[toString(dt_MenuPointer)] = qVariantFromValue(this); action->setData(QVariant(map)); } } /** * Convert enum MenuType to string. */ QString ListPopupMenu::toString(MenuType menu) { return QLatin1String(ENUM_NAME(ListPopupMenu, MenuType, menu)); } /** * Convert enum DataType to string. */ QString ListPopupMenu::toString(DataType data) { return QLatin1String(ENUM_NAME(ListPopupMenu, DataType, data)); } //QList &ListPopupMenu::debugActions() //{ // return d->debugActions; //} /** * dump collected actions - * @param type optional menu type + * @param title optional menu title */ void ListPopupMenu::dumpActions(const QString &title) { qDebug().nospace() << title; foreach(DebugMenu e, d->debugActions) { if (!e.menu.isEmpty()) qDebug().nospace() << " " << e.menu; else qDebug().nospace() << " " << toString(e.m); } } diff --git a/umbrello/menus/widgetbasepopupmenu.cpp b/umbrello/menus/widgetbasepopupmenu.cpp index abd230db5..2097d17a1 100644 --- a/umbrello/menus/widgetbasepopupmenu.cpp +++ b/umbrello/menus/widgetbasepopupmenu.cpp @@ -1,678 +1,678 @@ /*************************************************************************** * 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) 2018 * * Umbrello UML Modeller Authors * ***************************************************************************/ #include "widgetbasepopupmenu.h" // app includes #include "activitywidget.h" #include "category.h" #include "classifier.h" #include "combinedfragmentwidget.h" #include "debug_utils.h" #include "entitywidget.h" #include "floatingtextwidget.h" #include "forkjoinwidget.h" #include "notewidget.h" #include "objectwidget.h" #include "objectnodewidget.h" #include "pinportbase.h" #include "statewidget.h" #include "uml.h" #include "umllistview.h" #include "umlscene.h" // kde includes #include static const bool CHECKABLE = true; /** * Constructs the popup menu for a scene widget. * * @param parent The parent to ListPopupMenu. - * @param object The WidgetBase to represent a menu for. + * @param widget The WidgetBase to represent a menu for. * @param multi True if multiple items are selected. * @param uniqueType The type of widget shared by all selected widgets */ WidgetBasePopupMenu::WidgetBasePopupMenu(QWidget * parent, WidgetBase * widget, bool multi, WidgetBase::WidgetType uniqueType) : ListPopupMenu(parent) { if (!widget) return; if (multi) { insertMultiSelectionMenu(uniqueType); } else { insertSingleSelectionMenu(widget); } bool bCutState = UMLApp::app()->isCutCopyState(); setActionEnabled(mt_Cut, bCutState); setActionEnabled(mt_Copy, bCutState); bool pasteAvailable = false; if (widget->isNoteWidget() && UMLApp::app()->listView()->startedCopy()) { NoteWidget::s_pCurrentNote = widget->asNoteWidget(); pasteAvailable = true; } setActionEnabled(mt_Paste, pasteAvailable); setActionChecked(mt_AutoResize, widget->autoResize()); setupActionsData(); } /** * Creates the "Show" submenu in the context menu of one classifier widget */ void WidgetBasePopupMenu::makeClassifierShowPopup(ClassifierWidget *c) { WidgetBase::WidgetType type = c->baseType(); KMenu* show = newMenu(i18n("Show"), this); show->setIcon(Icon_Utils::SmallIcon(Icon_Utils::it_Show)); #ifdef ENABLE_WIDGET_SHOW_DOC insert(mt_Show_Documentation, show, i18n("Documentation"), CHECKABLE); setActionChecked(mt_Show_Documentation, c->visualProperty(ClassifierWidget::ShowDocumentation)); #endif if (type == WidgetBase::wt_Class) { insert(mt_Show_Attributes, show, i18n("Attributes"), CHECKABLE); setActionChecked(mt_Show_Attributes, c->visualProperty(ClassifierWidget::ShowAttributes)); } insert(mt_Show_Operations, show, i18n("Operations"), CHECKABLE); setActionChecked(mt_Show_Operations, c->visualProperty(ClassifierWidget::ShowOperations)); insert(mt_Show_Public_Only, show, i18n("Public Only"), CHECKABLE); setActionChecked(mt_Show_Public_Only, c->visualProperty(ClassifierWidget::ShowPublicOnly)); insert(mt_Visibility, show, i18n("Visibility"), CHECKABLE); setActionChecked(mt_Visibility, c->visualProperty(ClassifierWidget::ShowVisibility)); insert(mt_Show_Operation_Signature, show, i18n("Operation Signature"), CHECKABLE); bool sig = (c->operationSignature() == Uml::SignatureType::SigNoVis || c->operationSignature() == Uml::SignatureType::ShowSig); setActionChecked(mt_Show_Operation_Signature, sig); if (type == WidgetBase::wt_Class) { insert(mt_Show_Attribute_Signature, show, i18n("Attribute Signature"), CHECKABLE); sig = (c->attributeSignature() == Uml::SignatureType::SigNoVis || c->attributeSignature() == Uml::SignatureType::ShowSig); setActionChecked(mt_Show_Attribute_Signature, sig); } insert(mt_Show_Packages, show, i18n("Package"), CHECKABLE); setActionChecked(mt_Show_Packages, c->visualProperty(ClassifierWidget::ShowPackage)); insert(mt_Show_Stereotypes, show, i18n("Stereotype"), CHECKABLE); setActionChecked(mt_Show_Stereotypes, c->visualProperty(ClassifierWidget::ShowStereotype)); addMenu(show); } /** * Creates the "Show" submenu the context menu of multiple classifier widgets */ void WidgetBasePopupMenu::makeMultiClassifierShowPopup(WidgetBase::WidgetType type) { KMenu* show = newMenu(i18n("Show"), this); show->setIcon(Icon_Utils::SmallIcon(Icon_Utils::it_Show)); KMenu* attributes = newMenu(i18n("Attributes"), this); if (type == WidgetBase::wt_Class) { insert(mt_Show_Attributes_Selection, attributes, i18n("Show")); insert(mt_Hide_Attributes_Selection, attributes, i18n("Hide")); insert(mt_Show_Attribute_Signature_Selection, attributes, i18n("Show Signatures")); insert(mt_Hide_Attribute_Signature_Selection, attributes, i18n("Hide Signatures")); } show->addMenu(attributes); KMenu* operations = newMenu(i18n("Operations"), this); insert(mt_Show_Operations_Selection, operations, i18n("Show")); insert(mt_Hide_Operations_Selection, operations, i18n("Hide")); insert(mt_Show_Operation_Signature_Selection, operations, i18n("Show Signatures")); insert(mt_Hide_Operation_Signature_Selection, operations, i18n("Hide Signatures")); show->addMenu(operations); KMenu* visibility = newMenu(i18n("Visibility"), this); insert(mt_Show_Visibility_Selection, visibility, i18n("Show")); insert(mt_Hide_Visibility_Selection, visibility, i18n("Hide")); insert(mt_Hide_NonPublic_Selection, visibility, i18n("Hide Non-public members")); insert(mt_Show_NonPublic_Selection, visibility, i18n("Show Non-public members")); show->addMenu(visibility); KMenu* packages = newMenu(i18n("Packages"), this); insert(mt_Show_Packages_Selection, packages, i18n("Show")); insert(mt_Hide_Packages_Selection, packages, i18n("Hide")); show->addMenu(packages); if (type == WidgetBase::wt_Class) { KMenu* stereotypes = newMenu(i18n("Stereotypes"), this); insert(mt_Show_Stereotypes_Selection, stereotypes, i18n("Show")); insert(mt_Hide_Stereotypes_Selection, stereotypes, i18n("Hide")); show->addMenu(stereotypes); } addMenu(show); } /** * Inserts the menu actions for a widget * * @param widget widget to generate the menu for */ void WidgetBasePopupMenu::insertSingleSelectionMenu(WidgetBase* widget) { WidgetBase::WidgetType type = widget->baseType(); switch (type) { case WidgetBase::wt_Actor: case WidgetBase::wt_UseCase: insertSubMenuNew(type); insertSubMenuColor(widget->useFillColor()); insertStdItems(true, type); insert(mt_Rename); insert(mt_Change_Font); insert(mt_Properties); break; case WidgetBase::wt_Category: { insertSubMenuNew(type); insertSubMenuCategoryType(widget->umlObject()->asUMLCategory()); insertSubMenuColor(widget->useFillColor()); insertStdItems(true, type); insert(mt_Rename); insert(mt_Change_Font); break; } case WidgetBase::wt_Class: { ClassifierWidget* c = widget->asClassifierWidget(); if (!c) break; insertSubMenuNew(type); makeClassifierShowPopup(c); insertSubMenuColor(c->useFillColor()); insertStdItems(true, type); insert(mt_Rename); insert(mt_Change_Font); if (c->umlObject() && c->umlObject()->stereotype() == QLatin1String("class-or-package")) { insert(mt_ChangeToClass, i18n("Change into Class")); insert(mt_ChangeToPackage, i18n("Change into Package")); } else { insert(mt_Refactoring, Icon_Utils::SmallIcon(Icon_Utils::it_Refactor), i18n("Refactor")); insert(mt_ViewCode, Icon_Utils::SmallIcon(Icon_Utils::it_View_Code), i18n("View Code")); UMLClassifier *umlc = c->classifier(); if (umlc->isAbstract() && umlc->getAttributeList().size() == 0) insert(mt_ChangeToInterface, i18n("Change into Interface")); } insert(mt_Properties); } break; case WidgetBase::wt_Interface: { ClassifierWidget* c = widget->asClassifierWidget(); if (!c) break; insertSubMenuNew(type); makeClassifierShowPopup(c); insertSubMenuColor(c->useFillColor()); insertStdItems(true, type); insert(mt_Rename); insert(mt_Change_Font); insert(mt_DrawAsCircle, i18n("Draw as Circle"), CHECKABLE); setActionChecked(mt_DrawAsCircle, c->visualProperty(ClassifierWidget::DrawAsCircle)); insert(mt_ChangeToClass, i18n("Change into Class")); insert(mt_Properties); } break; case WidgetBase::wt_Instance: insertSubMenuNew(type); insert(mt_InstanceAttribute); insert(mt_Rename_Object); insert(mt_Rename, i18n("Rename Class...")); insertStdItems(true, type); insert(mt_Change_Font); insert(mt_Properties); break; case WidgetBase::wt_Enum: insertSubMenuNew(type); insertSubMenuColor(widget->useFillColor()); insertStdItems(true, type); insert(mt_Rename); insert(mt_Change_Font); insert(mt_Properties); break; case WidgetBase::wt_Entity: insertSubMenuNew(type); insertSubMenuShowEntity(widget->asEntityWidget()); insertSubMenuColor(widget->useFillColor()); insertStdItems(true, type); insert(mt_Rename); insert(mt_Change_Font); insert(mt_Properties); break; case WidgetBase::wt_Datatype: case WidgetBase::wt_Package: case WidgetBase::wt_Component: case WidgetBase::wt_Node: case WidgetBase::wt_Artifact: insertSubMenuNew(type); insertSubMenuColor(widget->useFillColor()); insertStdItems(false, type); insert(mt_Rename); insert(mt_Change_Font); insert(mt_Properties); break; case WidgetBase::wt_Port: insertSubMenuNew(type); insertSubMenuColor(widget->useFillColor()); insertStdItems(false); insert(mt_NameAsTooltip, i18n("Name as Tooltip"), true); { PinPortBase *pW = static_cast(widget); FloatingTextWidget *ft = pW->floatingTextWidget(); if (ft == 0) m_actions[mt_NameAsTooltip]->setChecked(true); } insert(mt_Properties); break; case WidgetBase::wt_Object: //Used for sequence diagram and collaboration diagram widgets insertSubMenuNew(type); insertSubMenuColor(widget->useFillColor()); if (widget->umlScene() && widget->umlScene()->type() == Uml::DiagramType::Sequence) { addSeparator(); MenuType tabUp = mt_Up; insert(mt_Up, Icon_Utils::SmallIcon(Icon_Utils::it_Arrow_Up), i18n("Move Up")); insert(mt_Down, Icon_Utils::SmallIcon(Icon_Utils::it_Arrow_Down), i18n("Move Down")); if (!(static_cast(widget))->canTabUp()) { setActionEnabled(tabUp, false); } } insertStdItems(true, type); insert(mt_Rename_Object); insert(mt_Rename, i18n("Rename Class...")); insert(mt_Change_Font); insert(mt_Properties); break; case WidgetBase::wt_Message: insertSubMenuNew(type); insertSubMenuColor(widget->useFillColor()); insertStdItems(false, type); //insert(mt_Change_Font); //insert(mt_Operation, Icon_Utils::SmallIcon(Icon_Utils::it_Operation_New), i18n("New Operation...")); //insert(mt_Select_Operation, i18n("Select Operation...")); break; case WidgetBase::wt_Note: insertSubMenuNew(type); insertSubMenuColor(widget->useFillColor()); addSeparator(); insert(mt_Cut); insert(mt_Copy); insert(mt_Paste); insert(mt_Clear, Icon_Utils::SmallIcon(Icon_Utils::it_Clear), i18nc("clear note", "Clear")); addSeparator(); insert(mt_Rename, i18n("Change Text...")); insert(mt_Resize); insert(mt_AutoResize, i18n("Auto resize"), CHECKABLE); insert(mt_Delete); insert(mt_Change_Font); insert(mt_Properties); break; case WidgetBase::wt_Box: insertSubMenuNew(type); insertStdItems(false, type); insert(mt_Line_Color); break; case WidgetBase::wt_State: { StateWidget* pState = static_cast< StateWidget *>(widget); if (pState->stateType() == StateWidget::Normal) { // FIXME: why not using wt_state insertSubMenuNew(WidgetBase::wt_Activity); } else { insertSubMenuNew(type); } insertSubMenuColor(widget->useFillColor()); insertStdItems(false, type); switch (pState->stateType()) { case StateWidget::Normal: insert(mt_Rename, i18n("Change State Name...")); insert(mt_Change_Font); insert(mt_Properties); break; case StateWidget::Fork: case StateWidget::Join: if (pState->drawVertical()) insert(mt_Flip, i18n("Flip Horizontal")); else insert(mt_Flip, i18n("Flip Vertical")); break; default: break; } } break; case WidgetBase::wt_ForkJoin: insertSubMenuNew(type); { ForkJoinWidget *pForkJoin = static_cast(widget); if (pForkJoin->orientation() == Qt::Vertical) { insert(mt_Flip, i18n("Flip Horizontal")); } else { insert(mt_Flip, i18n("Flip Vertical")); } m_actions[mt_Fill_Color] = addAction(Icon_Utils::SmallIcon(Icon_Utils::it_Color_Fill), i18n("Fill Color...")); } break; case WidgetBase::wt_Activity: insertSubMenuNew(type); { ActivityWidget* pActivity = static_cast(widget); if(pActivity->activityType() == ActivityWidget::Normal || pActivity->activityType() == ActivityWidget::Invok || pActivity->activityType() == ActivityWidget::Param) { insertSubMenuColor(widget->useFillColor()); } insertStdItems(false, type); if(pActivity->activityType() == ActivityWidget::Normal || pActivity->activityType() == ActivityWidget::Invok || pActivity->activityType() == ActivityWidget::Param) { insert(mt_Rename, i18n("Change Activity Name...")); insert(mt_Change_Font); insert(mt_Properties); } } break; case WidgetBase::wt_ObjectNode: insertSubMenuNew(type); { ObjectNodeWidget* objWidget = static_cast(widget); if (objWidget->objectNodeType() == ObjectNodeWidget::Buffer || objWidget->objectNodeType() == ObjectNodeWidget::Data || objWidget->objectNodeType() == ObjectNodeWidget::Flow) { insertSubMenuColor(widget->useFillColor()); } insertStdItems(false, type); if (objWidget->objectNodeType() == ObjectNodeWidget::Buffer || objWidget->objectNodeType() == ObjectNodeWidget::Data || objWidget->objectNodeType() == ObjectNodeWidget::Flow) { insert(mt_Rename, i18n("Change Object Node Name...")); insert(mt_Change_Font); insert(mt_Properties); } } break; case WidgetBase::wt_Pin: case WidgetBase::wt_Signal: case WidgetBase::wt_FloatingDashLine: case WidgetBase::wt_Precondition: insertSubMenuNew(type); insertSubMenuColor(widget->useFillColor()); addSeparator(); insert(mt_Cut); insert(mt_Copy); insert(mt_Paste); insert(mt_Clear, Icon_Utils::SmallIcon(Icon_Utils::it_Clear), i18nc("clear precondition", "Clear")); addSeparator(); insert(mt_Rename, i18n("Change Text...")); if (type == WidgetBase::wt_Pin) { insert(mt_NameAsTooltip, i18n("Name as Tooltip"), true); PinPortBase *pW = static_cast(widget); FloatingTextWidget *ft = pW->floatingTextWidget(); if (ft == 0) m_actions[mt_NameAsTooltip]->setChecked(true); } insert(mt_Delete); insert(mt_Change_Font); break; case WidgetBase::wt_CombinedFragment: insertSubMenuNew(type); // for alternative and parallel combined fragments if ((static_cast(widget))->combinedFragmentType() == CombinedFragmentWidget::Alt || (static_cast(widget))->combinedFragmentType() == CombinedFragmentWidget::Par) { insert(mt_AddInteractionOperand, i18n("Add Interaction Operand")); addSeparator(); } insertSubMenuColor(widget->useFillColor()); addSeparator(); insert(mt_Cut); insert(mt_Copy); insert(mt_Paste); insert(mt_Clear, Icon_Utils::SmallIcon(Icon_Utils::it_Clear), i18nc("clear combined fragment", "Clear")); addSeparator(); insert(mt_Rename, i18n("Change Text...")); insert(mt_Delete); insert(mt_Change_Font); break; case WidgetBase::wt_Text: insertSubMenuNew(type); switch((static_cast(widget))->textRole()) { case Uml::TextRole::MultiB: insertAssociationTextItem(i18n("Change Multiplicity..."), mt_Rename_MultiB); break; case Uml::TextRole::MultiA: insertAssociationTextItem(i18n("Change Multiplicity..."), mt_Rename_MultiA); break; case Uml::TextRole::Name: insertAssociationTextItem(i18n("Change Name"), mt_Rename_Name); break; case Uml::TextRole::RoleAName: insertAssociationTextItem(i18n("Change Role A Name..."), mt_Rename_RoleAName); break; case Uml::TextRole::RoleBName: insertAssociationTextItem(i18n("Change Role B Name..."), mt_Rename_RoleBName); break; case Uml::TextRole::ChangeA: case Uml::TextRole::ChangeB: insert(mt_Change_Font); insert(mt_Reset_Label_Positions); insert(mt_Properties); break; case Uml::TextRole::Coll_Message_Self: case Uml::TextRole::Coll_Message: case Uml::TextRole::Seq_Message_Self: case Uml::TextRole::Seq_Message: insert(mt_Change_Font); insert(mt_Operation, Icon_Utils::SmallIcon(Icon_Utils::it_Operation_New), i18n("New Operation...")); insert(mt_Select_Operation, i18n("Select Operation...")); break; case Uml::TextRole::Floating: default: insertStdItems(false, type); insert(mt_Rename, i18n("Change Text...")); insert(mt_Change_Font); break; } break; default: uWarning() << "unhandled WidgetType " << WidgetBase::toString(type); break; }//end switch } /** * Inserts the menu actions that work on the whole selection of widgets */ void WidgetBasePopupMenu::insertMultiSelectionMenu(WidgetBase::WidgetType uniqueType) { insertSubMenuAlign(); KMenu* color = newMenu(i18nc("color menu", "Color"), this); insert(mt_Line_Color_Selection, color, Icon_Utils::SmallIcon(Icon_Utils::it_Color_Line), i18n("Line Color...")); insert(mt_Fill_Color_Selection, color, Icon_Utils::SmallIcon(Icon_Utils::it_Color_Fill), i18n("Fill Color...")); insert(mt_Set_Use_Fill_Color_Selection, color, i18n("Use Fill Color")); insert(mt_Unset_Use_Fill_Color_Selection, color, i18n("No Fill Color")); // Add menu actions specific to classifiers if (uniqueType == WidgetBase::wt_Class || uniqueType == WidgetBase::wt_Interface) { makeMultiClassifierShowPopup(uniqueType); } addMenu(color); addSeparator(); insert(mt_Cut); insert(mt_Copy); addSeparator(); insert(mt_Clone); insert(mt_Delete); insert(mt_Resize); addSeparator(); insert(mt_Change_Font_Selection, Icon_Utils::SmallIcon(Icon_Utils::it_Change_Font), i18n("Change Font...")); } /** * Shortcut for the frequently used insert() calls. * * @param insertLeadingSeparator Set this true if the group shall * start with a separator. * @param type The WidgetType for which to insert the menu items. * If no argument is supplied then a Rename item will be * included. */ void WidgetBasePopupMenu::insertStdItems(bool insertLeadingSeparator, WidgetBase::WidgetType type) { if (insertLeadingSeparator) addSeparator(); insert(mt_Cut); insert(mt_Copy); insert(mt_Paste); addSeparator(); if (type == WidgetBase::wt_UMLWidget) insert(mt_Rename); else if (Model_Utils::isCloneable(type)) { insert(mt_Clone); insert(mt_Remove); } else insert(mt_Delete); insert(mt_Resize); insert(mt_AutoResize, i18n("Auto resize"), CHECKABLE); } /** * Add the align actions submenu */ void WidgetBasePopupMenu::insertSubMenuAlign() { KMenu* alignment = newMenu(i18nc("align menu", "Align"), this); insert(mt_Align_Right, alignment, Icon_Utils::SmallIcon(Icon_Utils::it_Align_Right), i18n("Align Right")); insert(mt_Align_Left, alignment, Icon_Utils::SmallIcon(Icon_Utils::it_Align_Left), i18n("Align Left")); insert(mt_Align_Top, alignment, Icon_Utils::SmallIcon(Icon_Utils::it_Align_Top), i18n("Align Top")); insert(mt_Align_Bottom, alignment, Icon_Utils::SmallIcon(Icon_Utils::it_Align_Bottom), i18n("Align Bottom")); insert(mt_Align_VerticalMiddle, alignment, Icon_Utils::SmallIcon(Icon_Utils::it_Align_VerticalMiddle), i18n("Align Vertical Middle")); insert(mt_Align_HorizontalMiddle, alignment, Icon_Utils::SmallIcon(Icon_Utils::it_Align_HorizontalMiddle), i18n("Align Horizontal Middle")); insert(mt_Align_VerticalDistribute, alignment, Icon_Utils::SmallIcon(Icon_Utils::it_Align_VerticalDistribute), i18n("Align Vertical Distribute")); insert(mt_Align_HorizontalDistribute, alignment, Icon_Utils::SmallIcon(Icon_Utils::it_Align_HorizontalDistribute), i18n("Align Horizontal Distribute")); addMenu(alignment); } /** * Shortcut for commonly used sub menu initializations. * * @param fc The "Use Fill Color" is checked. */ void WidgetBasePopupMenu::insertSubMenuColor(bool fc) { KMenu* color = newMenu(i18nc("color menu", "Color"), this); insert(mt_Line_Color, color, Icon_Utils::SmallIcon(Icon_Utils::it_Color_Line), i18n("Line Color...")); insert(mt_Fill_Color, color, Icon_Utils::SmallIcon(Icon_Utils::it_Color_Fill), i18n("Fill Color...")); insert(mt_Use_Fill_Color, color, i18n("Use Fill Color"), CHECKABLE); setActionChecked(mt_Use_Fill_Color, fc); addMenu(color); } /** * Shortcut for commonly used sub menu initializations. * * @param type The widget type for which to set up the menu. */ void WidgetBasePopupMenu::insertSubMenuNew(WidgetBase::WidgetType type, KMenu *menu) { if (!menu) menu = makeNewMenu(); switch (type) { case WidgetBase::wt_Actor: case WidgetBase::wt_UseCase: insert(mt_Actor, menu); insert(mt_UseCase, menu); break; case WidgetBase::wt_Component: insert(mt_Component, menu); if (Settings::optionState().generalState.uml2) insert(mt_Port, menu); insert(mt_Artifact, menu); break; case WidgetBase::wt_Class: insert(mt_Attribute, menu, Icon_Utils::SmallIcon(Icon_Utils::it_Public_Attribute), i18n("Attribute...")); insert(mt_Operation, menu, Icon_Utils::SmallIcon(Icon_Utils::it_Public_Method), i18n("Operation...")); insert(mt_Template, menu, Icon_Utils::SmallIcon(Icon_Utils::it_Template_New), i18n("Template...")); insert(mt_Class, menu, Icon_Utils::SmallIcon(Icon_Utils::it_Class), i18nc("new class menu item", "Class...")); insert(mt_Interface, menu, Icon_Utils::SmallIcon(Icon_Utils::it_Interface), i18n("Interface...")); insert(mt_Datatype, menu, Icon_Utils::SmallIcon(Icon_Utils::it_Datatype), i18n("Datatype...")); insert(mt_Enum, menu, Icon_Utils::SmallIcon(Icon_Utils::it_Enum), i18n("Enum...")); break; case WidgetBase::wt_Interface: insert(mt_Operation, menu, Icon_Utils::SmallIcon(Icon_Utils::it_Public_Method), i18n("Operation...")); insert(mt_Template, menu, Icon_Utils::SmallIcon(Icon_Utils::it_Template_New), i18n("Template...")); insert(mt_Class, menu, Icon_Utils::SmallIcon(Icon_Utils::it_Class), i18nc("new class menu item", "Class...")); insert(mt_Interface, menu, Icon_Utils::SmallIcon(Icon_Utils::it_Interface), i18n("Interface...")); insert(mt_Datatype, menu, Icon_Utils::SmallIcon(Icon_Utils::it_Datatype), i18n("Datatype...")); insert(mt_Enum, menu, Icon_Utils::SmallIcon(Icon_Utils::it_Enum), i18n("Enum...")); break; case WidgetBase::wt_Entity: insert(mt_EntityAttribute, menu); insert(mt_PrimaryKeyConstraint, menu); insert(mt_UniqueConstraint, menu); insert(mt_ForeignKeyConstraint, menu); insert(mt_CheckConstraint, menu); break; case WidgetBase::wt_Enum: insert(mt_EnumLiteral, menu); break; default: break; } insert(mt_Note, menu); insert(mt_FloatText, menu); addMenu(menu); } void WidgetBasePopupMenu::insertSubMenuShowEntity(EntityWidget *widget) { KMenu* show = newMenu(i18n("Show"), this); show->setIcon(Icon_Utils::SmallIcon(Icon_Utils::it_Show)); insert(mt_Show_Attribute_Signature, show, i18n("Attribute Signature"), CHECKABLE); setActionChecked(mt_Show_Attribute_Signature, widget->showAttributeSignature()); insert(mt_Show_Stereotypes, show, i18n("Stereotype"), CHECKABLE); setActionChecked(mt_Show_Stereotypes, widget->showStereotype()); addMenu(show); } diff --git a/umbrello/model_utils.cpp b/umbrello/model_utils.cpp index 30b7a8d55..9fa7f3efa 100644 --- a/umbrello/model_utils.cpp +++ b/umbrello/model_utils.cpp @@ -1,2095 +1,2095 @@ /*************************************************************************** * 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; } /** * 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; case UMLListViewItem::lvt_Component: case UMLListViewItem::lvt_Port: return parentType == UMLListViewItem::lvt_Component_Folder || 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; case UMLListViewItem::lvt_Entity: case UMLListViewItem::lvt_EntityRelationship_Diagram: case UMLListViewItem::lvt_Category: return parentType == UMLListViewItem::lvt_EntityRelationship_Folder; 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 lvt The ListViewType to convert. + * @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/refactoring/refactoringassistant.cpp b/umbrello/refactoring/refactoringassistant.cpp index 4a4059a67..90619c8bf 100644 --- a/umbrello/refactoring/refactoringassistant.cpp +++ b/umbrello/refactoring/refactoringassistant.cpp @@ -1,867 +1,865 @@ /*************************************************************************** * 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 Luis De la Parra * * copyright (C) 2004-2014 * * Umbrello UML Modeller Authors * ***************************************************************************/ #include "refactoringassistant.h" #include "attribute.h" #include "basictypes.h" #include "classifier.h" #include "classpropertiesdialog.h" #include "debug_utils.h" #include "object_factory.h" #include "operation.h" #include "umlattributedialog.h" #include "umldoc.h" #include "umloperationdialog.h" #include #include #include #include #include DEBUG_REGISTER(RefactoringAssistant) /** * Constructor. * @param doc the UML document * @param obj the UML classifier to refactor * @param parent the parent widget * @param name the object name */ RefactoringAssistant::RefactoringAssistant(UMLDoc *doc, UMLClassifier *obj, QWidget *parent, const QString &name) : QTreeWidget(parent), m_doc(doc) { setObjectName(name); setRootIsDecorated(true); setAcceptDrops(true); setDropIndicatorShown(true); setSelectionMode(QAbstractItemView::SingleSelection); setDragEnabled(true); setHeaderLabel(i18n("Name")); setContextMenuPolicy(Qt::CustomContextMenu); m_menu = new QMenu(this); connect(this, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), this, SLOT(itemExecuted(QTreeWidgetItem*,int))); connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); resize(300, 400); refactor(obj); } /** * Destructor. */ RefactoringAssistant::~RefactoringAssistant() { m_umlObjectMap.clear(); clear(); } /** * Builds up the tree for the classifier. * @param obj the classifier which has to be refactored */ void RefactoringAssistant::refactor(UMLClassifier *obj) { clear(); m_umlObjectMap.clear(); m_umlObject = obj; if (! m_umlObject) { return; } DEBUG(DBG_SRC) << "called for " << m_umlObject->name(); m_alreadySeen.clear(); addClassifier(obj, 0, true, true, true); QTreeWidgetItem *item = topLevelItem(0); item->setExpanded(true); for (int i = 0; i < item->childCount(); ++i) { item->setExpanded(true); } } /** * Find UML object from tree item. * @param item the item from the tree widget * @return the UML object behind the item */ UMLObject* RefactoringAssistant::findUMLObject(const QTreeWidgetItem *item) { if (!item) { return 0; } QTreeWidgetItem *i = const_cast(item); if (m_umlObjectMap.find(i) == m_umlObjectMap.end()) { uWarning() << "Item with text " << item->text(0) << "not found in uml map!"; return 0; } return m_umlObjectMap[i]; } /** * Find tree item from UML object. * @param obj the UML object to search in tree * @return the found tree widget item or 0 */ QTreeWidgetItem* RefactoringAssistant::findListViewItem(const UMLObject *obj) { QMapIterator it(m_umlObjectMap); while (it.hasNext()) { it.next(); if (it.value() == obj) { return it.key(); } } uWarning() << "Object id " << Uml::ID::toString(obj->id()) << "does not have an item in the tree"; return 0; } /** * Slot for double clicking on a tree widget item. * @param item tree widget item on which the user clicked * @param column the column of the tree on which the user clicked. */ void RefactoringAssistant::itemExecuted(QTreeWidgetItem *item, int column) { Q_UNUSED(column); UMLObject *obj = findUMLObject(item); if (obj) { editProperties(); } } /** * Set the icon representing the visibility of the given item. * @param item the tree item * @param obj the UML object behind the tree item */ void RefactoringAssistant::setVisibilityIcon(QTreeWidgetItem *item, const UMLObject *obj) { UMLObject::ObjectType t = obj->baseType(); switch (obj->visibility()) { case Uml::Visibility::Public: if (t == UMLObject::ot_Operation) { item->setIcon(0, Icon_Utils::SmallIcon(Icon_Utils::it_Public_Method)); } else { item->setIcon(0, Icon_Utils::SmallIcon(Icon_Utils::it_Public_Attribute)); } break; case Uml::Visibility::Protected: if (t == UMLObject::ot_Operation) { item->setIcon(0, Icon_Utils::SmallIcon(Icon_Utils::it_Protected_Method)); } else { item->setIcon(0, Icon_Utils::SmallIcon(Icon_Utils::it_Protected_Attribute)); } break; case Uml::Visibility::Private: if (t == UMLObject::ot_Operation) { item->setIcon(0, Icon_Utils::SmallIcon(Icon_Utils::it_Private_Method)); } else { item->setIcon(0, Icon_Utils::SmallIcon(Icon_Utils::it_Private_Attribute)); } break; case Uml::Visibility::Implementation: if (t == UMLObject::ot_Operation) { item->setIcon(0, Icon_Utils::SmallIcon(Icon_Utils::it_Implementation_Method)); } else { item->setIcon(0, Icon_Utils::SmallIcon(Icon_Utils::it_Implementation_Attribute)); } break; default: break; } } /** * Slot for updating the tree item properties according to the given UML object. - * If no parameter is given the sender is used. - * @param obj the UML object */ void RefactoringAssistant::objectModified() { const UMLObject *obj = dynamic_cast(sender()); if (!obj) return; QTreeWidgetItem *item = findListViewItem(obj); if (!item) { return; } item->setText(0, obj->name()); if (typeid(*obj) == typeid(UMLOperation) || typeid(*obj) == typeid(UMLAttribute)) { setVisibilityIcon(item, obj); } } /** * Slot for adding an operation to the tree. * @param listItem the new operation to add */ void RefactoringAssistant::operationAdded(UMLClassifierListItem *listItem) { UMLOperation *op = listItem->asUMLOperation(); DEBUG(DBG_SRC) << "operation = " << op->name(); //:TODO: UMLClassifier *parent = op->umlParent()->asUMLClassifier(); if (!parent) { uWarning() << op->name() << " - Parent of operation is not a classifier!"; return; } QTreeWidgetItem *item = findListViewItem(parent); if (!item) { return; } for (int i = 0; i < item->childCount(); ++i) { QTreeWidgetItem *folder = item->child(i); if (folder->text(1) == QLatin1String("operations")) { item = new QTreeWidgetItem(folder, QStringList(op->name())); m_umlObjectMap[item] = op; connect(op, SIGNAL(modified()), this, SLOT(objectModified())); setVisibilityIcon(item, op); DEBUG(DBG_SRC) << "operation = " << op->name() << " added!"; //:TODO: break; } } } /** * Slot for removing an operation from the tree. * @param listItem the operation to be removed */ void RefactoringAssistant::operationRemoved(UMLClassifierListItem *listItem) { UMLOperation *op = listItem->asUMLOperation(); QTreeWidgetItem *item = findListViewItem(op); if (!item) { return; } disconnect(op, SIGNAL(modified()), this, SLOT(objectModified())); m_umlObjectMap.remove(item); delete item; } /** * Slot for adding an attribute to the tree. * @param listItem the new attribute to add */ void RefactoringAssistant::attributeAdded(UMLClassifierListItem *listItem) { UMLAttribute *att = listItem->asUMLAttribute(); DEBUG(DBG_SRC) << "attribute = " << att->name(); //:TODO: UMLClassifier *parent = att->umlParent()->asUMLClassifier(); if (!parent) { uWarning() << att->name() << " - Parent of attribute is not a classifier!"; return; } QTreeWidgetItem *item = findListViewItem(parent); if (!item) { uWarning() << "Parent is not in tree!"; return; } for (int i = 0; i < item->childCount(); ++i) { QTreeWidgetItem *folder = item->child(i); if (folder->text(1) == QLatin1String("attributes")) { item = new QTreeWidgetItem(folder, QStringList(att->name())); m_umlObjectMap[item] = att; connect(att, SIGNAL(modified()), this, SLOT(objectModified())); setVisibilityIcon(item, att); DEBUG(DBG_SRC) << "attribute = " << att->name() << " added!"; //:TODO: break; } } } /** * Slot for removing an attribute from the tree. * @param listItem the attribute to be removed */ void RefactoringAssistant::attributeRemoved(UMLClassifierListItem *listItem) { UMLAttribute *att = listItem->asUMLAttribute(); DEBUG(DBG_SRC) << "attribute = " << att->name(); //:TODO: QTreeWidgetItem *item = findListViewItem(att); if (!item) { uWarning() << "Attribute is not in tree!"; return; } disconnect(att, SIGNAL(modified()), this, SLOT(objectModified())); m_umlObjectMap.remove(item); delete item; DEBUG(DBG_SRC) << "attribute = " << att->name() << " deleted!"; //:TODO: } /** * Slot for calling editProperties with the current item. */ void RefactoringAssistant::editProperties() { QTreeWidgetItem* item = currentItem(); if (item) { UMLObject* obj = findUMLObject(item); if (obj) { editProperties(obj); } } } /** * Show the dialog with data from the given UML object. * @param obj the UML object to edit */ void RefactoringAssistant::editProperties(UMLObject *obj) { #if QT_VERSION >= 0x050000 QDialog *dia(0); #else KDialog *dia(0); #endif UMLObject::ObjectType t = obj->baseType(); if (t == UMLObject::ot_Class || t == UMLObject::ot_Interface) { ClassPropertiesDialog *dialog = new ClassPropertiesDialog(this, obj, true); if (dialog && dialog->exec()) { // need to update something? } delete dialog; } else if (t == UMLObject::ot_Operation) { dia = new UMLOperationDialog(this, obj->asUMLOperation()); } else if (t == UMLObject::ot_Attribute) { dia = new UMLAttributeDialog(this, obj->asUMLAttribute()); } else { uWarning() << "Called for unknown type " << typeid(*obj).name(); return; } if (dia && dia->exec()) { // need to update something? } delete dia; } /** * Slot for deleting an item called from the popup menu. */ void RefactoringAssistant::deleteItem() { QTreeWidgetItem *item = currentItem(); if (item) { UMLObject *o = findUMLObject(item); if (o) { deleteItem(item, o); } } } /** * Delete an item from the tree. * @param item the tree widget item * @param obj the uml object */ void RefactoringAssistant::deleteItem(QTreeWidgetItem *item, UMLObject *obj) { UMLObject::ObjectType t = obj->baseType(); if (t == UMLObject::ot_Class || t == UMLObject::ot_Interface) { DEBUG(DBG_SRC) << "Delete class or interface - not yet implemented!"; //:TODO: } else if (t == UMLObject::ot_Operation) { QTreeWidgetItem *opNode = item->parent(); if (opNode) { QTreeWidgetItem *parent = opNode->parent(); UMLClassifier* c = findUMLObject(parent)->asUMLClassifier(); if (!c) { uWarning() << "No classifier - cannot delete!"; return; } UMLOperation* op = obj->asUMLOperation(); c->removeOperation(op); } } else if (t == UMLObject::ot_Attribute) { QTreeWidgetItem *attrNode = item->parent(); if (attrNode) { QTreeWidgetItem *parent = attrNode->parent(); UMLClassifier* c = findUMLObject(parent)->asUMLClassifier(); if (!c) { uWarning() << "No classifier - cannot delete!"; return; } UMLAttribute* attr = obj->asUMLAttribute(); c->removeAttribute(attr); } } else { uWarning() << "Called for unknown type " << typeid(*obj).name(); } } /** * Create an action for an entry in the context menu. * @param text the text of the action * @param method the method to call when triggered * @param icon the shown icon * @return the created action */ QAction* RefactoringAssistant::createAction(const QString& text, const char * method, const Icon_Utils::IconType icon) { Q_UNUSED(method); QAction* action = new QAction(this); action->setText(text); if (icon != Icon_Utils::N_ICONTYPES) { action->setIcon(Icon_Utils::SmallIcon(icon)); } return action; } /** * Slot for the context menu by right clicking in the tree widget. * @param p point of the right click inside the tree widget */ void RefactoringAssistant::showContextMenu(const QPoint& p) { QTreeWidgetItem* item = itemAt(p); if (!item) { return; } m_menu->clear(); UMLObject *obj = findUMLObject(item); if (obj) { // Menu for UMLObjects UMLObject::ObjectType t = obj->baseType(); if (t == UMLObject::ot_Class) { m_menu->addAction(createAction(i18n("Add Base Class"), SLOT(addBaseClassifier()), Icon_Utils::it_Generalisation)); m_menu->addAction(createAction(i18n("Add Derived Class"), SLOT(addDerivedClassifier()), Icon_Utils::it_Uniassociation)); // m_menu->addAction(createAction(i18n("Add Interface Implementation"), SLOT(addInterfaceImplementation()), Icon_Utils::it_Implementation)); m_menu->addAction(createAction(i18n("Add Operation"), SLOT(createOperation()), Icon_Utils::it_Public_Method)); m_menu->addAction(createAction(i18n("Add Attribute"), SLOT(createAttribute()), Icon_Utils::it_Public_Attribute)); } else if (t == UMLObject::ot_Interface) { m_menu->addAction(createAction(i18n("Add Base Interface"), SLOT(addSuperClassifier()), Icon_Utils::it_Generalisation)); m_menu->addAction(createAction(i18n("Add Derived Interface"), SLOT(addDerivedClassifier()), Icon_Utils::it_Uniassociation)); m_menu->addAction(createAction(i18n("Add Operation"), SLOT(createOperation()), Icon_Utils::it_Public_Method)); } // else { // DEBUG(DBG_SRC) << "No context menu for objects of type " << typeid(*obj).name(); // return; // } m_menu->addSeparator(); m_menu->addAction(createAction(i18n("Properties"), SLOT(editProperties()), Icon_Utils::it_Properties)); m_menu->addAction(createAction(i18n("Delete"), SLOT(deleteItem()), Icon_Utils::it_Delete)); } else { //menu for other ViewItems if (item->text(1) == QLatin1String("operations")) { m_menu->addAction(createAction(i18n("Add Operation"), SLOT(createOperation()), Icon_Utils::it_Public_Method)); } else if (item->text(1) == QLatin1String("attributes")) { m_menu->addAction(createAction(i18n("Add Attribute"), SLOT(createAttribute()), Icon_Utils::it_Public_Attribute)); } else { uWarning() << "Called for unsupported item."; return; } } m_menu->exec(mapToGlobal(p) + QPoint(0, 20)); } /** * Slot for adding a base classifier. */ void RefactoringAssistant::addBaseClassifier() { QTreeWidgetItem *item = currentItem(); if (!item) { uWarning() << "Called with no item selected"; return; } UMLObject *obj = findUMLObject(item); if (!obj->asUMLClassifier()) { uWarning() << "Called for a non-classifier object."; return; } //classes have classes and interfaces interfaces as super/derived classifiers UMLObject::ObjectType t = obj->baseType(); UMLClassifier *super = Object_Factory::createUMLObject(t)->asUMLClassifier(); if (!super) { return; } m_doc->createUMLAssociation(obj, super, Uml::AssociationType::Generalization); ////////////////////// Manually add the classifier to the assitant - would be nicer to do it with ///////////////////// a signal, like operations and attributes QTreeWidgetItem *baseFolder = 0; for (int i = 0; i < item->childCount(); ++i) { baseFolder = item->child(i); if (!baseFolder) { uWarning() << "Cannot find base folder!"; return; } if (baseFolder->text(0) == i18n("Base Classifiers")) { item = new QTreeWidgetItem(baseFolder, QStringList(super->name())); item->setIcon(0, Icon_Utils::SmallIcon(Icon_Utils::it_Generalisation)); item->setExpanded(true); m_umlObjectMap[item] = super; addClassifier(super, item, true, false, true); break; } } ///////////////////////// } /** * Slot for adding a derived classifier. */ void RefactoringAssistant::addDerivedClassifier() { QTreeWidgetItem *item = currentItem(); if (!item) { uWarning() << "Called with no item selected."; return; } UMLObject *obj = findUMLObject(item); if (!obj->asUMLClassifier()) { uWarning() << "Called for a non-classifier object."; return; } //classes have classes and interfaces have interfaces as super/derived classifiers UMLObject::ObjectType t = obj->baseType(); UMLClassifier *derived = Object_Factory::createUMLObject(t)->asUMLClassifier(); if (!derived) { return; } m_doc->createUMLAssociation(derived, obj, Uml::AssociationType::Generalization); ////////////////////// Manually add the classifier to the assitant - would be nicer to do it with ///////////////////// a signal, like operations and attributes QTreeWidgetItem *derivedFolder = 0; for (int i = 0; i < item->childCount(); ++i) { derivedFolder = item->child(i); if (!derivedFolder) { uWarning() << "Cannot find derived folder!"; return; } if (derivedFolder->text(0) == i18n("Derived Classifiers")) { item = new QTreeWidgetItem(derivedFolder, QStringList(derived->name())); item->setIcon(0, Icon_Utils::SmallIcon(Icon_Utils::it_Uniassociation)); item->setExpanded(true); m_umlObjectMap[item] = derived; addClassifier(derived, item, false, true, true); } } ///////////////////////// } /** * Slot for adding an interface implementation. * @todo not yet implemented, needs addSuperClassifier() first */ void RefactoringAssistant::addInterfaceImplementation() { uWarning() << "Not implemented... finish addSuperClassifier() first!!"; return; // QTreeWidgetItem *item = selectedListViewItem(); // UMLObject *obj = findUMLObject(item); // if(!obj->asUMLClassifier()) // return; // UMLObject *n = Object_Factory::createUMLObject(UMLObject::ot_Interface)); // if (!n) { // return; // } // m_doc->createUMLAssociation(n, obj, UMLObject::at_Realization); // //refresh, add classifier to assistant } /** * Create new operation. */ void RefactoringAssistant::createOperation() { QTreeWidgetItem *item = currentItem(); if (!item) { uWarning() << "Called with no item selected."; return; } UMLClassifier *c = findUMLObject(item)->asUMLClassifier(); if (!c) { // find parent QTreeWidgetItem *parent = item->parent(); c = findUMLObject(parent)->asUMLClassifier(); if (!c) { uWarning() << "No classifier - cannot create!"; return; } } c->createOperation(); } /** * Create new attribute. */ void RefactoringAssistant::createAttribute() { QTreeWidgetItem *item = currentItem(); if (!item) { uWarning() << "Called with no item selected."; return; } UMLClassifier *c = findUMLObject(item)->asUMLClassifier(); if (!c) { // find parent QTreeWidgetItem *parent = item->parent(); c = findUMLObject(parent)->asUMLClassifier(); if (!c) { uWarning() << "No classifier - cannot create!"; return; } } c->createAttribute(); } /** * Add a classifier to the data structure. * @param classifier the classifier to add * @param parent the tree item under which the classifier is placed * @param addSuper add it to the base classifier folder * @param addSub add it to the derived classifier folder * @param recurse ... */ void RefactoringAssistant::addClassifier(UMLClassifier *classifier, QTreeWidgetItem *parent, bool addSuper, bool addSub, bool recurse) { if (!classifier) { uWarning() << "No classifier given - do nothing!"; return; } DEBUG(DBG_SRC) << classifier->name() << " added."; QTreeWidgetItem *classifierItem, *item; if (parent) { classifierItem = parent; } else { classifierItem = new QTreeWidgetItem(this, QStringList(classifier->name())); m_umlObjectMap[classifierItem] = classifier; } m_alreadySeen << classifier; connect(classifier, SIGNAL(modified()), this, SLOT(objectModified())); // add attributes connect(classifier, SIGNAL(attributeAdded(UMLClassifierListItem*)), this, SLOT(attributeAdded(UMLClassifierListItem*))); connect(classifier, SIGNAL(attributeRemoved(UMLClassifierListItem*)), this, SLOT(attributeRemoved(UMLClassifierListItem*))); QStringList itemTextAt; itemTextAt << i18n("Attributes") << QLatin1String("attributes"); QTreeWidgetItem *attsFolder = new QTreeWidgetItem(classifierItem, itemTextAt); attsFolder->setIcon(0, Icon_Utils::SmallIcon(Icon_Utils::it_Folder_Orange)); attsFolder->setExpanded(true); UMLAttributeList atts(classifier->getAttributeList()); foreach(UMLAttribute* att, atts) { attributeAdded(att); } // add operations connect(classifier, SIGNAL(operationAdded(UMLClassifierListItem*)), this, SLOT(operationAdded(UMLClassifierListItem*))); connect(classifier, SIGNAL(operationRemoved(UMLClassifierListItem*)), this, SLOT(operationRemoved(UMLClassifierListItem*))); QStringList itemTextOp; itemTextOp << i18n("Operations") << QLatin1String("operations"); QTreeWidgetItem *opsFolder = new QTreeWidgetItem(classifierItem, itemTextOp); opsFolder->setIcon(0, Icon_Utils::SmallIcon(Icon_Utils::it_Folder_Orange)); opsFolder->setExpanded(true); UMLOperationList ops(classifier->getOpList()); foreach(UMLOperation* op, ops) { operationAdded(op); } if (addSuper) { // add base classifiers QTreeWidgetItem *superFolder = new QTreeWidgetItem(classifierItem, QStringList(i18n("Base Classifiers"))); superFolder->setExpanded(true); UMLClassifierList super = classifier->findSuperClassConcepts(); foreach(UMLClassifier* cl, super) { item = new QTreeWidgetItem(superFolder, QStringList(cl->name())); item->setIcon(0, Icon_Utils::SmallIcon(Icon_Utils::it_Generalisation)); item->setExpanded(true); m_umlObjectMap[item] = cl; if (recurse) { if (m_alreadySeen.contains(cl)) { DEBUG(DBG_SRC) << "super class already seen" << cl; continue; } addClassifier(cl, item, true, false, true); } } } if (addSub) { // add derived classifiers QTreeWidgetItem *derivedFolder = new QTreeWidgetItem(classifierItem, QStringList(i18n("Derived Classifiers"))); derivedFolder->setExpanded(true); UMLClassifierList derived = classifier->findSubClassConcepts(); foreach(UMLClassifier* d, derived) { item = new QTreeWidgetItem(derivedFolder, QStringList(d->name())); item->setIcon(0, Icon_Utils::SmallIcon(Icon_Utils::it_Uniassociation)); item->setExpanded(true); m_umlObjectMap[item] = d; if (recurse) { if (m_alreadySeen.contains(d)) { DEBUG(DBG_SRC) << "derived class already seen" << d; continue; } addClassifier(d, item, false, true, true); } } } } /** * Reimplementation of the drag move event. * @param event the drag move event */ void RefactoringAssistant::dragMoveEvent(QDragMoveEvent *event) { //first check if we can accept dropping if (event->source() == this) { event->setDropAction(Qt::MoveAction); QTreeWidgetItem* target = itemAt(event->pos()); QTreeWidgetItem* item = currentItem(); if (target && item) { //first check if we can accept dropping QTreeWidgetItem* parent = item->parent(); if (parent) { if ((target->text(1) == QLatin1String("operations")) && (parent->text(1) == QLatin1String("operations"))) { DEBUG(DBG_SRC) << "accept operation " << item->text(0); //:TODO:fischer event->accept(); return; } if ((target->text(1) == QLatin1String("attributes")) && (parent->text(1) == QLatin1String("attributes"))) { DEBUG(DBG_SRC) << "accept attribute " << item->text(0); //:TODO:fischer event->accept(); return; } } } event->ignore(); } else { event->acceptProposedAction(); } } /** * Reimplementation of the drop event. * @param event the drop event */ void RefactoringAssistant::dropEvent(QDropEvent *event) { QTreeWidgetItem* movingItem = currentItem(); if (!movingItem) { event->ignore(); return; // no item ? } DEBUG(DBG_SRC) << "dropping=" << movingItem->text(0); if (event->source() == this) { event->setDropAction(Qt::MoveAction); event->accept(); DEBUG(DBG_SRC) << "accept"; //:TODO:fischer } else { event->acceptProposedAction(); DEBUG(DBG_SRC) << "acceptProposedAction"; //:TODO:fischer return; } QTreeWidgetItem* afterme = itemAt(event->pos()); if (!afterme) { uWarning() << "Drop target not found - aborting drop!"; return; } DEBUG(DBG_SRC) << "Dropping after item = " << afterme->text(0); //:TODO:fischer // when dropping on a class, we have to put the item in the appropriate folder! UMLObject *movingObject; UMLClassifier *newClassifier; if ((movingItem == afterme) || !(movingObject = findUMLObject(movingItem))) { uWarning() << "Moving item not found or dropping after itself or item not found in uml obj map. aborting. (drop had already been accepted)"; return; } QTreeWidgetItem* parentItem = afterme->parent(); UMLObject::ObjectType t = movingObject->baseType(); newClassifier = findUMLObject(parentItem)->asUMLClassifier(); if (!newClassifier) { const QString parentText = parentItem->text(1); if ((parentText == QLatin1String("operations") && t == UMLObject::ot_Operation) || (parentText == QLatin1String("attributes") && t == UMLObject::ot_Attribute)) { newClassifier = findUMLObject(parentItem->parent())->asUMLClassifier(); } if (!newClassifier) { uWarning() << "New parent of object is not a Classifier - Drop had already been accepted - check!"; return; } } if (t == UMLObject::ot_Operation) { DEBUG(DBG_SRC) << "Moving operation"; UMLOperation *op = movingObject->asUMLOperation(); if (newClassifier->checkOperationSignature(op->name(), op->getParmList())) { QString msg = i18n("An operation with that signature already exists in %1.\n", newClassifier->name()) + i18n("Choose a different name or parameter list."); KMessageBox::error(this, msg, i18n("Operation Name Invalid"), 0); return; } UMLOperation* newOp = op->clone()->asUMLOperation(); UMLClassifier *oldClassifier = op->umlParent()->asUMLClassifier(); if (oldClassifier) { oldClassifier->removeOperation(op); DEBUG(DBG_SRC) << "oldClassifier=" << oldClassifier->name() << " / newClassifier=" << newClassifier->name(); //:TODO:fischer } newClassifier->addOperation(newOp); m_doc->signalUMLObjectCreated(newOp); //:TODO: really? } else if (t == UMLObject::ot_Attribute) { DEBUG(DBG_SRC) << "Moving attribute"; UMLAttribute *att = movingObject->asUMLAttribute(); if (newClassifier->getAttributeList().contains(att)) { QString msg = i18n("An attribute with that name already exists in %1.\n", newClassifier->name()) + i18n("Choose a different name."); KMessageBox::error(this, msg, i18n("Attribute Name Invalid"), 0); return; } UMLAttribute* newAtt = att->clone()->asUMLAttribute(); UMLClassifier *oldClassifier = att->umlParent()->asUMLClassifier(); if (oldClassifier) { oldClassifier->removeAttribute(att); DEBUG(DBG_SRC) << "oldClassifier=" << oldClassifier->name() << " / newClassifier=" << newClassifier->name(); //:TODO:fischer } newClassifier->addAttribute(newAtt); m_doc->signalUMLObjectCreated(newAtt); //:TODO: really? } // emit moved(); refactor(m_umlObject); //:TODO:fischer } diff --git a/umbrello/uml1model/datatype.cpp b/umbrello/uml1model/datatype.cpp index 24f6674fb..4a1619766 100644 --- a/umbrello/uml1model/datatype.cpp +++ b/umbrello/uml1model/datatype.cpp @@ -1,101 +1,101 @@ /*************************************************************************** * 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-2016 * * Umbrello UML Modeller Authors * ***************************************************************************/ // own header #include "datatype.h" /** * Sets up a Datatype. * @param name The name of the Datatype. * @param id The unique id of the Concept. */ UMLDatatype::UMLDatatype(const QString & name, Uml::ID::Type id) : UMLClassifier(name, id), m_isRef(false) { m_BaseType = UMLObject::ot_Datatype; } /** * Destructor. */ UMLDatatype::~UMLDatatype() { } /** * Set the origin type (in case of e.g. typedef) * @param origType the origin type to set */ void UMLDatatype::setOriginType(UMLClassifier *origType) { m_pSecondary = origType; } /** * Get the origin type (in case of e.g. typedef) * @return the origin type */ UMLClassifier * UMLDatatype::originType() const { return m_pSecondary->asUMLClassifier(); } /** * Set the m_isRef flag (true when dealing with a pointer type) * @param isRef the flag to set */ void UMLDatatype::setIsReference(bool isRef) { m_isRef = isRef; } /** * Get the m_isRef flag. * @return true if is reference, otherwise false */ bool UMLDatatype::isReference() const { return m_isRef; } /** * Loads object from QDomElement. * - * @param qElement A QDomElement which contains xml info for this object. + * @param element A QDomElement which contains xml info for this object. */ bool UMLDatatype::loadFromXMI1(QDomElement & element) { if (!UMLObject::loadFromXMI1(element)) return false; m_SecondaryId = element.attribute(QLatin1String("elementReference")); if (!m_SecondaryId.isEmpty()) { // @todo We do not currently support composition. m_isRef = true; } return true; } /** * Creates the UML:Datatype element. * * @param qDoc the xml document * @param qElement the xml element */ void UMLDatatype::saveToXMI1(QDomDocument & qDoc, QDomElement & qElement) { QDomElement classifierElement = UMLObject::save1(QLatin1String("UML:DataType"), qDoc); if (m_pSecondary != 0) classifierElement.setAttribute(QLatin1String("elementReference"), Uml::ID::toString(m_pSecondary->id())); qElement.appendChild(classifierElement); } diff --git a/umbrello/uml1model/umlobject.cpp b/umbrello/uml1model/umlobject.cpp index 15d7cf8a1..f2365f241 100644 --- a/umbrello/uml1model/umlobject.cpp +++ b/umbrello/uml1model/umlobject.cpp @@ -1,1335 +1,1335 @@ /*************************************************************************** * 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 "umlobject.h" // app includes #include "classpropertiesdialog.h" #include "debug_utils.h" #include "enumliteral.h" #include "uniqueid.h" #include "uml.h" #include "umldoc.h" #include "umllistview.h" #include "umlobjectprivate.h" #include "models/objectsmodel.h" #include "package.h" #include "folder.h" #include "stereotype.h" #include "object_factory.h" #include "model_utils.h" #include "import_utils.h" #include "docwindow.h" #include "cmds.h" // kde includes #include // qt includes #include #include using namespace Uml; DEBUG_REGISTER_DISABLED(UMLObject) /** * Creates a UMLObject. * @param other object to created from */ UMLObject::UMLObject(const UMLObject &other) : QObject(other.umlParent()), m_d(new UMLObjectPrivate) { other.copyInto(this); UMLApp::app()->document()->objectsModel()->add(this); } /** * Creates a UMLObject. * @param parent The parent of the object. * @param name The name of the object. * @param id The ID of the object (optional.) If omitted * then a new ID will be assigned internally. */ UMLObject::UMLObject(UMLObject* parent, const QString& name, ID::Type id) : QObject(parent), m_nId(id), m_name(name), m_d(new UMLObjectPrivate) { init(); if (id == Uml::ID::None) m_nId = UniqueID::gen(); UMLApp::app()->document()->objectsModel()->add(this); } /** * Creates a UMLObject. * @param name The name of the object. * @param id The ID of the object (optional.) If omitted * then a new ID will be assigned internally. */ UMLObject::UMLObject(const QString& name, ID::Type id) : QObject(0), m_nId(id), m_name(name), m_d(new UMLObjectPrivate) { init(); if (id == Uml::ID::None) m_nId = UniqueID::gen(); UMLApp::app()->document()->objectsModel()->add(this); } /** * Creates a UMLObject. * @param parent The parent of the object. */ UMLObject::UMLObject(UMLObject * parent) : QObject(parent), m_nId(Uml::ID::None), m_name(QString()), m_d(new UMLObjectPrivate) { init(); UMLApp::app()->document()->objectsModel()->add(this); } /** * Standard destructor. */ UMLObject::~UMLObject() { // unref stereotype setUMLStereotype(0); if (m_pSecondary && m_pSecondary->baseType() == ot_Stereotype) { UMLStereotype* stereotype = m_pSecondary->asUMLStereotype(); if (stereotype) stereotype->decrRefCount(); } UMLApp::app()->document()->objectsModel()->remove(this); delete m_d; } /** * Initializes key variables of the class. */ void UMLObject::init() { setObjectName(QLatin1String("UMLObject")); m_BaseType = ot_UMLObject; m_visibility = Uml::Visibility::Public; m_pStereotype = 0; m_Doc.clear(); m_bAbstract = false; m_bStatic = false; m_bCreationWasSignalled = false; m_pSecondary = 0; } /** * Display the properties configuration dialog for the object. * * @param parent The parent widget. * @return True for success of this operation. */ bool UMLObject::showPropertiesDialog(QWidget *parent) { DocWindow *docwindow = UMLApp::app()->docWindow(); docwindow->updateDocumentation(false); QPointer dlg = new ClassPropertiesDialog(parent, this, false); bool modified = false; if (dlg->exec()) { docwindow->showDocumentation(this, true); UMLApp::app()->document()->setModified(true); modified = true; } dlg->close(); delete dlg; return modified; } /** * This should be reimplemented by subclasses if they wish to * accept certain types of associations. Note that this only * tells if this UMLObject can accept the association * type. When creating an association another check is made to * see if the association is valid. For example a UMLClass * (UMLClassifier) can accept generalizations and should * return true. If while creating a generalization the * superclass is already subclassed from this, the association * is not valid and will not be created. The default accepts * nothing (returns false) */ bool UMLObject::acceptAssociationType(Uml::AssociationType::Enum type) { Q_UNUSED(type); // A UMLObject accepts nothing. This should be reimplemented by the subclasses return false; } /** * Assigns a new Id to the object */ void UMLObject::setID(ID::Type NewID) { m_nId = NewID; emitModified(); } /** * Set the UMLObject's name */ void UMLObject::setName(const QString &strName) { if (name() != strName) { UMLApp::app()->executeCommand(new Uml::CmdRenameUMLObject(this, strName)); } } /** * Method used by setName: it is called by cmdSetName, Don't use it! */ void UMLObject::setNameCmd(const QString &strName) { m_name = strName; emitModified(); } /** * Returns a copy of m_name */ QString UMLObject::name() const { return m_name; } /** * Returns the fully qualified name, i.e. all package prefixes and then m_name. * * @param separator The separator string to use (optional.) * If not given then the separator is chosen according * to the currently selected active programming language * of import and code generation. * @param includeRoot Whether to prefix the root folder name to the FQN. * See UMLDoc::getRootFolder(). Default: false. * @return The fully qualified name of this UMLObject. */ QString UMLObject::fullyQualifiedName(const QString& separator, bool includeRoot /* = false */) const { QString fqn; UMLPackage *parent = umlPackage(); if (parent && parent != this) { bool skipPackage = false; if (!includeRoot) { UMLDoc *umldoc = UMLApp::app()->document(); if ((umldoc->rootFolderType(parent) != Uml::ModelType::N_MODELTYPES) || (parent == umldoc->datatypeFolder())) skipPackage = true; } if (!skipPackage) { QString tempSeparator = separator; if (tempSeparator.isEmpty()) tempSeparator = UMLApp::app()->activeLanguageScopeSeparator(); fqn = parent->fullyQualifiedName(tempSeparator, includeRoot); fqn.append(tempSeparator); } } fqn.append(m_name); return fqn; } /** * Overloaded '==' operator */ bool UMLObject::operator==(const UMLObject & rhs) const { if (this == &rhs) return true; //don't compare IDs, these are program specific and //don't mean the objects are the same //***** CHECK: Who put in this comment? What was the reason? //***** Currently some operator== in umbrello compare the IDs //***** while others don't. if (m_name != rhs.m_name) return false; // Packages create different namespaces, therefore they should be // part of the equality test. if (umlParent() != rhs.umlParent()) return false; // Making the type part of an object's identity has its problems: // Not all programming languages support declarations of the same // name but different type. // In such cases, the code generator is responsible for generating // the appropriate error message. if (m_BaseType != rhs.m_BaseType) return false; // The documentation should not be part of the equality test. // If two objects are the same but differ only in their documentation, // what does that mean? //if(m_Doc != rhs.m_Doc) // return false; // The visibility should not be part of the equality test. // What does it mean if two objects are the same but differ in their // visibility? - I'm not aware of any programming language that would // support that. //if(m_visibility != rhs.m_visibility) // return false; // See comments above //if(m_pStereotype != rhs.m_pStereotype) // return false; // See comments above //if(m_bAbstract != rhs.m_bAbstract) // return false; // See comments above //if(m_bStatic != rhs.m_bStatic) // return false; return true; } /** * Copy the internal presentation of this object into the new * object. */ void UMLObject::copyInto(UMLObject *lhs) const { // Data members with copy constructor lhs->m_Doc = m_Doc; lhs->m_pStereotype = m_pStereotype; if (lhs->m_pStereotype) lhs->m_pStereotype->incrRefCount(); lhs->m_bAbstract = m_bAbstract; lhs->m_bStatic = m_bStatic; lhs->m_BaseType = m_BaseType; lhs->m_visibility = m_visibility; lhs->setUMLParent(umlParent()); // We don't want the same name existing twice. lhs->m_name = Model_Utils::uniqObjectName(m_BaseType, umlPackage(), m_name); // Create a new ID. lhs->m_nId = UniqueID::gen(); // Hope that the parent from QObject is okay. if (lhs->umlParent() != umlParent()) uDebug() << "copyInto has a wrong parent"; } UMLObject *UMLObject::clone() const { UMLObject *clone = new UMLObject; UMLObject::copyInto(clone); return clone; } /** * Returns the abstract state of the object. */ bool UMLObject::isAbstract() const { return m_bAbstract; } /** * Sets the paste state of the object. */ void UMLObject::setAbstract(bool bAbstract) { m_bAbstract = bAbstract; emitModified(); } /** * Returns true if this UMLObject has classifier scope, * otherwise false (the default). */ bool UMLObject::isStatic() const { return m_bStatic; } /** * Sets the value for m_bStatic. */ void UMLObject::setStatic(bool bStatic) { m_bStatic = bStatic; emitModified(); } /** * Forces the emission of the modified signal. Useful when * updating several attributes at a time: you can block the * signals, update all atts, and then force the signal. */ void UMLObject::emitModified() { UMLDoc *umldoc = UMLApp::app()->document(); if (!umldoc->loading() && !umldoc->closing()) emit modified(); } /** * Returns the type of the object. * * @return Returns the type of the object. */ UMLObject::ObjectType UMLObject::baseType() const { return m_BaseType; } /** * @return The type used for rtti as string. */ QLatin1String UMLObject::baseTypeStr() const { return QLatin1String(ENUM_NAME(UMLObject, ObjectType, m_BaseType)); } /** * Set the type of the object. * * @param ot The ObjectType to set. */ void UMLObject::setBaseType(ObjectType ot) { m_BaseType = ot; } /** * Returns the ID of the object. * * @return Returns the ID of the object. */ ID::Type UMLObject::id() const { return m_nId; } /** * Returns the documentation for the object. * * @return Returns the documentation for the object. */ QString UMLObject::doc() const { return m_Doc; } /** * Returns state of documentation for the object. * * @return false if documentation is empty */ bool UMLObject::hasDoc() const { return !m_Doc.isEmpty(); } /** * Sets the documentation for the object. * * @param d The documentation for the object. */ void UMLObject::setDoc(const QString &d) { m_Doc = d; //emit modified(); No, this is done centrally at DocWindow::updateDocumentation() } /** * Returns the visibility of the object. * * @return Returns the visibility of the object. */ Visibility::Enum UMLObject::visibility() const { return m_visibility; } /** * Sets the visibility of the object. * * @param visibility The visibility of the object. */ void UMLObject::setVisibility(Visibility::Enum visibility) { if (m_visibility != visibility) { UMLApp::app()->executeCommand(new CmdSetVisibility(this, visibility)); } } /** * Method used by setVisibility: it is called by cmdSetVisibility, Don't use it! */ void UMLObject::setVisibilityCmd(Visibility::Enum visibility) { m_visibility = visibility; emitModified(); } /** * Sets the class' UMLStereotype. Adjusts the reference counts * at the previously set stereotype and at the new stereotype. * If the previously set UMLStereotype's reference count drops * to zero then the UMLStereotype is removed at the UMLDoc and * it is then physically deleted. * * @param stereo Sets the classes UMLStereotype. */ void UMLObject::setUMLStereotype(UMLStereotype *stereo) { if (stereo == m_pStereotype) return; if (stereo) { stereo->incrRefCount(); } if (m_pStereotype) { m_pStereotype->decrRefCount(); if (m_pStereotype->refCount() == 0) { UMLDoc *pDoc = UMLApp::app()->document(); pDoc->removeStereotype(m_pStereotype); delete m_pStereotype; } } m_pStereotype = stereo; // TODO: don't emit modified() if predefined folder emitModified(); } /** * Sets the classes stereotype name. * Internally uses setUMLStereotype(). * * @param name Sets the classes stereotype name. */ void UMLObject::setStereotype(const QString &name) { if (name != stereotype()) { UMLApp::app()->executeCommand(new CmdSetStereotype(this, name)); } } void UMLObject::setStereotypeCmd(const QString& name) { if (name.isEmpty()) { setUMLStereotype(0); return; } UMLDoc *pDoc = UMLApp::app()->document(); UMLStereotype *s = pDoc->findOrCreateStereotype(name); setUMLStereotype(s); } /** * Returns the classes UMLStereotype object. * * @return Returns the classes UMLStereotype object. */ UMLStereotype * UMLObject::umlStereotype() { return m_pStereotype; } /** * Returns the stereotype. */ QString UMLObject::stereotype(bool includeAdornments /* = false */) const { if (m_pStereotype == 0) return QString(); return m_pStereotype->name(includeAdornments); } /** * Return the package(s) in which this UMLObject is contained * as a text. * * @param separator Separator string for joining together the * individual package prefixes (optional.) * If no separator is given then the separator * of the currently selected language is used. * @param includeRoot Whether to prefix the root folder name. * Default: false. * @return The UMLObject's enclosing package(s) as a text. */ QString UMLObject::package(const QString& separator, bool includeRoot) { QString tempSeparator = separator; if (tempSeparator.isEmpty()) tempSeparator = UMLApp::app()->activeLanguageScopeSeparator(); QString fqn = fullyQualifiedName(tempSeparator, includeRoot); if (!fqn.contains(tempSeparator)) return QString(); QString scope = fqn.left(fqn.length() - tempSeparator.length() - m_name.length()); return scope; } /** * Return a list of the packages in which this class is embedded. * The outermost package is first in the list. * * @param includeRoot Whether to prefix the root folder name. * Default: false. * @return UMLPackageList of the containing packages. */ UMLPackageList UMLObject::packages(bool includeRoot) const { UMLPackageList pkgList; UMLPackage* pkg = umlPackage(); while (pkg != 0) { pkgList.prepend(pkg); pkg = pkg->umlPackage(); } if (!includeRoot) pkgList.removeFirst(); return pkgList; } /** * Sets the UMLPackage in which this class is located. * * @param pPkg Pointer to the class' UMLPackage. */ bool UMLObject::setUMLPackage(UMLPackage *pPkg) { if (pPkg == this) { uDebug() << "setting parent to myself is not allowed"; return false; } if (pPkg == 0) { // Allow setting to NULL for stereotypes setParent(pPkg); return true; } if (pPkg->umlPackage() == this) { uDebug() << "setting parent to an object of which I'm already the parent is not allowed"; return false; } setParent(pPkg); emitModified(); return true; } /** * Returns the UMLPackage that this class is located in. * * This method is a shortcut for calling umlParent()->asUMLPackage(). * * @return Pointer to the UMLPackage of this class. */ UMLPackage* UMLObject::umlPackage() const { return dynamic_cast(parent()); } /** * Set UML model parent. * * @param parent object to set as parent * - * @TODO prevent setting parent to myself + * TODO prevent setting parent to myself */ void UMLObject::setUMLParent(UMLObject *parent) { setParent(parent); } /** * Return UML model parent. * * Model classes of type UMLClassifierListItem and below * uses QObject::parent to hold the model parent * * @return parent of uml object */ UMLObject *UMLObject::umlParent() const { return dynamic_cast(parent()); } /** * Return secondary ID. Required by resolveRef(). */ QString UMLObject::secondaryId() const { return m_SecondaryId; } /** * Set the secondary ID. * Currently only required by petalTree2Uml(); all other setting of the * m_SecondaryID is internal to the UMLObject class hierarchy. */ void UMLObject::setSecondaryId(const QString& id) { m_SecondaryId = id; } /** * Return secondary ID fallback. * Required by resolveRef() for imported model files. */ QString UMLObject::secondaryFallback() const { return m_SecondaryFallback; } /** * Set the secondary ID fallback. * Currently only used by petalTree2Uml(). */ void UMLObject::setSecondaryFallback(const QString& id) { m_SecondaryFallback = id; } /** * Calls UMLDoc::signalUMLObjectCreated() if m_BaseType affords * doing so. */ void UMLObject::maybeSignalObjectCreated() { if (!m_bCreationWasSignalled && m_BaseType != ot_Stereotype && m_BaseType != ot_Association && m_BaseType != ot_Role) { m_bCreationWasSignalled = true; UMLDoc* umldoc = UMLApp::app()->document(); umldoc->signalUMLObjectCreated(this); } } /** * Resolve referenced objects (if any.) * Needs to be called after all UML objects are loaded from file. * This needs to be done after all model objects are loaded because * some of the xmi.id's might be forward references, i.e. they may * identify model objects which were not yet loaded at the point of * reference. * The default implementation attempts resolution of the m_SecondaryId. * * @return True for success. */ bool UMLObject::resolveRef() { if (m_pSecondary || (m_SecondaryId.isEmpty() && m_SecondaryFallback.isEmpty())) { maybeSignalObjectCreated(); return true; } #ifdef VERBOSE_DEBUGGING uDebug() << m_name << ": m_SecondaryId is " << m_SecondaryId; #endif UMLDoc *pDoc = UMLApp::app()->document(); // In the new, XMI standard compliant save format, // the type is the xmi.id of a UMLClassifier. if (! m_SecondaryId.isEmpty()) { m_pSecondary = pDoc->findObjectById(Uml::ID::fromString(m_SecondaryId)); if (m_pSecondary != 0) { if (m_pSecondary->baseType() == ot_Stereotype) { if (m_pStereotype) m_pStereotype->decrRefCount(); m_pStereotype = m_pSecondary->asUMLStereotype(); m_pStereotype->incrRefCount(); m_pSecondary = 0; } m_SecondaryId = QString(); maybeSignalObjectCreated(); return true; } if (m_SecondaryFallback.isEmpty()) { uDebug() << "object with xmi.id=" << m_SecondaryId << " not found, setting to undef"; UMLFolder *datatypes = pDoc->datatypeFolder(); m_pSecondary = Object_Factory::createUMLObject(ot_Datatype, QLatin1String("undef"), datatypes, false); return true; } } if (m_SecondaryFallback.isEmpty()) { uError() << m_name << ": cannot find type with id " << m_SecondaryId; return false; } #ifdef VERBOSE_DEBUGGING uDebug() << m_name << ": could not resolve secondary ID " << m_SecondaryId << ", using secondary fallback " << m_SecondaryFallback; #endif m_SecondaryId = m_SecondaryFallback; // Assume we're dealing with the older Umbrello format where // the type name was saved in the "type" attribute rather // than the xmi.id of the model object of the attribute type. m_pSecondary = pDoc->findUMLObject(m_SecondaryId, ot_UMLObject, this); if (m_pSecondary) { m_SecondaryId = QString(); maybeSignalObjectCreated(); return true; } // Work around Object_Factory::createUMLObject()'s incapability // of on-the-fly scope creation: if (m_SecondaryId.contains(QLatin1String("::"))) { // TODO: Merge Import_Utils::createUMLObject() into Object_Factory::createUMLObject() m_pSecondary = Import_Utils::createUMLObject(ot_UMLObject, m_SecondaryId, umlPackage()); if (m_pSecondary) { if (Import_Utils::newUMLObjectWasCreated()) { maybeSignalObjectCreated(); qApp->processEvents(); uDebug() << "Import_Utils::createUMLObject() created a new type for " << m_SecondaryId; } else { uDebug() << "Import_Utils::createUMLObject() returned an existing type for " << m_SecondaryId; } m_SecondaryId = QString(); return true; } uError() << "Import_Utils::createUMLObject() failed to create a new type for " << m_SecondaryId; return false; } uDebug() << "Creating new type for " << m_SecondaryId; // This is very C++ specific - we rely on some '*' or // '&' to decide it's a ref type. Plus, we don't recognize // typedefs of ref types. bool isReferenceType = (m_SecondaryId.contains(QLatin1Char('*')) || m_SecondaryId.contains(QLatin1Char('&'))); ObjectType ot = ot_Class; if (isReferenceType) { ot = ot_Datatype; } else { if (Model_Utils::isCommonDataType(m_SecondaryId)) ot = ot_Datatype; } m_pSecondary = Object_Factory::createUMLObject(ot, m_SecondaryId, 0); if (m_pSecondary == 0) return false; m_SecondaryId = QString(); maybeSignalObjectCreated(); //qApp->processEvents(); return true; } void UMLObject::saveToXMI1(QDomDocument &qDoc, QDomElement &qElement) { Q_UNUSED(qDoc); Q_UNUSED(qElement); } /** * Auxiliary to saveToXMI1. * Create a QDomElement with the given tag, and save the XMI attributes * that are common to all child classes to the newly created element. * This method does not need to be overridden by child classes. */ QDomElement UMLObject::save1(const QString &tag, QDomDocument & qDoc) { m_d->isSaved = true; /* Call as the first action of saveToXMI1() in child class: This creates the QDomElement with which to work. */ QDomElement qElement = qDoc.createElement(tag); qElement.setAttribute(QLatin1String("isSpecification"), QLatin1String("false")); if (m_BaseType != ot_Association && m_BaseType != ot_Role && m_BaseType != ot_Attribute && m_BaseType != ot_Instance) { qElement.setAttribute(QLatin1String("isLeaf"), QLatin1String("false")); qElement.setAttribute(QLatin1String("isRoot"), QLatin1String("false")); if (m_bAbstract) qElement.setAttribute(QLatin1String("isAbstract"), QLatin1String("true")); else qElement.setAttribute(QLatin1String("isAbstract"), QLatin1String("false")); } qElement.setAttribute(QLatin1String("xmi.id"), Uml::ID::toString(m_nId)); qElement.setAttribute(QLatin1String("name"), m_name); if (m_BaseType != ot_Operation && m_BaseType != ot_Role && m_BaseType != ot_Attribute) { Uml::ID::Type nmSpc; if (umlPackage()) nmSpc = umlPackage()->id(); else nmSpc = UMLApp::app()->document()->modelID(); qElement.setAttribute(QLatin1String("namespace"), Uml::ID::toString(nmSpc)); } if (! m_Doc.isEmpty()) qElement.setAttribute(QLatin1String("comment"), m_Doc); //CHECK: uml13.dtd compliance #ifdef XMI_FLAT_PACKAGES if (umlParent()->asUMLPackage()) //FIXME: uml13.dtd compliance qElement.setAttribute(QLatin1String("package"), umlParent()->asUMLPackage()->ID()); #endif QString visibility = Uml::Visibility::toString(m_visibility, false); qElement.setAttribute(QLatin1String("visibility"), visibility); if (m_pStereotype != 0) qElement.setAttribute(QLatin1String("stereotype"), Uml::ID::toString(m_pStereotype->id())); if (m_bStatic) qElement.setAttribute(QLatin1String("ownerScope"), QLatin1String("classifier")); /* else qElement.setAttribute("ownerScope", "instance"); *** ownerScope defaults to instance if not set **********/ return qElement; } /** * Auxiliary to loadFromXMI. * This method is usually overridden by child classes. * It is responsible for loading the specific XMI structure * of the child class. */ bool UMLObject::load1(QDomElement&) { // This body is not usually executed because child classes // overwrite the load method. return true; } /** * Analyzes the given QDomElement for a reference to a stereotype. * * @param element QDomElement to analyze. * @return True if a stereotype reference was found, else false. */ bool UMLObject::loadStereotype(QDomElement & element) { QString tag = element.tagName(); if (!UMLDoc::tagEq(tag, QLatin1String("stereotype"))) return false; QString stereo = element.attribute(QLatin1String("xmi.value")); if (stereo.isEmpty() && element.hasChildNodes()) { /* like so: */ QDomNode stereoNode = element.firstChild(); QDomElement stereoElem = stereoNode.toElement(); tag = stereoElem.tagName(); if (UMLDoc::tagEq(tag, QLatin1String("Stereotype"))) { stereo = stereoElem.attribute(QLatin1String("xmi.idref")); } } if (stereo.isEmpty()) return false; Uml::ID::Type stereoID = Uml::ID::fromString(stereo); UMLDoc *pDoc = UMLApp::app()->document(); if (m_pStereotype) m_pStereotype->decrRefCount(); m_pStereotype = pDoc->findStereotypeById(stereoID); if (m_pStereotype) m_pStereotype->incrRefCount(); else m_SecondaryId = stereo; // leave it to resolveRef() return true; } /** * This method loads the generic parts of the XMI common to most model * classes. It is not usually reimplemented by child classes. * Instead, it invokes the load() method which implements the loading * of the specifics of each child class. * * @param element The QDomElement from which to load. */ bool UMLObject::loadFromXMI1(QDomElement & element) { UMLDoc* umldoc = UMLApp::app()->document(); if (umldoc == 0) { uError() << "umldoc is NULL"; return false; } // Read the name first so that if we encounter a problem, the error // message can say the name. m_name = element.attribute(QLatin1String("name")); QString id = Model_Utils::getXmiId(element); if (id.isEmpty() || id == QLatin1String("-1")) { // Before version 1.4, Umbrello did not save the xmi.id of UMLRole objects. // Some tools (such as Embarcadero's) do not have an xmi.id on all attributes. m_nId = UniqueID::gen(); uWarning() << m_name << ": xmi.id not present, generating a new one"; } else { Uml::ID::Type nId = Uml::ID::fromString(id); if (m_BaseType == ot_Role) { // Some older Umbrello versions had a problem with xmi.id's // of other objects being reused for the UMLRole, see e.g. // attachment 21179 at http://bugs.kde.org/147988 . // If the xmi.id is already being used then we generate a new one. UMLObject *o = umldoc->findObjectById(nId); if (o) { uError() << "loadFromXMI1(UMLRole): id " << id << " is already in use!!! Please fix your XMI file."; } } m_nId = nId; } if (element.hasAttribute(QLatin1String("documentation"))) // for bkwd compat. m_Doc = element.attribute(QLatin1String("documentation")); else m_Doc = element.attribute(QLatin1String("comment")); //CHECK: need a UML:Comment? m_visibility = Uml::Visibility::Public; if (element.hasAttribute(QLatin1String("scope"))) { // for bkwd compat. QString scope = element.attribute(QLatin1String("scope")); if (scope == QLatin1String("instance_level")) // nsuml compat. m_bStatic = false; else if (scope == QLatin1String("classifier_level")) // nsuml compat. m_bStatic = true; else { int nScope = scope.toInt(); switch (nScope) { case 200: m_visibility = Uml::Visibility::Public; break; case 201: m_visibility = Uml::Visibility::Private; break; case 202: m_visibility = Uml::Visibility::Protected; break; default: uError() << m_name << ": illegal scope " << nScope; } } } else { QString visibility = element.attribute(QLatin1String("visibility"), QLatin1String("public")); if (visibility == QLatin1String("private") || visibility == QLatin1String("private_vis")) // for compatibility with other programs m_visibility = Uml::Visibility::Private; else if (visibility == QLatin1String("protected") || visibility == QLatin1String("protected_vis")) // for compatibility with other programs m_visibility = Uml::Visibility::Protected; else if (visibility == QLatin1String("implementation")) m_visibility = Uml::Visibility::Implementation; } QString stereo = element.attribute(QLatin1String("stereotype")); if (!stereo.isEmpty()) { Uml::ID::Type stereoID = Uml::ID::fromString(stereo); if (m_pStereotype) m_pStereotype->decrRefCount(); m_pStereotype = umldoc->findStereotypeById(stereoID); if (m_pStereotype) { m_pStereotype->incrRefCount(); } else { uDebug() << m_name << ": UMLStereotype " << Uml::ID::toString(stereoID) << " not found, creating now."; setStereotypeCmd(stereo); } } if (element.hasAttribute(QLatin1String("abstract"))) { // for bkwd compat. QString abstract = element.attribute(QLatin1String("abstract"), QLatin1String("0")); m_bAbstract = (bool)abstract.toInt(); } else { QString isAbstract = element.attribute(QLatin1String("isAbstract"), QLatin1String("false")); m_bAbstract = (isAbstract == QLatin1String("true")); } if (element.hasAttribute(QLatin1String("static"))) { // for bkwd compat. QString staticScope = element.attribute(QLatin1String("static"), QLatin1String("0")); m_bStatic = (bool)staticScope.toInt(); } else { QString ownerScope = element.attribute(QLatin1String("ownerScope"), QLatin1String("instance")); m_bStatic = (ownerScope == QLatin1String("classifier")); } // If the node has child nodes, check whether attributes can be // extracted from them. if (element.hasChildNodes()) { QDomNode node = element.firstChild(); if (node.isComment()) node = node.nextSibling(); QDomElement elem = node.toElement(); while (!elem.isNull()) { QString tag = elem.tagName(); if (UMLDoc::tagEq(tag, QLatin1String("name"))) { m_name = elem.attribute(QLatin1String("xmi.value")); if (m_name.isEmpty()) m_name = elem.text(); } else if (UMLDoc::tagEq(tag, QLatin1String("visibility"))) { QString vis = elem.attribute(QLatin1String("xmi.value")); if (vis.isEmpty()) vis = elem.text(); if (vis == QLatin1String("private") || vis == QLatin1String("private_vis")) m_visibility = Uml::Visibility::Private; else if (vis == QLatin1String("protected") || vis == QLatin1String("protected_vis")) m_visibility = Uml::Visibility::Protected; else if (vis == QLatin1String("implementation")) m_visibility = Uml::Visibility::Implementation; } else if (UMLDoc::tagEq(tag, QLatin1String("isAbstract"))) { QString isAbstract = elem.attribute(QLatin1String("xmi.value")); if (isAbstract.isEmpty()) isAbstract = elem.text(); m_bAbstract = (isAbstract == QLatin1String("true")); } else if (UMLDoc::tagEq(tag, QLatin1String("ownerScope"))) { QString ownerScope = elem.attribute(QLatin1String("xmi.value")); if (ownerScope.isEmpty()) ownerScope = elem.text(); m_bStatic = (ownerScope == QLatin1String("classifier")); } else { loadStereotype(elem); } node = node.nextSibling(); if (node.isComment()) node = node.nextSibling(); elem = node.toElement(); } } // Operations, attributes, enum literals, templates, stereotypes, // and association role objects get added and signaled elsewhere. if (m_BaseType != ot_Operation && m_BaseType != ot_Attribute && m_BaseType != ot_EnumLiteral && m_BaseType != ot_EntityAttribute && m_BaseType != ot_Template && m_BaseType != ot_Stereotype && m_BaseType != ot_Role && m_BaseType != ot_UniqueConstraint && m_BaseType != ot_ForeignKeyConstraint && m_BaseType != ot_CheckConstraint && m_BaseType != ot_InstanceAttribute ) { if (umlPackage()) { umlPackage()->addObject(this); } else if (umldoc->rootFolderType(this) == Uml::ModelType::N_MODELTYPES) { // umlPackage() is not set on the root folders. uDebug() << m_name << ": umlPackage() is not set"; } } return load1(element); } /** * Helper function for debug output. * Returns the given enum value as string. * @param ot ObjectType of which a string representation is wanted * @return the ObjectType as string */ QString UMLObject::toString(ObjectType ot) { return QLatin1String(ENUM_NAME(UMLObject, ObjectType, ot)); } /** * Returns the given object type value as localized string. - * @param ot ObjectType of which a string representation is wanted + * @param t ObjectType of which a string representation is wanted * @return the ObjectType as localized string */ QString UMLObject::toI18nString(ObjectType t) { QString name; switch (t) { case UMLObject::ot_Actor: name = i18n("Actor &name:"); break; case UMLObject::ot_Artifact: name = i18n("Artifact &name:"); break; case UMLObject::ot_Association: name = i18n("Association &name:"); break; case UMLObject::ot_Class: name = i18n("Class &name:"); break; case UMLObject::ot_Component: name = i18n("Component &name:"); break; case UMLObject::ot_Datatype: name = i18n("Datatype &name:"); break; case UMLObject::ot_Entity: name = i18n("Entity &name:"); break; case UMLObject::ot_Enum: name = i18n("Enum &name:"); break; case UMLObject::ot_Folder: name = i18n("Folder &name:"); break; case UMLObject::ot_Interface: name = i18n("Interface &name:"); break; case UMLObject::ot_Node: name = i18n("Node &name:"); break; case UMLObject::ot_Package: name = i18n("Package &name:"); break; case UMLObject::ot_Port: name = i18n("Port &name:"); break; case UMLObject::ot_Stereotype: name = i18n("Stereotype &name:"); break; case UMLObject::ot_UseCase: name = i18n("Use case &name:"); break; case UMLObject::ot_Instance: name = i18n("Instance name:"); break; default: name = QLatin1String(" &name:"); uWarning() << "unknown object type"; break; } return name; } /** * Returns the given object type value as icon type. * @param ot ObjectType of which an icon type is wanted * @return the ObjectType as icon type */ Icon_Utils::IconType UMLObject::toIcon(ObjectType t) { Icon_Utils::IconType icon; switch (t) { case UMLObject::ot_Actor: icon = Icon_Utils::it_Actor; break; case UMLObject::ot_Artifact: icon = Icon_Utils::it_Artifact; break; case UMLObject::ot_Association: icon = Icon_Utils::it_Association; break; case UMLObject::ot_Class: icon = Icon_Utils::it_Class; break; case UMLObject::ot_Component: icon = Icon_Utils::it_Component; break; case UMLObject::ot_Datatype: icon = Icon_Utils::it_Datatype; break; case UMLObject::ot_Entity: icon = Icon_Utils::it_Entity; break; case UMLObject::ot_Enum: icon = Icon_Utils::it_Enum; break; case UMLObject::ot_Folder: icon = Icon_Utils::it_Folder; break; case UMLObject::ot_Instance: icon = Icon_Utils::it_Instance; break; case UMLObject::ot_Interface: icon = Icon_Utils::it_Interface; break; case UMLObject::ot_Node: icon = Icon_Utils::it_Node; break; case UMLObject::ot_Package: icon = Icon_Utils::it_Package; break; case UMLObject::ot_Port: icon = Icon_Utils::it_Port; break; // case UMLObject::ot_Stereotype: // icon = Icon_Utils::it_Stereotype; // break; case UMLObject::ot_UseCase: icon = Icon_Utils::it_UseCase; break; default: icon = Icon_Utils::it_Home; uWarning() << "unknown object type"; break; } return icon; } /** * Print UML Object to debug output stream, so it can be used like * uDebug() << "This object shouldn't be here: " << illegalObject; */ QDebug operator<<(QDebug out, const UMLObject& obj) { out.nospace() << "UMLObject: name= " << obj.name() << ", type= " << UMLObject::toString(obj.m_BaseType); return out.space(); } //only required for getting types #include "actor.h" #include "artifact.h" #include "association.h" #include "attribute.h" #include "umlcanvasobject.h" #include "category.h" #include "checkconstraint.h" #include "classifier.h" #include "component.h" #include "datatype.h" #include "entity.h" #include "entityattribute.h" #include "entityconstraint.h" #include "enum.h" #include "enumliteral.h" #include "folder.h" #include "foreignkeyconstraint.h" #include "instance.h" #include "instanceattribute.h" #include "node.h" #include "operation.h" #include "package.h" #include "port.h" #include "umlrole.h" #include "stereotype.h" #include "template.h" #include "uniqueconstraint.h" #include "usecase.h" UMLActor* UMLObject::asUMLActor() { return dynamic_cast(this); } UMLArtifact* UMLObject::asUMLArtifact() { return dynamic_cast(this); } UMLAssociation* UMLObject::asUMLAssociation() { return dynamic_cast(this); } UMLAttribute* UMLObject::asUMLAttribute() { return dynamic_cast(this); } UMLCanvasObject* UMLObject::asUMLCanvasObject() { return dynamic_cast(this); } UMLCategory* UMLObject::asUMLCategory() { return dynamic_cast(this); } UMLCheckConstraint* UMLObject::asUMLCheckConstraint() { return dynamic_cast(this); } UMLClassifier* UMLObject::asUMLClassifier() { return dynamic_cast(this); } UMLClassifierListItem *UMLObject::asUMLClassifierListItem() { return dynamic_cast(this); } UMLComponent* UMLObject::asUMLComponent() { return dynamic_cast(this); } UMLDatatype *UMLObject::asUMLDatatype() { return dynamic_cast(this); } UMLEntity* UMLObject::asUMLEntity() { return dynamic_cast(this); } UMLEntityAttribute* UMLObject::asUMLEntityAttribute() { return dynamic_cast(this); } UMLEntityConstraint* UMLObject::asUMLEntityConstraint() { return dynamic_cast(this); } UMLEnum* UMLObject::asUMLEnum() { return dynamic_cast(this); } UMLEnumLiteral* UMLObject::asUMLEnumLiteral() { return dynamic_cast(this); } UMLFolder* UMLObject::asUMLFolder() { return dynamic_cast(this); } UMLForeignKeyConstraint* UMLObject::asUMLForeignKeyConstraint() { return dynamic_cast(this); } UMLInstance *UMLObject::asUMLInstance() { return dynamic_cast(this); } UMLInstanceAttribute *UMLObject::asUMLInstanceAttribute() { return dynamic_cast(this); } UMLNode* UMLObject::asUMLNode() { return dynamic_cast(this); } UMLObject* UMLObject::asUMLObject() { return dynamic_cast(this); } UMLOperation* UMLObject::asUMLOperation() { return dynamic_cast(this); } UMLPackage* UMLObject::asUMLPackage() { return dynamic_cast(this); } UMLPort* UMLObject::asUMLPort() { return dynamic_cast(this); } UMLRole* UMLObject::asUMLRole() { return dynamic_cast(this); } UMLStereotype* UMLObject::asUMLStereotype() { return dynamic_cast(this); } UMLTemplate* UMLObject::asUMLTemplate() { return dynamic_cast(this); } UMLUniqueConstraint* UMLObject::asUMLUniqueConstraint() { return dynamic_cast(this); } UMLUseCase* UMLObject::asUMLUseCase() { return dynamic_cast(this); } diff --git a/umbrello/umldoc.cpp b/umbrello/umldoc.cpp index 77469c0b5..402013607 100644 --- a/umbrello/umldoc.cpp +++ b/umbrello/umldoc.cpp @@ -1,3474 +1,3474 @@ /*************************************************************************** * 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 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 document resolution in DPI + * @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 @ref loadDiagrams(). + * 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 packageList; m_root[Uml::ModelType::Logical]->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/umlwidgets/associationwidget.cpp b/umbrello/umlwidgets/associationwidget.cpp index a4502d6bd..1da3ea875 100644 --- a/umbrello/umlwidgets/associationwidget.cpp +++ b/umbrello/umlwidgets/associationwidget.cpp @@ -1,4391 +1,4389 @@ /*************************************************************************** * 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. - * - * @param op Return this AssociationWidget's operation string. */ 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); } }//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); } }//end if widgetB moved if (m_role[RoleType::A].roleWidget && !m_role[RoleType::A].roleWidget->isSelected()) { setTextPositionRelatively(TextRole::RoleAName, m_oldRoleAPoint); } if (m_role[RoleType::B].roleWidget && !m_role[RoleType::B].roleWidget->isSelected()) { setTextPositionRelatively(TextRole::RoleBName, m_oldRoleBPoint); } if (m_role[RoleType::A].multiplicityWidget && !m_role[RoleType::A].multiplicityWidget->isSelected()) { setTextPositionRelatively(TextRole::MultiA, m_oldMultiAPoint); } if (m_role[RoleType::B].multiplicityWidget && !m_role[RoleType::B].multiplicityWidget->isSelected()) { setTextPositionRelatively(TextRole::MultiB, m_oldMultiBPoint); } if (m_role[RoleType::A].changeabilityWidget && !m_role[RoleType::A].changeabilityWidget->isSelected()) { setTextPositionRelatively(TextRole::ChangeA, m_oldChangeAPoint); } if (m_role[RoleType::B].changeabilityWidget && !m_role[RoleType::B].changeabilityWidget->isSelected()) { setTextPositionRelatively(TextRole::ChangeB, m_oldChangeBPoint); } } /** * Creates the points of the self association. * Method called when a widget end points are calculated by calculateEndingPoints(). */ void AssociationWidget::createPointsSelfAssociation() { UMLWidget *pWidgetA = m_role[RoleType::A].umlWidget; const int DISTANCE = 50; qreal x = pWidgetA->x(); qreal y = pWidgetA->y(); qreal h = pWidgetA->height(); qreal w = pWidgetA->width(); // see if above widget ok to start if (y - DISTANCE > 0) { m_associationLine->setEndPoints(QPointF(x + w / 4, y) , QPointF(x + w * 3 / 4, y)); m_associationLine->insertPoint(1, QPointF(x + w / 4, y - DISTANCE)); m_associationLine->insertPoint(2, QPointF(x + w * 3 / 4, y - DISTANCE)); m_role[RoleType::A].m_WidgetRegion = m_role[RoleType::B].m_WidgetRegion = Uml::Region::North; } else { m_associationLine->setEndPoints(QPointF(x + w / 4, y + h), QPointF(x + w * 3 / 4, y + h)); m_associationLine->insertPoint(1, QPointF(x + w / 4, y + h + DISTANCE)); m_associationLine->insertPoint(2, QPointF(x + w * 3 / 4, y + h + DISTANCE)); m_role[RoleType::A].m_WidgetRegion = m_role[RoleType::B].m_WidgetRegion = Uml::Region::South; } } /** * Adjusts the points of the self association. * Method called when a widget was moved by widgetMoved(widget, x, y). */ void AssociationWidget::updatePointsSelfAssociation() { UMLWidget *pWidgetA = m_role[RoleType::A].umlWidget; const int DISTANCE = 50; qreal x = pWidgetA->x(); qreal y = pWidgetA->y(); qreal h = pWidgetA->height(); qreal w = pWidgetA->width(); // see if above widget ok to start if (y - DISTANCE > 0) { m_associationLine->setEndPoints(QPointF(x + w / 4, y) , QPointF(x + w * 3 / 4, y)); m_associationLine->setPoint(1, QPointF(x + w / 4, y - DISTANCE)); m_associationLine->setPoint(2, QPointF(x + w * 3 / 4, y - DISTANCE)); m_role[RoleType::A].m_WidgetRegion = m_role[RoleType::B].m_WidgetRegion = Uml::Region::North; } else { m_associationLine->setEndPoints(QPointF(x + w / 4, y + h), QPointF(x + w * 3 / 4, y + h)); m_associationLine->setPoint(1, QPointF(x + w / 4, y + h + DISTANCE)); m_associationLine->setPoint(2, QPointF(x + w * 3 / 4, y + h + DISTANCE)); m_role[RoleType::A].m_WidgetRegion = m_role[RoleType::B].m_WidgetRegion = Uml::Region::South; } } /** * Creates the points of the association exception. * Method called when a widget end points are calculated by calculateEndingPoints(). */ void AssociationWidget::createPointsException() { UMLWidget *pWidgetA = m_role[RoleType::A].umlWidget; UMLWidget *pWidgetB = m_role[RoleType::B].umlWidget; qreal xa = pWidgetA->x(); qreal ya = pWidgetA->y(); qreal ha = pWidgetA->height(); qreal wa = pWidgetA->width(); qreal xb = pWidgetB->x(); qreal yb = pWidgetB->y(); qreal hb = pWidgetB->height(); //qreal wb = pWidgetB->width(); m_associationLine->setEndPoints(QPointF(xa + wa , ya + ha/2) , QPointF(xb , yb + hb/2)); m_associationLine->insertPoint(1, QPointF(xa + wa , ya + ha/2)); m_associationLine->insertPoint(2, QPointF(xb , yb + hb/2)); } /** * Adjusts the points of the association exception. * Method called when a widget was moved by widgetMoved(widget, x, y). */ void AssociationWidget::updatePointsException() { UMLWidget *pWidgetA = m_role[RoleType::A].umlWidget; UMLWidget *pWidgetB = m_role[RoleType::B].umlWidget; qreal xa = pWidgetA->x(); qreal ya = pWidgetA->y(); qreal ha = pWidgetA->height(); qreal wa = pWidgetA->width(); qreal xb = pWidgetB->x(); qreal yb = pWidgetB->y(); qreal hb = pWidgetB->height(); qreal wb = pWidgetB->width(); qreal xmil, ymil; qreal xdeb, ydeb; qreal xfin, yfin; qreal ESPACEX, ESPACEY; QPointF p1; QPointF p2; //calcul des coordonnées au milieu de la flèche eclair if (xb - xa - wa >= 45) { ESPACEX = 0; xdeb = xa + wa; xfin = xb; } else if (xa - xb - wb > 45) { ESPACEX = 0; xdeb = xa; xfin = xb + wb; } else { ESPACEX = 15; xdeb = xa + wa/2; xfin = xb + wb/2; } xmil = xdeb + (xfin - xdeb)/2; if (yb - ya - ha >= 45) { ESPACEY = 0; ydeb = ya + ha; yfin = yb; } else if (ya - yb - hb > 45) { ESPACEY = 0; ydeb = ya; yfin = yb + hb; } else { ESPACEY = 15; ydeb = ya + ha/2; yfin = yb + hb/2; } ymil = ydeb + (yfin - ydeb)/2; p1.setX(xmil + (xfin - xmil)*1/2); p1.setY(ymil + (yfin - ymil)*1/3); p2.setX(xmil - (xmil - xdeb)*1/2); p2.setY(ymil - (ymil - ydeb)*1/3); if (fabs(p1.x() - p2.x()) <= 10) ESPACEX = 15; if (fabs(p1.y() - p2.y()) <= 10) ESPACEY = 15; m_associationLine->setEndPoints(QPointF(xdeb, ydeb), QPointF(xfin, yfin)); m_associationLine->setPoint(1, QPointF(p1.x() + ESPACEX, p1.y() + ESPACEY)); m_associationLine->setPoint(2, QPointF(p2.x() - ESPACEX, p2.y() - ESPACEY)); m_role[RoleType::A].m_WidgetRegion = m_role[RoleType::B].m_WidgetRegion = Uml::Region::North; } /** * Finds out which region of rectangle 'rect' contains the point 'pos' and returns the region * number: * 1 = Region 1 * 2 = Region 2 * 3 = Region 3 * 4 = Region 4 * 5 = On diagonal 2 between Region 1 and 2 * 6 = On diagonal 1 between Region 2 and 3 * 7 = On diagonal 2 between Region 3 and 4 * 8 = On diagonal 1 between Region 4 and 1 * 9 = On diagonal 1 and On diagonal 2 (the center) */ Uml::Region::Enum AssociationWidget::findPointRegion(const QRectF& rect, const QPointF &pos) { qreal w = rect.width(); qreal h = rect.height(); qreal x = rect.x(); qreal y = rect.y(); qreal slope2 = w / h; qreal slope1 = slope2 *(-1.0); qreal b1 = x + w - (slope1 * y); qreal b2 = x - (slope2 * y); qreal eval1 = slope1 * pos.y() + b1; qreal eval2 = slope2 * pos.y() + b2; Uml::Region::Enum result = Uml::Region::Error; //if inside region 1 if (eval1 > pos.x() && eval2 > pos.x()) { result = Uml::Region::West; } //if inside region 2 else if (eval1 > pos.x() && eval2 < pos.x()) { result = Uml::Region::North; } //if inside region 3 else if (eval1 < pos.x() && eval2 < pos.x()) { result = Uml::Region::East; } //if inside region 4 else if (eval1 < pos.x() && eval2 > pos.x()) { result = Uml::Region::South; } //if inside region 5 else if (eval1 == pos.x() && eval2 < pos.x()) { result = Uml::Region::NorthWest; } //if inside region 6 else if (eval1 < pos.x() && eval2 == pos.x()) { result = Uml::Region::NorthEast; } //if inside region 7 else if (eval1 == pos.x() && eval2 > pos.x()) { result = Uml::Region::SouthEast; } //if inside region 8 else if (eval1 > pos.x() && eval2 == pos.x()) { result = Uml::Region::SouthWest; } //if inside region 9 else if (eval1 == pos.x() && eval2 == pos.x()) { result = Uml::Region::Center; } return result; } /** * Returns a point with interchanged X and Y coordinates. */ QPointF AssociationWidget::swapXY(const QPointF &p) { QPointF swapped( p.y(), p.x() ); return swapped; } #if 0 // not used at the moment /** * Calculates which point of segment P1P2 has a distance equal to * Distance from P1. * Let's say such point is PX, the distance from P1 to PX must be equal * to Distance and if PX is not a point of the segment P1P2 then the * function returns (-1, -1). */ QPointF AssociationWidget::calculatePointAtDistance(const QPointF &P1, const QPointF &P2, float Distance) { /* the distance D between points (x1, y1) and (x3, y3) has the following formula: --- ------------------------------ D = \ / 2 2 \ / (x3 - x1) + (y3 - y1) D, x1 and y1 are known and the point (x3, y3) is inside line (x1, y1)(x2, y2), so if the that line has the formula y = mx + b then y3 = m*x3 + b 2 2 2 D = (x3 - x1) + (y3 - y1) 2 2 2 2 2 D = x3 - 2*x3*x1 + x1 + y3 - 2*y3*y1 + y1 2 2 2 2 2 D - x1 - y1 = x3 - 2*x3*x1 + y3 - 2*y3*y1 2 2 2 2 2 D - x1 - y1 = x3 - 2*x3*x1 + (m*x3 + b) - 2*(m*x3 + b)*y1 2 2 2 2 2 2 D - x1 - y1 + 2*b*y1 - b = (m + 1)*x3 + (-2*x1 + 2*m*b -2*m*y1)*x3 2 2 2 2 C = - D + x1 + y1 - 2*b*y1 + b 2 A = (m + 1) B = (-2*x1 + 2*m*b -2*m*y1) and we have 2 A * x3 + B * x3 - C = 0 --------------- -B + --- / 2 \/ B - 4*A*C sol_1 = -------------------------------- 2*A --------------- -B - --- / 2 \/ B - 4*A*C sol_2 = -------------------------------- 2*A then in the distance formula we have only one variable x3 and that is easy to calculate */ int x1 = P1.y(); int y1 = P1.x(); int x2 = P2.y(); int y2 = P2.x(); if (x2 == x1) { return QPointF(x1, y1 + (int)Distance); } float slope = ((float)y2 - (float)y1) / ((float)x2 - (float)x1); float b = (y1 - slope*x1); float A = (slope * slope) + 1; float B = (2*slope*b) - (2*x1) - (2*slope*y1); float C = (b*b) - (Distance*Distance) + (x1*x1) + (y1*y1) - (2*b*y1); float t = B*B - 4*A*C; if (t < 0) { return QPointF(-1, -1); } float sol_1 = ((-1* B) + sqrt(t)) / (2*A); float sol_2 = ((-1*B) - sqrt(t)) / (2*A); if (sol_1 < 0.0 && sol_2 < 0.0) { return QPointF(-1, -1); } QPointF sol1Point((int)(slope*sol_1 + b), (int)(sol_1)); QPointF sol2Point((int)(slope*sol_2 + b), (int)(sol_2)); if (sol_1 < 0 && sol_2 >=0) { if (x2 > x1) { if (x1 <= sol_2 && sol_2 <= x2) return sol2Point; } else { if (x2 <= sol_2 && sol_2 <= x1) return sol2Point; } } else if (sol_1 >= 0 && sol_2 < 0) { if (x2 > x1) { if (x1 <= sol_1 && sol_1 <= x2) return sol1Point; } else { if (x2 <= sol_1 && sol_1 <= x1) return sol1Point; } } else { if (x2 > x1) { if (x1 <= sol_1 && sol_1 <= x2) return sol1Point; if (x1 <= sol_2 && sol_2 <= x2) return sol2Point; } else { if (x2 <= sol_1 && sol_1 <= x1) return sol1Point; if (x2 <= sol_2 && sol_2 <= x1) return sol2Point; } } return QPointF(-1, -1); } /** * Calculates which point of a perpendicular line to segment P1P2 that contains P2 * has a distance equal to Distance from P2, * Lets say such point is P3, the distance from P2 to P3 must be equal to Distance */ QPointF AssociationWidget::calculatePointAtDistanceOnPerpendicular(const QPointF &P1, const QPointF &P2, float Distance) { /* the distance D between points (x2, y2) and (x3, y3) has the following formula: --- ------------------------------ D = \ / 2 2 \ / (x3 - x2) + (y3 - y2) D, x2 and y2 are known and line P2P3 is perpendicular to line (x1, y1)(x2, y2), so if the line P1P2 has the formula y = m*x + b, then (x1 - x2) m = -----------, because it is perpendicular to line P1P2 (y2 - y1) also y2 = m*x2 + b => b = y2 - m*x2 then P3 = (x3, m*x3 + b) 2 2 2 D = (x3 - x2) + (y3 - y2) 2 2 2 2 2 D = x3 - 2*x3*x2 + x2 + y3 - 2*y3*y2 + y2 2 2 2 2 2 D - x2 - y2 = x3 - 2*x3*x2 + y3 - 2*y3*y2 2 2 2 2 2 D - x2 - y2 = x3 - 2*x3*x2 + (m*x3 + b) - 2*(m*x3 + b)*y2 2 2 2 2 2 2 D - x2 - y2 + 2*b*y2 - b = (m + 1)*x3 + (-2*x2 + 2*m*b -2*m*y2)*x3 2 2 2 2 C = - D + x2 + y2 - 2*b*y2 + b 2 A = (m + 1) B = (-2*x2 + 2*m*b -2*m*y2) and we have 2 A * x3 + B * x3 - C = 0 --------------- --- / 2 -B + \/ B - 4*A*C sol_1 = -------------------------------- 2*A --------------- --- / 2 -B - \/ B - 4*A*C sol_2 = -------------------------------- 2*A then in the distance formula we have only one variable x3 and that is easy to calculate */ if (P1.x() == P2.x()) { return QPointF((int)(P2.x() + Distance), P2.y()); } const int x1 = P1.y(); const int y1 = P1.x(); const int x2 = P2.y(); const int y2 = P2.x(); float slope = ((float)x1 - (float)x2) / ((float)y2 - (float)y1); float b = (y2 - slope*x2); float A = (slope * slope) + 1; float B = (2*slope*b) - (2*x2) - (2*slope*y2); float C = (b*b) - (Distance*Distance) + (x2*x2) + (y2*y2) - (2*b*y2); float t = B*B - 4*A*C; if (t < 0) { return QPointF(-1, -1); } float sol_1 = ((-1* B) + sqrt(t)) / (2*A); float sol_2 = ((-1*B) - sqrt(t)) / (2*A); if (sol_1 < 0 && sol_2 < 0) { return QPointF(-1, -1); } QPointF sol1Point((int)(slope*sol_1 + b), (int)sol_1); QPointF sol2Point((int)(slope*sol_2 + b), (int)sol_2); if (sol_1 < 0 && sol_2 >=0) { return sol2Point; } else if (sol_1 >= 0 && sol_2 < 0) { return sol1Point; } else { // Choose one solution, either will work fine if (slope >= 0) { if (sol_1 <= sol_2) return sol2Point; else return sol1Point; } else { if (sol_1 <= sol_2) return sol1Point; else return sol2Point; } } return QPointF(-1, -1); // never reached, just keep compilers happy } /** * Calculates the intersection (PS) between line P1P2 and a perpendicular line containing * P3, the result is returned in ResultingPoint. and result value represents the distance * between ResultingPoint and P3; if this value is negative an error ocurred. */ float AssociationWidget::perpendicularProjection(const QPointF& P1, const QPointF& P2, const QPointF& P3, QPointF& ResultingPoint) { //line P1P2 is Line 1 = y=slope1*x + b1 //line P3PS is Line 1 = y=slope2*x + b2 float slope2 = 0; float slope1 = 0; float sx = 0, sy = 0; int y2 = P2.x(); int y1 = P1.x(); int x2 = P2.y(); int x1 = P1.y(); int y3 = P3.x(); int x3 = P3.y(); float distance = 0; float b1 = 0; float b2 = 0; if (x2 == x1) { sx = x2; sy = y3; } else if (y2 == y1) { sy = y2; sx = x3; } else { slope1 = (y2 - y1)/ (x2 - x1); slope2 = (x1 - x2)/ (y2 - y1); b1 = y2 - (slope1 * x2); b2 = y3 - (slope2 * x3); sx = (b2 - b1) / (slope1 - slope2); sy = slope1*sx + b1; } distance = (int)(sqrt(((x3 - sx)*(x3 - sx)) + ((y3 - sy)*(y3 - sy)))); ResultingPoint.setX((int)sy); ResultingPoint.setY((int)sx); return distance; } #endif /** * Calculates the position of the text widget depending on the role * that widget is playing. * Returns the point at which to put the widget. */ QPointF AssociationWidget::calculateTextPosition(Uml::TextRole::Enum role) { const int SPACE = 2; QPointF p(-1, -1), q(-1, -1); // used to find out if association end point (p) // is at top or bottom edge of widget. if (role == TextRole::MultiA || role == TextRole::ChangeA || role == TextRole::RoleAName) { p = m_associationLine->point(0); q = m_associationLine->point(1); } else if (role == TextRole::MultiB || role == TextRole::ChangeB || role == TextRole::RoleBName) { const int lastSegment = m_associationLine->count() - 1; p = m_associationLine->point(lastSegment); q = m_associationLine->point(lastSegment - 1); } else if (role != TextRole::Name) { uError() << "called with unsupported TextRole::Enum " << role; return QPointF(-1, -1); } FloatingTextWidget *text = textWidgetByRole(role); int textW = 0, textH = 0; if (text) { textW = text->width(); textH = text->height(); } qreal x = 0.0, y = 0.0; if (role == TextRole::MultiA || role == TextRole::MultiB) { const bool isHorizontal = (p.y() == q.y()); const int atBottom = p.y() + SPACE; const int atTop = p.y() - SPACE - textH; const int atLeft = p.x() - SPACE - textW; const int atRight = p.x() + SPACE; y = (p.y() > q.y()) == isHorizontal ? atBottom : atTop; x = (p.x() < q.x()) == isHorizontal ? atRight : atLeft; } else if (role == TextRole::ChangeA || role == TextRole::ChangeB) { if (p.y() > q.y()) y = p.y() - SPACE - (textH * 2); else y = p.y() + SPACE + textH; if (p.x() < q.x()) x = p.x() + SPACE; else x = p.x() - SPACE - textW; } else if (role == TextRole::RoleAName || role == TextRole::RoleBName) { if (p.y() > q.y()) y = p.y() - SPACE - textH; else y = p.y() + SPACE; if (p.x() < q.x()) x = p.x() + SPACE; else x = p.x() - SPACE - textW; } else if (role == TextRole::Name) { calculateNameTextSegment(); if (m_unNameLineSegment == -1) { uWarning() << "TODO:negative line segment index"; m_unNameLineSegment = 0; } x = ( m_associationLine->point(m_unNameLineSegment).x() + m_associationLine->point(m_unNameLineSegment + 1).x() ) / 2; y = ( m_associationLine->point(m_unNameLineSegment).y() + m_associationLine->point(m_unNameLineSegment + 1).y() ) / 2; } if (text) { constrainTextPos(x, y, textW, textH, role); } p = QPointF( x, y ); return p; } /** * Return the mid point between p0 and p1 */ QPointF AssociationWidget::midPoint(const QPointF& p0, const QPointF& p1) { QPointF midP; if (p0.x() < p1.x()) midP.setX(p0.x() + (p1.x() - p0.x()) / 2); else midP.setX(p1.x() + (p0.x() - p1.x()) / 2); if (p0.y() < p1.y()) midP.setY(p0.y() + (p1.y() - p0.y()) / 2); else midP.setY(p1.y() + (p0.y() - p1.y()) / 2); return midP; } /** * Constrains the FloatingTextWidget X and Y values supplied. * Implements the abstract operation from LinkWidget. * * @param textX Candidate X value (may be modified by the constraint.) * @param textY Candidate Y value (may be modified by the constraint.) * @param textWidth Width of the text. * @param textHeight Height of the text. * @param tr Uml::Text_Role of the text. */ void AssociationWidget::constrainTextPos(qreal &textX, qreal &textY, qreal textWidth, qreal textHeight, Uml::TextRole::Enum tr) { const int textCenterX = textX + textWidth / 2; const int textCenterY = textY + textHeight / 2; const int lastSegment = m_associationLine->count() - 1; QPointF p0, p1; switch (tr) { case TextRole::RoleAName: case TextRole::MultiA: case TextRole::ChangeA: p0 = m_associationLine->point(0); p1 = m_associationLine->point(1); // If we are dealing with a single line then tie the // role label to the proper half of the line, i.e. // the role label must be closer to the "other" // role object. if (lastSegment == 1) p1 = midPoint(p0, p1); break; case TextRole::RoleBName: case TextRole::MultiB: case TextRole::ChangeB: p0 = m_associationLine->point(lastSegment - 1); p1 = m_associationLine->point(lastSegment); if (lastSegment == 1) p0 = midPoint(p0, p1); break; case TextRole::Name: case TextRole::Coll_Message: // CHECK: collab.msg texts seem to be TextRole::Name case TextRole::State: // CHECK: is this used? // Find the linepath segment to which the (textX, textY) is closest // and constrain to the corridor of that segment (see farther below) { int minDistSquare = 100000; // utopian initial value int lpIndex = 0; for (int i = 0; i < lastSegment; ++i) { p0 = m_associationLine->point(i); p1 = m_associationLine->point(i + 1); QPointF midP = midPoint(p0, p1); const int deltaX = textCenterX - midP.x(); const int deltaY = textCenterY - midP.y(); const int cSquare = deltaX * deltaX + deltaY * deltaY; if (cSquare < minDistSquare) { minDistSquare = cSquare; lpIndex = i; } } p0 = m_associationLine->point(lpIndex); p1 = m_associationLine->point(lpIndex + 1); } break; default: uError() << "unexpected TextRole::Enum " << tr; return; break; } /* Constraint: The midpoint between p0 and p1 is taken to be the center of a circle with radius D/2 where D is the distance between p0 and p1. The text center needs to be within this circle else it is constrained to the nearest point on the circle. */ p0 = swapXY(p0); // go to the natural coordinate system p1 = swapXY(p1); // with (0,0) in the lower left corner QPointF midP = midPoint(p0, p1); // If (textX,textY) is not inside the circle around midP then // constrain (textX,textY) to the nearest point on that circle. const int x0 = p0.x(); const int y0 = p0.y(); const int x1 = p1.x(); const int y1 = p1.y(); double r = sqrt((double)((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0))) / 2; if (textWidth > r) r = textWidth; // swap textCenter{X,Y} to convert from Qt coord.system. const QPointF origTextCenter(textCenterY, textCenterX); const int relX = fabs(origTextCenter.x() - midP.x()); const int relY = fabs(origTextCenter.y() - midP.y()); const double negativeWhenInsideCircle = relX * relX + relY * relY - r * r; if (negativeWhenInsideCircle <= 0.0) { return; } /* The original constraint was to snap the text position to the midpoint but that creates unpleasant visual jitter: textX = midP.y() - textWidth / 2; // go back to Qt coord.sys. textY = midP.x() - textHeight / 2; // go back to Qt coord.sys. Rather, we project the text position onto the closest point on the circle: Circle equation: relX^2 + relY^2 - r^2 = 0, or in other words relY^2 = r^2 - relX^2, or relY = sqrt(r^2 - relX^2) Line equation: relY = a * relX + b We can omit "b" because relX and relY are already relative to the circle origin, therefore we can also write: a = relY / relX To obtain the point of intersection between the circle of radius r and the line connecting the circle origin with the point (relX, relY), we equate the relY: a * x = sqrt(r^2 - x^2), or in other words a^2 * x^2 = r^2 - x^2, or x^2 * (a^2 + 1) = r^2, or x^2 = r^2 / (a^2 + 1), or x = sqrt(r^2 / (a^2 + 1)) and then y = a * x The resulting x and y are relative to the circle origin so we just add the circle origin (X, Y) to obtain the constrained (textX, textY). */ // Handle the special case, relX = 0. if (relX == 0) { if (origTextCenter.y() > midP.y()) textX = midP.y() + (int)r; // go back to Qt coord.sys. else textX = midP.y() - (int)r; // go back to Qt coord.sys. textX -= textWidth / 2; return; } const double a = (double)relY / (double)relX; const double x = sqrt(r*r / (a*a + 1)); const double y = a * x; if (origTextCenter.x() > midP.x()) textY = midP.x() + (int)x; // go back to Qt coord.sys. else textY = midP.x() - (int)x; // go back to Qt coord.sys. textY -= textHeight / 2; if (origTextCenter.y() > midP.y()) textX = midP.y() + (int)y; // go back to Qt coord.sys. else textX = midP.y() - (int)y; // go back to Qt coord.sys. textX -= textWidth / 2; } /** * Puts the text widget with the given role at the given position. * This method calls @ref calculateTextPostion to get the needed position. * I.e. the line segment it is on has moved and it should move the same * amount as the line. */ void AssociationWidget::setTextPosition(Uml::TextRole::Enum role) { bool startMove = false; if (m_role[RoleType::A].multiplicityWidget && m_role[RoleType::A].multiplicityWidget->getStartMove()) startMove = true; else if (m_role[RoleType::B].multiplicityWidget && m_role[RoleType::B].multiplicityWidget->getStartMove()) startMove = true; else if (m_role[RoleType::A].changeabilityWidget && m_role[RoleType::A].changeabilityWidget->getStartMove()) startMove = true; else if (m_role[RoleType::B].changeabilityWidget && m_role[RoleType::B].changeabilityWidget->getStartMove()) startMove = true; else if (m_role[RoleType::A].roleWidget && m_role[RoleType::A].roleWidget->getStartMove()) startMove = true; else if (m_role[RoleType::B].roleWidget && m_role[RoleType::B].roleWidget->getStartMove()) startMove = true; else if (m_nameWidget && m_nameWidget->getStartMove()) startMove = true; if (startMove) { return; } FloatingTextWidget *ft = textWidgetByRole(role); if (ft == 0) return; QPointF pos = calculateTextPosition(role); ft->setX(pos.x()); ft->setY(pos.y()); } /** * Moves the text widget with the given role by the difference between * the two points. */ void AssociationWidget::setTextPositionRelatively(Uml::TextRole::Enum role, const QPointF &oldPosition) { bool startMove = false; if (m_role[RoleType::A].multiplicityWidget && m_role[RoleType::A].multiplicityWidget->getStartMove()) startMove = true; else if (m_role[RoleType::B].multiplicityWidget && m_role[RoleType::B].multiplicityWidget->getStartMove()) startMove = true; else if (m_role[RoleType::A].changeabilityWidget && m_role[RoleType::A].changeabilityWidget->getStartMove()) startMove = true; else if (m_role[RoleType::B].changeabilityWidget && m_role[RoleType::B].changeabilityWidget->getStartMove()) startMove = true; else if (m_role[RoleType::A].roleWidget && m_role[RoleType::A].roleWidget->getStartMove()) startMove = true; else if (m_role[RoleType::B].roleWidget && m_role[RoleType::B].roleWidget->getStartMove()) startMove = true; else if (m_nameWidget && m_nameWidget->getStartMove()) startMove = true; if (startMove) { return; } FloatingTextWidget *ft = textWidgetByRole(role); if (ft == 0) return; qreal ftX = ft->x(); qreal ftY = ft->y(); QPointF pos = calculateTextPosition(role); int relX = pos.x() - oldPosition.x(); int relY = pos.y() - oldPosition.y(); qreal ftNewX = ftX + relX; qreal ftNewY = ftY + relY; bool oldIgnoreSnapToGrid = ft->getIgnoreSnapToGrid(); ft->setIgnoreSnapToGrid(true); ft->setX(ftNewX); ft->setY(ftNewY); ft->setIgnoreSnapToGrid(oldIgnoreSnapToGrid); } /** * Remove dashed connecting line for association class. */ void AssociationWidget::removeAssocClassLine() { delete m_pAssocClassLineSel0; m_pAssocClassLineSel0 = 0; delete m_pAssocClassLineSel1; m_pAssocClassLineSel1 = 0; delete m_pAssocClassLine; m_pAssocClassLine = 0; if (m_associationClass) { m_associationClass->setClassAssociationWidget(0); m_associationClass = 0; } } /** * Creates the association class connecting line. */ void AssociationWidget::createAssocClassLine() { if (m_pAssocClassLine == 0) { m_pAssocClassLine = new QGraphicsLineItem(this); } QPen pen(lineColor(), lineWidth(), Qt::DashLine); m_pAssocClassLine->setPen(pen); // decoration points m_pAssocClassLineSel0 = Widget_Utils::decoratePoint(m_pAssocClassLine->line().p1(), m_pAssocClassLine); m_pAssocClassLineSel1 = Widget_Utils::decoratePoint(m_pAssocClassLine->line().p2(), m_pAssocClassLine); computeAssocClassLine(); selectAssocClassLine(false); } /** * Creates the association class connecting line using the specified * ClassifierWidget. * * @param classifier The ClassifierWidget to use. * @param linePathSegmentIndex The index of the segment where the * association class is created. */ void AssociationWidget::createAssocClassLine(ClassifierWidget* classifier, int linePathSegmentIndex) { m_nLinePathSegmentIndex = linePathSegmentIndex; if (m_nLinePathSegmentIndex < 0) { return; } m_associationClass = classifier; m_associationClass->setClassAssociationWidget(this); m_associationClass->addAssoc(this); // to get widgetMoved(...) for association classes createAssocClassLine(); } /** * Compute the end points of m_pAssocClassLine in case this * association has an attached association class. * TODO: The decoration points make no sense for now, because they are not movable. */ void AssociationWidget::computeAssocClassLine() { if (m_associationClass == 0 || m_pAssocClassLine == 0) { return; } if (m_nLinePathSegmentIndex < 0) { uError() << "m_nLinePathSegmentIndex is not set"; return; } QPointF segStart = m_associationLine->point(m_nLinePathSegmentIndex); QPointF segEnd = m_associationLine->point(m_nLinePathSegmentIndex + 1); const qreal midSegX = segStart.x() + (segEnd.x() - segStart.x()) / 2.0; const qreal midSegY = segStart.y() + (segEnd.y() - segStart.y()) / 2.0; QPointF segmentMidPoint(midSegX, midSegY); QLineF possibleAssocLine = QLineF(segmentMidPoint, m_associationClass->mapRectToScene(m_associationClass->rect()).center()); QPointF intersectionPoint; QLineF::IntersectType type = intersect(m_associationClass->mapRectToScene(m_associationClass->boundingRect()), possibleAssocLine, &intersectionPoint); // DEBUG(DBG_SRC) << "intersect type=" << type << " / point=" << intersectionPoint; if (type == QLineF::BoundedIntersection) { m_pAssocClassLine->setLine(midSegX, midSegY, intersectionPoint.x(), intersectionPoint.y()); if (m_pAssocClassLineSel0 && m_pAssocClassLineSel1) { m_pAssocClassLineSel0->setPos(m_pAssocClassLine->line().p1()); m_pAssocClassLineSel1->setPos(m_pAssocClassLine->line().p2()); } } } /** * Renders the association class connecting line selected. */ void AssociationWidget::selectAssocClassLine(bool sel) { if (m_pAssocClassLineSel0 && m_pAssocClassLineSel1) { m_pAssocClassLineSel0->setVisible(sel); m_pAssocClassLineSel1->setVisible(sel); } } /** * Sets the association to be selected. */ void AssociationWidget::mousePressEvent(QGraphicsSceneMouseEvent * me) { // clear other selected stuff on the screen of ShiftKey if (me->modifiers() != Qt::ShiftModifier) { m_scene->clearSelected(); } if (me->button() == Qt::LeftButton && me->modifiers() == Qt::ControlModifier) { if (checkRemovePoint(me->scenePos())) return; } // make sure we should be here depending on the button if (me->button() != Qt::RightButton && me->button() != Qt::LeftButton) { return; } QPointF mep = me->scenePos(); // see if `mep' is on the connecting line to the association class if (onAssocClassLine(mep)) { setSelected(true); selectAssocClassLine(); return; } setSelected(!isSelected()); associationLine()->mousePressEvent(me); } /** * Displays the right mouse buttom menu if right button is pressed. */ void AssociationWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent * me) { associationLine()->mouseReleaseEvent(me); } /** * Handles the selection from the popup menu. */ void AssociationWidget::slotMenuSelection(QAction* action) { QString oldText, newText; bool ok = false; Uml::AssociationType::Enum atype = associationType(); Uml::RoleType::Enum r = RoleType::B; ListPopupMenu::MenuType sel = ListPopupMenu::typeFromAction(action); DEBUG(DBG_SRC) << "menu selection = " << ListPopupMenu::toString(sel); // if it's a collaboration message we now just use the code in floatingtextwidget // this means there's some redundant code below but that's better than duplicated code if (isCollaboration() && sel != ListPopupMenu::mt_Delete) { m_nameWidget->slotMenuSelection(action); return; } switch(sel) { case ListPopupMenu::mt_Properties: if (atype == AssociationType::Seq_Message || atype == AssociationType::Seq_Message_Self) { // show op dlg for seq. diagram here // don't worry about here, I don't think it can get here as // line is widget on seq. diagram // here just in case - remove later after testing DEBUG(DBG_SRC) << "mt_Properties: assoctype is " << atype; } else { //standard assoc dialog UMLApp::app()->docWindow()->updateDocumentation(false); showPropertiesDialog(); } break; case ListPopupMenu::mt_Add_Point: checkAddPoint(m_eventScenePos); break; case ListPopupMenu::mt_Delete_Point: checkRemovePoint(m_eventScenePos); break; case ListPopupMenu::mt_Delete: if (!Dialog_Utils::askDeleteAssociation()) break; if (m_pAssocClassLineSel0) removeAssocClassLine(); else if (association()) m_scene->removeAssocInViewAndDoc(this); else m_scene->removeWidgetCmd(this); break; case ListPopupMenu::mt_Rename_MultiA: r = RoleType::A; // fall through case ListPopupMenu::mt_Rename_MultiB: if (m_role[r].multiplicityWidget) oldText = m_role[r].multiplicityWidget->text(); else oldText = QString(); newText = oldText; ok = Dialog_Utils::askName(i18n("Multiplicity"), i18n("Enter multiplicity:"), newText); if (ok && newText != oldText) { if (FloatingTextWidget::isTextValid(newText)) { setMultiplicity(newText, r); } else { m_scene->removeWidget(m_role[r].multiplicityWidget); m_role[r].multiplicityWidget = 0; } } break; case ListPopupMenu::mt_Rename_Name: if (m_nameWidget) oldText = m_nameWidget->text(); else oldText = QString(); newText = oldText; ok = Dialog_Utils::askName(i18n("Association Name"), i18n("Enter association name:"), newText); if (ok && newText != oldText) { if (FloatingTextWidget::isTextValid(newText)) { setName(newText); } else if (m_nameWidget) { m_scene->removeWidget(m_nameWidget); m_nameWidget = 0; } } break; case ListPopupMenu::mt_Rename_RoleAName: r = RoleType::A; // fall through case ListPopupMenu::mt_Rename_RoleBName: if (m_role[r].roleWidget) oldText = m_role[r].roleWidget->text(); else oldText = QString(); newText = oldText; ok = Dialog_Utils::askName(i18n("Role Name"), i18n("Enter role name:"), newText); if (ok && newText != oldText) { if (FloatingTextWidget::isTextValid(newText)) { setRoleName(newText, r); } else { m_scene->removeWidget(m_role[r].roleWidget); m_role[r].roleWidget = 0; } } break; case ListPopupMenu::mt_Change_Font: { #if QT_VERSION >= 0x050000 bool ok = false; QFont fnt = QFontDialog::getFont(&ok, font(), m_scene->activeView()); if (ok) #else QFont fnt = font(); if (KFontDialog::getFont(fnt, KFontChooser::NoDisplayFlags, m_scene->activeView())) #endif lwSetFont(fnt); } break; case ListPopupMenu::mt_Line_Color: { #if QT_VERSION >= 0x050000 QColor newColor = QColorDialog::getColor(lineColor()); if (newColor != lineColor()) { #else QColor newColor; if (KColorDialog::getColor(newColor)) { #endif m_scene->selectionSetLineColor(newColor); umlDoc()->setModified(true); } } break; case ListPopupMenu::mt_Cut: m_scene->setStartedCut(); UMLApp::app()->slotEditCut(); break; case ListPopupMenu::mt_Copy: UMLApp::app()->slotEditCopy(); break; case ListPopupMenu::mt_Paste: UMLApp::app()->slotEditPaste(); break; case ListPopupMenu::mt_Reset_Label_Positions: resetTextPositions(); break; case ListPopupMenu::mt_LayoutDirect: m_associationLine->setLayout(AssociationLine::Direct); break; case ListPopupMenu::mt_LayoutSpline: m_associationLine->setLayout(AssociationLine::Spline); break; case ListPopupMenu::mt_LayoutOrthogonal: m_associationLine->setLayout(AssociationLine::Orthogonal); break; case ListPopupMenu::mt_LayoutPolyline: m_associationLine->setLayout(AssociationLine::Polyline); break; default: DEBUG(DBG_SRC) << "MenuType " << ListPopupMenu::toString(sel) << " not implemented"; break; }//end switch } /** * Return the first font found being used by any child widget. (They * could be different fonts, so this is a slightly misleading method.) */ QFont AssociationWidget::font() const { //:TODO: find a general font for the association QFont font; if (m_role[RoleType::A].roleWidget) font = m_role[RoleType::A].roleWidget->font(); else if (m_role[RoleType::B].roleWidget) font = m_role[RoleType::B].roleWidget->font(); else if (m_role[RoleType::A].multiplicityWidget) font = m_role[RoleType::A].multiplicityWidget->font(); else if (m_role[RoleType::B].multiplicityWidget) font = m_role[RoleType::B].multiplicityWidget->font(); else if (m_role[RoleType::A].changeabilityWidget) font = m_role[RoleType::A].changeabilityWidget->font(); else if (m_role[RoleType::B].changeabilityWidget) font = m_role[RoleType::B].changeabilityWidget->font(); else if (m_nameWidget) font = m_nameWidget->font(); else font = m_role[RoleType::A].umlWidget->font(); return font; } /** * Set all 'owned' child widgets to this text color. */ void AssociationWidget::setTextColor(const QColor &color) { WidgetBase::setTextColor(color); if (m_nameWidget) { m_nameWidget->setTextColor(color); } if (m_role[RoleType::A].roleWidget) { m_role[RoleType::A].roleWidget->setTextColor(color); } if (m_role[RoleType::B].roleWidget) { m_role[RoleType::B].roleWidget->setTextColor(color); } if (m_role[RoleType::A].multiplicityWidget) { m_role[RoleType::A].multiplicityWidget->setTextColor(color); } if (m_role[RoleType::B].multiplicityWidget) { m_role[RoleType::B].multiplicityWidget->setTextColor(color); } if (m_role[RoleType::A].changeabilityWidget) m_role[RoleType::A].changeabilityWidget->setTextColor(color); if (m_role[RoleType::B].changeabilityWidget) m_role[RoleType::B].changeabilityWidget->setTextColor(color); } void AssociationWidget::setLineColor(const QColor &color) { WidgetBase::setLineColor(color); QPen pen = m_associationLine->pen(); pen.setColor(color); m_associationLine->setPen(pen); } void AssociationWidget::setLineWidth(uint width) { WidgetBase::setLineWidth(width); QPen pen = m_associationLine->pen(); pen.setWidth(width); m_associationLine->setPen(pen); } bool AssociationWidget::checkAddPoint(const QPointF &scenePos) { if (associationType() == AssociationType::Exception) { return false; } // if there is no point around the mouse pointer, we insert a new one if (m_associationLine->closestPointIndex(scenePos) < 0) { int i = m_associationLine->closestSegmentIndex(scenePos); if (i < 0) { DEBUG(DBG_SRC) << "no closest segment found!"; return false; } m_associationLine->insertPoint(i + 1, scenePos); if (m_nLinePathSegmentIndex == i) { QPointF segStart = m_associationLine->point(i); QPointF segEnd = m_associationLine->point(i + 2); const int midSegX = segStart.x() + (segEnd.x() - segStart.x()) / 2; const int midSegY = segStart.y() + (segEnd.y() - segStart.y()) / 2; /* DEBUG(DBG_SRC) << "segStart=" << segStart << ", segEnd=" << segEnd << ", midSeg=(" << midSegX << "," << midSegY << "), mp=" << mp; */ if (midSegX > scenePos.x() || midSegY < scenePos.y()) { m_nLinePathSegmentIndex++; DEBUG(DBG_SRC) << "setting m_nLinePathSegmentIndex to " << m_nLinePathSegmentIndex; computeAssocClassLine(); } m_associationLine->update(); calculateNameTextSegment(); umlDoc()->setModified(true); setSelected(true); } return true; } else { DEBUG(DBG_SRC) << "found point already close enough!"; return false; } } /** * Remove point close to the given point and redraw the association. * @param scenePos point which should be removed * @return success status of the remove action */ bool AssociationWidget::checkRemovePoint(const QPointF &scenePos) { int i = m_associationLine->closestPointIndex(scenePos); if (i == -1) return false; m_associationLine->setSelected(false); // there was a point so we remove the point m_associationLine->removePoint(i); // Maybe reattach association class connecting line // to different association linepath segment. const int numberOfLines = m_associationLine->count() - 1; if (m_nLinePathSegmentIndex >= numberOfLines) { m_nLinePathSegmentIndex = numberOfLines - 1; } calculateEndingPoints(); // select the line path m_associationLine->setSelected(true); m_associationLine->update(); calculateNameTextSegment(); umlDoc()->setModified(true); return true; } /** * Moves the break point being dragged. */ void AssociationWidget::mouseMoveEvent(QGraphicsSceneMouseEvent* me) { if (me->buttons() != Qt::LeftButton) { return; } setSelected(true); associationLine()->mouseMoveEvent(me); moveEvent(me); m_scene->resizeSceneToItems(); } /** * Returns the Region the widget to line intersection is for the given * widget in this Association. If the given widget is not in the * Association then Region::Error is returned. * Used by @ref calculateEndingPoints to work these positions out for * another Association - since the number of Associations on the same * region for the same widget will mean the lines will need to be * spread out across the region. */ //Uml::Region::Enum AssociationWidget::getWidgetRegion(AssociationWidget * widget) const //{ // if (widget->widgetForRole(RoleType::A) == m_role[RoleType::A].umlWidget) // return m_role[RoleType::A].m_WidgetRegion; // if (widget->widgetForRole(RoleType::B) == m_role[RoleType::B].umlWidget) // return m_role[RoleType::B].m_WidgetRegion; // return Uml::Region::Error; //} /** * Returns the number of lines there are on the given region for * either widget A or B of the association. */ int AssociationWidget::getRegionCount(Uml::Region::Enum region, Uml::RoleType::Enum role) { if ((region == Uml::Region::Error) | (umlScene() == 0)) { return 0; } int widgetCount = 0; AssociationWidgetList list = m_scene->associationList(); foreach (AssociationWidget* assocwidget, list) { //don't count this association if (assocwidget == this) continue; const WidgetRole& otherA = assocwidget->m_role[RoleType::A]; const WidgetRole& otherB = assocwidget->m_role[RoleType::B]; const UMLWidget *a = otherA.umlWidget; const UMLWidget *b = otherB.umlWidget; /* //don't count associations to self if both of their end points are on the same region //they are different and placement won't interfere with them if (a == b && otherA.m_WidgetRegion == otherB.m_WidgetRegion) continue; */ if (m_role[role].umlWidget == a && region == otherA.m_WidgetRegion) widgetCount++; else if (m_role[role].umlWidget == b && region == otherB.m_WidgetRegion) widgetCount++; }//end foreach return widgetCount; } /** * Find the border point of the given rect when a line is drawn from the * given point to the rect. * @param rect rect of a classifier * @param line a line to the rect * @param intersectionPoint the intercept point on the border of the rect * @return the type of the intersection @ref QLineF::IntersectType */ QLineF::IntersectType AssociationWidget::intersect(const QRectF &rect, const QLineF &line, QPointF* intersectionPoint) { QList lines; lines << QLineF(rect.topLeft(), rect.topRight()); lines << QLineF(rect.topRight(), rect.bottomRight()); lines << QLineF(rect.bottomRight(), rect.bottomLeft()); lines << QLineF(rect.bottomLeft(), rect.topLeft()); foreach (const QLineF& rectLine, lines) { QLineF::IntersectType type = rectLine.intersect(line, intersectionPoint); if (type == QLineF::BoundedIntersection) { return type; } } return QLineF::NoIntersection; } /** * Given a rectangle and a point, findInterceptOnEdge computes the * connecting line between the middle point of the rectangle and * the point, and returns the intercept of this line with the * the edge of the rectangle identified by `region'. * When the region is North or South, the X value is returned (Y is * constant.) * When the region is East or West, the Y value is returned (X is * constant.) * @todo This is buggy. Try replacing by intersect() */ qreal AssociationWidget::findInterceptOnEdge(const QRectF &rect, Uml::Region::Enum region, const QPointF &point) { // The Qt coordinate system has (0, 0) in the top left corner. // In order to go to the regular XY coordinate system with (0, 0) // in the bottom left corner, we swap the X and Y axis. // That's why the following assignments look twisted. const qreal rectHalfWidth = rect.height() / 2.0; const qreal rectHalfHeight = rect.width() / 2.0; const qreal rectMidX = rect.y() + rectHalfWidth; const qreal rectMidY = rect.x() + rectHalfHeight; const qreal dX = rectMidX - point.y(); const qreal dY = rectMidY - point.x(); switch (region) { case Uml::Region::West: region = Uml::Region::South; break; case Uml::Region::North: region = Uml::Region::West; break; case Uml::Region::East: region = Uml::Region::North; break; case Uml::Region::South: region = Uml::Region::East; break; default: break; } // Now we have regular coordinates with the point (0, 0) in the // bottom left corner. if (region == Uml::Region::North || region == Uml::Region::South) { if (dX == 0) return rectMidY; // should be rectMidX, but we go back to Qt coord.sys. if (dY == 0) { uError() << "usage error: " << "North/South (dY == 0)"; return -1.0; } const qreal m = dY / dX; qreal relativeX; if (region == Uml::Region::North) relativeX = rectHalfHeight / m; else relativeX = -rectHalfHeight / m; return (rectMidY + relativeX); // should be rectMidX, but we go back to Qt coord.sys. } else { if (dY == 0) return rectMidX; // should be rectMidY, but we go back to Qt coord.sys. if (dX == 0) { uError() << "usage error: " << "East/West (dX == 0)"; return -1.0; } const qreal m = dY / dX; qreal relativeY = m * rectHalfWidth; if (region == Uml::Region::West) relativeY = -relativeY; return (rectMidX + relativeY); // should be rectMidY, but we go back to Qt coord.sys. } } /** * Auxiliary method for updateAssociations(): * Put position into m_positions and assoc into m_ordered at the * correct index. * m_positions and m_ordered move in parallel and are sorted by * ascending position. */ void AssociationWidget::insertIntoLists(qreal position, const AssociationWidget* assoc) { bool did_insertion = false; for (int index = 0; index < m_positions_len; ++index) { if (position < m_positions[index]) { for (int moveback = m_positions_len; moveback > index; moveback--) m_positions[moveback] = m_positions[moveback - 1]; m_positions[index] = position; m_ordered.insert(index, const_cast(assoc)); did_insertion = true; break; } } if (! did_insertion) { m_positions[m_positions_len] = position; m_ordered.append(const_cast(assoc)); } m_positions_len++; } /** * Tells all the other view associations the new count for the * given widget on a certain region. And also what index they should be. */ void AssociationWidget::updateAssociations(int totalCount, Uml::Region::Enum region, Uml::RoleType::Enum role) { if ((region == Uml::Region::Error) | (umlScene() == 0)) { return; } AssociationWidgetList list = m_scene->associationList(); UMLWidget *ownWidget = m_role[role].umlWidget; m_positions_len = 0; m_ordered.clear(); // we order the AssociationWidget list by region and x/y value foreach (AssociationWidget* assocwidget, list) { WidgetRole *roleA = &assocwidget->m_role[RoleType::A]; WidgetRole *roleB = &assocwidget->m_role[RoleType::B]; UMLWidget *wA = roleA->umlWidget; UMLWidget *wB = roleB->umlWidget; // Skip self associations. if (wA == wB) continue; // Now we must find out with which end the assocwidget connects // to the input widget (ownWidget). bool inWidgetARegion = (ownWidget == wA && region == roleA->m_WidgetRegion); bool inWidgetBRegion = (ownWidget == wB && region == roleB->m_WidgetRegion); if (!inWidgetARegion && !inWidgetBRegion) continue; // Determine intercept position on the edge indicated by `region'. UMLWidget * otherWidget = (inWidgetARegion ? wB : wA); AssociationLine *linepath = assocwidget->associationLine(); QPointF refpoint; if (assocwidget->linePathStartsAt(otherWidget)) refpoint = linepath->point(linepath->count() - 2); else refpoint = linepath->point(1); // The point is authoritative if we're called for the second time // (i.e. role==B) or it is a waypoint on the line path. bool pointIsAuthoritative = (role == RoleType::B || linepath->count() > 2); if (! pointIsAuthoritative) { // If the point is not authoritative then we use the other // widget's center. refpoint.setX(otherWidget->scenePos().x() + otherWidget->width() / 2); refpoint.setY(otherWidget->scenePos().y() + otherWidget->height() / 2); } qreal intercept = findInterceptOnEdge(ownWidget->rect(), region, refpoint); if (intercept < 0) { DEBUG(DBG_SRC) << "error from findInterceptOnEdge for" << " assocType=" << assocwidget->associationType() << " ownWidget=" << ownWidget->name() << " otherWidget=" << otherWidget->name(); continue; } insertIntoLists(intercept, assocwidget); } // while ((assocwidget = assoc_it.current())) // we now have an ordered list and we only have to call updateRegionLineCount int index = 1; foreach (AssociationWidget* assocwidget, m_ordered ) { if (ownWidget == assocwidget->widgetForRole(RoleType::A)) { assocwidget->updateRegionLineCount(index++, totalCount, region, RoleType::A); } else if (ownWidget == assocwidget->widgetForRole(RoleType::B)) { assocwidget->updateRegionLineCount(index++, totalCount, region, RoleType::B); } } // for (assocwidget = ordered.first(); ...) } /** * Called to tell the association that another association has added * a line to the region of one of its widgets. The widget is identified * by its role (A or B). * * Called by @ref updateAssociations which is called by * @ref calculateEndingPoints when required. */ void AssociationWidget::updateRegionLineCount(int index, int totalCount, Uml::Region::Enum region, Uml::RoleType::Enum role) { if ((region == Uml::Region::Error) | (umlScene() == 0)) { return; } // If the association is to self and the line ends are on the same region then // use a different calculation. if (isSelf() && m_role[RoleType::A].m_WidgetRegion == m_role[RoleType::B].m_WidgetRegion) { UMLWidget * pWidget = m_role[RoleType::A].umlWidget; qreal x = pWidget->scenePos().x(); qreal y = pWidget->scenePos().y(); qreal wh = pWidget->height(); qreal ww = pWidget->width(); int size = m_associationLine->count(); // See if above widget ok to place assoc. switch( m_role[RoleType::A].m_WidgetRegion ) { case Uml::Region::North: m_associationLine->setPoint( 0, QPointF( x + ( ww / 4 ), y ) ); m_associationLine->setPoint( size - 1, QPointF(x + ( ww * 3 / 4 ), y ) ); break; case Uml::Region::South: m_associationLine->setPoint( 0, QPointF( x + ( ww / 4 ), y + wh ) ); m_associationLine->setPoint( size - 1, QPointF( x + ( ww * 3 / 4 ), y + wh ) ); break; case Uml::Region::East: m_associationLine->setPoint( 0, QPointF( x + ww, y + ( wh / 4 ) ) ); m_associationLine->setPoint( size - 1, QPointF( x + ww, y + ( wh * 3 / 4 ) ) ); break; case Uml::Region::West: m_associationLine->setPoint( 0, QPointF( x, y + ( wh / 4 ) ) ); m_associationLine->setPoint( size - 1, QPointF( x, y + ( wh * 3 / 4 ) ) ); break; default: break; }//end switch return; } WidgetRole& robj = m_role[role]; UMLWidget * pWidget = robj.umlWidget; robj.m_nIndex = index; robj.m_nTotalCount = totalCount; qreal x = pWidget->scenePos().x(); qreal y = pWidget->scenePos().y(); qreal ww = pWidget->width(); qreal wh = pWidget->height(); const bool angular = Settings::optionState().generalState.angularlines; qreal ch = 0; qreal cw = 0; if (angular) { uint nind = (role == RoleType::A ? 1 : m_associationLine->count() - 2); QPointF neighbour = m_associationLine->point(nind); if (neighbour.x() < x) cw = 0; else if (neighbour.x() > x + ww) cw = 0 + ww; else cw = neighbour.x() - x; if (neighbour.y() < y) ch = 0; else if (neighbour.y() > y + wh) ch = 0 + wh; else ch = neighbour.y() - y; } else { ch = wh * index / totalCount; cw = ww * index / totalCount; } qreal newX = x + cw; qreal newY = y + ch; QPointF pt; if (angular) { pt = QPointF(newX, newY); } else { UMLWidget *pWidgetA = m_role[RoleType::A].umlWidget; UMLWidget *pWidgetB = m_role[RoleType::B].umlWidget; QList polyListA = pWidgetA->shape().toSubpathPolygons(); QPolygonF polyA = polyListA.at(0); if (polyListA.size() > 1) { for (int i = 1; i < polyListA.size(); i++) { polyA = polyA.united(polyListA.at(i)); } } polyA = polyA.translated(pWidgetA->pos()); QList polyListB = pWidgetB->shape().toSubpathPolygons(); QPolygonF polyB = polyListB.at(0); if (polyListB.size() > 1) { for (int i = 1; i < polyListB.size(); i++) { polyB = polyB.united(polyListB.at(i)); } } polyB = polyB.translated(pWidgetB->pos()); QLineF nearestPoints = Widget_Utils::closestPoints(polyA, polyB); if (nearestPoints.isNull()) { uError() << "Widget_Utils::closestPoints failed, falling back to simple widget positions"; switch(region) { case Uml::Region::West: pt.setX(x); pt.setY(newY); break; case Uml::Region::North: pt.setX(newX); pt.setY(y); break; case Uml::Region::East: pt.setX(x + ww); pt.setY(newY); break; case Uml::Region::South: pt.setX(newX); pt.setY(y + wh); break; case Uml::Region::Center: pt.setX(x + ww / 2); pt.setY(y + wh / 2); break; default: break; } } else { if (role == RoleType::A) pt = nearestPoints.p1(); else pt = nearestPoints.p2(); } } if (role == RoleType::A) { m_associationLine->setPoint(0, pt); } else { m_associationLine->setPoint(m_associationLine->count() - 1, pt); } } /** * Sets the state of whether the widget is selected. * * @param _select The state of whether the widget is selected. */ void AssociationWidget::setSelected(bool _select /* = true */) { WidgetBase::setSelected(_select); if ( m_nameWidget) m_nameWidget->setSelected( _select ); if ( m_role[RoleType::A].roleWidget ) m_role[RoleType::A].roleWidget->setSelected( _select ); if ( m_role[RoleType::B].roleWidget ) m_role[RoleType::B].roleWidget->setSelected( _select ); if ( m_role[RoleType::A].multiplicityWidget ) m_role[RoleType::A].multiplicityWidget->setSelected( _select ); if ( m_role[RoleType::B].multiplicityWidget ) m_role[RoleType::B].multiplicityWidget->setSelected( _select ); if ( m_role[RoleType::A].changeabilityWidget) m_role[RoleType::A].changeabilityWidget->setSelected( _select ); if ( m_role[RoleType::B].changeabilityWidget) m_role[RoleType::B].changeabilityWidget->setSelected( _select ); // Update the docwindow for this association. // This is done last because each of the above setSelected calls // overwrites the docwindow, but we want the main association doc // to win. if (_select) { UMLApp::app()->docWindow()->showDocumentation(this, false); } else UMLApp::app()->docWindow()->updateDocumentation(true); m_associationLine->setSelected(_select); if (! _select) { // For now, if _select is true we don't make the assoc class line // selected. But that's certainly open for discussion. // At any rate, we need to deselect the assoc class line // if _select is false. selectAssocClassLine(false); } UMLApp::app()->document()->writeToStatusBar(_select ? i18n("Press Ctrl with left mouse click to delete a point") : QString()); } /** * Reimplement method from WidgetBase in order to check owned floating texts. * * @param p Point to be checked. * * @return m_nameWidget if m_nameWidget is non NULL and m_nameWidget->onWidget(p) returns non 0; * m_role[0].(multiplicity|changeability|role)Widget if the resp. widget is non NULL and * its onWidget(p) returns non 0; * m_role[1].(multiplicity|changeability|role)Widget if the resp. widget is non NULL and * its onWidget(p) returns non 0; * else NULL. */ UMLWidget* AssociationWidget::onWidget(const QPointF &p) { if (m_nameWidget && m_nameWidget->onWidget(p)) { return m_nameWidget; } for (int i = 0; i <= 1; i++) { const WidgetRole& r = m_role[i]; if (r.multiplicityWidget && r.multiplicityWidget->onWidget(p)) return r.multiplicityWidget; if (r.changeabilityWidget && r.changeabilityWidget->onWidget(p)) return r.changeabilityWidget; if (r.roleWidget && r.roleWidget->onWidget(p)) return r.roleWidget; } return 0; } /** * Returns true if the given point is on the connecting line to * the association class. Returns false if there is no association * class attached, or if the given point is not on the connecting * line. */ bool AssociationWidget::onAssocClassLine(const QPointF &point) { bool onLine = false; if (m_pAssocClassLine) { //:TODO: // const QPointF mapped = m_pAssocClassLine->mapFromParent(point); // bool onLine = m_pAssocClassLine->contains(mapped); // return onLine; UMLSceneItemList list = m_scene->collisions(point); UMLSceneItemList::iterator end(list.end()); for (UMLSceneItemList::iterator item_it(list.begin()); item_it != end; ++item_it) { if (*item_it == m_pAssocClassLine) { onLine = true; break; } } } DEBUG(DBG_SRC) << onLine; return onLine; } /** * Returns true if the given point is on the association line. * A circle (rectangle) around the point is used to obtain more tolerance. * @param point the point to check * @return flag whether point is on association line */ bool AssociationWidget::onAssociation(const QPointF& point) { // check the path const qreal diameter(4.0); QPainterPath path = m_associationLine->shape(); if (path.contains(point)) { DEBUG(DBG_SRC) << "on path"; return true; } // check also the points if (m_associationLine->layout() == AssociationLine::Spline) { if (m_associationLine->closestPointIndex(point, diameter) > -1) { DEBUG(DBG_SRC) << "on spline point"; return true; } } return onAssocClassLine(point); } /** * Set all association points to x coordinate. */ void AssociationWidget::setXEntireAssoc(qreal x) { for (int i = 0; i < m_associationLine->count(); ++i) { QPointF p = m_associationLine->point(i); p.setX(x); m_associationLine->setPoint(i, p); } } /** * Set all association points to y coordinate. */ void AssociationWidget::setYEntireAssoc(qreal y) { for (int i = 0; i < m_associationLine->count(); ++i) { QPointF p = m_associationLine->point(i); p.setY(y); m_associationLine->setPoint(i, p); } } /** * Moves all the mid points (all expcept start /end) by the given amount. */ void AssociationWidget::moveMidPointsBy(qreal x, qreal y) { int pos = m_associationLine->count() - 1; for (int i = 1; i < (int)pos; ++i) { QPointF p = m_associationLine->point( i ); qreal newX = p.x() + x; qreal newY = p.y() + y; p.setX( newX ); p.setY( newY ); m_associationLine->setPoint( i, p ); } } /** * Moves the entire association by the given offset. */ void AssociationWidget::moveEntireAssoc(qreal x, qreal y) { //TODO: ADD SUPPORT FOR ASSOC. ON SEQ. DIAGRAMS WHEN NOTES BACK IN. moveMidPointsBy(x, y); // multi select if (umlScene()->selectedCount() > 1) { QPointF d(x, y); QPointF s = m_associationLine->startPoint() + d; QPointF e = m_associationLine->endPoint() + d; m_associationLine->setEndPoints(s, e); } calculateEndingPoints(); calculateNameTextSegment(); resetTextPositions(); } /** * Returns the bounding rectangle of all segments of the association. */ QRectF AssociationWidget::boundingRect() const { return m_associationLine->boundingRect(); } /** * Returns the shape of all segments of the association. */ QPainterPath AssociationWidget::shape() const { return m_associationLine->shape(); } /** * Connected to UMLClassifier::attributeRemoved() or UMLEntity::constraintRemoved() * in case this AssociationWidget is linked to a clasifier list item * (an attribute or a foreign key constraint) * * @param obj The UMLClassifierListItem removed. */ void AssociationWidget::slotClassifierListItemRemoved(UMLClassifierListItem* obj) { if (obj != m_umlObject) { DEBUG(DBG_SRC) << "obj=" << obj << ": m_umlObject=" << m_umlObject; return; } m_umlObject = 0; m_scene->removeWidgetCmd(this); } /** * Connected to UMLObject::modified() in case this * AssociationWidget is linked to a classifer's attribute type. */ void AssociationWidget::slotAttributeChanged() { UMLAttribute *attr = attribute(); if (attr == 0) { uError() << "attribute() returns NULL"; return; } setVisibility(attr->visibility(), RoleType::B); setRoleName(attr->name(), RoleType::B); } void AssociationWidget::clipSize() { if (m_nameWidget) m_nameWidget->clipSize(); if (m_role[RoleType::A].multiplicityWidget) m_role[RoleType::A].multiplicityWidget->clipSize(); if (m_role[RoleType::B].multiplicityWidget) m_role[RoleType::B].multiplicityWidget->clipSize(); if (m_role[RoleType::A].roleWidget) m_role[RoleType::A].roleWidget->clipSize(); if (m_role[RoleType::B].roleWidget) m_role[RoleType::B].roleWidget->clipSize(); if (m_role[RoleType::A].changeabilityWidget) m_role[RoleType::A].changeabilityWidget->clipSize(); if (m_role[RoleType::B].changeabilityWidget) m_role[RoleType::B].changeabilityWidget->clipSize(); if (m_associationClass) m_associationClass->clipSize(); } /** * Event handler for context menu events, called from the line segments. */ void AssociationWidget::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) { event->accept(); UMLScene *scene = umlScene(); QWidget *parent = 0; if (scene) { parent = scene->activeView(); } if (!isSelected() && scene && !scene->selectedItems().isEmpty()) { Qt::KeyboardModifiers forSelection = (Qt::ControlModifier | Qt::ShiftModifier); if ((event->modifiers() & forSelection) == 0) { scene->clearSelection(); } } setSelected(true); m_eventScenePos = event->scenePos(); const Uml::AssociationType::Enum type = onAssocClassLine(event->scenePos()) ? Uml::AssociationType::Anchor : associationType(); AssociationWidgetPopupMenu popup(parent, type, this); QAction *triggered = popup.exec(event->screenPos()); slotMenuSelection(triggered); } /** * Reimplemented event handler for hover enter events. */ void AssociationWidget::hoverEnterEvent(QGraphicsSceneHoverEvent *event) { associationLine()->hoverEnterEvent(event); } /** * Reimplemented event handler for hover leave events. */ void AssociationWidget::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) { associationLine()->hoverLeaveEvent(event); } /** * Reimplemented event handler for hover move events. */ void AssociationWidget::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { associationLine()->hoverMoveEvent(event); } /** * Saves this widget to the "assocwidget" XMI element. */ void AssociationWidget::saveToXMI1(QDomDocument &qDoc, QDomElement &qElement) { QDomElement assocElement = qDoc.createElement(QLatin1String("assocwidget")); WidgetBase::saveToXMI1(qDoc, assocElement); LinkWidget::saveToXMI1(qDoc, assocElement); if (m_umlObject) { assocElement.setAttribute(QLatin1String("xmi.id"), Uml::ID::toString(m_umlObject->id())); } assocElement.setAttribute(QLatin1String("type"), associationType()); if (!association()) { assocElement.setAttribute(QLatin1String("visibilityA"), visibility(RoleType::A)); assocElement.setAttribute(QLatin1String("visibilityB"), visibility(RoleType::B)); assocElement.setAttribute(QLatin1String("changeabilityA"), changeability(RoleType::A)); assocElement.setAttribute(QLatin1String("changeabilityB"), changeability(RoleType::B)); if (m_umlObject == 0) { assocElement.setAttribute(QLatin1String("roleAdoc"), roleDocumentation(RoleType::A)); assocElement.setAttribute(QLatin1String("roleBdoc"), roleDocumentation(RoleType::B)); assocElement.setAttribute(QLatin1String("documentation"), documentation()); } } assocElement.setAttribute(QLatin1String("widgetaid"), Uml::ID::toString(widgetIDForRole(RoleType::A))); assocElement.setAttribute(QLatin1String("widgetbid"), Uml::ID::toString(widgetIDForRole(RoleType::B))); assocElement.setAttribute(QLatin1String("indexa"), m_role[RoleType::A].m_nIndex); assocElement.setAttribute(QLatin1String("indexb"), m_role[RoleType::B].m_nIndex); assocElement.setAttribute(QLatin1String("totalcounta"), m_role[RoleType::A].m_nTotalCount); assocElement.setAttribute(QLatin1String("totalcountb"), m_role[RoleType::B].m_nTotalCount); m_associationLine->saveToXMI1(qDoc, assocElement); if (m_nameWidget) { m_nameWidget->saveToXMI1(qDoc, assocElement); } if (multiplicityWidget(RoleType::A)) { multiplicityWidget(RoleType::A)->saveToXMI1(qDoc, assocElement); } if (multiplicityWidget(RoleType::B)) { multiplicityWidget(RoleType::B)->saveToXMI1(qDoc, assocElement); } if (roleWidget(RoleType::A)) { roleWidget(RoleType::A)->saveToXMI1(qDoc, assocElement); } if (roleWidget(RoleType::B)) { roleWidget(RoleType::B)->saveToXMI1(qDoc, assocElement); } if (changeabilityWidget(RoleType::A)) { changeabilityWidget(RoleType::A)->saveToXMI1(qDoc, assocElement); } if (changeabilityWidget(RoleType::B)) { changeabilityWidget(RoleType::B)->saveToXMI1(qDoc, assocElement); } if (m_associationClass) { QString acid = Uml::ID::toString(m_associationClass->id()); assocElement.setAttribute(QLatin1String("assocclass"), acid); assocElement.setAttribute(QLatin1String("aclsegindex"), m_nLinePathSegmentIndex); } qElement.appendChild(assocElement); } /** * Uses the supplied widgetList for resolving * the role A and role B widgets. (The other loadFromXMI1() queries * the UMLView for these widgets.) * Required for clipboard operations. */ bool AssociationWidget::loadFromXMI1(QDomElement& qElement, const UMLWidgetList& widgets, const MessageWidgetList* messages) { if (!WidgetBase::loadFromXMI1(qElement)) { return false; } if (!LinkWidget::loadFromXMI1(qElement)) { return false; } // load child widgets first QString widgetaid = qElement.attribute(QLatin1String("widgetaid"), QLatin1String("-1")); QString widgetbid = qElement.attribute(QLatin1String("widgetbid"), QLatin1String("-1")); Uml::ID::Type aId = Uml::ID::fromString(widgetaid); Uml::ID::Type bId = Uml::ID::fromString(widgetbid); UMLWidget *pWidgetA = Widget_Utils::findWidget(aId, widgets, messages); if (!pWidgetA) { uError() << "cannot find widget for roleA id " << Uml::ID::toString(aId); return false; } UMLWidget *pWidgetB = Widget_Utils::findWidget(bId, widgets, messages); if (!pWidgetB) { uError() << "cannot find widget for roleB id " << Uml::ID::toString(bId); return false; } setWidgetForRole(pWidgetA, RoleType::A); setWidgetForRole(pWidgetB, RoleType::B); QString type = qElement.attribute(QLatin1String("type"), QLatin1String("-1")); Uml::AssociationType::Enum aType = Uml::AssociationType::fromInt(type.toInt()); QString id = qElement.attribute(QLatin1String("xmi.id"), QLatin1String("-1")); bool oldStyleLoad = false; if (id == QLatin1String("-1")) { // xmi.id not present, ergo either a pure widget association, // or old (pre-1.2) style: // Everything is loaded from the AssociationWidget. // UMLAssociation may or may not be saved - if it is, it's a dummy. // Create the UMLAssociation if both roles are UML objects; // else load the info locally. if (Uml::AssociationType::hasUMLRepresentation(aType)) { // lack of an association in our widget AND presence of // both uml objects for each role clearly identifies this // as reading in an old-school file. Note it as such, and // create, and add, the UMLAssociation for this widget. // Remove this special code when backwards compatibility // with older files isn't important anymore. -b.t. UMLObject* umlRoleA = pWidgetA->umlObject(); UMLObject* umlRoleB = pWidgetB->umlObject(); if (!m_umlObject && umlRoleA && umlRoleB) { oldStyleLoad = true; // flag for further special config below if (aType == AssociationType::Aggregation || aType == AssociationType::Composition) { uWarning()<<" Old Style save file? swapping roles on association widget"<umlObject(); umlRoleB = pWidgetB->umlObject(); } setUMLAssociation(umlDoc()->createUMLAssociation(umlRoleA, umlRoleB, aType)); } } setDocumentation(qElement.attribute(QLatin1String("documentation"))); setRoleDocumentation(qElement.attribute(QLatin1String("roleAdoc")), RoleType::A); setRoleDocumentation(qElement.attribute(QLatin1String("roleBdoc")), RoleType::B); // visibility defaults to Public if it cant set it here.. QString visibilityA = qElement.attribute(QLatin1String("visibilityA"), QLatin1String("0")); int vis = visibilityA.toInt(); if (vis >= 200) { // bkwd compat. vis -= 200; } setVisibility((Uml::Visibility::Enum)vis, RoleType::A); QString visibilityB = qElement.attribute(QLatin1String("visibilityB"), QLatin1String("0")); vis = visibilityB.toInt(); if (vis >= 200) { // bkwd compat. vis -= 200; } setVisibility((Uml::Visibility::Enum)vis, RoleType::B); // Changeability defaults to "Changeable" if it cant set it here.. QString changeabilityA = qElement.attribute(QLatin1String("changeabilityA"), QLatin1String("0")); if (changeabilityA.toInt() > 0) setChangeability(Uml::Changeability::fromInt(changeabilityA.toInt()), RoleType::A); QString changeabilityB = qElement.attribute(QLatin1String("changeabilityB"), QLatin1String("0")); if (changeabilityB.toInt() > 0) setChangeability(Uml::Changeability::fromInt(changeabilityB.toInt()), RoleType::B); } else { // we should disconnect any prior association (can this happen??) if (m_umlObject && m_umlObject->baseType() == UMLObject::ot_Association) { UMLAssociation *umla = association(); umla->disconnect(this); umla->nrof_parent_widgets--; } // New style: The xmi.id is a reference to the UMLAssociation. // If the UMLObject is not found right now, we try again later // during the type resolution pass - see activate(). m_nId = Uml::ID::fromString(id); UMLObject *myObj = umlDoc()->findObjectById(m_nId); if (myObj) { const UMLObject::ObjectType ot = myObj->baseType(); if (ot != UMLObject::ot_Association) { setUMLObject(myObj); } else { UMLAssociation * myAssoc = myObj->asUMLAssociation(); setUMLAssociation(myAssoc); if (type == QLatin1String("-1")) aType = myAssoc->getAssocType(); } } } setAssociationType(aType); QString indexa = qElement.attribute(QLatin1String("indexa"), QLatin1String("0")); QString indexb = qElement.attribute(QLatin1String("indexb"), QLatin1String("0")); QString totalcounta = qElement.attribute(QLatin1String("totalcounta"), QLatin1String("0")); QString totalcountb = qElement.attribute(QLatin1String("totalcountb"), QLatin1String("0")); m_role[RoleType::A].m_nIndex = indexa.toInt(); m_role[RoleType::B].m_nIndex = indexb.toInt(); m_role[RoleType::A].m_nTotalCount = totalcounta.toInt(); m_role[RoleType::B].m_nTotalCount = totalcountb.toInt(); QString assocclassid = qElement.attribute(QLatin1String("assocclass")); if (! assocclassid.isEmpty()) { Uml::ID::Type acid = Uml::ID::fromString(assocclassid); UMLWidget *w = Widget_Utils::findWidget(acid, widgets); if (w) { ClassifierWidget* aclWidget = static_cast(w); QString aclSegIndex = qElement.attribute(QLatin1String("aclsegindex"), QLatin1String("0")); createAssocClassLine(aclWidget, aclSegIndex.toInt()); } else { uError() << "cannot find assocclass " << assocclassid; } } //now load child elements QDomNode node = qElement.firstChild(); QDomElement element = node.toElement(); while (!element.isNull()) { QString tag = element.tagName(); if (tag == QLatin1String("linepath")) { if (!m_associationLine->loadFromXMI1(element)) { return false; } } else if (tag == QLatin1String("floatingtext") || tag == QLatin1String("UML:FloatingTextWidget")) { // for bkwd compatibility QString r = element.attribute(QLatin1String("role"), QLatin1String("-1")); if (r == QLatin1String("-1")) return false; Uml::TextRole::Enum role = Uml::TextRole::fromInt(r.toInt()); FloatingTextWidget *ft = new FloatingTextWidget(m_scene, role, QString(), Uml::ID::Reserved); if (! ft->loadFromXMI1(element)) { // Most likely cause: The FloatingTextWidget is empty. delete ft; node = element.nextSibling(); element = node.toElement(); continue; } // always need this ft->setParentItem(this); ft->setLink(this); ft->setSequenceNumber(m_SequenceNumber); ft->setFontCmd(ft->font()); switch(role) { case Uml::TextRole::MultiA: m_role[RoleType::A].multiplicityWidget = ft; if (oldStyleLoad) setMultiplicity(m_role[RoleType::A].multiplicityWidget->text(), RoleType::A); break; case Uml::TextRole::MultiB: m_role[RoleType::B].multiplicityWidget = ft; if (oldStyleLoad) setMultiplicity(m_role[RoleType::B].multiplicityWidget->text(), RoleType::B); break; case Uml::TextRole::ChangeA: m_role[RoleType::A].changeabilityWidget = ft; break; case Uml::TextRole::ChangeB: m_role[RoleType::B].changeabilityWidget = ft; break; case Uml::TextRole::Name: m_nameWidget = ft; if (oldStyleLoad) setName(m_nameWidget->text()); break; case Uml::TextRole::Coll_Message: case Uml::TextRole::Coll_Message_Self: m_nameWidget = ft; ft->setLink(this); ft->setActivated(); if (FloatingTextWidget::isTextValid(ft->text())) ft->show(); else ft->hide(); break; case Uml::TextRole::RoleAName: m_role[RoleType::A].roleWidget = ft; setRoleName(ft->text(), RoleType::A); break; case Uml::TextRole::RoleBName: m_role[RoleType::B].roleWidget = ft; setRoleName(ft->text(), RoleType::B); break; default: DEBUG(DBG_SRC) << "unexpected FloatingTextWidget (TextRole::Enum " << role << ")"; delete ft; break; } } node = element.nextSibling(); element = node.toElement(); } return true; } /** * Queries the UMLView for resolving the role A and role B widgets. * .... */ bool AssociationWidget::loadFromXMI1(QDomElement& qElement) { UMLScene *scene = umlScene(); if (scene) { const UMLWidgetList& widgetList = scene->widgetList(); const MessageWidgetList& messageList = scene->messageList(); return loadFromXMI1(qElement, widgetList, &messageList); } else { DEBUG(DBG_SRC) << "This isn't on UMLScene yet, so can neither fetch" "messages nor widgets on umlscene"; return false; } } diff --git a/umbrello/umlwidgets/classifierwidget.cpp b/umbrello/umlwidgets/classifierwidget.cpp index e174b9805..5a15ed712 100644 --- a/umbrello/umlwidgets/classifierwidget.cpp +++ b/umbrello/umlwidgets/classifierwidget.cpp @@ -1,1494 +1,1494 @@ /*************************************************************************** * 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 c The UMLClassifier to represent. + * @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() + * @note keep fetching attributes in sync with calculateSize() */ void ClassifierWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); setPenFromSettings(painter); if (UMLWidget::useFillColor()) painter->setBrush(UMLWidget::fillColor()); else { painter->setBrush(m_scene->backgroundColor()); } if (m_umlObject->baseType() == UMLObject::ot_Package) { drawAsPackage(painter, option); UMLWidget::paint(painter, option, widget); return; } UMLClassifier *umlc = this->classifier(); if (!umlc) { uError() << "Internal error - classifier() returns NULL"; return; } if (umlc->isInterface() && visualProperty(DrawAsCircle)) { drawAsCircle(painter, option); UMLWidget::paint(painter, option, widget); return; } // Draw the bounding rectangle QSize templatesBoxSize = calculateTemplatesBoxSize(); int bodyOffsetY = 0; if (templatesBoxSize.height() > 0) bodyOffsetY += templatesBoxSize.height() - defaultMargin; int w = width(); if (templatesBoxSize.width() > 0) w -= templatesBoxSize.width() / 2; int h = height(); if (templatesBoxSize.height() > 0) h -= templatesBoxSize.height() - defaultMargin; painter->drawRect(0, bodyOffsetY, w, h); QFont font = UMLWidget::font(); font.setUnderline(false); font.setItalic(false); const QFontMetrics &fm = UMLWidget::getFontMetrics(UMLWidget::FT_NORMAL); const int fontHeight = fm.lineSpacing(); //If there are any templates then draw them UMLTemplateList tlist = umlc->getTemplateList(); if (tlist.count() > 0) { setPenFromSettings(painter); QPen pen = painter->pen(); pen.setStyle(Qt::DotLine); painter->setPen(pen); painter->drawRect(width() - templatesBoxSize.width(), 0, templatesBoxSize.width(), templatesBoxSize.height()); painter->setPen(QPen(textColor())); font.setBold(false); painter->setFont(font); const int x = width() - templatesBoxSize.width() + defaultMargin; int y = defaultMargin; foreach (UMLTemplate *t, tlist) { QString text = t->toString(Uml::SignatureType::NoSig, visualProperty(ShowStereotype)); painter->drawText(x, y, fm.size(0, text).width(), fontHeight, Qt::AlignVCenter, text); y += fontHeight; } } const int textX = defaultMargin; const int textWidth = w - defaultMargin * 2; painter->setPen(QPen(textColor())); // draw stereotype font.setBold(true); const bool showNameOnly = !visualProperty(ShowAttributes) && !visualProperty(ShowOperations) && !visualProperty(ShowDocumentation); int nameHeight = fontHeight; if (visualProperty(ShowStereotype) && !m_umlObject->stereotype().isEmpty()) { painter->setFont(font); painter->drawText(textX, bodyOffsetY, textWidth, fontHeight, Qt::AlignCenter, m_umlObject->stereotype(true)); bodyOffsetY += fontHeight; } else if (showNameOnly) { nameHeight = h; } // draw name QString name; UMLObject *o; if (m_umlObject && m_umlObject->isUMLInstance() && m_umlObject->asUMLInstance()->classifier()) o = m_umlObject->asUMLInstance()->classifier(); else o = m_umlObject; if (!o) name = m_Text; else if (visualProperty(ShowPackage)) name = o->fullyQualifiedName(); else name = o->name(); QString displayedName; if (m_umlObject->isUMLInstance()) displayedName = m_umlObject->name() + QLatin1String(" : ") + name; else displayedName = name; font.setItalic(m_umlObject->isAbstract()); painter->setFont(font); painter->drawText(textX, bodyOffsetY, textWidth, nameHeight, Qt::AlignCenter, displayedName); bodyOffsetY += fontHeight; font.setBold(false); font.setItalic(false); painter->setFont(font); #ifdef ENABLE_WIDGET_SHOW_DOC // draw documentation if (visualProperty(ShowDocumentation)) { setPenFromSettings(painter); painter->drawLine(0, bodyOffsetY, w, bodyOffsetY); painter->setPen(textColor()); if (!documentation().isEmpty()) { QRect brect = fm.boundingRect(QRect(0, 0, w-2*defaultMargin, h-bodyOffsetY), Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, documentation()); if (brect.width() > width() + 2*defaultMargin) brect.setWidth(width()-2*defaultMargin); brect.adjust(textX, bodyOffsetY, textX, bodyOffsetY); painter->drawText(brect, Qt::AlignCenter | Qt::TextWordWrap, documentation()); bodyOffsetY += brect.height(); } else bodyOffsetY += fontHeight / 2; } #endif // draw attributes if (visualProperty(ShowAttributes)) { // draw dividing line between doc/name and attributes setPenFromSettings(painter); painter->drawLine(0, bodyOffsetY, w, bodyOffsetY); painter->setPen(textColor()); const int numAtts = displayedAttributes(); if (numAtts > 0) { if (baseType() == WidgetBase::wt_Instance) { drawMembers(painter, UMLObject::ot_InstanceAttribute, m_attributeSignature, textX, bodyOffsetY, fontHeight); } else { drawMembers(painter, UMLObject::ot_Attribute, m_attributeSignature, textX, bodyOffsetY, fontHeight); } bodyOffsetY += fontHeight * numAtts; } else bodyOffsetY += fontHeight / 2; } // draw operations if (visualProperty(ShowOperations)) { // draw dividing line between attributes and operations setPenFromSettings(painter); painter->drawLine(0, bodyOffsetY, w, bodyOffsetY); painter->setPen(QPen(textColor())); const int numOps = displayedOperations(); if (numOps >= 0) { drawMembers(painter, UMLObject::ot_Operation, m_operationSignature, textX, bodyOffsetY, fontHeight); } } UMLWidget::paint(painter, option, widget); } /** * @return The shape of the ClassifierWidget. */ QPainterPath ClassifierWidget::shape() const { QPainterPath path; if (classifier() && classifier()->isInterface() && visualProperty(DrawAsCircle)) { path.addEllipse(rect()); 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) { 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; } updateGeometry(); update(); } /** * Returns whether to draw as circle. * Only applies when m_umlObject->getBaseType() is ot_Interface. * * @return True if widget is drawn as circle. */ bool ClassifierWidget::getDrawAsCircle() const { return visualProperty(DrawAsCircle); } /** * Toggles whether to draw as circle. * Only applies when m_umlObject->getBaseType() is ot_Interface. */ void ClassifierWidget::toggleDrawAsCircle() { toggleVisualProperty(DrawAsCircle); updateSignatureTypes(); updateGeometry(); update(); } /** * Changes this classifier from an interface to a class. * Attributes and stereotype visibility is got from the view OptionState. * This widget is also updated. */ void ClassifierWidget::changeToClass() { setBaseType(WidgetBase::wt_Class); m_umlObject->setBaseType(UMLObject::ot_Class); setVisualPropertyCmd(DrawAsCircle, false); const Settings::OptionState& ops = m_scene->optionState(); setVisualProperty(ShowAttributes, ops.classState.showAtts); setVisualProperty(ShowStereotype, ops.classState.showStereoType); updateGeometry(); update(); } /** * Changes this classifier from a class to an interface. * Attributes are hidden and stereotype is shown. * This widget is also updated. */ void ClassifierWidget::changeToInterface() { setBaseType(WidgetBase::wt_Interface); m_umlObject->setBaseType(UMLObject::ot_Interface); setVisualProperty(ShowAttributes, false); setVisualProperty(ShowStereotype, true); updateGeometry(); update(); } /** * Changes this classifier from an "class-or-package" to a package. * This widget is also updated. */ void ClassifierWidget::changeToPackage() { setBaseType(WidgetBase::wt_Package); m_umlObject->setBaseType(UMLObject::ot_Package); setVisualProperty(ShowAttributes, false); setVisualProperty(ShowStereotype, true); updateGeometry(); update(); } /** * Extends base method to adjust also the association of a class * association. * Executes the base method and then, if file isn't loading and the * classifier acts as a class association, the association position is * updated. * TODO: This is never called. * * param x The x-coordinate. * param y The y-coordinate. */ //void ClassifierWidget::adjustAssociations(int x, int y) //{ // DEBUG(DBG_SRC) << "x=" << x << " / y=" << y; // UMLWidget::adjustAssocs(x, y); // if (m_doc->loading() || m_pAssocWidget == 0) { // return; // } // //:TODO: the following is also called from UMLWidgetr::ajdustAssocs(...) // // and then AssociationWidget::widgetMoved(...) // //m_pAssocWidget->computeAssocClassLine(); //} /** * Loads the "classwidget" or "interfacewidget" XML element. */ bool ClassifierWidget::loadFromXMI1(QDomElement & qElement) { if (!UMLWidget::loadFromXMI1(qElement)) { return false; } bool loadShowAttributes = true; if (umlObject() && (umlObject()->isUMLPackage() || umlObject()->isUMLInstance())) { loadShowAttributes = false; } if (loadShowAttributes) { QString showatts = qElement.attribute(QLatin1String("showattributes"), QLatin1String("0")); QString showops = qElement.attribute(QLatin1String("showoperations"), QLatin1String("1")); QString showpubliconly = qElement.attribute(QLatin1String("showpubliconly"), QLatin1String("0")); QString showattsigs = qElement.attribute(QLatin1String("showattsigs"), QLatin1String("600")); QString showopsigs = qElement.attribute(QLatin1String("showopsigs"), QLatin1String("600")); QString showpackage = qElement.attribute(QLatin1String("showpackage"), QLatin1String("0")); QString showscope = qElement.attribute(QLatin1String("showscope"), QLatin1String("0")); QString drawascircle = qElement.attribute(QLatin1String("drawascircle"), QLatin1String("0")); QString showstereotype = qElement.attribute(QLatin1String("showstereotype"), QLatin1String("1")); setVisualPropertyCmd(ShowAttributes, (bool)showatts.toInt()); setVisualPropertyCmd(ShowOperations, (bool)showops.toInt()); setVisualPropertyCmd(ShowPublicOnly, (bool)showpubliconly.toInt()); setVisualPropertyCmd(ShowPackage, (bool)showpackage.toInt()); setVisualPropertyCmd(ShowVisibility, (bool)showscope.toInt()); setVisualPropertyCmd(DrawAsCircle, (bool)drawascircle.toInt()); setVisualPropertyCmd(ShowStereotype, (bool)showstereotype.toInt()); m_attributeSignature = Uml::SignatureType::fromInt(showattsigs.toInt()); m_operationSignature = Uml::SignatureType::fromInt(showopsigs.toInt()); } #ifdef ENABLE_WIDGET_SHOW_DOC QString showDocumentation = qElement.attribute(QLatin1String("showdocumentation"), QLatin1String("0")); setVisualPropertyCmd(ShowDocumentation, (bool)showDocumentation.toInt()); #endif if (!getDrawAsCircle()) return true; // Optional child element: floatingtext QDomNode node = qElement.firstChild(); QDomElement element = node.toElement(); if (!element.isNull()) { QString tag = element.tagName(); if (tag == QLatin1String("floatingtext")) { if (m_pInterfaceName == 0) { m_pInterfaceName = new FloatingTextWidget(m_scene, Uml::TextRole::Floating, name(), Uml::ID::Reserved); m_pInterfaceName->setParentItem(this); } if (!m_pInterfaceName->loadFromXMI1(element)) { // Most likely cause: The FloatingTextWidget is empty. delete m_pInterfaceName; m_pInterfaceName = 0; } else { m_pInterfaceName->activate(); m_pInterfaceName->update(); } } else { uError() << "unknown tag " << tag; } } return true; } /** * Creates the "classwidget" or "interfacewidget" XML element. */ void ClassifierWidget::saveToXMI1(QDomDocument & qDoc, QDomElement & qElement) { QDomElement conceptElement; bool saveShowAttributes = true; UMLClassifier *umlc = classifier(); if (umlObject() && umlObject()->baseType() == UMLObject::ot_Package) { conceptElement = qDoc.createElement(QLatin1String("packagewidget")); saveShowAttributes = false; } else if(umlObject()->baseType() == UMLObject::ot_Instance) { conceptElement = qDoc.createElement(QLatin1String("instancewidget")); saveShowAttributes = false; } else if (umlc && umlc->isInterface()) { conceptElement = qDoc.createElement(QLatin1String("interfacewidget")); } else { conceptElement = qDoc.createElement(QLatin1String("classwidget")); } UMLWidget::saveToXMI1(qDoc, conceptElement); if (saveShowAttributes) { conceptElement.setAttribute(QLatin1String("showoperations"), visualProperty(ShowOperations)); conceptElement.setAttribute(QLatin1String("showpubliconly"), visualProperty(ShowPublicOnly)); conceptElement.setAttribute(QLatin1String("showopsigs"), m_operationSignature); conceptElement.setAttribute(QLatin1String("showpackage"), visualProperty(ShowPackage)); conceptElement.setAttribute(QLatin1String("showscope"), visualProperty(ShowVisibility)); conceptElement.setAttribute(QLatin1String("showattributes"), visualProperty(ShowAttributes)); conceptElement.setAttribute(QLatin1String("showattsigs"), m_attributeSignature); conceptElement.setAttribute(QLatin1String("showstereotype"), visualProperty(ShowStereotype)); } #ifdef ENABLE_WIDGET_SHOW_DOC conceptElement.setAttribute(QLatin1String("showdocumentation"),visualProperty(ShowDocumentation)); #endif if (umlc && (umlc->isInterface() || umlc->isAbstract())) { conceptElement.setAttribute(QLatin1String("drawascircle"), visualProperty(DrawAsCircle)); if (visualProperty(DrawAsCircle) && m_pInterfaceName) { m_pInterfaceName->saveToXMI1(qDoc, conceptElement); } } qElement.appendChild(conceptElement); } /** * Will be called when a menu selection has been made from the * popup menu. * * @param action The action that has been selected. */ void ClassifierWidget::slotMenuSelection(QAction* action) { ListPopupMenu::MenuType sel = ListPopupMenu::typeFromAction(action); switch (sel) { case ListPopupMenu::mt_Attribute: case ListPopupMenu::mt_Operation: case ListPopupMenu::mt_Template: case ListPopupMenu::mt_InstanceAttribute: { UMLObject::ObjectType ot = ListPopupMenu::convert_MT_OT(sel); UMLClassifier *umlc = classifier(); if (!umlc) { uError() << "Internal error - classifier() returns NULL"; return; } if (Object_Factory::createChildObject(umlc, ot)) { updateGeometry(); update(); UMLApp::app()->document()->setModified(); } break; } case ListPopupMenu::mt_Class: case ListPopupMenu::mt_Datatype: case ListPopupMenu::mt_Enum: case ListPopupMenu::mt_Interface: { UMLObject::ObjectType ot = ListPopupMenu::convert_MT_OT(sel); UMLClassifier *umlc = classifier(); if (!umlc) { uError() << "Internal error - classifier() returns NULL"; return; } umlScene()->setCreateObject(true); if (Object_Factory::createUMLObject(ot, QString(), umlc)) { updateGeometry(); update(); UMLApp::app()->document()->setModified(); } break; } case ListPopupMenu::mt_Show_Operations: toggleVisualProperty(ShowOperations); break; case ListPopupMenu::mt_Show_Attributes: toggleVisualProperty(ShowAttributes); break; case ListPopupMenu::mt_Show_Documentation: toggleVisualProperty(ShowDocumentation); break; case ListPopupMenu::mt_Show_Public_Only: toggleVisualProperty(ShowPublicOnly); break; case ListPopupMenu::mt_Show_Operation_Signature: toggleVisualProperty(ShowOperationSignature); break; case ListPopupMenu::mt_Show_Attribute_Signature: toggleVisualProperty(ShowAttributeSignature); break; case ListPopupMenu::mt_Visibility: toggleVisualProperty(ShowVisibility); break; case ListPopupMenu::mt_Show_Packages: toggleVisualProperty(ShowPackage); break; case ListPopupMenu::mt_Show_Stereotypes: toggleVisualProperty(ShowStereotype); break; case ListPopupMenu::mt_DrawAsCircle: toggleVisualProperty(DrawAsCircle); break; case ListPopupMenu::mt_ChangeToClass: changeToClass(); break; case ListPopupMenu::mt_ChangeToInterface: changeToInterface(); break; case ListPopupMenu::mt_ChangeToPackage: changeToPackage(); break; default: UMLWidget::slotMenuSelection(action); break; } } /** * Slot to show/hide attributes based on \a state. */ void ClassifierWidget::slotShowAttributes(bool state) { setVisualProperty(ShowAttributes, state); } /** * Slot to show/hide operations based on \a state. */ void ClassifierWidget::slotShowOperations(bool state) { setVisualProperty(ShowOperations, state); } diff --git a/umbrello/umlwidgets/umlwidget.cpp b/umbrello/umlwidgets/umlwidget.cpp index 4d0ff10a0..33f951f50 100644 --- a/umbrello/umlwidgets/umlwidget.cpp +++ b/umbrello/umlwidgets/umlwidget.cpp @@ -1,2018 +1,2018 @@ /*************************************************************************** * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * copyright (C) 2002-2014 * * Umbrello UML Modeller Authors * ***************************************************************************/ #include "umlwidget.h" // local includes #include "actor.h" #include "actorwidget.h" #include "associationwidget.h" #include "classifier.h" #include "classpropertiesdialog.h" #include "cmds.h" #include "debug_utils.h" #include "dialog_utils.h" #include "docwindow.h" #include "associationwidget.h" #include "floatingtextwidget.h" #include "notewidget.h" #include "object_factory.h" #include "idchangelog.h" #include "menus/listpopupmenu.h" #include "port.h" #include "portwidget.h" #include "settingsdialog.h" #include "uml.h" #include "umldoc.h" #include "umllistview.h" #include "umlobject.h" #include "umlscene.h" #include "umlview.h" #include "usecase.h" #include "usecasewidget.h" #include "uniqueid.h" #include "widget_factory.h" // kde includes #include #include // qt includes #include #include #include #include using namespace Uml; DEBUG_REGISTER_DISABLED(UMLWidget) const QSizeF UMLWidget::DefaultMinimumSize(50, 20); const QSizeF UMLWidget::DefaultMaximumSize(1000, 5000); const int UMLWidget::defaultMargin = 5; const int UMLWidget::selectionMarkerSize = 4; const int UMLWidget::resizeMarkerLineCount = 3; /** * Creates a UMLWidget object. * * @param scene The view to be displayed on. * @param type The WidgetType to construct. * This must be set to the appropriate value by the constructors of inheriting classes. * @param o The UMLObject to represent. */ UMLWidget::UMLWidget(UMLScene * scene, WidgetType type, UMLObject * o) : WidgetBase(scene, type) { init(); m_umlObject = o; if (m_umlObject) { connect(m_umlObject, SIGNAL(modified()), this, SLOT(updateWidget())); m_nId = m_umlObject->id(); } } /** * Creates a UMLWidget object. * * @param scene The view to be displayed on. * @param type The WidgetType to construct. * This must be set to the appropriate value by the constructors of inheriting classes. * @param id The id of the widget. * The default value (id_None) will prompt generation of a new ID. */ UMLWidget::UMLWidget(UMLScene *scene, WidgetType type, Uml::ID::Type id) : WidgetBase(scene, type) { init(); if (id == Uml::ID::None) m_nId = UniqueID::gen(); else m_nId = id; } /** * Destructor. */ UMLWidget::~UMLWidget() { cleanup(); } /** * Assignment operator */ UMLWidget& UMLWidget::operator=(const UMLWidget & other) { if (this == &other) return *this; WidgetBase::operator=(other); // assign members loaded/saved m_useFillColor = other.m_useFillColor; m_usesDiagramFillColor = other.m_usesDiagramFillColor; m_usesDiagramUseFillColor = other.m_usesDiagramUseFillColor; m_fillColor = other.m_fillColor; m_Assocs = other.m_Assocs; m_isInstance = other.m_isInstance; m_instanceName = other.m_instanceName; m_instanceName = other.m_instanceName; m_showStereotype = other.m_showStereotype; setX(other.x()); setY(other.y()); setRect(rect().x(), rect().y(), other.width(), other.height()); // assign volatile (non-saved) members m_startMove = other.m_startMove; m_nPosX = other.m_nPosX; m_doc = other.m_doc; //new m_resizable = other.m_resizable; for (unsigned i = 0; i < FT_INVALID; ++i) m_pFontMetrics[i] = other.m_pFontMetrics[i]; m_activated = other.m_activated; m_ignoreSnapToGrid = other.m_ignoreSnapToGrid; m_ignoreSnapComponentSizeToGrid = other.m_ignoreSnapComponentSizeToGrid; return *this; } /** * Overload '==' operator */ bool UMLWidget::operator==(const UMLWidget& other) const { if (this == &other) return true; if (baseType() != other.baseType()) { return false; } if (id() != other.id()) return false; /* Testing the associations is already an exaggeration, no? The type and ID should uniquely identify an UMLWidget. */ if (m_Assocs.count() != other.m_Assocs.count()) { return false; } // if(getBaseType() != wt_Text) // DON'T do this for floatingtext widgets, an infinite loop will result // { AssociationWidgetListIt assoc_it(m_Assocs); AssociationWidgetListIt assoc_it2(other.m_Assocs); AssociationWidget * assoc = 0, *assoc2 = 0; while (assoc_it.hasNext() && assoc_it2.hasNext()) { assoc = assoc_it.next(); assoc2 = assoc_it2.next(); if (!(*assoc == *assoc2)) { return false; } } // } return true; // NOTE: In the comparison tests we are going to do, we don't need these values. // They will actually stop things functioning correctly so if you change these, be aware of that. /* if(m_useFillColor != other.m_useFillColor) return false; if(m_nId != other.m_nId) return false; if(m_nX != other.m_nX) return false; if(m_nY != other.m_nY) return false; */ } /** * Sets the local id of the object. * * @param id The local id of the object. */ void UMLWidget::setLocalID(Uml::ID::Type id) { m_nLocalID = id; } /** * Returns the local ID for this object. This ID is used so that * many objects of the same @ref UMLObject instance can be on the * same diagram. * * @return The local ID. */ Uml::ID::Type UMLWidget::localID() const { return m_nLocalID; } /** * Returns the widget with the given ID. * The default implementation tests the following IDs: * - m_nLocalID * - if m_umlObject is non NULL: m_umlObject->id() * - m_nID * Composite widgets override this function to test further owned widgets. * * @param id The ID to test this widget against. * @return 'this' if id is either of m_nLocalID, m_umlObject->id(), or m_nId; * else NULL. */ UMLWidget* UMLWidget::widgetWithID(Uml::ID::Type id) { if (id == m_nLocalID || (m_umlObject != 0 && id == m_umlObject->id()) || id == m_nId) return this; return 0; } /** * Compute the minimum possible width and height. * * @return QSizeF(mininum_width, minimum_height) */ QSizeF UMLWidget::minimumSize() const { return m_minimumSize; } /** * This method is used to set the minimum size variable for this * widget. * * @param newSize The size being set as minimum. */ void UMLWidget::setMinimumSize(const QSizeF& newSize) { m_minimumSize = newSize; } /** * Compute the maximum possible width and height. * * @return maximum size */ QSizeF UMLWidget::maximumSize() { return m_maximumSize; } /** * This method is used to set the maximum size variable for this * widget. * * @param newSize The size being set as maximum. */ void UMLWidget::setMaximumSize(const QSizeF& newSize) { m_maximumSize = newSize; } /** * Event handler for context menu events. */ void UMLWidget::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) { WidgetBase::contextMenuEvent(event); } /** * Moves the widget to a new position using the difference between the * current position and the new position. * This method doesn't adjust associations. It only moves the widget. * * It can be overridden to constrain movement only in one axis even when * the user isn't constraining the movement with shift or control buttons, for example. * The movement policy set here is applied whenever the widget is moved, being it * moving it explicitly, or as a part of a selection but not receiving directly the * mouse events. * * Default behaviour is move the widget to the new position using the diffs. * @see constrainMovementForAllWidgets * * @param diffX The difference between current X position and new X position. * @param diffY The difference between current Y position and new Y position. */ void UMLWidget::moveWidgetBy(qreal diffX, qreal diffY) { setX(x() + diffX); setY(y() + diffY); } /** * Modifies the value of the diffX and diffY variables used to move the widgets. * * It can be overridden to constrain movement of all the selected widgets only in one * axis even when the user isn't constraining the movement with shift or control * buttons, for example. * The difference with moveWidgetBy is that the diff positions used here are * applied to all the selected widgets instead of only to m_widget, and that * moveWidgetBy, in fact, moves the widget, and here simply the diff positions * are modified. * * Default behaviour is do nothing. * @see moveWidgetBy * * @param diffX The difference between current X position and new X position. * @param diffY The difference between current Y position and new Y position. */ void UMLWidget::constrainMovementForAllWidgets(qreal &diffX, qreal &diffY) { Q_UNUSED(diffX) Q_UNUSED(diffY) } /** * Bring the widget at the pressed position to the foreground. */ void UMLWidget::toForeground() { QRectF rect = QRectF(scenePos(), QSizeF(width(), height())); QList items = scene()->items(rect, Qt::IntersectsItemShape, Qt::DescendingOrder); DEBUG(DBG_SRC) << "items at " << rect << " = " << items.count(); if (items.count() > 1) { foreach(QGraphicsItem* i, items) { UMLWidget* w = dynamic_cast(i); if (w) { DEBUG(DBG_SRC) << "item=" << w->name() << " with zValue=" << w->zValue(); if (w->name() != name()) { if (w->zValue() >= zValue()) { setZValue(w->zValue() + 1.0); DEBUG(DBG_SRC) << "bring to foreground with zValue: " << zValue(); } } } } } else { setZValue(0.0); } DEBUG(DBG_SRC) << "zValue is " << zValue(); } /** * Handles a mouse press event. * It'll select the widget (or mark it to be deselected) and prepare it to * be moved or resized. Go on reading for more info about this. * * Widget values and message bar status are saved. * * If shift or control buttons are pressed, we're in move area no matter * where the button was pressed in the widget. Moreover, if the widget * wasn't already selected, it's added to the selection. If already selected, * it's marked to be deselected when releasing the button (provided it isn't * moved). * Also, if the widget is already selected with other widgets but shift nor * control buttons are pressed, we're in move area. If finally we don't move * the widget, it's selected and the other widgets deselected when releasing * the left button. * * If shift nor control buttons are pressed, we're facing a single selection. * Depending on the position of the cursor, we're in move or in resize area. * If the widget wasn't selected (both when there are no widgets selected, or * when there're other widgets selected but not the one receiving the press * event) it's selected and the others deselected, if any. If already selected, * it's marked to be deselected when releasing the button (provided it wasn't * moved or resized). * * @param event The QGraphicsSceneMouseEvent event. */ void UMLWidget::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (event->button() != Qt::LeftButton) { event->ignore(); return; } event->accept(); DEBUG(DBG_SRC) << "widget = " << name() << " / type = " << baseTypeStr(); toForeground(); m_startMovePostion = pos(); m_startResizeSize = QSizeF(width(), height()); // saving the values of the widget m_pressOffset = event->scenePos() - pos(); DEBUG(DBG_SRC) << "press offset=" << m_pressOffset; m_oldStatusBarMsg = UMLApp::app()->statusBarMsg(); if (event->modifiers() == Qt::ShiftModifier || event->modifiers() == Qt::ControlModifier) { m_shiftPressed = true; if (event->button() == Qt::LeftButton) { m_inMoveArea = true; } if (!isSelected()) { selectMultiple(event); } return; } m_shiftPressed = false; int count = m_scene->selectedCount(true); if (event->button() == Qt::LeftButton) { if (isSelected() && count > 1) { // single selection is made in release event if the widget wasn't moved m_inMoveArea = true; m_oldPos = pos(); return; } if (isInResizeArea(event)) { m_inResizeArea = true; m_oldW = width(); m_oldH = height(); } else { m_inMoveArea = true; } } // if widget wasn't selected, or it was selected but with other widgets also selected if (!isSelected() || count > 1) { selectSingle(event); } } /** * Handles a mouse move event. * It resizes or moves the widget, depending on where the cursor is pressed * on the widget. Go on reading for more info about this. * * If resizing, the widget is resized using UMLWidget::resizeWidget (where specific * widget resize constraint can be applied), and then the associations are * adjusted. * The resizing can be constrained also to a specific axis using control * and shift buttons. If on or another is pressed, it's constrained to X axis. * If both are pressed, it's constrained to Y axis. * * If not resizing, the widget is being moved. If the move is being started, * the selection bounds are set (which includes updating the list of selected * widgets). * The difference between the previous position of the selection and the new * one is got (taking in account the selection bounds so widgets don't go * beyond the scene limits). Then, it's constrained to X or Y axis depending * on shift and control buttons. * A further constraint is made using constrainMovementForAllWidgets (for example, * if the widget that receives the event can only be moved in Y axis, with this * method the movement of all the widgets in the selection can be constrained to * be moved only in Y axis). * Then, all the selected widgets are moved using moveWidgetBy (where specific * widget movement constraint can be applied) and, if a certain amount of time * passed from the last move event, the associations are also updated (they're * not updated always to be easy on the CPU). Finally, the scene is resized, * and selection bounds updated. * * @param event The QGraphicsSceneMouseEvent event. */ void UMLWidget::mouseMoveEvent(QGraphicsSceneMouseEvent* event) { if (m_inResizeArea) { resize(event); return; } if (!m_moved) { UMLApp::app()->document()->writeToStatusBar(i18n("Hold shift or ctrl to move in X axis. Hold shift and control to move in Y axis. Right button click to cancel move.")); m_moved = true; //Maybe needed by AssociationWidget m_startMove = true; setSelectionBounds(); } QPointF position = event->scenePos() - m_pressOffset; qreal diffX = position.x() - x(); qreal diffY = position.y() - y(); if ((event->modifiers() & Qt::ShiftModifier) && (event->modifiers() & Qt::ControlModifier)) { // move only in Y axis diffX = 0; } else if ((event->modifiers() & Qt::ShiftModifier) || (event->modifiers() & Qt::ControlModifier)) { // move only in X axis diffY = 0; } constrainMovementForAllWidgets(diffX, diffY); // nothing to move if (diffX == 0 && diffY == 0) { return; } QPointF delta = event->scenePos() - event->lastScenePos(); adjustUnselectedAssocs(delta.x(), delta.y()); DEBUG(DBG_SRC) << "diffX=" << diffX << " / diffY=" << diffY; foreach(UMLWidget* widget, umlScene()->selectedWidgets()) { if ((widget->parentItem() == 0) || (!widget->parentItem()->isSelected())) { widget->moveWidgetBy(diffX, diffY); widget->adjustUnselectedAssocs(delta.x(), delta.y()); widget->slotSnapToGrid(); } } // Move any selected associations. foreach(AssociationWidget* aw, m_scene->selectedAssocs()) { if (aw->isSelected()) { aw->moveEntireAssoc(diffX, diffY); } } umlScene()->resizeSceneToItems(); } /** * Handles a mouse release event. * It selects or deselects the widget and cancels or confirms the move or * resize. Go on reading for more info about this. * No matter which tool is selected, Z position of widget is updated. * * Middle button release resets the selection. * Left button release, if it wasn't moved nor resized, selects the widget * and deselect the others if it wasn't selected and there were other widgets * selected. If the widget was marked to be deselected, deselects it. * If it was moved or resized, the document is set to modified if position * or size changed. Also, if moved, all the associations are adjusted because * the timer could have prevented the adjustment in the last move event before * the release. * If mouse was pressed in resize area, cursor is set again to normal cursor * Right button release if right button was pressed shows the pop up menu for * the widget. * If left button was pressed, it cancels the move or resize with a mouse move * event at the same position than the cursor was when pressed. Another left * button release is also sent. * * @param event The QGraphicsSceneMouseEvent event. */ void UMLWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { if (!m_moved && !m_resized) { if (!m_shiftPressed && (m_scene->selectedCount(true) > 1)) { selectSingle(event); } else if (!isSelected()) { deselect(event); } } else { // Commands if (m_moved) { int selectionCount = umlScene()->selectedWidgets().count(); if (selectionCount > 1) { UMLApp::app()->beginMacro(i18n("Move widgets")); } foreach(UMLWidget* widget, umlScene()->selectedWidgets()) { UMLApp::app()->executeCommand(new Uml::CmdMoveWidget(widget)); } if (selectionCount > 1) { UMLApp::app()->endMacro(); } m_moved = false; } else { UMLApp::app()->executeCommand(new Uml::CmdResizeWidget(this)); m_autoResize = false; m_resized = false; } if ((m_inMoveArea && wasPositionChanged()) || (m_inResizeArea && wasSizeChanged())) { umlDoc()->setModified(true); } UMLApp::app()->document()->writeToStatusBar(m_oldStatusBarMsg); } if (m_inResizeArea) { m_inResizeArea = false; m_scene->activeView()->setCursor(Qt::ArrowCursor); } else { m_inMoveArea = false; } m_startMove = false; } /** * Event handler for mouse double click events. * @param event the QGraphicsSceneMouseEvent event. */ void UMLWidget::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) { if (event->button() == Qt::LeftButton) { DEBUG(DBG_SRC) << "widget = " << name() << " / type = " << baseTypeStr(); showPropertiesDialog(); event->accept(); } } /** * Return the start position of the move action. * @return point where the move began */ QPointF UMLWidget::startMovePosition() const { return m_startMovePostion; } /** * Set the start position of the move action. * @param position point where the move began */ void UMLWidget::setStartMovePosition(const QPointF &position) { m_startMovePostion = position; } /** * Return the start size of the resize action. * @return size where the resize began */ QSizeF UMLWidget::startResizeSize() const { return m_startResizeSize; } /** * Resizes the widget. * It's called from resize, after the values are constrained and before * the associations are adjusted. * * Default behaviour is resize the widget using the new size values. * @see resize * * @param newW The new width for the widget. * @param newH The new height for the widget. */ void UMLWidget::resizeWidget(qreal newW, qreal newH) { setSize(newW, newH); } /** * Notify child widget about parent resizes. * Child widgets can override this function to move when their parent is resized. */ void UMLWidget::notifyParentResize() { } /** * When a widget changes this slot captures that signal. */ void UMLWidget::updateWidget() { updateGeometry(); switch (baseType()) { case WidgetBase::wt_Class: m_scene->createAutoAttributeAssociations(this); break; case WidgetBase::wt_Entity: m_scene->createAutoConstraintAssociations(this); break; default: break; } if (isVisible()) update(); } /** * Apply possible constraints to the given candidate width and height. * The default implementation limits input values to the bounds returned * by minimumSize()/maximumSize(). * * @param width input value, may be modified by the constraint * @param height input value, may be modified by the constraint */ void UMLWidget::constrain(qreal& width, qreal& height) { QSizeF minSize = minimumSize(); if (width < minSize.width()) width = minSize.width(); if (height < minSize.height()) height = minSize.height(); QSizeF maxSize = maximumSize(); if (width > maxSize.width()) width = maxSize.width(); if (height > maxSize.height()) height = maxSize.height(); if (fixedAspectRatio()) { QSizeF size = rect().size(); float aspectRatio = size.width() > 0 ? (float)size.height()/size.width() : 1; height = width * aspectRatio; } } /** * Initializes key attributes of the class. */ void UMLWidget::init() { m_nId = Uml::ID::None; m_nLocalID = UniqueID::gen(); m_isInstance = false; setMinimumSize(DefaultMinimumSize); setMaximumSize(DefaultMaximumSize); m_font = QApplication::font(); for (int i = (int)FT_INVALID - 1; i >= 0; --i) { FontType fontType = (FontType)i; setupFontType(m_font, fontType); m_pFontMetrics[fontType] = new QFontMetrics(m_font); } if (m_scene) { m_useFillColor = true; m_usesDiagramFillColor = true; m_usesDiagramUseFillColor = true; const Settings::OptionState& optionState = m_scene->optionState(); m_fillColor = optionState.uiState.fillColor; m_showStereotype = optionState.classState.showStereoType; } else { uError() << "SERIOUS PROBLEM - m_scene is NULL"; m_useFillColor = false; m_usesDiagramFillColor = false; m_usesDiagramUseFillColor = false; m_showStereotype = false; } m_resizable = true; m_fixedAspectRatio = false; m_startMove = false; m_activated = false; m_ignoreSnapToGrid = false; m_ignoreSnapComponentSizeToGrid = false; m_doc = UMLApp::app()->document(); m_nPosX = 0; connect(m_scene, SIGNAL(sigFillColorChanged(Uml::ID::Type)), this, SLOT(slotFillColorChanged(Uml::ID::Type))); connect(m_scene, SIGNAL(sigLineColorChanged(Uml::ID::Type)), this, SLOT(slotLineColorChanged(Uml::ID::Type))); connect(m_scene, SIGNAL(sigTextColorChanged(Uml::ID::Type)), this, SLOT(slotTextColorChanged(Uml::ID::Type))); connect(m_scene, SIGNAL(sigLineWidthChanged(Uml::ID::Type)), this, SLOT(slotLineWidthChanged(Uml::ID::Type))); m_umlObject = 0; m_oldPos = QPointF(); m_pressOffset = QPointF(); m_oldW = 0; m_oldH = 0; m_shiftPressed = false; m_inMoveArea = false; m_inResizeArea = false; m_moved = false; m_resized = false; // propagate line color set by base class constructor // which does not call the virtual methods from this class. setLineColor(lineColor()); setZValue(2.0); // default for most widgets } /** * This is usually called synchronously after menu.exec() and \a * trigger's parent is always the ListPopupMenu which can be used to * get the type of action of \a trigger. * * @note Subclasses can reimplement to handle specific actions and * leave the rest to WidgetBase::slotMenuSelection. */ void UMLWidget::slotMenuSelection(QAction *trigger) { if (!trigger) { return; } ListPopupMenu::MenuType sel = ListPopupMenu::typeFromAction(trigger); switch (sel) { case ListPopupMenu::mt_Resize: umlScene()->resizeSelection(); break; case ListPopupMenu::mt_AutoResize: setAutoResize(trigger->isChecked()); updateGeometry(); break; case ListPopupMenu::mt_Rename_Object: { QString name = m_instanceName; bool ok = Dialog_Utils::askName(i18n("Rename Object"), i18n("Enter object name:"), name); if (ok) { m_instanceName = name; updateGeometry(); moveEvent(0); update(); UMLApp::app()->document()->setModified(true); } break; } case ListPopupMenu::mt_FloatText: { FloatingTextWidget* ft = new FloatingTextWidget(umlScene()); ft->showChangeTextDialog(); //if no text entered delete if (!FloatingTextWidget::isTextValid(ft->text())) { delete ft; } else { ft->setID(UniqueID::gen()); addWidget(ft, false); } break; } case ListPopupMenu::mt_Actor: { UMLActor *actor = new UMLActor; UMLWidget *widget = new ActorWidget(umlScene(), actor); addConnectedWidget(widget, Uml::AssociationType::Association); break; } case ListPopupMenu::mt_Note: { NoteWidget *widget = new NoteWidget(umlScene()); addConnectedWidget(widget, Uml::AssociationType::Anchor); break; } case ListPopupMenu::mt_Port: { // TODO: merge with ToolbarStateOneWidget::setWidget() UMLPackage* component = umlObject()->asUMLPackage(); QString name = Model_Utils::uniqObjectName(UMLObject::ot_Port, component); if (Dialog_Utils::askName(i18n("Enter Port Name"), i18n("Enter the port"), name)) { UMLPort *port = Object_Factory::createUMLObject(UMLObject::ot_Port, name, component)->asUMLPort(); UMLWidget *umlWidget = Widget_Factory::createWidget(umlScene(), port); umlScene()->setupNewWidget(umlWidget); } break; } case ListPopupMenu::mt_UseCase: { UMLUseCase *useCase = new UMLUseCase; UMLWidget *widget = new UseCaseWidget(umlScene(), useCase); addConnectedWidget(widget, Uml::AssociationType::Association); break; } default: WidgetBase::slotMenuSelection(trigger); break; } } /** * Captures when another widget moves if this widget is linked to it. * @see sigWidgetMoved * * @param id The id of object behind the widget. */ void UMLWidget::slotWidgetMoved(Uml::ID::Type /*id*/) { } /** * Captures a color change signal. * * @param viewID The id of the UMLScene behind the widget. */ void UMLWidget::slotFillColorChanged(Uml::ID::Type viewID) { //only change if on the diagram concerned if (m_scene->ID() != viewID) { return; } if (m_usesDiagramFillColor) { WidgetBase::setFillColor(m_scene->fillColor()); } if (m_usesDiagramUseFillColor) { WidgetBase::setUseFillColor(m_scene->useFillColor()); } update(); } /** * Captures a text color change signal. * * @param viewID The id of the UMLScene behind the widget. */ void UMLWidget::slotTextColorChanged(Uml::ID::Type viewID) { //only change if on the diagram concerned if (m_scene->ID() != viewID) return; WidgetBase::setTextColor(m_scene->textColor()); update(); } /** * Captures a line color change signal. * * @param viewID The id of the UMLScene behind the widget. */ void UMLWidget::slotLineColorChanged(Uml::ID::Type viewID) { //only change if on the diagram concerned if (m_scene->ID() != viewID) return; if (m_usesDiagramLineColor) { WidgetBase::setLineColor(m_scene->lineColor()); } update(); } /** * Captures a linewidth change signal. * * @param viewID The id of the UMLScene behind the widget. */ void UMLWidget::slotLineWidthChanged(Uml::ID::Type viewID) { //only change if on the diagram concerned if (m_scene->ID() != viewID) { return; } if (m_usesDiagramLineWidth) { WidgetBase::setLineWidth(m_scene->lineWidth()); } update(); } /** * Set the status of using fill color (undo action) * * @param fc the status of using fill color. */ void UMLWidget::setUseFillColor(bool fc) { if (useFillColor() != fc) { UMLApp::app()->executeCommand(new CmdChangeUseFillColor(this, fc)); } } /** * Set the status of using fill color. * * @param fc the status of using fill color. */ void UMLWidget::setUseFillColorCmd(bool fc) { WidgetBase::setUseFillColor(fc); update(); } /** * Overrides the method from WidgetBase. */ void UMLWidget::setTextColorCmd(const QColor &color) { WidgetBase::setTextColor(color); update(); } /** * Overrides the method from WidgetBase. */ void UMLWidget::setTextColor(const QColor &color) { if (textColor() != color) { UMLApp::app()->executeCommand(new CmdChangeTextColor(this, color)); update(); } } /** * Overrides the method from WidgetBase. */ void UMLWidget::setLineColorCmd(const QColor &color) { WidgetBase::setLineColor(color); update(); } /** * Overrides the method from WidgetBase. */ void UMLWidget::setLineColor(const QColor &color) { if (lineColor() != color) { UMLApp::app()->executeCommand(new CmdChangeLineColor(this, color)); } } /** * Overrides the method from WidgetBase, execute CmdChangeLineWidth */ void UMLWidget::setLineWidth(uint width) { if (lineWidth() != width) { UMLApp::app()->executeCommand(new CmdChangeLineWidth(this, width)); } } /** * Overrides the method from WidgetBase. */ void UMLWidget::setLineWidthCmd(uint width) { WidgetBase::setLineWidth(width); update(); } /** * Sets the background fill color * * @param color the new fill color */ void UMLWidget::setFillColor(const QColor &color) { if (fillColor() != color) { UMLApp::app()->executeCommand(new CmdChangeFillColor(this, color)); } } /** * Sets the background fill color * * @param color the new fill color */ void UMLWidget::setFillColorCmd(const QColor &color) { WidgetBase::setFillColor(color); update(); } /** * Activate the object after serializing it from a QDataStream * * @param ChangeLog * @return true for success */ bool UMLWidget::activate(IDChangeLog* /*ChangeLog = 0 */) { if (widgetHasUMLObject(baseType()) && m_umlObject == 0) { m_umlObject = m_doc->findObjectById(m_nId); if (m_umlObject == 0) { uError() << "cannot find UMLObject with id=" << Uml::ID::toString(m_nId); return false; } } setFontCmd(m_font); setSize(width(), height()); m_activated = true; updateGeometry(); if (m_scene->getPaste()) { FloatingTextWidget * ft = 0; QPointF point = m_scene->getPastePoint(); int x = point.x() + this->x(); int y = point.y() + this->y(); if (m_scene->type() == Uml::DiagramType::Sequence) { switch (baseType()) { case WidgetBase::wt_Object: case WidgetBase::wt_Precondition : setY(this->y()); setX(x); break; case WidgetBase::wt_Message: setY(this->y()); setX(x); break; case WidgetBase::wt_Text: ft = static_cast(this); if (ft->textRole() == Uml::TextRole::Seq_Message) { setX(x); setY(this->y()); } else { setX(this->x()); setY(this->y()); } break; default: setY(y); break; }//end switch base type }//end if sequence else { setX(x); setY(y); } }//end if pastepoint else { setX(this->x()); setY(this->y()); } if (m_scene->getPaste()) m_scene->createAutoAssociations(this); updateGeometry(); return true; } /** * Returns true if the Activate method has been called for this instance * * @return The activate status. */ bool UMLWidget::isActivated() const { return m_activated; } /** * Set the m_activated flag of a widget but does not perform the Activate method * * @param active Status of activation is to be set. */ void UMLWidget::setActivated(bool active /*=true*/) { m_activated = active; } /** * Adds an already created association to the list of * associations that include this UMLWidget */ void UMLWidget::addAssoc(AssociationWidget* pAssoc) { if (pAssoc && !associationWidgetList().contains(pAssoc)) { associationWidgetList().append(pAssoc); } } /** * Returns the list of associations connected to this widget. */ AssociationWidgetList &UMLWidget::associationWidgetList() const { m_Assocs.removeAll(0); return m_Assocs; } /** * Removes an already created association from the list of * associations that include this UMLWidget */ void UMLWidget::removeAssoc(AssociationWidget* pAssoc) { if (pAssoc) { associationWidgetList().removeAll(pAssoc); } } /** * Adjusts associations with the given co-ordinates * * @param dx The amount by which the widget moved in X direction. * @param dy The amount by which the widget moved in Y direction. */ void UMLWidget::adjustAssocs(qreal dx, qreal dy) { // don't adjust Assocs on file load, as // the original positions, which are stored in XMI // should be reproduced exactly // (don't try to reposition assocs as long // as file is only partly loaded -> reposition // could be misguided) /// @todo avoid trigger of this event during load if (m_doc->loading()) { // don't recalculate the assocs during load of XMI // -> return immediately without action return; } foreach(AssociationWidget* assocwidget, associationWidgetList()) { assocwidget->saveIdealTextPositions(); } foreach(AssociationWidget* assocwidget, associationWidgetList()) { assocwidget->widgetMoved(this, dx, dy); } } /** * Adjusts all unselected associations with the given co-ordinates * * @param dx The amount by which the widget moved in X direction. * @param dy The amount by which the widget moved in Y direction. */ void UMLWidget::adjustUnselectedAssocs(qreal dx, qreal dy) { foreach(AssociationWidget* assocwidget, associationWidgetList()) { if (!assocwidget->isSelected()) assocwidget->saveIdealTextPositions(); } foreach(AssociationWidget* assocwidget, associationWidgetList()) { if (!assocwidget->isSelected()) { assocwidget->widgetMoved(this, dx, dy); } } } /** * Show a properties dialog for a UMLWidget. */ bool UMLWidget::showPropertiesDialog() { bool result = false; // will already be selected so make sure docWindow updates the doc // back it the widget UMLApp::app()->docWindow()->updateDocumentation(false); QPointer dlg = new ClassPropertiesDialog((QWidget*)UMLApp::app(), this); if (dlg->exec()) { UMLApp::app()->docWindow()->showDocumentation(umlObject(), true); m_doc->setModified(true); result = true; } dlg->close(); //wipe from memory delete dlg; return result; } /** * Move the widget by an X and Y offset relative to * the current position. */ void UMLWidget::moveByLocal(qreal dx, qreal dy) { qreal newX = x() + dx; qreal newY = y() + dy; setX(newX); setY(newY); adjustAssocs(dx, dy); } /** * Set the pen. */ void UMLWidget::setPenFromSettings(QPainter & p) { p.setPen(QPen(m_lineColor, m_lineWidth)); } /** * Set the pen. */ void UMLWidget::setPenFromSettings(QPainter *p) { p->setPen(QPen(m_lineColor, m_lineWidth)); } /** * Returns the cursor to be shown when resizing the widget. * Default cursor is KCursor::sizeFDiagCursor(). * * @return The cursor to be shown when resizing the widget. */ QCursor UMLWidget::resizeCursor() const { return Qt::SizeFDiagCursor; } /** * Checks if the mouse is in resize area (right bottom corner), and sets * the cursor depending on that. * The cursor used when resizing is gotten from resizeCursor(). * * @param me The QMouseEVent to check. * @return true if the mouse is in resize area, false otherwise. */ bool UMLWidget::isInResizeArea(QGraphicsSceneMouseEvent *me) { qreal m = 10.0; const qreal w = width(); const qreal h = height(); // If the widget itself is very small then make the resize area small, too. // Reason: Else it becomes impossible to do a move instead of resize. if (w - m < m || h - m < m) { m = 2.0; } if (m_resizable && me->scenePos().x() >= (x() + w - m) && me->scenePos().y() >= (y() + h - m)) { m_scene->activeView()->setCursor(resizeCursor()); return true; } else { m_scene->activeView()->setCursor(Qt::ArrowCursor); return false; } } /** * calculate content related size of widget. * * @return calculated widget size */ QSizeF UMLWidget::calculateSize(bool withExtensions /* = true */) const { Q_UNUSED(withExtensions) const QFontMetrics &fm = getFontMetrics(UMLWidget::FT_NORMAL); const int fontHeight = fm.lineSpacing(); if (m_umlObject) { qreal width = 0, height = defaultMargin; if (!m_umlObject->stereotype().isEmpty()) { height += fontHeight; const QFontMetrics &bfm = UMLWidget::getFontMetrics(UMLWidget::FT_BOLD); const int stereoWidth = bfm.size(0, m_umlObject->stereotype(true)).width(); if (stereoWidth > width) width = stereoWidth; } height += fontHeight; const QFontMetrics &bfm = UMLWidget::getFontMetrics(UMLWidget::FT_BOLD); const int nameWidth = bfm.size(0, m_umlObject->name()).width(); if (nameWidth > width) width = nameWidth; return QSizeF(width + 2*defaultMargin, height); } else return QSizeF(width(), height()); } /** * Resize widget to minimum size. */ void UMLWidget::resize() { qreal oldW = width(); qreal oldH = height(); // @TODO minimumSize() do not work in all cases, we need a dedicated autoResize() method QSizeF size = minimumSize(); setSize(size.width(), size.height()); DEBUG(DBG_SRC) << "size=" << size; adjustAssocs(size.width()-oldW, size.height()-oldH); } /** * Resizes the widget and adjusts the associations. * It's called when a mouse move event happens and the cursor was * in resize area when pressed. * Resizing can be constrained to an specific axis using control and shift buttons. * * @param me The QGraphicsSceneMouseEvent to get the values from. */ void UMLWidget::resize(QGraphicsSceneMouseEvent *me) { // TODO the status message lies for at least MessageWidget which could only be resized vertical UMLApp::app()->document()->writeToStatusBar(i18n("Hold shift or ctrl to move in X axis. Hold shift and control to move in Y axis. Right button click to cancel resize.")); m_resized = true; qreal newW = m_oldW + me->scenePos().x() - x() - m_pressOffset.x(); qreal newH = m_oldH + me->scenePos().y() - y() - m_pressOffset.y(); if ((me->modifiers() & Qt::ShiftModifier) && (me->modifiers() & Qt::ControlModifier)) { //Move in Y axis newW = m_oldW; } else if ((me->modifiers() & Qt::ShiftModifier) || (me->modifiers() & Qt::ControlModifier)) { //Move in X axis newH = m_oldH; } constrain(newW, newH); resizeWidget(newW, newH); DEBUG(DBG_SRC) << "event=" << me->scenePos() << "/ pos=" << pos() << " / newW=" << newW << " / newH=" << newH; QPointF delta = me->scenePos() - me->lastScenePos(); adjustAssocs(delta.x(), delta.y()); m_scene->resizeSceneToItems(); } /** * Checks if the size of the widget changed respect to the size that * it had when press event was fired. * * @return true if was resized, false otherwise. */ bool UMLWidget::wasSizeChanged() { return m_oldW != width() || m_oldH != height(); } /** * Checks if the position of the widget changed respect to the position that * it had when press event was fired. * * @return true if was moved, false otherwise. */ bool UMLWidget::wasPositionChanged() { return m_oldPos != pos(); } /** * Fills m_selectedWidgetsList and sets the selection bounds ((m_min/m_max)X/Y attributes). */ void UMLWidget::setSelectionBounds() { } void UMLWidget::setSelectedFlag(bool _select) { WidgetBase::setSelected(_select); } /** * Sets the state of whether the widget is selected. * * @param _select The state of whether the widget is selected. */ void UMLWidget::setSelected(bool _select) { WidgetBase::setSelected(_select); const WidgetBase::WidgetType wt = baseType(); if (_select) { if (m_scene->selectedCount() == 0) { if (widgetHasUMLObject(wt)) { UMLApp::app()->docWindow()->showDocumentation(m_umlObject, false); } else { UMLApp::app()->docWindow()->showDocumentation(this, false); } }//end if /* if (wt != wt_Text && wt != wt_Box) { setZ(9);//keep text on top and boxes behind so don't touch Z value } */ } else { /* if (wt != wt_Text && wt != wt_Box) { setZ(m_origZ); } */ if (isSelected()) UMLApp::app()->docWindow()->updateDocumentation(true); } update(); // selection changed, we have to make sure the copy and paste items // are correctly enabled/disabled UMLApp::app()->slotCopyChanged(); // select in tree view as done for diagrams if (_select) { UMLListViewItem * item = UMLApp::app()->listView()->findItem(id()); if (item) UMLApp::app()->listView()->setCurrentItem(item); else UMLApp::app()->listView()->clearSelection(); } } /** * Selects the widget and clears the other selected widgets, if any. * * @param me The QGraphicsSceneMouseEvent which made the selection. */ void UMLWidget::selectSingle(QGraphicsSceneMouseEvent *me) { m_scene->clearSelected(); // Adds the widget to the selected widgets list, but as it has been cleared // only the current widget is selected. selectMultiple(me); } /** * Selects the widget and adds it to the list of selected widgets. * * @param me The QGraphicsSceneMouseEvent which made the selection. */ void UMLWidget::selectMultiple(QGraphicsSceneMouseEvent *me) { Q_UNUSED(me); setSelected(true); } /** * Deselects the widget and removes it from the list of selected widgets. * * @param me The QGraphicsSceneMouseEvent which made the selection. */ void UMLWidget::deselect(QGraphicsSceneMouseEvent *me) { Q_UNUSED(me); setSelected(false); } /** * Clears the selection, resets the toolbar and deselects the widget. */ //void UMLWidget::resetSelection() //{ // m_scene->clearSelected(); // m_scene->resetToolbar(); // setSelected(false); //} /** * Sets the view the widget is on. * * @param scene The UMLScene the widget is on. */ void UMLWidget::setScene(UMLScene *scene) { //remove signals from old view - was probably 0 anyway disconnect(m_scene, SIGNAL(sigFillColorChanged(Uml::ID::Type)), this, SLOT(slotFillColorChanged(Uml::ID::Type))); disconnect(m_scene, SIGNAL(sigTextColorChanged(Uml::ID::Type)), this, SLOT(slotTextColorChanged(Uml::ID::Type))); disconnect(m_scene, SIGNAL(sigLineWidthChanged(Uml::ID::Type)), this, SLOT(slotLineWidthChanged(Uml::ID::Type))); m_scene = scene; connect(m_scene, SIGNAL(sigFillColorChanged(Uml::ID::Type)), this, SLOT(slotFillColorChanged(Uml::ID::Type))); connect(m_scene, SIGNAL(sigTextColorChanged(Uml::ID::Type)), this, SLOT(slotTextColorChanged(Uml::ID::Type))); connect(m_scene, SIGNAL(sigLineWidthChanged(Uml::ID::Type)), this, SLOT(slotLineWidthChanged(Uml::ID::Type))); } /** * Sets the x-coordinate. * Currently, the only class that reimplements this method is * ObjectWidget. * * @param x The x-coordinate to be set. */ void UMLWidget::setX(qreal x) { QGraphicsObject::setX(x); } /** * Sets the y-coordinate. * Currently, the only class that reimplements this method is * ObjectWidget. * * @param y The y-coordinate to be set. */ void UMLWidget::setY(qreal y) { QGraphicsObject::setY(y); } /** * Used to cleanup any other widget it may need to delete. * Used by child classes. This should be called before deleting a widget of a diagram. */ void UMLWidget::cleanup() { } /** * Tells the widget to snap to grid. * Will use the grid settings of the @ref UMLView it belongs to. */ void UMLWidget::slotSnapToGrid() { if (!m_ignoreSnapToGrid) { qreal newX = m_scene->snappedX(x()); setX(newX); qreal newY = m_scene->snappedY(y()); setY(newY); } } /** * Returns whether the widget type has an associated UMLObject */ bool UMLWidget::widgetHasUMLObject(WidgetBase::WidgetType type) { if (type == WidgetBase::wt_Actor || type == WidgetBase::wt_UseCase || type == WidgetBase::wt_Class || type == WidgetBase::wt_Interface || type == WidgetBase::wt_Enum || type == WidgetBase::wt_Datatype || type == WidgetBase::wt_Package || type == WidgetBase::wt_Component || type == WidgetBase::wt_Port || type == WidgetBase::wt_Node || type == WidgetBase::wt_Artifact || type == WidgetBase::wt_Object) { return true; } else { return false; } } /** * Set m_ignoreSnapToGrid. */ void UMLWidget::setIgnoreSnapToGrid(bool to) { m_ignoreSnapToGrid = to; } /** * Return the value of m_ignoreSnapToGrid. */ bool UMLWidget::getIgnoreSnapToGrid() const { return m_ignoreSnapToGrid; } /** * Sets the size. * If m_scene->snapComponentSizeToGrid() is true, then * set the next larger size that snaps to the grid. */ void UMLWidget::setSize(qreal width, qreal height) { // snap to the next larger size that is a multiple of the grid if (!m_ignoreSnapComponentSizeToGrid && m_scene->snapComponentSizeToGrid()) { // integer divisions int numX = width / m_scene->snapX(); int numY = height / m_scene->snapY(); // snap to the next larger valid value if (width > numX * m_scene->snapX()) width = (numX + 1) * m_scene->snapX(); if (height > numY * m_scene->snapY()) height = (numY + 1) * m_scene->snapY(); } const QRectF newRect(rect().x(), rect().y(), width, height); setRect(newRect); foreach(QGraphicsItem* child, childItems()) { UMLWidget* umlChild = static_cast(child); umlChild->notifyParentResize(); } } /** * Sets the size with another size. */ void UMLWidget::setSize(const QSizeF& size) { setSize(size.width(), size.height()); } /** * Update the size of this widget. */ void UMLWidget::updateGeometry() { if (m_doc->loading()) { return; } if (!m_autoResize) return; qreal oldW = width(); qreal oldH = height(); QSizeF size = calculateSize(); qreal clipWidth = size.width(); qreal clipHeight = size.height(); constrain(clipWidth, clipHeight); setSize(clipWidth, clipHeight); slotSnapToGrid(); adjustAssocs(size.width()-oldW, size.height()-oldH); } /** * 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 type association type + * @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); }