modeleditor: Intersect relations with shape of item

Change-Id: I40d898715772f74ffa225ac27b91ee7ad4d8fedc
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
Jochen Becher
2019-11-16 17:43:31 +01:00
parent 7dbf2c01c7
commit 23946de457
17 changed files with 331 additions and 38 deletions

View File

@@ -42,6 +42,10 @@
// ArcTo { x: <center_x>; y: <center_y>; radiusX: <radius_x>; radiusY: <radius_y>; start: <start_angle>; span: <span_angle> }
// Close
// }
// Outline {
// // same as in Shape to define the geometrical ouline of the icon
// // if the outline is not defined it defaults to the shape
// }
// }
//
//
@@ -165,6 +169,10 @@ Icon {
Line { x0: 0.0; y0: 0.0; x1: 0.0; y1: 20.0 }
Line { x0: 0.0; y0: 10.0; x1: 4.0; y1: 10.0 }
}
Outline {
Circle { x: 14.0; y: 10.0; radius: 10.0 }
Rect { x: 0; y: 0; width: 14; height: 20 }
}
}
Icon {
@@ -178,6 +186,9 @@ Icon {
Line { x0: 10.0; y0: 0.0; x1: 14.0; y1: -2.0 }
Line { x0: 10.0; y0: 0.0; x1: 14.0; y1: 4.0 }
}
Outline {
Circle { x: 10.0; y: 10.0; radius: 10.0 }
}
}
Icon {
@@ -190,6 +201,10 @@ Icon {
Circle { x: 10.0; y: 10.0; radius: 10.0 }
Line { x0: 0.0; y0: 20.0; x1: 20.0; y1: 20.0 }
}
Outline {
Circle { x: 10.0; y: 10.0; radius: 10.0 }
Rect { x: 0; y: 10; width: 20; height: 10 }
}
}
Association {
@@ -313,6 +328,10 @@ Icon {
Line { x0: 10; y0: 25; x1: 3; y1: 40 }
Line { x0: 10; y0: 25; x1: 17; y1: 40 }
}
Outline {
Circle { x: 10; y: 5; radius: 5 }
Rect { x: 3; y: 5; width: 14; height: 35 }
}
}
Relation {
@@ -522,6 +541,9 @@ Icon {
Line { x0: 0; y0: 0; x1: 10; y1: 10 }
Line { x0: 0; y0: 10; x1: 10; y1: 0 }
}
Outline {
Circle { x: 5; y: 5; radius: 2 }
}
}
Icon {

View File

@@ -55,6 +55,7 @@ static const int KEYWORD_DISPLAY = 11;
static const int KEYWORD_TEXTALIGN = 12;
static const int KEYWORD_BASECOLOR = 13;
static const int KEYWORD_SHAPE = 14;
static const int KEYWORD_OUTLINE = 15;
// Shape items
static const int KEYWORD_CIRCLE = 30;
@@ -245,6 +246,7 @@ void StereotypeDefinitionParser::parse(ITextSource *source)
<< qMakePair(QString("textalignment"), KEYWORD_TEXTALIGN)
<< qMakePair(QString("basecolor"), KEYWORD_BASECOLOR)
<< qMakePair(QString("shape"), KEYWORD_SHAPE)
<< qMakePair(QString("outline"), KEYWORD_OUTLINE)
<< qMakePair(QString("circle"), KEYWORD_CIRCLE)
<< qMakePair(QString("ellipse"), KEYWORD_ELLIPSE)
<< qMakePair(QString("line"), KEYWORD_LINE)
@@ -436,6 +438,9 @@ void StereotypeDefinitionParser::parseIcon()
case KEYWORD_SHAPE:
stereotypeIcon.setIconShape(parseIconShape());
break;
case KEYWORD_OUTLINE:
stereotypeIcon.setOutlineShape(parseIconShape());
break;
case KEYWORD_NAME:
stereotypeIcon.setName(parseStringProperty());
stereotypeIcon.setHasName(true);

View File

@@ -287,8 +287,13 @@ void ClassItem::update()
bool ClassItem::intersectShapeWithLine(const QLineF &line, QPointF *intersectionPoint, QLineF *intersectionLine) const
{
if (m_customIcon) {
QList<QPolygonF> polygons = m_customIcon->outline();
for (int i = 0; i < polygons.size(); ++i)
polygons[i].translate(object()->pos() + object()->rect().topLeft());
return GeometryUtilities::intersect(polygons, line, nullptr, intersectionPoint, intersectionLine);
}
QPolygonF polygon;
// TODO if m_customIcon then use that shape + label's shape as intersection path
QRectF rect = object()->rect();
rect.translate(object()->pos());
polygon << rect.topLeft() << rect.topRight() << rect.bottomRight() << rect.bottomLeft() << rect.topLeft();

View File

@@ -160,13 +160,14 @@ void ComponentItem::update()
bool ComponentItem::intersectShapeWithLine(const QLineF &line, QPointF *intersectionPoint, QLineF *intersectionLine) const
{
QPolygonF polygon;
if (m_customIcon) {
// TODO use customIcon path as shape
QRectF rect = object()->rect();
rect.translate(object()->pos());
polygon << rect.topLeft() << rect.topRight() << rect.bottomRight() << rect.bottomLeft() << rect.topLeft();
} else if (hasPlainShape()) {
QList<QPolygonF> polygons = m_customIcon->outline();
for (int i = 0; i < polygons.size(); ++i)
polygons[i].translate(object()->pos() + object()->rect().topLeft());
return GeometryUtilities::intersect(polygons, line, nullptr, intersectionPoint, intersectionLine);
}
QPolygonF polygon;
if (hasPlainShape()) {
QRectF rect = object()->rect();
rect.translate(object()->pos());
polygon << rect.topLeft() << rect.topRight() << rect.bottomRight() << rect.bottomLeft() << rect.topLeft();

View File

@@ -122,17 +122,17 @@ void DiagramItem::update()
bool DiagramItem::intersectShapeWithLine(const QLineF &line, QPointF *intersectionPoint, QLineF *intersectionLine) const
{
QPolygonF polygon;
if (m_customIcon) {
// TODO use customIcon path as shape
QRectF rect = object()->rect();
rect.translate(object()->pos());
polygon << rect.topLeft() << rect.topRight() << rect.bottomRight() << rect.bottomLeft() << rect.topLeft();
} else {
QRectF rect = object()->rect();
rect.translate(object()->pos());
polygon << rect.topLeft() << rect.topRight() << rect.bottomRight() << rect.bottomLeft() << rect.topLeft();
QList<QPolygonF> polygons = m_customIcon->outline();
for (int i = 0; i < polygons.size(); ++i)
polygons[i].translate(object()->pos() + object()->rect().topLeft());
return GeometryUtilities::intersect(polygons, line, nullptr, intersectionPoint, intersectionLine);
}
QPolygonF polygon;
QRectF rect = object()->rect();
rect.translate(object()->pos());
// TODO use real outline
polygon << rect.topLeft() << rect.topRight() << rect.bottomRight() << rect.bottomLeft() << rect.topLeft();
return GeometryUtilities::intersect(polygon, line, intersectionPoint, intersectionLine);
}

View File

@@ -130,17 +130,16 @@ void ItemItem::update()
bool ItemItem::intersectShapeWithLine(const QLineF &line, QPointF *intersectionPoint, QLineF *intersectionLine) const
{
QPolygonF polygon;
if (m_customIcon) {
// TODO use customIcon path as shape
QRectF rect = object()->rect();
rect.translate(object()->pos());
polygon << rect.topLeft() << rect.topRight() << rect.bottomRight() << rect.bottomLeft() << rect.topLeft();
} else {
QRectF rect = object()->rect();
rect.translate(object()->pos());
polygon << rect.topLeft() << rect.topRight() << rect.bottomRight() << rect.bottomLeft() << rect.topLeft();
QList<QPolygonF> polygons = m_customIcon->outline();
for (int i = 0; i < polygons.size(); ++i)
polygons[i].translate(object()->pos() + object()->rect().topLeft());
return GeometryUtilities::intersect(polygons, line, nullptr, intersectionPoint, intersectionLine);
}
QRectF rect = object()->rect();
rect.translate(object()->pos());
QPolygonF polygon;
polygon << rect.topLeft() << rect.topRight() << rect.bottomRight() << rect.bottomLeft() << rect.topLeft();
return GeometryUtilities::intersect(polygon, line, intersectionPoint, intersectionLine);
}

View File

@@ -141,13 +141,13 @@ void PackageItem::update()
bool PackageItem::intersectShapeWithLine(const QLineF &line, QPointF *intersectionPoint, QLineF *intersectionLine) const
{
QPolygonF polygon;
if (m_customIcon) {
// TODO use customIcon path as shape
QRectF rect = object()->rect();
rect.translate(object()->pos());
polygon << rect.topLeft() << rect.topRight() << rect.bottomRight() << rect.bottomLeft() << rect.topLeft();
} else {
QList<QPolygonF> polygons = m_customIcon->outline();
for (int i = 0; i < polygons.size(); ++i)
polygons[i].translate(object()->pos() + object()->rect().topLeft());
return GeometryUtilities::intersect(polygons, line, nullptr, intersectionPoint, intersectionLine);
}
QPolygonF polygon;
QRectF rect = object()->rect();
rect.translate(object()->pos());
ShapeGeometry shape = calcMinimumGeometry();
@@ -155,7 +155,6 @@ bool PackageItem::intersectShapeWithLine(const QLineF &line, QPointF *intersecti
<< (rect.topLeft() + QPointF(shape.m_minimumTabSize.width(), shape.m_minimumTabSize.height()))
<< rect.topRight() + QPointF(0.0, shape.m_minimumTabSize.height())
<< rect.bottomRight() << rect.bottomLeft() << rect.topLeft();
}
return GeometryUtilities::intersect(polygon, line, intersectionPoint, intersectionLine);
}

View File

@@ -33,6 +33,8 @@
#include <QPainter>
//#define DEBUG_OUTLINE
namespace qmt {
CustomIconItem::CustomIconItem(DiagramSceneModel *diagramSceneModel, QGraphicsItem *parent)
@@ -102,9 +104,30 @@ void CustomIconItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *op
painter->save();
painter->setBrush(m_brush);
painter->setPen(m_pen);
#ifdef DEBUG_OUTLINE
ShapePolygonVisitor visitor(QPointF(0.0, 0.0), QSizeF(m_stereotypeIcon.width(), m_stereotypeIcon.height()), m_baseSize, m_actualSize);
IconShape shape = m_stereotypeIcon.outlineShape();
if (shape.isEmpty())
shape = m_stereotypeIcon.iconShape();
shape.visitShapes(&visitor);
painter->drawPath(visitor.path());
ShapePaintVisitor visitor1(painter, QPointF(0.0, 0.0), QSizeF(m_stereotypeIcon.width(), m_stereotypeIcon.height()), m_baseSize, m_actualSize);
m_stereotypeIcon.iconShape().visitShapes(&visitor1);
#else
ShapePaintVisitor visitor(painter, QPointF(0.0, 0.0), QSizeF(m_stereotypeIcon.width(), m_stereotypeIcon.height()), m_baseSize, m_actualSize);
m_stereotypeIcon.iconShape().visitShapes(&visitor);
#endif
painter->restore();
}
QList<QPolygonF> CustomIconItem::outline() const
{
ShapePolygonVisitor visitor(QPointF(0.0, 0.0), QSizeF(m_stereotypeIcon.width(), m_stereotypeIcon.height()), m_baseSize, m_actualSize);
IconShape shape = m_stereotypeIcon.outlineShape();
if (shape.isEmpty())
shape = m_stereotypeIcon.iconShape();
shape.visitShapes(&visitor);
return visitor.toPolygons();
}
} // namespace qmt

View File

@@ -55,6 +55,8 @@ public:
QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
QList<QPolygonF> outline() const;
private:
DiagramSceneModel *m_diagramSceneModel = nullptr;
QString m_stereotypeIconId;

View File

@@ -88,6 +88,49 @@ bool GeometryUtilities::intersect(const QPolygonF &polygon, const QLineF &line,
return found;
}
bool GeometryUtilities::intersect(const QList<QPolygonF> &polygons, const QLineF &line,
int *intersectionPolygon, QPointF *intersectionPoint,
QLineF *intersectionLine, int nearestPoint)
{
bool found = false;
qreal mindist = 0;
int ipolygon = -1;
QPointF ipoint;
QLineF iline;
for (int p = 0; p < polygons.size(); ++p) {
const QPolygonF polygon = polygons.at(p);
for (int i = 0; i <= polygon.size() - 2; ++i) {
const QLineF polygonLine(polygon.at(i), polygon.at(i + 1));
QPointF point;
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
QLineF::IntersectType intersectionType = polygonLine.intersect(line, &point);
#else
QLineF::IntersectType intersectionType = polygonLine.intersects(line, &point);
#endif
if (intersectionType == QLineF::BoundedIntersection) {
qreal dist = QLineF(point, nearestPoint <= 0 ? line.p1() : line.p2()).length();
if (!found || dist < mindist) {
mindist = dist;
ipolygon = p;
ipoint = point;
iline = polygonLine;
found = true;
}
}
}
}
if (found) {
if (intersectionPolygon)
*intersectionPolygon = ipolygon;
if (intersectionPoint)
*intersectionPoint = ipoint;
if (intersectionLine)
*intersectionLine = iline;
}
return found;
}
namespace {
class Candidate

View File

@@ -54,6 +54,9 @@ public:
static bool intersect(const QPolygonF &polygon, const QLineF &line,
QPointF *intersectionPoint = nullptr, QLineF *intersectionLine = nullptr,
int nearestPoint = 1);
static bool intersect(const QList<QPolygonF> &polygons, const QLineF &line,
int *intersectionPolygon, QPointF *intersectionPoint = nullptr, QLineF *intersectionLine = nullptr,
int nearestPoint = 1);
static bool placeRectAtLine(const QRectF &rect, const QLineF &line, double lineOffset,
double distance, const QLineF &intersectionLine, QPointF *placement,
Side *horizontalAlignedSide);

View File

@@ -107,6 +107,11 @@ IconShape &IconShape::operator=(const IconShape &rhs)
return *this;
}
bool IconShape::isEmpty() const
{
return d->m_shapes.isEmpty();
}
void IconShape::addLine(const ShapePointF &pos1, const ShapePointF &pos2)
{
d->m_shapes.append(new LineShape(pos1, pos2));

View File

@@ -46,6 +46,7 @@ public:
IconShape &operator=(const IconShape &rhs);
bool isEmpty() const;
QSizeF size() const;
void setSize(const QSizeF &size);

View File

@@ -261,4 +261,152 @@ void ShapeSizeVisitor::visitPath(const PathShape *shapePath)
m_boundingRect |= path.boundingRect();
}
ShapePolygonVisitor::ShapePolygonVisitor(const QPointF &scaledOrigin, const QSizeF &originalSize,
const QSizeF &baseSize, const QSizeF &size)
: m_scaledOrigin(scaledOrigin),
m_originalSize(originalSize),
m_baseSize(baseSize),
m_size(size)
{
m_path.setFillRule(Qt::WindingFill);
}
QList<QPolygonF> ShapePolygonVisitor::toPolygons() const
{
return m_path.toSubpathPolygons();
}
void ShapePolygonVisitor::visitLine(const LineShape *shapeLine)
{
QPointF p1 = shapeLine->pos1().mapScaledTo(m_scaledOrigin, m_originalSize, m_baseSize, m_size);
QPointF p2 = shapeLine->pos2().mapScaledTo(m_scaledOrigin, m_originalSize, m_baseSize, m_size);
m_path.moveTo(p1);
m_path.lineTo(p2);
}
void ShapePolygonVisitor::visitRect(const RectShape *shapeRect)
{
m_path.addRect(QRectF(shapeRect->pos().mapScaledTo(m_scaledOrigin, m_originalSize,
m_baseSize, m_size),
shapeRect->size().mapScaledTo(m_scaledOrigin, m_originalSize,
m_baseSize, m_size)));
}
void ShapePolygonVisitor::visitRoundedRect(const RoundedRectShape *shapeRoundedRect)
{
qreal radiusX = shapeRoundedRect->radius().mapScaledTo(0, m_originalSize.width(),
m_baseSize.width(), m_size.width());
qreal radiusY = shapeRoundedRect->radius().mapScaledTo(0, m_originalSize.height(),
m_baseSize.height(), m_size.height());
m_path.addRoundedRect(QRectF(shapeRoundedRect->pos().mapScaledTo(m_scaledOrigin, m_originalSize,
m_baseSize, m_size),
shapeRoundedRect->size().mapScaledTo(m_scaledOrigin, m_originalSize,
m_baseSize, m_size)),
radiusX, radiusY);
}
void ShapePolygonVisitor::visitCircle(const CircleShape *shapeCircle)
{
m_path.addEllipse(shapeCircle->center().mapScaledTo(m_scaledOrigin, m_originalSize,
m_baseSize, m_size),
shapeCircle->radius().mapScaledTo(m_scaledOrigin.x(), m_originalSize.width(),
m_baseSize.width(), m_size.width()),
shapeCircle->radius().mapScaledTo(m_scaledOrigin.y(), m_originalSize.height(),
m_baseSize.height(), m_size.height()));
}
void ShapePolygonVisitor::visitEllipse(const EllipseShape *shapeEllipse)
{
QSizeF radius = shapeEllipse->radius().mapScaledTo(m_scaledOrigin, m_originalSize,
m_baseSize, m_size);
m_path.addEllipse(shapeEllipse->center().mapScaledTo(m_scaledOrigin, m_originalSize,
m_baseSize, m_size),
radius.width(), radius.height());
}
void ShapePolygonVisitor::visitDiamond(const DiamondShape *shapeDiamond)
{
QPainterPath path;
QPointF center = shapeDiamond->center().mapScaledTo(m_scaledOrigin, m_originalSize,
m_baseSize, m_size);
QSizeF size = shapeDiamond->size().mapScaledTo(m_scaledOrigin, m_originalSize,
m_baseSize, m_size);
path.moveTo(center + QPointF(0.0, size.height() / 2.0));
path.lineTo(center + QPointF(-size.width() / 2.0, 0.0));
path.lineTo(center + QPointF(0.0, -size.height() / 2.0));
path.lineTo(center + QPointF(size.width() / 2.0, 0.0));
path.closeSubpath();
m_path.addPath(path);
}
void ShapePolygonVisitor::visitTriangle(const TriangleShape *shapeTriangle)
{
QPainterPath path;
QPointF center = shapeTriangle->center().mapScaledTo(m_scaledOrigin, m_originalSize,
m_baseSize, m_size);
QSizeF size = shapeTriangle->size().mapScaledTo(m_scaledOrigin, m_originalSize,
m_baseSize, m_size);
path.moveTo(center + QPointF(size.width() / 2.0, size.height() / 2.0));
path.lineTo(center + QPointF(-size.width() / 2.0, size.height() / 2.0));
path.lineTo(center + QPointF(0.0, -size.height() / 2.0));
path.closeSubpath();
m_path.addPath(path);
}
void ShapePolygonVisitor::visitArc(const ArcShape *shapeArc)
{
QSizeF radius = shapeArc->radius().mapScaledTo(m_scaledOrigin, m_originalSize,
m_baseSize, m_size);
QRectF rect(shapeArc->center().mapScaledTo(m_scaledOrigin, m_originalSize, m_baseSize, m_size)
- QPointF(radius.width(), radius.height()), radius * 2.0);
m_path.arcMoveTo(rect, shapeArc->startAngle());
m_path.arcTo(rect, shapeArc->startAngle(), shapeArc->spanAngle());
}
void ShapePolygonVisitor::visitPath(const PathShape *shapePath)
{
QPainterPath path;
for (const PathShape::Element &element: shapePath->elements()) {
switch (element.m_elementType) {
case PathShape::TypeNone:
// nothing to do
break;
case PathShape::TypeMoveto:
path.moveTo(element.m_position.mapScaledTo(m_scaledOrigin, m_originalSize,
m_baseSize, m_size));
break;
case PathShape::TypeLineto:
path.lineTo(element.m_position.mapScaledTo(m_scaledOrigin, m_originalSize,
m_baseSize, m_size));
break;
case PathShape::TypeArcmoveto:
{
QSizeF radius = element.m_size.mapScaledTo(m_scaledOrigin, m_originalSize,
m_baseSize, m_size);
path.arcMoveTo(QRectF(element.m_position.mapScaledTo(m_scaledOrigin, m_originalSize,
m_baseSize, m_size)
- QPointF(radius.width(), radius.height()),
radius * 2.0),
element.m_angle1);
break;
}
case PathShape::TypeArcto:
{
QSizeF radius = element.m_size.mapScaledTo(m_scaledOrigin, m_originalSize,
m_baseSize, m_size);
path.arcTo(QRectF(element.m_position.mapScaledTo(m_scaledOrigin, m_originalSize,
m_baseSize, m_size)
- QPointF(radius.width(), radius.height()),
radius * 2.0),
element.m_angle1, element.m_angle2);
break;
}
case PathShape::TypeClose:
path.closeSubpath();
break;
}
}
m_path.addPath(path);
}
} // namespace qmt

View File

@@ -31,6 +31,8 @@
#include <QPainter>
#include <QPointF>
#include <QSizeF>
#include <QPolygonF>
#include <QPainterPath>
namespace qmt {
@@ -84,4 +86,31 @@ private:
QRectF m_boundingRect;
};
class QMT_EXPORT ShapePolygonVisitor : public ShapeConstVisitor
{
public:
ShapePolygonVisitor(const QPointF &scaledOrigin, const QSizeF &originalSize,
const QSizeF &baseSize, const QSizeF &size);
QPainterPath path() const { return m_path; }
QList<QPolygonF> toPolygons() const;
void visitLine(const LineShape *shapeLine) override;
void visitRect(const RectShape *shapeRect) override;
void visitRoundedRect(const RoundedRectShape *shapeRoundedRect) override;
void visitCircle(const CircleShape *shapeCircle) override;
void visitEllipse(const EllipseShape *shapeEllipse) override;
void visitDiamond(const DiamondShape *shapeDiamond) override;
void visitTriangle(const TriangleShape *shapeTriangle) override;
void visitArc(const ArcShape *shapeArc) override;
void visitPath(const PathShape *shapePath) override;
private:
QPointF m_scaledOrigin;
QSizeF m_originalSize;
QSizeF m_baseSize;
QSizeF m_size;
QPainterPath m_path;
};
} // namespace qmt

View File

@@ -109,4 +109,9 @@ void StereotypeIcon::setIconShape(const IconShape &iconShape)
m_iconShape = iconShape;
}
void StereotypeIcon::setOutlineShape(const IconShape &outlineShape)
{
m_outlineShape = outlineShape;
}
} // namespace qmt

View File

@@ -100,6 +100,8 @@ public:
void setBaseColor(const QColor &baseColor);
IconShape iconShape() const { return m_iconShape; }
void setIconShape(const IconShape &iconShape);
IconShape outlineShape() const { return m_outlineShape; }
void setOutlineShape(const IconShape &outlineShape);
private:
QString m_id;
@@ -117,6 +119,7 @@ private:
TextAlignment m_textAlignment = TextalignBelow;
QColor m_baseColor;
IconShape m_iconShape;
IconShape m_outlineShape;
};
} // namespace qmt