Commit 96ad2a5e authored by Ralf Habacker's avatar Ralf Habacker
Browse files

Add support for importing simple statements into class diagrams.



Class and association statements could be imported by pasting from
the clipboard or by selecting "from file" in the "new" submenu of
a class diagram context menu.

Parsing errors occured on import are logged into the log window.

See test/import/diagrams/class/examples.txt for examples of
supported statements.

BUG:380266
FIXED-IN:2.22.80 (KDE Applications 17.07.80)
Signed-off-by: Ralf Habacker's avatarRalf Habacker <ralf.habacker@freenet.de>
parent 736763ff
# import into class diagram
TestClass1
member11
member12
method11()
method12()
TestClass2
member21
member22
method21()
method22()
TestClass3
int member11
test member12
int method12()
# comments
# int method11()
// int method11()
TestClass4
public int publicMember41
protected int proctedMember42
private test privateMember43
public int publicMethod41()
protected int protectedMethod42()
private int privateMethod43()
TestClass5
+ int publicMember51
# test protectedMember52
- test privateMember53
+ int publicMethod51()
# int protectedMethod52()
- int privateMethod53()
~ int implementationMethod54()
# association
TestClass1 --- TestClass5
# uniassociation
TestClass4 --> TestClass2
# generalization
TestClass4 -|> TestClass1
# aggregation
TestClass2 -<> TestClass3
# composition
TestClass5 --* TestClass2
# parse error
# unknown class
TestClass --* TestClass2
# invalid syntax
TestClass --*
## not yet implemented
# type modifiers
# method parameter
# multiplicity e.g TestClass2 {1..*} -<> {1} TestClass3
\ No newline at end of file
......@@ -124,7 +124,7 @@ QString toString(Enum item, bool mnemonic)
* @param item item to convert
* @return Visibility enum
*/
Enum fromString(const QString& item)
Enum fromString(const QString& item, bool checkUnkown)
{
if (item == QLatin1String("public") || item == QLatin1String("+"))
return Public;
......@@ -138,6 +138,8 @@ Enum fromString(const QString& item)
return Protected;
else if (item == QLatin1String("class"))
return Private;
else if (checkUnkown)
return Unknown;
else
return Public;
}
......
......@@ -61,10 +61,11 @@ namespace Uml
Private,
Protected,
Implementation, // objects marked with this are declared in the implementation file.
FromParent = 3 // alias for Implementation, used by CodeGenerationPolicy
FromParent = 3, // alias for Implementation, used by CodeGenerationPolicy
Unknown
};
QString toString(Enum item, bool mnemonic = false);
Enum fromString(const QString& item);
Enum fromString(const QString& item, bool checkUnknown = false);
Enum fromInt(int item);
}
......
......@@ -197,9 +197,8 @@ bool UMLClipboard::paste(const QMimeData* data)
int codingType = UMLDragData::getCodingType(data);
if (codingType == 6
&& UMLApp::app()->currentView()
&& UMLApp::app()->currentView()->umlScene()->type() == Uml::DiagramType::Sequence) {
return Diagram_Utils::importSequences(data, UMLApp::app()->currentView()->umlScene());
&& UMLApp::app()->currentView()) {
return Diagram_Utils::importGraph(data, UMLApp::app()->currentView()->umlScene());
}
QString mimeType = QLatin1String("application/x-uml-clip") + QString::number(codingType);
uDebug() << "Pasting mimeType=" << mimeType << "data=" << data->data(mimeType);
......
......@@ -530,7 +530,7 @@ void addEnumLiteral(UMLEnum *enumType, const QString &literal, const QString &co
* Create a generalization from the given child classifier to the given
* parent classifier.
*/
void createGeneralization(UMLClassifier *child, UMLClassifier *parent)
UMLAssociation *createGeneralization(UMLClassifier *child, UMLClassifier *parent)
{
// if the child is an interface, so is the parent.
if (child->isInterface())
......@@ -547,6 +547,7 @@ void createGeneralization(UMLClassifier *child, UMLClassifier *parent)
UMLDoc *umldoc = UMLApp::app()->document();
assoc->setUMLPackage(umldoc->rootFolder(Uml::ModelType::Logical));
umldoc->addAssociation(assoc);
return assoc;
}
/**
......
......@@ -81,7 +81,7 @@ namespace Import_Utils {
const QString &comment = QString(),
const QString &value = QString());
void createGeneralization(UMLClassifier *child, UMLClassifier *parent);
UMLAssociation *createGeneralization(UMLClassifier *child, UMLClassifier *parent);
void createGeneralization(UMLClassifier *child, const QString &parentName);
UMLEnum *remapUMLEnum(UMLObject *ns, UMLPackage *currentScope);
......
......@@ -12,7 +12,10 @@
#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"
......@@ -23,6 +26,7 @@
#include "widget_factory.h"
//// qt includes
#include <QListWidget>
#include <QMap>
#include <QMimeData>
#include <QRegExp>
......@@ -80,13 +84,13 @@ SequenceLineFormat detectSequenceLineFormat(const QStringList &lines)
* @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)
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) {
DEBUG(DBG_SRC) << "could not parse" << s;
error = QLatin1String("could not parse");
return false;
}
// skip comments
......@@ -176,6 +180,7 @@ bool parseSequenceLine(const QString &s, QString &sequence, QString &package, QS
package = b.join(QLatin1String("::"));
return true;
}
error = QLatin1String("unsupported line format");
return false;
}
......@@ -184,10 +189,11 @@ bool parseSequenceLine(const QString &s, QString &sequence, QString &package, QS
*
* @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)
bool importSequences(const QStringList &lines, UMLScene *scene, const QString &fileName)
{
// object widget cache map
QMap<QString, ObjectWidget*> objectsMap;
......@@ -224,10 +230,17 @@ bool importSequences(const QStringList &lines, UMLScene *scene)
// for each line
int index = 1;
foreach(const QString &line, l) {
QString stackframe, package, method;
if (!parseSequenceLine(line, stackframe, package, method))
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))
......@@ -292,14 +305,162 @@ bool importSequences(const QStringList &lines, UMLScene *scene)
}
/**
* Import sequence diagram entries from clipboard
* Import sequence diagram entries from a string list.
*
* @param mimeData instance of mime data to import from
* @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<QString, QPointer<UMLWidget>> 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);
w->activate();
scene->addWidgetCmd(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<UMLWidget> w1 = 0;
QPointer<UMLWidget> 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 importSequences(const QMimeData* mimeData, UMLScene *scene)
bool importGraph(const QMimeData* mimeData, UMLScene *scene)
{
QString requestedFormat = QLatin1String("text/plain");
if (!mimeData->hasFormat(requestedFormat))
......@@ -314,20 +475,24 @@ bool importSequences(const QMimeData* mimeData, UMLScene *scene)
UMLDoc *doc = UMLApp::app()->document();
doc->beginPaste();
bool result = importSequences(lines, scene);
bool result = false;
if (scene->type() == Uml::DiagramType::Sequence)
result = importSequences(lines, scene);
else
result = importGraph(lines, scene);
doc->endPaste();
return result;
}
/**
* Import sequence diagram entries from file
* Import graph entries from file
*
* @param fileName filename to import the sequences from.
* @param scene The diagram to import the sequences into.
* @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 importSequences(const QString &fileName, UMLScene *scene)
bool importGraph(const QString &fileName, UMLScene *scene)
{
QFile file(fileName);
......@@ -340,7 +505,7 @@ bool importSequences(const QString &fileName, UMLScene *scene)
while (!in.atEnd()) {
lines.append(in.readLine());
}
return importSequences(lines, scene);
return importGraph(lines, scene, fileName);
}
} // end namespace Diagram_Utils
......@@ -11,9 +11,10 @@
#ifndef DIAGRAM_UTILS_H
#define DIAGRAM_UTILS_H
#include <QString>
class UMLScene;
class QMimeData;
class QString;
class QStringList;
/**
......@@ -24,9 +25,10 @@ class QStringList;
namespace Diagram_Utils {
typedef enum { Invalid, GDB, QtCreatorGDB, Simple} SequenceLineFormat;
SequenceLineFormat detectSequenceLineFormat(const QStringList &lines);
bool importSequences(const QStringList &lines, UMLScene *scene);
bool importSequences(const QString &fileName, UMLScene *scene);
bool importSequences(const QMimeData *mimeData, UMLScene *scene);
bool importSequences(const QStringList &lines, UMLScene *scene, const QString &fileName=QString());
bool importGraph(const QStringList &lines, UMLScene *scene, const QString &fileName=QString());
bool importGraph(const QString &fileName, UMLScene *scene);
bool importGraph(const QMimeData *mimeData, UMLScene *scene);
} // end namespace Diagram_Utils
#endif
......@@ -1611,6 +1611,7 @@ void ListPopupMenu::insertSubMenuNew(MenuType type)
insert(mt_FloatText, menu);
break;
case mt_On_Class_Diagram:
insert(mt_Import_from_File, menu, Icon_Utils::SmallIcon(Icon_Utils::it_Import_File), i18n("from file..."));
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..."));
......@@ -1655,7 +1656,7 @@ void ListPopupMenu::insertSubMenuNew(MenuType type)
insert(mt_Category, menu, Icon_Utils::SmallIcon(Icon_Utils::it_Category), i18n("Category..."));
break;
case mt_On_Sequence_Diagram:
insert(mt_Import_from_File, menu, Icon_Utils::SmallIcon(Icon_Utils::it_Import_File), i18n("Import stack trace..."));
insert(mt_Import_from_File, menu, Icon_Utils::SmallIcon(Icon_Utils::it_Import_File), i18n("from file..."));
insert(mt_Object, menu, Icon_Utils::SmallIcon(Icon_Utils::it_Object), i18n("Object..."));
insert(mt_FloatText, menu);
break;
......
......@@ -3136,8 +3136,8 @@ void UMLScene::slotMenuSelection(QAction* action)
dialog->exec();
QUrl url = dialog->selectedUrl();
if (!url.isEmpty())
if (!Diagram_Utils::importSequences(url.toLocalFile(), this))
UMLApp::app()->slotStatusMsg(i18n("Failed to import stack trace."));
if (!Diagram_Utils::importGraph(url.toLocalFile(), this))
UMLApp::app()->slotStatusMsg(i18n("Failed to import from file."));
break;
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment