ModelEditor: Allow relocation of ends of relations in diagrams

Change-Id: I8dbf536731cb24819f70dc6acacc4658fd257259
Reviewed-by: Tobias Hunger <tobias.hunger@theqtcompany.com>
This commit is contained in:
Jochen Becher
2015-12-03 17:12:30 +01:00
parent fb99157abd
commit 4a06fc63a9
11 changed files with 248 additions and 32 deletions

View File

@@ -156,6 +156,39 @@ void DUpdateVisitor::visitMRelation(const MRelation *relation)
drelation->setStereotypes(relation->stereotypes());
if (isUpdating(relation->name() != drelation->name()))
drelation->setName(relation->name());
// TODO improve performance of MDiagram::findDiagramElement
DObject *endAObject = dynamic_cast<DObject *>(m_diagram->findDiagramElement(drelation->endAUid()));
if (!endAObject || relation->endAUid() != endAObject->modelUid()) {
isUpdating(true);
endAObject = 0;
// TODO use DiagramController::findDelegate (and improve performance of that method)
foreach (DElement *diagramElement, m_diagram->diagramElements()) {
if (diagramElement->modelUid().isValid() && diagramElement->modelUid() == relation->endAUid()) {
endAObject = dynamic_cast<DObject *>(diagramElement);
break;
}
}
if (endAObject)
drelation->setEndAUid(endAObject->uid());
else
drelation->setEndAUid(Uid::invalidUid());
}
DObject *endBObject = dynamic_cast<DObject *>(m_diagram->findDiagramElement(drelation->endBUid()));
if (!endBObject || relation->endBUid() != endBObject->modelUid()) {
isUpdating(true);
endBObject = 0;
// TODO use DiagramController::findDelegate
foreach (DElement *diagramElement, m_diagram->diagramElements()) {
if (diagramElement->modelUid().isValid() && diagramElement->modelUid() == relation->endBUid()) {
endBObject = dynamic_cast<DObject *>(diagramElement);
break;
}
}
if (endBObject)
drelation->setEndBUid(endBObject->uid());
else
drelation->setEndBUid(Uid::invalidUid());
}
visitMElement(relation);
}

View File

@@ -44,11 +44,11 @@ class IWindable
public:
virtual ~IWindable() { }
virtual QPointF handlePos(int index) = 0;
virtual void insertHandle(int beforeIndex, const QPointF &pos) = 0;
virtual QPointF grabHandle(int index) = 0;
virtual void insertHandle(int beforeIndex, const QPointF &pos, double rasterWidth, double rasterHeight) = 0;
virtual void deleteHandle(int index) = 0;
virtual void setHandlePos(int index, const QPointF &pos) = 0;
virtual void alignHandleToRaster(int index, double rasterWidth, double rasterHeight) = 0;
virtual void dropHandle(int index, double rasterWidth, double rasterHeight) = 0;
};
} // namespace qmt

View File

@@ -232,6 +232,20 @@ DElement *DiagramSceneModel::findTopmostElement(const QPointF &scenePos) const
return 0;
}
DObject *DiagramSceneModel::findTopmostObject(const QPointF &scenePos) const
{
// fetch affected items from scene in correct drawing order to find topmost element
QList<QGraphicsItem *> items = m_graphicsScene->items(scenePos);
foreach (QGraphicsItem *item, items) {
if (m_graphicsItems.contains(item)) {
DObject *object = dynamic_cast<DObject *>(m_itemToElementMap.value(item));
if (object)
return object;
}
}
return 0;
}
QGraphicsItem *DiagramSceneModel::graphicsItem(DElement *element) const
{
return m_elementToItemMap.value(element);

View File

@@ -102,6 +102,7 @@ public:
bool hasMultiObjectsSelection() const;
DSelection selectedElements() const;
DElement *findTopmostElement(const QPointF &scenePos) const;
DObject *findTopmostObject(const QPointF &scenePos) const;
QList<QGraphicsItem *> graphicsItems() const { return m_graphicsItems; }
QGraphicsItem *graphicsItem(DElement *element) const;

View File

@@ -174,6 +174,10 @@ private:
QList<QPointF> m_points;
};
bool RelationItem::m_grabbedEndA = false;
bool RelationItem::m_grabbedEndB = false;
QPointF RelationItem::m_grabbedEndPos;
RelationItem::RelationItem(DRelation *relation, DiagramSceneModel *diagramSceneModel, QGraphicsItem *parent)
: QGraphicsItem(parent),
m_relation(relation),
@@ -271,14 +275,19 @@ void RelationItem::setFocusSelected(bool focusSelected)
}
}
QPointF RelationItem::handlePos(int index)
QPointF RelationItem::grabHandle(int index)
{
if (index == 0) {
// TODO implement
return QPointF(0,0);
m_grabbedEndA = true;
QPointF endBPos = calcEndPoint(m_relation->endBUid(), m_relation->endAUid(), m_relation->intermediatePoints().size() - 1);
QPointF endAPos = calcEndPoint(m_relation->endAUid(), endBPos, 0);
m_grabbedEndPos = endAPos;
return endAPos;
} else if (index == m_relation->intermediatePoints().size() + 1) {
// TODO implement
return QPointF(0,0);
m_grabbedEndB = true;
QPointF endBPos = calcEndPoint(m_relation->endBUid(), m_relation->endAUid(), m_relation->intermediatePoints().size() - 1);
m_grabbedEndPos = endBPos;
return endBPos;
} else {
QList<DRelation::IntermediatePoint> intermediatePoints = m_relation->intermediatePoints();
--index;
@@ -287,11 +296,15 @@ QPointF RelationItem::handlePos(int index)
}
}
void RelationItem::insertHandle(int beforeIndex, const QPointF &pos)
void RelationItem::insertHandle(int beforeIndex, const QPointF &pos, double rasterWidth, double rasterHeight)
{
if (beforeIndex == 0)
++beforeIndex;
if (beforeIndex >= 1 && beforeIndex <= m_relation->intermediatePoints().size() + 1) {
QList<DRelation::IntermediatePoint> intermediatePoints = m_relation->intermediatePoints();
intermediatePoints.insert(beforeIndex - 1, DRelation::IntermediatePoint(pos));
double x = qRound(pos.x() / rasterWidth) * rasterWidth;
double y = qRound(pos.y() / rasterHeight) * rasterHeight;
intermediatePoints.insert(beforeIndex - 1, DRelation::IntermediatePoint(QPointF(x, y)));
m_diagramSceneModel->diagramController()->startUpdateElement(m_relation, m_diagramSceneModel->diagram(), DiagramController::UpdateMajor);
m_relation->setIntermediatePoints(intermediatePoints);
@@ -301,6 +314,10 @@ void RelationItem::insertHandle(int beforeIndex, const QPointF &pos)
void RelationItem::deleteHandle(int index)
{
if (index == 0)
++index;
else if (index == m_relation->intermediatePoints().size() + 1)
--index;
if (index >= 1 && index <= m_relation->intermediatePoints().size()) {
QList<DRelation::IntermediatePoint> intermediatePoints = m_relation->intermediatePoints();
intermediatePoints.removeAt(index - 1);
@@ -314,9 +331,11 @@ void RelationItem::deleteHandle(int index)
void RelationItem::setHandlePos(int index, const QPointF &pos)
{
if (index == 0) {
// TODO implement
m_grabbedEndPos = pos;
update();
} else if (index == m_relation->intermediatePoints().size() + 1) {
// TODO implement
m_grabbedEndPos = pos;
update();
} else {
QList<DRelation::IntermediatePoint> intermediatePoints = m_relation->intermediatePoints();
--index;
@@ -329,12 +348,18 @@ void RelationItem::setHandlePos(int index, const QPointF &pos)
}
}
void RelationItem::alignHandleToRaster(int index, double rasterWidth, double rasterHeight)
void RelationItem::dropHandle(int index, double rasterWidth, double rasterHeight)
{
if (index == 0) {
// TODO implement
} else if (index ==m_relation->intermediatePoints().size() + 1) {
// TODO implement
m_grabbedEndA = false;
DObject *targetObject = m_diagramSceneModel->findTopmostObject(m_grabbedEndPos);
if (!m_diagramSceneModel->diagramSceneController()->relocateRelationEndA(m_relation, targetObject))
update();
} else if (index == m_relation->intermediatePoints().size() + 1) {
m_grabbedEndB = false;
DObject *targetObject = m_diagramSceneModel->findTopmostObject(m_grabbedEndPos);
if (!m_diagramSceneModel->diagramSceneController()->relocateRelationEndB(m_relation, targetObject))
update();
} else {
QList<DRelation::IntermediatePoint> intermediatePoints = m_relation->intermediatePoints();
--index;
@@ -365,8 +390,19 @@ void RelationItem::update()
void RelationItem::update(const Style *style)
{
QPointF endBPos = calcEndPoint(m_relation->endBUid(), m_relation->endAUid(), m_relation->intermediatePoints().size() - 1);
QPointF endAPos = calcEndPoint(m_relation->endAUid(), endBPos, 0);
QPointF endAPos;
QPointF endBPos;
if (m_grabbedEndA) {
endAPos = m_grabbedEndPos;
endBPos = calcEndPoint(m_relation->endBUid(), endAPos, m_relation->intermediatePoints().size() - 1);
} else if (m_grabbedEndB) {
endBPos = m_grabbedEndPos;
endAPos = calcEndPoint(m_relation->endAUid(), endBPos, 0);
} else {
endBPos = calcEndPoint(m_relation->endBUid(), m_relation->endAUid(), m_relation->intermediatePoints().size() - 1);
endAPos = calcEndPoint(m_relation->endAUid(), endBPos, 0);
}
setPos(endAPos);

View File

@@ -77,11 +77,11 @@ public:
bool isFocusSelected() const override;
void setFocusSelected(bool focusSelected) override;
QPointF handlePos(int index) override;
void insertHandle(int beforeIndex, const QPointF &pos) override;
QPointF grabHandle(int index) override;
void insertHandle(int beforeIndex, const QPointF &pos, double rasterWidth, double rasterHeight) override;
void deleteHandle(int index) override;
void setHandlePos(int index, const QPointF &pos) override;
void alignHandleToRaster(int index, double rasterWidth, double rasterHeight) override;
void dropHandle(int index, double rasterWidth, double rasterHeight) override;
virtual void update();
@@ -108,6 +108,9 @@ protected:
QGraphicsSimpleTextItem *m_name = 0;
StereotypesItem *m_stereotypes = 0;
PathSelectionItem *m_selectionHandles = 0;
static bool m_grabbedEndA;
static bool m_grabbedEndB;
static QPointF m_grabbedEndPos;
};
} // namespace qmt

View File

@@ -33,12 +33,14 @@
#include "qmt/diagram_scene/capabilities/windable.h"
#include "qmt/diagram_scene/diagramsceneconstants.h"
#include "qmt/infrastructure/geometryutilities.h"
#include "qmt/infrastructure/qmtassert.h"
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QBrush>
#include <QLineF>
#include <QPainter>
#include <QKeyEvent>
namespace qmt {
@@ -58,6 +60,12 @@ public:
m_owner(parent),
m_pointIndex(pointIndex)
{
setFlag(QGraphicsItem::ItemIsFocusable);
}
void setPointIndex(int pointIndex)
{
m_pointIndex = pointIndex;
}
void setPointSize(const QSizeF &pointSize)
@@ -79,21 +87,31 @@ public:
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event)
{
m_startPos = mapToScene(event->pos());
m_startPos = event->scenePos();
m_lastPos = m_startPos;
m_qualifier = event->modifiers() & Qt::ControlModifier ? DeleteHandle : None;
m_owner->moveHandle(m_pointIndex, QPointF(0.0, 0.0), Press, m_qualifier);
setFocus();
}
void mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
QPointF delta = mapToScene(event->pos()) - m_startPos;
m_lastPos = event->scenePos();
QPointF delta = m_lastPos - m_startPos;
m_owner->moveHandle(m_pointIndex, delta, Move, m_qualifier);
}
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
QPointF delta = mapToScene(event->pos()) - m_startPos;
m_lastPos = event->scenePos();
QPointF delta = m_lastPos - m_startPos;
m_owner->moveHandle(m_pointIndex, delta, Release, m_qualifier);
clearFocus();
}
void keyPressEvent(QKeyEvent *event)
{
m_owner->keyPressed(m_pointIndex, event, m_lastPos);
}
private:
@@ -122,6 +140,7 @@ private:
QSizeF m_pointSize;
Selection m_selection = NotSelected;
QPointF m_startPos;
QPointF m_lastPos;
PathSelectionItem::HandleQualifier m_qualifier = PathSelectionItem::None;
};
@@ -193,11 +212,23 @@ QList<QPointF> PathSelectionItem::points() const
void PathSelectionItem::setPoints(const QList<QPointF> &points)
{
QMT_CHECK(points.size() >= 2);
prepareGeometryChange();
GraphicsHandleItem *focusEndBItem = 0;
if (!m_handles.isEmpty() && m_focusHandleItem == m_handles.last()) {
focusEndBItem = m_focusHandleItem;
m_handles.removeLast();
}
int pointIndex = 0;
foreach (const QPointF &point, points) {
GraphicsHandleItem *handle;
if (pointIndex >= m_handles.size()) {
if (focusEndBItem && pointIndex == points.size() - 1) {
handle = focusEndBItem;
handle->setPointIndex(pointIndex);
m_handles.insert(pointIndex, handle);
focusEndBItem = 0;
} else if (pointIndex >= m_handles.size()) {
handle = new GraphicsHandleItem(pointIndex, this);
handle->setPointSize(m_pointSize);
m_handles.append(handle);
@@ -207,6 +238,7 @@ void PathSelectionItem::setPoints(const QList<QPointF> &points)
handle->setPos(point);
++pointIndex;
}
QMT_CHECK(!focusEndBItem);
while (m_handles.size() > pointIndex) {
m_handles.last()->scene()->removeItem(m_handles.last());
delete m_handles.last();
@@ -229,7 +261,7 @@ void PathSelectionItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
for (int i = 0; i < m_handles.size() - 1; ++i) {
qreal distance = GeometryUtilities::calcDistancePointToLine(event->pos(), QLineF(m_handles.at(i)->pos(), m_handles.at(i+1)->pos()));
if (distance < MAX_SELECTION_DISTANCE_FROM_PATH) {
m_windable->insertHandle(i + 1, event->scenePos());
m_windable->insertHandle(i + 1, event->scenePos(), RASTER_WIDTH, RASTER_HEIGHT);
event->accept();
return;
}
@@ -239,15 +271,19 @@ void PathSelectionItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
}
}
bool PathSelectionItem::isEndHandle(int pointIndex) const
{
return pointIndex == 0 || pointIndex == m_handles.size() - 1;
}
void PathSelectionItem::update()
{
prepareGeometryChange();
int i = 0;
foreach (GraphicsHandleItem *handle, m_handles) {
handle->setPointSize(m_pointSize);
bool isEndPoint = (i == 0 || i == m_handles.size() - 1);
handle->setSelection(m_isSecondarySelected
? (isEndPoint ? GraphicsHandleItem::NotSelected : GraphicsHandleItem::SecondarySelected)
? (isEndHandle(i) ? GraphicsHandleItem::NotSelected : GraphicsHandleItem::SecondarySelected)
: GraphicsHandleItem::Selected);
++i;
}
@@ -258,12 +294,16 @@ void PathSelectionItem::moveHandle(int pointIndex, const QPointF &deltaMove, Han
switch (handleQualifier) {
case None:
{
if (handleStatus == Press)
m_originalHandlePos = m_windable->handlePos(pointIndex);
if (handleStatus == Press) {
m_focusHandleItem = m_handles.at(pointIndex);
m_originalHandlePos = m_windable->grabHandle(pointIndex);
}
QPointF newPos = m_originalHandlePos + deltaMove;
m_windable->setHandlePos(pointIndex, newPos);
if (handleStatus == Release)
m_windable->alignHandleToRaster(pointIndex, RASTER_WIDTH, RASTER_HEIGHT);
if (handleStatus == Release) {
m_windable->dropHandle(pointIndex, RASTER_WIDTH, RASTER_HEIGHT);
m_focusHandleItem = 0;
}
break;
}
case DeleteHandle:
@@ -273,4 +313,14 @@ void PathSelectionItem::moveHandle(int pointIndex, const QPointF &deltaMove, Han
}
}
void PathSelectionItem::keyPressed(int pointIndex, QKeyEvent *event, const QPointF &pos)
{
if (isEndHandle(pointIndex)) {
if (event->key() == Qt::Key_Shift)
m_windable->insertHandle(pointIndex, pos, RASTER_WIDTH, RASTER_HEIGHT);
else if (event->key() == Qt::Key_Control)
m_windable->deleteHandle(pointIndex);
}
}
} // namespace qmt

View File

@@ -72,14 +72,17 @@ protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
private:
bool isEndHandle(int pointIndex) const;
void update();
void moveHandle(int pointIndex, const QPointF &deltaMove, HandleStatus handleStatus,
HandleQualifier handleQualifier);
void keyPressed(int pointIndex, QKeyEvent *event, const QPointF &pos);
IWindable *m_windable = 0;
QSizeF m_pointSize;
bool m_isSecondarySelected = false;
QList<GraphicsHandleItem *> m_handles;
GraphicsHandleItem *m_focusHandleItem = 0;
QPointF m_originalHandlePos;
};

View File

@@ -109,6 +109,8 @@ void MFlatAssignmentVisitor::visitMRelation(const MRelation *relation)
auto targetRelation = dynamic_cast<MRelation *>(m_target);
QMT_CHECK(targetRelation);
targetRelation->setName(relation->name());
targetRelation->setEndAUid(relation->endAUid());
targetRelation->setEndBUid(relation->endBUid());
}
void MFlatAssignmentVisitor::visitMDependency(const MDependency *dependency)

View File

@@ -44,6 +44,7 @@
#include "qmt/diagram_ui/diagram_mime_types.h"
#include "qmt/model_controller/modelcontroller.h"
#include "qmt/model_controller/mselection.h"
#include "qmt/model_controller/mvoidvisitor.h"
#include "qmt/model/massociation.h"
#include "qmt/model/mcanvasdiagram.h"
#include "qmt/model/mclass.h"
@@ -71,6 +72,35 @@ namespace {
static VoidElementTasks dummyElementTasks;
}
class DiagramSceneController::AcceptRelationVisitor : public MVoidConstVisitor
{
public:
AcceptRelationVisitor(const MRelation *relation)
: m_relation(relation)
{
}
bool isAccepted() const { return m_accepted; }
void visitMObject(const MObject *object) override
{
Q_UNUSED(object);
m_accepted = dynamic_cast<const MDependency *>(m_relation) != 0;
}
void visitMClass(const MClass *klass) override
{
Q_UNUSED(klass);
m_accepted = dynamic_cast<const MDependency *>(m_relation) != 0
|| dynamic_cast<const MInheritance *>(m_relation) != 0
|| dynamic_cast<const MAssociation *>(m_relation) != 0;
}
private:
const MRelation *m_relation = 0;
bool m_accepted = false;
};
DiagramSceneController::DiagramSceneController(QObject *parent)
: QObject(parent),
m_modelController(0),
@@ -225,6 +255,16 @@ void DiagramSceneController::createAssociation(DClass *endAClass, DClass *endBCl
emit newElementCreated(relation, diagram);
}
bool DiagramSceneController::relocateRelationEndA(DRelation *relation, DObject *targetObject)
{
return relocateRelationEnd(relation, targetObject, &MRelation::endAUid, &MRelation::setEndAUid);
}
bool DiagramSceneController::relocateRelationEndB(DRelation *relation, DObject *targetObject)
{
return relocateRelationEnd(relation, targetObject, &MRelation::endBUid, &MRelation::setEndBUid);
}
bool DiagramSceneController::isAddingAllowed(const Uid &modelElementKey, MDiagram *diagram)
{
MElement *modelElement = m_modelController->findElement(modelElementKey);
@@ -673,4 +713,32 @@ DRelation *DiagramSceneController::addRelation(MRelation *modelRelation, const Q
return diagramRelation;
}
bool DiagramSceneController::relocateRelationEnd(DRelation *relation, DObject *targetObject,
Uid (MRelation::*endUid)() const,
void (MRelation::*setEndUid)(const Uid &))
{
QMT_CHECK(relation);
if (targetObject && targetObject->uid() != relation->endAUid()) {
MRelation *modelRelation = m_modelController->findRelation(relation->modelUid());
QMT_CHECK(modelRelation);
MObject *targetMObject = m_modelController->findObject(targetObject->modelUid());
QMT_CHECK(targetMObject);
AcceptRelationVisitor visitor(modelRelation);
targetMObject->accept(&visitor);
if (visitor.isAccepted()) {
MObject *currentTargetMObject = m_modelController->findObject((modelRelation->*endUid)());
QMT_CHECK(currentTargetMObject);
m_modelController->undoController()->beginMergeSequence(tr("Relocate Relation"));
if (currentTargetMObject == modelRelation->owner())
m_modelController->moveRelation(targetMObject, modelRelation);
m_modelController->startUpdateRelation(modelRelation);
(modelRelation->*setEndUid)(targetMObject->uid());
m_modelController->finishUpdateRelation(modelRelation, false);
m_modelController->undoController()->endMergeSequence();
return true;
}
}
return false;
}
} // namespace qmt

View File

@@ -59,6 +59,8 @@ class QMT_EXPORT DiagramSceneController : public QObject
{
Q_OBJECT
class AcceptRelationVisitor;
public:
explicit DiagramSceneController(QObject *parent = 0);
~DiagramSceneController() override;
@@ -85,6 +87,8 @@ public:
const QList<QPointF> &intermediatePoints, MDiagram *diagram);
void createAssociation(DClass *endAClass, DClass *endBClass,
const QList<QPointF> &intermediatePoints, MDiagram *diagram);
bool relocateRelationEndA(DRelation *relation, DObject *targetObject);
bool relocateRelationEndB(DRelation *relation, DObject *targetObject);
bool isAddingAllowed(const Uid &modelElementKey, MDiagram *diagram);
void addExistingModelElement(const Uid &modelElementKey, const QPointF &pos, MDiagram *diagram);
@@ -124,6 +128,8 @@ private:
DObject *addObject(MObject *modelObject, const QPointF &pos, MDiagram *diagram);
DRelation *addRelation(MRelation *modelRelation, const QList<QPointF> &intermediatePoints,
MDiagram *diagram);
bool relocateRelationEnd(DRelation *relation, DObject *targetObject, Uid (MRelation::*endUid)() const,
void (MRelation::*setEndUid)(const Uid &));
ModelController *m_modelController;
DiagramController *m_diagramController;