Commit 86af674e authored by Jochen Becher's avatar Jochen Becher
Browse files

ModelEditor: Make item names in-place editable



Change-Id: Ie686e570307dcc4a980174c6b07ce5d8b7929334
Reviewed-by: default avatarTobias Hunger <tobias.hunger@theqtcompany.com>
parent e3d0a38b
......@@ -139,7 +139,7 @@ QString NameController::calcElementNameSearchId(const QString &elementName)
return searchId;
}
QList<QString> NameController::buildElementsPath(const QString &filePath, bool ignoreLastFilePathPart)
QStringList NameController::buildElementsPath(const QString &filePath, bool ignoreLastFilePathPart)
{
QList<QString> relativeElements;
......@@ -154,4 +154,126 @@ QList<QString> NameController::buildElementsPath(const QString &filePath, bool i
return relativeElements;
}
bool NameController::parseClassName(const QString &fullClassName, QString *umlNamespace,
QString *className, QStringList *templateParameters)
{
enum Status {
IdentifierFirstLetter,
Identifier,
FirstColon,
FirstColonOrBeginTemplate,
Template,
FirstColonOrEndOfName,
};
if (umlNamespace)
umlNamespace->clear();
className->clear();
templateParameters->clear();
Status status = IdentifierFirstLetter;
int identifierStart = -1;
int identifierEnd = -1;
int umlNamespaceStart = -1;
int umlNamespaceEnd = -1;
int templateDepth = 0;
int templateArgumentStart = -1;
int templateEnd = -1;
for (int i = 0; i < fullClassName.size(); ++i) {
QChar c = fullClassName.at(i);
switch (status) {
case IdentifierFirstLetter:
if (c.isLetter() || c == QLatin1Char('_')) {
identifierStart = i;
identifierEnd = i;
status = Identifier;
} else if (!c.isSpace()) {
return false;
}
break;
case Identifier:
if (c == QLatin1Char(':')) {
if (umlNamespaceStart < 0)
umlNamespaceStart = identifierStart;
umlNamespaceEnd = i - 1;
identifierStart = -1;
identifierEnd = -1;
status = FirstColon;
} else if (c == QLatin1Char('<')) {
templateDepth = 1;
templateArgumentStart = i + 1;
status = Template;
} else if (c.isSpace()) {
status = FirstColonOrBeginTemplate;
} else if (c.isLetterOrNumber() || c == QLatin1Char('_')) {
identifierEnd = i;
} else {
return false;
}
break;
case FirstColon:
if (c == QLatin1Char(':'))
status = IdentifierFirstLetter;
else
return false;
break;
case FirstColonOrBeginTemplate:
if (c == QLatin1Char(':')) {
if (umlNamespaceStart < 0)
umlNamespaceStart = identifierStart;
umlNamespaceEnd = identifierEnd;
identifierStart = -1;
identifierEnd = -1;
status = FirstColon;
} else if (c == QLatin1Char('<')) {
templateDepth = 1;
templateArgumentStart = i + 1;
status = Template;
} else if (!c.isSpace()) {
return false;
}
break;
case Template:
if (c == QLatin1Char('<')) {
++templateDepth;
} else if (c == QLatin1Char('>')) {
if (templateDepth == 0) {
return false;
} else if (templateDepth == 1) {
QString arg = fullClassName.mid(templateArgumentStart, i - templateArgumentStart).trimmed();
if (!arg.isEmpty())
templateParameters->append(arg);
templateEnd = i;
status = FirstColonOrEndOfName;
}
--templateDepth;
} else if (c == QLatin1Char(',') && templateDepth == 1) {
QString arg = fullClassName.mid(templateArgumentStart, i - templateArgumentStart).trimmed();
if (!arg.isEmpty())
templateParameters->append(arg);
templateArgumentStart = i + 1;
}
break;
case FirstColonOrEndOfName:
if (c == QLatin1Char(':')) {
templateParameters->clear();
if (umlNamespaceStart < 0)
umlNamespaceStart = identifierStart;
umlNamespaceEnd = templateEnd;
identifierStart = -1;
identifierEnd = -1;
status = FirstColon;
} else if (!c.isSpace()) {
return false;
}
}
}
if (umlNamespace && umlNamespaceStart >= 0 && umlNamespaceEnd >= 0)
*umlNamespace = fullClassName.mid(umlNamespaceStart, umlNamespaceEnd - umlNamespaceStart + 1);
if (identifierStart >= 0 && identifierEnd >= 0)
*className = fullClassName.mid(identifierStart, identifierEnd - identifierStart + 1);
return true;
}
} // namespace qmt
......@@ -35,7 +35,7 @@
#include "qmt/infrastructure/qmt_global.h"
#include <QString>
#include <QList>
#include <QStringList>
namespace qmt {
......@@ -52,7 +52,9 @@ public:
static QString convertElementNameToBaseFileName(const QString &elementName);
static QString calcRelativePath(const QString &absoluteFileName, const QString &anchorPath);
static QString calcElementNameSearchId(const QString &elementName);
static QList<QString> buildElementsPath(const QString &filePath, bool ignoreLastFilePathPart);
static QStringList buildElementsPath(const QString &filePath, bool ignoreLastFilePathPart);
static bool parseClassName(const QString &fullClassName, QString *umlNamespace,
QString *className, QStringList *templateParameters);
};
} // namespace qmt
......
......@@ -30,18 +30,22 @@
#include "classitem.h"
#include "qmt/controller/namecontroller.h"
#include "qmt/diagram/dclass.h"
#include "qmt/diagram_scene/diagramsceneconstants.h"
#include "qmt/diagram_scene/diagramscenemodel.h"
#include "qmt/diagram_scene/parts/contextlabelitem.h"
#include "qmt/diagram_scene/parts/customiconitem.h"
#include "qmt/diagram_scene/parts/editabletextitem.h"
#include "qmt/diagram_scene/parts/relationstarter.h"
#include "qmt/diagram_scene/parts/stereotypesitem.h"
#include "qmt/diagram_scene/parts/templateparameterbox.h"
#include "qmt/infrastructure/contextmenuaction.h"
#include "qmt/infrastructure/geometryutilities.h"
#include "qmt/infrastructure/qmtassert.h"
#include "qmt/model/mclass.h"
#include "qmt/model/mclassmember.h"
#include "qmt/model_controller/modelcontroller.h"
#include "qmt/stereotype/stereotypecontroller.h"
#include "qmt/stereotype/stereotypeicon.h"
#include "qmt/style/stylecontroller.h"
......@@ -138,34 +142,8 @@ void ClassItem::update()
m_namespace = 0;
}
DClass::TemplateDisplay templateDisplay = diagramClass->templateDisplay();
if (templateDisplay == DClass::TemplateSmart) {
if (m_customIcon)
templateDisplay = DClass::TemplateName;
else
templateDisplay = DClass::TemplateBox;
}
// class name
if (!m_className)
m_className = new QGraphicsSimpleTextItem(this);
m_className->setFont(style->headerFont());
m_className->setBrush(style->textBrush());
if (templateDisplay == DClass::TemplateName && !diagramClass->templateParameters().isEmpty()) {
QString name = object()->name();
name += QLatin1Char('<');
bool first = true;
foreach (const QString &p, diagramClass->templateParameters()) {
if (!first)
name += QLatin1Char(',');
name += p;
first = false;
}
name += QLatin1Char('>');
m_className->setText(name);
} else {
m_className->setText(object()->name());
}
updateNameItem(style);
// context
if (showContext()) {
......@@ -233,7 +211,11 @@ void ClassItem::update()
}
// template parameters
if (templateDisplay == DClass::TemplateBox && !diagramClass->templateParameters().isEmpty()) {
if (templateDisplay() == DClass::TemplateBox && !diagramClass->templateParameters().isEmpty()) {
// TODO due to a bug in Qt the m_nameItem may get focus back when this item is newly created
// 1. Select name item of class without template
// 2. Click into template property (item name loses focus) and enter a letter
// 3. Template box is created which gives surprisingly focus back to item name
if (!m_templateParameterBox)
m_templateParameterBox = new TemplateParameterBox(this);
QPen pen = style->outerLinePen();
......@@ -339,6 +321,67 @@ bool ClassItem::handleSelectedContextMenuAction(QAction *action)
return false;
}
QString ClassItem::buildDisplayName() const
{
auto diagramClass = dynamic_cast<DClass *>(object());
QMT_CHECK(diagramClass);
QString name;
if (templateDisplay() == DClass::TemplateName && !diagramClass->templateParameters().isEmpty()) {
name = object()->name();
name += QLatin1Char('<');
bool first = true;
foreach (const QString &p, diagramClass->templateParameters()) {
if (!first)
name += QLatin1Char(',');
name += p;
first = false;
}
name += QLatin1Char('>');
} else {
name = object()->name();
}
return name;
}
void ClassItem::setFromDisplayName(const QString &displayName)
{
if (templateDisplay() == DClass::TemplateName) {
QString name;
QStringList templateParameters;
// NOTE namespace is ignored because it has its own edit field
if (NameController::parseClassName(displayName, 0, &name, &templateParameters)) {
auto diagramClass = dynamic_cast<DClass *>(object());
QMT_CHECK(diagramClass);
ModelController *modelController = diagramSceneModel()->diagramSceneController()->modelController();
MClass *mklass = modelController->findObject<MClass>(diagramClass->modelUid());
if (mklass && (name != mklass->name() || templateParameters != mklass->templateParameters())) {
modelController->startUpdateObject(mklass);
mklass->setName(name);
mklass->setTemplateParameters(templateParameters);
modelController->finishUpdateObject(mklass, false);
}
}
} else {
ObjectItem::setFromDisplayName(displayName);
}
}
DClass::TemplateDisplay ClassItem::templateDisplay() const
{
auto diagramClass = dynamic_cast<DClass *>(object());
QMT_CHECK(diagramClass);
DClass::TemplateDisplay templateDisplay = diagramClass->templateDisplay();
if (templateDisplay == DClass::TemplateSmart) {
if (m_customIcon)
templateDisplay = DClass::TemplateName;
else
templateDisplay = DClass::TemplateBox;
}
return templateDisplay;
}
QSizeF ClassItem::calcMinimumGeometry() const
{
double width = 0.0;
......@@ -362,9 +405,9 @@ QSizeF ClassItem::calcMinimumGeometry() const
width = std::max(width, m_namespace->boundingRect().width() + 2 * BODY_HORIZ_BORDER);
height += m_namespace->boundingRect().height();
}
if (m_className) {
width = std::max(width, m_className->boundingRect().width() + 2 * BODY_HORIZ_BORDER);
height += m_className->boundingRect().height();
if (nameItem()) {
width = std::max(width, nameItem()->boundingRect().width() + 2 * BODY_HORIZ_BORDER);
height += nameItem()->boundingRect().height();
}
if (m_contextLabel)
height += m_contextLabel->height();
......@@ -450,9 +493,9 @@ void ClassItem::updateGeometry()
m_namespace->setPos(-m_namespace->boundingRect().width() / 2.0, y);
y += m_namespace->boundingRect().height();
}
if (m_className) {
m_className->setPos(-m_className->boundingRect().width() / 2.0, y);
y += m_className->boundingRect().height();
if (nameItem()) {
nameItem()->setPos(-nameItem()->boundingRect().width() / 2.0, y);
y += nameItem()->boundingRect().height();
}
if (m_contextLabel) {
if (m_customIcon)
......
......@@ -34,6 +34,7 @@
#include "objectitem.h"
#include "qmt/diagram_scene/capabilities/relationable.h"
#include "qmt/diagram/dclass.h"
QT_BEGIN_NAMESPACE
class QGraphicsRectItem;
......@@ -45,7 +46,6 @@ QT_END_NAMESPACE
namespace qmt {
class DiagramSceneModel;
class DClass;
class CustomIconItem;
class ContextLabelItem;
class TemplateParameterBox;
......@@ -72,8 +72,11 @@ public:
protected:
bool extendContextMenu(QMenu *menu) override;
bool handleSelectedContextMenuAction(QAction *action) override;
QString buildDisplayName() const override;
void setFromDisplayName(const QString &displayName) override;
private:
DClass::TemplateDisplay templateDisplay() const;
QSizeF calcMinimumGeometry() const;
void updateGeometry();
void updateMembers(const Style *style);
......@@ -81,7 +84,6 @@ private:
CustomIconItem *m_customIcon = 0;
QGraphicsRectItem *m_shape = 0;
QGraphicsSimpleTextItem *m_namespace = 0;
QGraphicsSimpleTextItem *m_className = 0;
ContextLabelItem *m_contextLabel = 0;
QGraphicsLineItem *m_attributesSeparator = 0;
QString m_attributesText;
......
......@@ -36,6 +36,7 @@
#include "qmt/diagram_scene/diagramscenemodel.h"
#include "qmt/diagram_scene/parts/contextlabelitem.h"
#include "qmt/diagram_scene/parts/customiconitem.h"
#include "qmt/diagram_scene/parts/editabletextitem.h"
#include "qmt/diagram_scene/parts/relationstarter.h"
#include "qmt/diagram_scene/parts/stereotypesitem.h"
#include "qmt/infrastructure/geometryutilities.h"
......@@ -142,11 +143,7 @@ void ComponentItem::update()
updateStereotypes(stereotypeIconId(), stereotypeIconDisplay(), style);
// component name
if (!m_componentName)
m_componentName = new QGraphicsSimpleTextItem(this);
m_componentName->setFont(style->headerFont());
m_componentName->setBrush(style->textBrush());
m_componentName->setText(object()->name());
updateNameItem(style);
// context
if (showContext()) {
......@@ -267,9 +264,9 @@ QSizeF ComponentItem::calcMinimumGeometry() const
width = std::max(width, stereotypesItem->boundingRect().width());
height += stereotypesItem->boundingRect().height();
}
if (m_componentName) {
width = std::max(width, m_componentName->boundingRect().width());
height += m_componentName->boundingRect().height();
if (nameItem()) {
width = std::max(width, nameItem()->boundingRect().width());
height += nameItem()->boundingRect().height();
}
if (m_contextLabel)
height += m_contextLabel->height();
......@@ -354,9 +351,9 @@ void ComponentItem::updateGeometry()
stereotypesItem->setPos(-stereotypesItem->boundingRect().width() / 2.0, y);
y += stereotypesItem->boundingRect().height();
}
if (m_componentName) {
m_componentName->setPos(-m_componentName->boundingRect().width() / 2.0, y);
y += m_componentName->boundingRect().height();
if (nameItem()) {
nameItem()->setPos(-nameItem()->boundingRect().width() / 2.0, y);
y += nameItem()->boundingRect().height();
}
if (m_contextLabel) {
if (m_customIcon) {
......
......@@ -79,7 +79,6 @@ private:
QGraphicsRectItem *m_shape = 0;
QGraphicsRectItem *m_upperRect = 0;
QGraphicsRectItem *m_lowerRect = 0;
QGraphicsSimpleTextItem *m_componentName = 0;
ContextLabelItem *m_contextLabel = 0;
RelationStarter *m_relationStarter = 0;
};
......
......@@ -34,6 +34,7 @@
#include "qmt/diagram_scene/diagramsceneconstants.h"
#include "qmt/diagram_scene/diagramscenemodel.h"
#include "qmt/diagram_scene/parts/customiconitem.h"
#include "qmt/diagram_scene/parts/editabletextitem.h"
#include "qmt/diagram_scene/parts/stereotypesitem.h"
#include "qmt/infrastructure/geometryutilities.h"
#include "qmt/stereotype/stereotypecontroller.h"
......@@ -117,11 +118,7 @@ void DiagramItem::update()
updateStereotypes(stereotypeIconId(), stereotypeIconDisplay(), style);
// diagram name
if (!m_diagramName)
m_diagramName = new QGraphicsSimpleTextItem(this);
m_diagramName->setFont(style->headerFont());
m_diagramName->setBrush(style->textBrush());
m_diagramName->setText(object()->name());
updateNameItem(style);
updateSelectionMarker(m_customIcon);
updateAlignmentButtons();
......@@ -170,9 +167,9 @@ QSizeF DiagramItem::calcMinimumGeometry() const
width = std::max(width, stereotypesItem->boundingRect().width() + 2 * BODY_HORIZ_BORDER);
height += stereotypesItem->boundingRect().height();
}
if (m_diagramName) {
width = std::max(width, m_diagramName->boundingRect().width() + 2 * BODY_HORIZ_BORDER);
height += m_diagramName->boundingRect().height();
if (nameItem()) {
width = std::max(width, nameItem()->boundingRect().width() + 2 * BODY_HORIZ_BORDER);
height += nameItem()->boundingRect().height();
}
height += BODY_VERT_BORDER;
......@@ -258,9 +255,9 @@ void DiagramItem::updateGeometry()
stereotypesItem->setPos(-stereotypesItem->boundingRect().width() / 2.0, y);
y += stereotypesItem->boundingRect().height();
}
if (m_diagramName) {
m_diagramName->setPos(-m_diagramName->boundingRect().width() / 2.0, y);
y += m_diagramName->boundingRect().height();
if (nameItem()) {
nameItem()->setPos(-nameItem()->boundingRect().width() / 2.0, y);
y += nameItem()->boundingRect().height();
}
updateSelectionMarkerGeometry(rect);
......
......@@ -60,7 +60,6 @@ private:
CustomIconItem *m_customIcon = 0;
QGraphicsPolygonItem *m_body = 0;
QGraphicsPolygonItem *m_fold = 0;
QGraphicsSimpleTextItem *m_diagramName = 0;
};
} // namespace qmt
......
......@@ -36,6 +36,7 @@
#include "qmt/diagram_scene/diagramscenemodel.h"
#include "qmt/diagram_scene/parts/contextlabelitem.h"
#include "qmt/diagram_scene/parts/customiconitem.h"
#include "qmt/diagram_scene/parts/editabletextitem.h"
#include "qmt/diagram_scene/parts/relationstarter.h"
#include "qmt/diagram_scene/parts/stereotypesitem.h"
#include "qmt/infrastructure/geometryutilities.h"
......@@ -112,11 +113,7 @@ void ItemItem::update()
updateStereotypes(stereotypeIconId(), stereotypeIconDisplay(), adaptedStyle(stereotypeIconId()));
// component name
if (!m_itemName)
m_itemName = new QGraphicsSimpleTextItem(this);
m_itemName->setFont(style->headerFont());
m_itemName->setBrush(style->textBrush());
m_itemName->setText(object()->name());
updateNameItem(style);
// context
if (showContext()) {
......@@ -219,9 +216,9 @@ QSizeF ItemItem::calcMinimumGeometry() const
width = std::max(width, stereotypesItem->boundingRect().width());
height += stereotypesItem->boundingRect().height();
}
if (m_itemName) {
width = std::max(width, m_itemName->boundingRect().width());
height += m_itemName->boundingRect().height();
if (nameItem()) {
width = std::max(width, nameItem()->boundingRect().width());
height += nameItem()->boundingRect().height();
}
if (m_contextLabel)
height += m_contextLabel->height();
......@@ -287,9 +284,9 @@ void ItemItem::updateGeometry()
stereotypesItem->setPos(-stereotypesItem->boundingRect().width() / 2.0, y);
y += stereotypesItem->boundingRect().height();
}
if (m_itemName) {
m_itemName->setPos(-m_itemName->boundingRect().width() / 2.0, y);
y += m_itemName->boundingRect().height();
if (nameItem()) {
nameItem()->setPos(-nameItem()->boundingRect().width() / 2.0, y);
y += nameItem()->boundingRect().height();
}
if (m_contextLabel) {
if (m_customIcon) {
......
......@@ -75,7 +75,6 @@ private:
CustomIconItem *m_customIcon = 0;
QGraphicsRectItem *m_shape = 0;
QGraphicsSimpleTextItem *m_itemName = 0;
ContextLabelItem *m_contextLabel = 0;
RelationStarter *m_relationStarter = 0;
};
......
......@@ -37,6 +37,7 @@
#include "qmt/diagram_scene/diagramscenemodel.h"
#include "qmt/diagram_scene/items/stereotypedisplayvisitor.h"
#include "qmt/diagram_scene/parts/alignbuttonsitem.h"
#include "qmt/diagram_scene/parts/editabletextitem.h"
#include "qmt/diagram_scene/parts/rectangularselectionitem.h"
#include "qmt/diagram_scene/parts/customiconitem.h"
#include "qmt/diagram_scene/parts/stereotypesitem.h"
......@@ -57,6 +58,7 @@
#include <QGraphicsView>
#include <QCursor>
#include <QMenu>
#include <QTextDocument>
namespace qmt {
......@@ -356,6 +358,18 @@ void ObjectItem::align(IAlignable::AlignType alignType, const QString &identifie
}
}
bool ObjectItem::isEditable() const
{
return true;
}
void ObjectItem::edit()
{
// TODO if name is initial name ("New Class" etc) select all text
if (m_nameItem)
m_nameItem->setFocus();
}
void ObjectItem::updateStereotypeIconDisplay()
{
StereotypeDisplayVisitor stereotypeDisplayVisitor;
......@@ -439,6 +453,50 @@ QSizeF ObjectItem::stereotypeIconMinimumSize(const StereotypeIcon &stereotypeIco
return QSizeF(width, height);
}
void ObjectItem::updateNameItem(const Style *style)
{
if (!m_nameItem) {
m_nameItem = new EditableTextItem(this);
m_nameItem->setShowFocus(true);
m_nameItem->setFilterReturnKey(true);
m_nameItem->setFilterTabKey(true);
QObject::connect(m_nameItem->document(), &QTextDocument::contentsChanged, m_nameItem,
[=]() { this->setFromDisplayName(m_nameItem->toPlainText()); });
QObject::connect(m_nameItem, &EditableTextItem::returnKeyPressed, m_nameItem,
[=]() { this->m_nameItem->clearFocus(); });
}
if (style->headerFont() != m_nameItem->font())
m_nameItem->setFont(style->headerFont());
if (style->textBrush().color() != m_nameItem->defaultTextColor())
m_nameItem->setDefaultTextColor(style->textBrush().color());
if (!m_nameItem->hasFocus()) {
QString name = buildDisplayName();
if (name != m_nameItem->toPlainText())
m_nameItem->setPlainText(name);
}