diff --git a/src/plugins/qmldesigner/components/formeditor/abstractformeditortool.cpp b/src/plugins/qmldesigner/components/formeditor/abstractformeditortool.cpp index 9d5d144e12c..0ed7abaefe3 100644 --- a/src/plugins/qmldesigner/components/formeditor/abstractformeditortool.cpp +++ b/src/plugins/qmldesigner/components/formeditor/abstractformeditortool.cpp @@ -186,7 +186,7 @@ FormEditorItem *AbstractFormEditorTool::topMovableFormEditorItem(const QList & itemList) +FormEditorItem* AbstractFormEditorTool::nearestFormEditorItem(const QPointF &point, const QList &itemList) { FormEditorItem* nearestItem = nullptr; foreach (QGraphicsItem *item, itemList) { diff --git a/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp b/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp index 13ab98daff8..46ba1839145 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp @@ -1,4 +1,4 @@ -/**************************************************************************** +/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ @@ -35,11 +35,13 @@ #include +#include #include #include #include #include +#include #include #include #include @@ -54,13 +56,12 @@ const int flowBlockSize = 200; const int blockRadius = 18; const int blockAdjust = 40; -const char startNodeIcon[] = "\u0055"; - void drawIcon(QPainter *painter, int x, int y, const QString &iconSymbol, - int fontSize, int iconSize, + int fontSize, + int iconSize, const QColor &penColor) { static QFontDatabase a; @@ -149,7 +150,7 @@ void FormEditorItem::updateGeometry() m_paintedBoundingRect = qmlItemNode().instancePaintedBoundingRect(); m_boundingRect = m_paintedBoundingRect.united(m_selectionBoundingRect); setTransform(qmlItemNode().instanceTransformWithContentTransform()); - //the property for zValue is called z in QGraphicsObject + // the property for zValue is called z in QGraphicsObject if (qmlItemNode().instanceValue("z").isValid() && !qmlItemNode().isRootModelNode()) setZValue(qmlItemNode().instanceValue("z").toDouble()); } @@ -396,7 +397,7 @@ QList FormEditorItem::offspringFormEditorItemsRecursive(const { QList formEditorItemList; - foreach (QGraphicsItem *item, formEditorItem->childItems()) { + for (QGraphicsItem *item : formEditorItem->childItems()) { FormEditorItem *formEditorItem = fromQGraphicsItem(item); if (formEditorItem) { formEditorItemList.append(formEditorItem); @@ -526,7 +527,7 @@ QList FormEditorItem::childFormEditorItems() const { QList formEditorItemList; - foreach (QGraphicsItem *item, childItems()) { + for (QGraphicsItem *item : childItems()) { FormEditorItem *formEditorItem = fromQGraphicsItem(item); if (formEditorItem) formEditorItemList.append(formEditorItem); @@ -564,15 +565,17 @@ void FormEditorFlowItem::setDataModelPosition(const QPointF &position) { qmlItemNode().setFlowItemPosition(position); updateGeometry(); + +/* TODO for (QGraphicsItem *item : scene()->items()) { if (item == this) continue; - auto formEditorItem = qgraphicsitem_cast(item); + auto formEditorItem = qgraphicsitem_cast(item); if (formEditorItem) formEditorItem->updateGeometry(); } - +*/ } void FormEditorFlowItem::setDataModelPositionInBaseState(const QPointF &position) @@ -586,17 +589,15 @@ void FormEditorFlowItem::updateGeometry() const QPointF pos = qmlItemNode().flowPosition(); setTransform(QTransform::fromTranslate(pos.x(), pos.y())); - QmlFlowItemNode flowItem(qmlItemNode()); - + // Call updateGeometry() on all related transitions + QmlFlowTargetNode flowItem(qmlItemNode()); if (flowItem.isValid() && flowItem.flowView().isValid()) { const auto nodes = flowItem.flowView().transitions(); for (const ModelNode &node : nodes) { - FormEditorItem *item = scene()->itemForQmlItemNode(node); - if (item) + if (FormEditorItem *item = scene()->itemForQmlItemNode(node)) item->updateGeometry(); } } - } QPointF FormEditorFlowItem::instancePosition() const @@ -604,6 +605,47 @@ QPointF FormEditorFlowItem::instancePosition() const return qmlItemNode().flowPosition(); } + +void FormEditorFlowActionItem::setDataModelPosition(const QPointF &position) +{ + qmlItemNode().setPosition(position); + updateGeometry(); + +/* TODO + for (QGraphicsItem *item : scene()->items()) { + if (item == this) + continue; + + auto formEditorItem = qgraphicsitem_cast(item); + if (formEditorItem) + formEditorItem->updateGeometry(); + } +*/ +} + +void FormEditorFlowActionItem::setDataModelPositionInBaseState(const QPointF &position) +{ + qmlItemNode().setPostionInBaseState(position); + updateGeometry(); +} + +void FormEditorFlowActionItem::updateGeometry() +{ + FormEditorItem::updateGeometry(); + //const QPointF pos = qmlItemNode().flowPosition(); + //setTransform(QTransform::fromTranslate(pos.x(), pos.y())); + + // Call updateGeometry() on all related transitions + QmlFlowItemNode flowItem = QmlFlowActionAreaNode(qmlItemNode()).flowItemParent(); + if (flowItem.isValid() && flowItem.flowView().isValid()) { + const auto nodes = flowItem.flowView().transitions(); + for (const ModelNode &node : nodes) { + if (FormEditorItem *item = scene()->itemForQmlItemNode(node)) + item->updateGeometry(); + } + } +} + void FormEditorFlowActionItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) { if (!painter->isActive()) @@ -690,105 +732,233 @@ void FormEditorTransitionItem::setDataModelPositionInBaseState(const QPointF &) } +static bool isValid(const QList &list) +{ + for (const auto &item : list) + if (!item.isValid()) + return false; + + return true; +} + +static bool isModelNodeValid(const QList &list) +{ + for (const auto &item : list) + if (!item.modelNode().isValid()) + return false; + + return true; +} + class ResolveConnection { public: - ResolveConnection(const QmlItemNode &node) : - from({}) - ,to(node.modelNode().bindingProperty("to").resolveToModelNode()) - ,areaNode(ModelNode()) + ResolveConnection(const QmlItemNode &node) + : from() + , to() + , areaNode(ModelNode()) { - if (node.modelNode().hasBindingProperty("from")) - from = node.modelNode().bindingProperty("from").resolveToModelNode(); - const QmlFlowItemNode to = node.modelNode().bindingProperty("to").resolveToModelNode(); + if (node.modelNode().hasBindingProperty("from")) { + if (node.modelNode().bindingProperty("from").isList()) + from = Utils::transform(node.modelNode().bindingProperty("from").resolveToModelNodeList(), + [](const ModelNode &node) { + return QmlFlowTargetNode(node); + }); + else + from = QList({node.modelNode().bindingProperty("from").resolveToModelNode()}); + } - if (from.isValid()) { - for (const QmlFlowActionAreaNode &area : from.flowActionAreas()) { - ModelNode target = area.targetTransition(); - if (target == node.modelNode()) { - areaNode = area; - } else { - const ModelNode decisionNode = area.decisionNodeForTransition(node.modelNode()); - if (decisionNode.isValid()) { - from = decisionNode; - areaNode = ModelNode(); - } + if (node.modelNode().hasBindingProperty("to")) { + if (node.modelNode().bindingProperty("to").isList()) + to = Utils::transform(node.modelNode().bindingProperty("to").resolveToModelNodeList(), + [](const ModelNode &node) { + return QmlFlowTargetNode(node); + }); + else + to = QList({node.modelNode().bindingProperty("to").resolveToModelNode()}); + } + + if (from.empty()) { + for (const ModelNode wildcard : QmlFlowViewNode(node.rootModelNode()).wildcards()) { + if (wildcard.bindingProperty("target").resolveToModelNode() == node.modelNode()) { + from.clear(); + from.append(wildcard); + isWildcardLine = true; } } - if (from.modelNode().hasAuxiliaryData("joinConnection")) - joinConnection = from.modelNode().auxiliaryData("joinConnection").toBool(); - } else { - if (from == node.rootModelNode()) { - isStartLine = true; - } else { - for (const ModelNode wildcard : QmlFlowViewNode(node.rootModelNode()).wildcards()) { - if (wildcard.bindingProperty("target").resolveToModelNode() == node.modelNode()) { - from = wildcard; - isWildcardLine = true; + } + + // Only assign area node if there is exactly one from (QmlFlowItemNode) + if (from.size() == 1) { + const QmlFlowTargetNode tmp = from.back(); + const QmlFlowItemNode f(tmp.modelNode()); + + if (f.isValid()) { + for (const QmlFlowActionAreaNode &area : f.flowActionAreas()) { + ModelNode target = area.targetTransition(); + if (target == node.modelNode()) { + areaNode = area; + } else { + const ModelNode decisionNode = area.decisionNodeForTransition(node.modelNode()); + if (decisionNode.isValid()) { + from.clear(); + from.append(decisionNode); + areaNode = ModelNode(); + } } } + if (f.modelNode().hasAuxiliaryData("joinConnection")) + joinConnection = f.modelNode().auxiliaryData("joinConnection").toBool(); + } else { + if (f == node.rootModelNode()) { + isStartLine = true; + } /*else { + for (const ModelNode wildcard : QmlFlowViewNode(node.rootModelNode()).wildcards()) { + if (wildcard.bindingProperty("target").resolveToModelNode() == node.modelNode()) { + from.clear(); + from.append(wildcard); + isWildcardLine = true; + } + } + }*/ } } } bool joinConnection = false; - bool isStartLine = false; - bool isWildcardLine = false; - QmlFlowItemNode from; - QmlFlowItemNode to; + QList from; + QList to; QmlFlowActionAreaNode areaNode; }; -void FormEditorTransitionItem::updateGeometry() +enum ConnectionType { - FormEditorItem::updateGeometry(); + Default = 0, + Bezier +}; - ResolveConnection resolved(qmlItemNode()); +class ConnectionConfiguration +{ +public: + ConnectionConfiguration(const QmlItemNode &node, + const ResolveConnection &resolveConnection, + const qreal scaleFactor, + bool hitTest = false) + : width(2) + , adjustedWidth(width / scaleFactor) + , color(QColor("#e71919")) + , lineBrush(QBrush(color)) + , penStyle(Qt::SolidLine) + , dashPattern() + , drawStart(true) + , drawEnd(true) + , joinEnd(false) + , outOffset(0) + , inOffset(0) + , breakOffset(50) + , radius(8) + , bezier(50) + , type(ConnectionType::Default) + , label() + , fontSize(16 / scaleFactor) + , labelOffset(14 / scaleFactor) + , labelPosition(50.0) + , labelFlags(Qt::AlignHCenter | Qt::AlignVCenter | Qt::TextDontClip) + , labelFlipSide(false) + , hitTesting(hitTest) + { + // width + if (node.modelNode().hasAuxiliaryData("width")) + width = node.modelNode().auxiliaryData("width").toInt(); + // adjusted width + if (node.modelNode().isSelected()) + width += 2; + if (hitTest) + width = qMax(width * 8, 20.0); + // color + if (resolveConnection.isStartLine) + color = QColor("blue"); + if (resolveConnection.isWildcardLine) + color = QColor("green"); + if (node.rootModelNode().hasAuxiliaryData("transitionColor")) + color = node.rootModelNode().auxiliaryData("transitionColor").value(); + if (node.modelNode().hasAuxiliaryData("color")) + color = node.modelNode().auxiliaryData("color").value(); + // linbe brush + lineBrush = QBrush(color); - QPointF fromP = QmlItemNode(resolved.from).flowPosition(); - QRectF sizeTo = resolved.to.instanceBoundingRect(); + // pen style - QPointF toP = QmlItemNode(resolved.to).flowPosition(); + // dash + if (node.modelNode().hasAuxiliaryData("dash") && node.modelNode().auxiliaryData("dash").toBool()) + penStyle = Qt::DashLine; + // in/out offset + if (node.modelNode().hasAuxiliaryData("outOffset")) + outOffset = node.modelNode().auxiliaryData("outOffset").toInt(); + if (node.modelNode().hasAuxiliaryData("inOffset")) + inOffset = node.modelNode().auxiliaryData("inOffset").toInt(); + // break offset + if (node.modelNode().hasAuxiliaryData("breakPoint")) + breakOffset = node.modelNode().auxiliaryData("breakPoint").toInt(); + // radius + if (node.rootModelNode().hasAuxiliaryData("transitionRadius")) + radius = node.rootModelNode().auxiliaryData("transitionRadius").toInt(); + if (node.modelNode().hasAuxiliaryData("radius")) + radius = node.modelNode().auxiliaryData("radius").toInt(); + // bezier + if (node.rootModelNode().hasAuxiliaryData("transitionBezier")) + bezier = node.rootModelNode().auxiliaryData("transitionBezier").toInt(); + if (node.modelNode().hasAuxiliaryData("bezier")) + bezier = node.modelNode().auxiliaryData("bezier").toInt(); + // type + if (node.rootModelNode().hasAuxiliaryData("transitionType")) + type = static_cast(node.rootModelNode().auxiliaryData("transitionType").toInt()); + if (node.modelNode().hasAuxiliaryData("type")) + type = static_cast(node.modelNode().auxiliaryData("type").toInt()); + // label + if (node.modelNode().hasBindingProperty("condition")) + label = node.modelNode().bindingProperty("condition").expression(); + if (node.modelNode().hasVariantProperty("question")) + label = node.modelNode().variantProperty("question").value().toString(); + // font size - if (QmlItemNode(resolved.to).isFlowDecision()) - sizeTo = QRectF(0, 0, flowBlockSize * 2, flowBlockSize * 2); + // label offset - qreal x1 = fromP.x(); - qreal x2 = toP.x(); - - if (x2 < x1) { - qreal s = x1; - x1 = x2; - x2 = s; + // label position + if (node.modelNode().hasAuxiliaryData("labelPosition")) + labelPosition = node.modelNode().auxiliaryData("labelPosition").toReal(); + // label flip side + if (node.modelNode().hasAuxiliaryData("labelFlipSide")) + labelFlipSide = node.modelNode().auxiliaryData("labelFlipSide").toBool(); } - qreal y1 = fromP.y(); - qreal y2 = toP.y(); - - if (y2 < y1) { - qreal s = y1; - y1 = y2; - y2 = s; - } - - x2 += sizeTo.width(); - y2 += sizeTo.height(); - - setX(x1); - setY(y1); - m_selectionBoundingRect = QRectF(0,0,x2-x1,y2-y1); - m_paintedBoundingRect = m_selectionBoundingRect; - m_boundingRect = m_selectionBoundingRect; - setZValue(10); -} - -QPointF FormEditorTransitionItem::instancePosition() const -{ - return qmlItemNode().flowPosition(); -} + qreal width; + qreal adjustedWidth; + QColor color; // TODO private/setter + QBrush lineBrush; + Qt::PenStyle penStyle; + QVector dashPattern; + bool drawStart; + bool drawEnd; + // Dirty hack for joining/merging arrow heads on many-to-one transitions + bool joinEnd; + int outOffset; + int inOffset; + int breakOffset; + int radius; + int bezier; + ConnectionType type; + QString label; + int fontSize; + qreal labelOffset; + qreal labelPosition; + int labelFlags; + bool labelFlipSide; + bool hitTesting; +}; static bool verticalOverlap(const QRectF &from, const QRectF &to) { @@ -801,7 +971,6 @@ static bool verticalOverlap(const QRectF &from, const QRectF &to) return false; } - static bool horizontalOverlap(const QRectF &from, const QRectF &to) { if (from.left() < to.right() && from.right() > to.left()) @@ -813,63 +982,6 @@ static bool horizontalOverlap(const QRectF &from, const QRectF &to) return false; } -static void drawLabel(QPainter *painter, - const QPainterPath &path, - const QString &text, - const qreal percent, - const qreal offset, - bool flipSide) -{ - if (text.isEmpty()) - return; - - const int flags = Qt::AlignHCenter | Qt::AlignVCenter | Qt::TextDontClip; - - QPointF pos = path.pointAtPercent(percent); - qreal angle = path.angleAtPercent(percent); - - QLineF tmp(pos, QPointF(10, 10)); - tmp.setLength(offset); - tmp.setAngle(angle + (flipSide ? 270 : 90)); - - QRectF textRect(0, 0, 100, 50); - textRect.moveCenter(tmp.p2()); - - auto normalizeAngle = [](int angle) { - int newAngle = angle; - while (newAngle <= -90) newAngle += 180; - while (newAngle > 90) newAngle -= 180; - return newAngle; - }; - - painter->save(); - painter->translate(textRect.center()); - painter->rotate(-normalizeAngle(angle)); - painter->translate(-textRect.center()); - painter->drawText(textRect, flags, text); - painter->restore(); -} - -static void drawArrow(QPainter *painter, - const QPointF &point, - const qreal &angle, - int arrowLength, - int arrowWidth) -{ - const QPointF peakP(0, 0); - const QPointF leftP(-arrowLength, -arrowWidth * 0.5); - const QPointF rightP(-arrowLength, arrowWidth * 0.5); - - painter->save(); - - painter->translate(point); - painter->rotate(-angle); - painter->drawLine(leftP, peakP); - painter->drawLine(rightP, peakP); - - painter->restore(); -} - static QPainterPath roundedCorner(const QPointF &s, const QPointF &m, const QPointF &e, @@ -971,8 +1083,8 @@ static QPainterPath quadBezier(const QPointF &s, int bezier, int breakOffset) { - QLineF se(s, e); - QPointF breakPoint = se.pointAt(breakOffset / 100.0); + const QLineF se(s, e); + const QPointF breakPoint = se.pointAt(breakOffset / 100.0); QLineF breakLine; if (counterClockWise({s, c, e}) == 1) @@ -1005,48 +1117,33 @@ static QPainterPath cubicBezier(const QPointF &s, return path; } - static QPainterPath lShapedConnection(const QPointF &start, + const QPointF &mid, const QPointF &end, - Qt::Orientation orientation, - const ConnectionStyle &style) + const ConnectionConfiguration &config) { - const QPointF mid = (orientation == Qt::Horizontal) ? QPointF(end.x(), start.y()) - : QPointF(start.x(), end.y()); - - if (style.type == ConnectionType::Default) { - if (style.radius == 0) { + if (config.type == ConnectionType::Default) { + if (config.radius == 0) { QPainterPath path(start); path.lineTo(mid); path.lineTo(end); return path; } else { - return roundedCorner(start, mid, end, style.radius); + return roundedCorner(start, mid, end, config.radius); } } else { - return quadBezier(start, mid, end, style.bezier, style.breakOffset); + return quadBezier(start, mid, end, config.bezier, config.breakOffset); } } static QPainterPath sShapedConnection(const QPointF &start, + const QPointF &mid1, + const QPointF &mid2, const QPointF &end, - Qt::Orientation orientation, - const ConnectionStyle &style) + const ConnectionConfiguration &config) { - const qreal middleFactor = style.breakOffset / 100.0; - QPointF mid1; - QPointF mid2; - - if (orientation == Qt::Horizontal) { - mid1 = QPointF(start.x() * middleFactor + end.x() * (1 - middleFactor), start.y()); - mid2 = QPointF(mid1.x(), end.y()); - } else { - mid1 = QPointF(start.x(), start.y() * middleFactor + end.y() * (1 - middleFactor)); - mid2 = QPointF(end.x(), mid1.y()); - } - - if (style.type == ConnectionType::Default) { - if (style.radius == 0) { + if (config.type == ConnectionType::Default) { + if (config.radius == 0) { QPainterPath path(start); path.lineTo(mid1); path.lineTo(mid2); @@ -1054,127 +1151,354 @@ static QPainterPath sShapedConnection(const QPointF &start, return path; } else { const QLineF breakLine(mid1, mid2); - QPainterPath path1 = roundedCorner(start, mid1, breakLine.center(), style.radius); - QPainterPath path2 = roundedCorner(breakLine.center(), mid2, end, style.radius); + QPainterPath path1 = roundedCorner(start, mid1, breakLine.center(), config.radius); + QPainterPath path2 = roundedCorner(breakLine.center(), mid2, end, config.radius); return path1 + path2; } } else { - return cubicBezier(start, mid1, mid2, end, style.bezier); + return cubicBezier(start, mid1, mid2, end, config.bezier); } } -static void paintConnection(QPainter *painter, - const QRectF &from, - const QRectF &to, - const ConnectionStyle &style, - const QString &label) +class Connection { +public: + Connection(const ResolveConnection &resolveConnection, + const QPointF &position, + const QmlFlowTargetNode &from, + const QmlFlowTargetNode &to, + const ConnectionConfiguration &connectionConfig) + : config(connectionConfig) + { + fromRect = QmlItemNode(from).instanceBoundingRect(); + if (QmlItemNode(from).isFlowDecision()) + fromRect = QRectF(0, 0, flowBlockSize, flowBlockSize); + + if (QmlItemNode(from).isFlowWildcard()) + fromRect = QRectF(0, 0, flowBlockSize, flowBlockSize); + + fromRect.translate(QmlItemNode(from).flowPosition()); + + if (!resolveConnection.joinConnection && resolveConnection.areaNode.isValid()) { + fromRect = QmlItemNode(resolveConnection.areaNode).instanceBoundingRect(); + fromRect.translate(QmlItemNode(from).flowPosition()); + fromRect.translate(resolveConnection.areaNode.instancePosition()); + } + + toRect = QmlItemNode(to).instanceBoundingRect(); + if (QmlItemNode(to).isFlowDecision()) + toRect = QRectF(0, 0, flowBlockSize,flowBlockSize); + + toRect.translate(QmlItemNode(to).flowPosition()); + + if (resolveConnection.isStartLine) { + fromRect = QRectF(0, 0, 96, 96); + fromRect.translate(QmlItemNode(to).flowPosition() + QPoint(-180, toRect.height() / 2 - 96 / 2)); + fromRect.translate(0, config.outOffset); + } + + fromRect.translate(-position); + toRect.translate(-position); + + bool horizontalFirst = true; + extraLine = false; + + if (horizontalFirst) { + if (toRect.center().x() > fromRect.left() && toRect.center().x() < fromRect.right()) { + horizontalFirst = false; + extraLine = true; + } else if (verticalOverlap(fromRect, toRect)) { + horizontalFirst = true; + extraLine = true; + } + } else { + if (toRect.center().y() > fromRect.top() && toRect.center().y() < fromRect.bottom()) { + horizontalFirst = true; + extraLine = true; + } else if (horizontalOverlap(fromRect, toRect)) { + horizontalFirst = false; + extraLine = true; + } + } + + const bool boolExitRight = fromRect.right() < toRect.center().x(); + const bool boolExitBottom = fromRect.bottom() < toRect.center().y(); + + const int padding = 2 * config.width + 2 * config.adjustedWidth; + + if (horizontalFirst) { + const qreal startX = boolExitRight ? fromRect.right() + padding : fromRect.x() - padding; + const qreal startY = fromRect.center().y() + config.outOffset; + start = QPointF(startX, startY); + + if (!extraLine) { + const qreal endY = (fromRect.bottom() > toRect.y()) ? toRect.bottom() + padding : toRect.top() - padding; + end = QPointF(toRect.center().x() + config.inOffset, endY); + mid1 = mid2 = QPointF(end.x(), start.y()); + path = lShapedConnection(start, mid1, end, config); + } else { + const qreal endX = (fromRect.right() > toRect.x()) ? toRect.right() + padding : toRect.left() - padding; + end = QPointF(endX, toRect.center().y() + config.inOffset); + const qreal middleFactor = config.breakOffset / 100.0; + mid1 = QPointF(start.x() * middleFactor + end.x() * (1 - middleFactor), start.y()); + mid2 = QPointF(mid1.x(), end.y()); + path = sShapedConnection(start, mid1, mid2, end, config); + } + } else { + const qreal startX = fromRect.center().x() + config.outOffset; + const qreal startY = boolExitBottom ? fromRect.bottom() + padding : fromRect.top() - padding; + start = QPointF(startX, startY); + + if (!extraLine) { + const qreal endX = (fromRect.right() > toRect.x()) ? toRect.right() + padding : toRect.left() - padding; + end = QPointF(endX, toRect.center().y() + config.inOffset); + mid1 = mid2 = QPointF(start.x(), end.y()); + path = lShapedConnection(start, mid1, end, config); + } else { + const qreal endY = (fromRect.bottom() > toRect.y()) ? toRect.bottom() + padding : toRect.top() - padding; + end = QPointF(toRect.center().x() + config.inOffset, endY); + const qreal middleFactor = config.breakOffset / 100.0; + mid1 = QPointF(start.x(), start.y() * middleFactor + end.y() * (1 - middleFactor)); + mid2 = QPointF(end.x(), mid1.y()); + path = sShapedConnection(start, mid1, mid2, end, config); + } + } + } + + QRectF fromRect; + QRectF toRect; + + QPointF start; + QPointF end; + + QPointF mid1; + QPointF mid2; + + bool extraLine; + + ConnectionConfiguration config; + QPainterPath path; +}; + +static int normalizeAngle(int angle) +{ + int newAngle = angle; + while (newAngle <= -90) newAngle += 180; + while (newAngle > 90) newAngle -= 180; + return newAngle; +} + +void FormEditorTransitionItem::updateGeometry() +{ + FormEditorItem::updateGeometry(); + + ResolveConnection resolved(qmlItemNode()); + + if (!isValid(resolved.from) || !isValid(resolved.to)) + return; + + QPointF min(std::numeric_limits::max(), std::numeric_limits::max()); + QPointF max(std::numeric_limits::min(), std::numeric_limits::min()); + + auto minMaxHelper = [&](const QList &items) { + QRectF boundingRect; + for (const auto &i : items) { + const QPointF p = QmlItemNode(i).flowPosition(); + + if (p.x() < min.x()) + min.setX(p.x()); + + if (p.y() < min.y()) + min.setY(p.y()); + + if (p.x() > max.x()) + max.setX(p.x()); + + if (p.y() > max.y()) + max.setY(p.y()); + + const QRectF tmp(p, i.instanceSize()); + boundingRect = boundingRect.united(tmp); + } + return boundingRect; + }; + + const QRectF fromBoundingRect = minMaxHelper(resolved.from); + const QRectF toBoundingRect = minMaxHelper(resolved.to); + + QRectF overallBoundingRect(min, max); + overallBoundingRect = overallBoundingRect.united(fromBoundingRect); + overallBoundingRect = overallBoundingRect.united(toBoundingRect); + + setPos(overallBoundingRect.topLeft()); + + // Needed due to the upcoming rects are relative to the set position. If this one is not + // translate to the newly set position, then th resulting bounding box would be to big. + overallBoundingRect.translate(-pos()); + + ConnectionConfiguration config(qmlItemNode(), resolved, viewportTransform().m11()); + + // Local painter is used to get the labels bounding rect by using drawText() + QPixmap pixmap(640, 480); + QPainter localPainter(&pixmap); + QFont font = localPainter.font(); + font.setPixelSize(config.fontSize); + localPainter.setFont(font); + + for (const auto &from : resolved.from) { + for (const auto &to : resolved.to) { + Connection connection(resolved, pos(), from, to, config); + + // Just add the configured transition width to the bounding box to make sure the BB is not cutting + // off half of the transition resulting in a bad selection experience. + QRectF pathBoundingRect = connection.path.boundingRect() + QMarginsF(config.width, config.width, config.width, config.width); + + overallBoundingRect = overallBoundingRect.united(connection.fromRect); + overallBoundingRect = overallBoundingRect.united(connection.toRect); + overallBoundingRect = overallBoundingRect.united(pathBoundingRect); + + // Calculate bounding rect for label + // TODO The calculation should be put into a separate function to avoid code duplication as this + // can also be found in drawLabel() + if (!connection.config.label.isEmpty()) { + const qreal percent = connection.config.labelPosition / 100.0; + const QPointF pos = connection.path.pointAtPercent(percent); + const qreal angle = connection.path.angleAtPercent(percent); + + QLineF tmp(pos, QPointF(10, 10)); + tmp.setLength(connection.config.labelOffset); + tmp.setAngle(angle + (connection.config.labelFlipSide ? 270 : 90)); + + QRectF textRect(0, 0, 100, 50); + textRect.moveCenter(tmp.p2()); + + QRectF labelRect; + + QTransform transform; + transform.translate(textRect.center().x(), textRect.center().y()); + transform.rotate(-normalizeAngle(angle)); + transform.translate(-textRect.center().x(), -textRect.center().y()); + + localPainter.setTransform(transform); + localPainter.drawText(textRect, + connection.config.labelFlags, + connection.config.label, + &labelRect); + QRectF labelBoundingBox = transform.mapRect(labelRect); + overallBoundingRect = overallBoundingRect.united(labelBoundingBox); + } + } + } + + m_selectionBoundingRect = overallBoundingRect; + m_paintedBoundingRect = m_selectionBoundingRect; + m_boundingRect = m_selectionBoundingRect; + setZValue(10); +} + +QPointF FormEditorTransitionItem::instancePosition() const +{ + return qmlItemNode().flowPosition(); +} + +static void drawLabel(QPainter *painter, const Connection &connection) +{ + if (connection.config.label.isEmpty()) + return; + + const qreal percent = connection.config.labelPosition / 100.0; + const QPointF pos = connection.path.pointAtPercent(percent); + const qreal angle = connection.path.angleAtPercent(percent); + + QLineF tmp(pos, QPointF(10, 10)); + tmp.setLength(connection.config.labelOffset); + tmp.setAngle(angle + (connection.config.labelFlipSide ? 270 : 90)); + + QRectF textRect(0, 0, 100, 50); + textRect.moveCenter(tmp.p2()); + + painter->save(); + painter->translate(textRect.center()); + painter->rotate(-normalizeAngle(angle)); + painter->translate(-textRect.center()); + painter->drawText(textRect, connection.config.labelFlags, connection.config.label); + painter->restore(); +} + +static void drawArrow(QPainter *painter, + const QPointF &point, + const qreal &angle, + int arrowLength, + int arrowWidth) +{ + const QPointF peakP(0, 0); + const QPointF leftP(-arrowLength, -arrowWidth * 0.5); + const QPointF rightP(-arrowLength, arrowWidth * 0.5); + + painter->save(); + + painter->translate(point); + painter->rotate(-angle); + painter->drawLine(leftP, peakP); + painter->drawLine(rightP, peakP); + + painter->restore(); +} + +static void paintConnection(QPainter *painter, const Connection &connection) +{ + const int arrowLength = 4 * connection.config.adjustedWidth; + const int arrowWidth = 8 * connection.config.adjustedWidth; + painter->save(); painter->setRenderHint(QPainter::Antialiasing); + // Draw path/connection line QPen pen; pen.setCosmetic(true); pen.setJoinStyle(Qt::MiterJoin); pen.setCapStyle(Qt::RoundCap); - pen.setColor(style.color); + pen.setBrush(connection.config.lineBrush); - if (style.dash) - pen.setStyle(Qt::DashLine); - else - pen.setStyle(Qt::SolidLine); - pen.setWidthF(style.width); + if (connection.config.dashPattern.size()) { + pen.setStyle(Qt::CustomDashLine); + pen.setDashPattern(connection.config.dashPattern); + } else { + pen.setStyle(connection.config.penStyle); + } + + pen.setWidthF(connection.config.width); painter->setPen(pen); - //const bool forceVertical = false; - //const bool forceHorizontal = false; + painter->drawPath(connection.path); - const int padding = 2 * style.width + 2 * style.adjustedWidth; - - const int arrowLength = 4 * style.adjustedWidth; - const int arrowWidth = 8 * style.adjustedWidth; - - const bool boolExitRight = from.right() < to.center().x(); - const bool boolExitBottom = from.bottom() < to.center().y(); - - bool horizontalFirst = true; - - /* - if (verticalOverlap(from, to) && !horizontalOverlap(from, to)) - horizontalFirst = false; - */ - - bool extraLine = false; - - if (horizontalFirst) { - if (to.center().x() > from.left() && to.center().x() < from.right()) { - horizontalFirst = false; - extraLine = true; - } else if (verticalOverlap(from, to)) { - horizontalFirst = true; - extraLine = true; - } - - } else { - if (to.center().y() > from.top() && to.center().y() < from.bottom()) { - horizontalFirst = true; - extraLine = true; - } else if (horizontalOverlap(from, to)) { - horizontalFirst = false; - extraLine = true; - } - } - - QPointF startP; - QPointF endP; - QPainterPath path; - - if (horizontalFirst) { - const qreal startX = boolExitRight ? from.right() + padding : from.x() - padding; - const qreal startY = from.center().y() + style.outOffset; - startP = QPointF(startX, startY); - - if (!extraLine) { - const qreal endY = (from.bottom() > to.y()) ? to.bottom() + padding : to.top() - padding; - endP = QPointF(to.center().x() + style.inOffset, endY); - path = lShapedConnection(startP, endP, Qt::Horizontal, style); - } else { - const qreal endX = (from.right() > to.x()) ? to.right() + padding : to.left() - padding; - endP = QPointF(endX, to.center().y() + style.inOffset); - path = sShapedConnection(startP, endP, Qt::Horizontal, style); - } - } else { - const qreal startX = from.center().x() + style.outOffset; - const qreal startY = boolExitBottom ? from.bottom() + padding : from.top() - padding; - startP = QPointF(startX, startY); - - if (!extraLine) { - const qreal endX = (from.right() > to.x()) ? to.right() + padding : to.left() - padding; - endP = QPointF(endX, to.center().y() + style.inOffset); - path = lShapedConnection(startP, endP, Qt::Vertical, style); - } else { - const qreal endY = (from.bottom() > to.y()) ? to.bottom() + padding : to.top() - padding; - endP = QPointF(to.center().x() + style.inOffset, endY); - path = sShapedConnection(startP, endP, Qt::Vertical, style); - } - } - - painter->drawPath(path); - - pen.setWidthF(style.width); + pen.setWidthF(connection.config.width); pen.setStyle(Qt::SolidLine); + pen.setColor(connection.config.color); painter->setPen(pen); - qreal anglePercent = 1.0; + // Draw arrow + qreal angle = QLineF(connection.mid2, connection.end).angle(); - if (extraLine && style.bezier < 80) - anglePercent = 1.0 - qMin(1.0, (80 - style.bezier) / 10.0) * 0.05; + if (!connection.config.joinEnd) { + qreal anglePercent = 1.0; + if (connection.extraLine && connection.config.bezier < 80) { + anglePercent = 1.0 - qMin(1.0, (80 - connection.config.bezier) / 10.0) * 0.05; + angle = connection.path.angleAtPercent(anglePercent); + } + } - drawArrow(painter, endP, path.angleAtPercent(anglePercent), arrowLength, arrowWidth); + if (connection.config.drawEnd) + drawArrow(painter, connection.end, angle, arrowLength, arrowWidth); - painter->setBrush(Qt::white); - painter->drawEllipse(startP, arrowLength / 3, arrowLength / 3); + // Draw start ellipse + if (connection.config.drawStart) { + painter->setBrush(Qt::white); + painter->drawEllipse(connection.start, arrowLength / 3, arrowLength / 3); + } - drawLabel(painter, path, label, style.labelPosition / 100.0, style.labelOffset, style.labelFlipSide); + // Draw label + drawLabel(painter, connection); painter->restore(); } @@ -1191,167 +1515,105 @@ void FormEditorTransitionItem::paint(QPainter *painter, const QStyleOptionGraphi return; painter->save(); - painter->setRenderHint(QPainter::Antialiasing); ResolveConnection resolved(qmlItemNode()); - if (!resolved.from.modelNode().isValid()) + if (!isModelNodeValid(resolved.from)) return; - QRectF fromRect = QmlItemNode(resolved.from).instanceBoundingRect(); - if (QmlItemNode(resolved.from).isFlowDecision()) - fromRect = QRectF(0, 0, flowBlockSize, flowBlockSize); - - if (QmlItemNode(resolved.from).isFlowWildcard()) - fromRect = QRectF(0, 0, flowBlockSize, flowBlockSize); - fromRect.translate(QmlItemNode(resolved.from).flowPosition()); - - if (resolved.isStartLine) { - fromRect = QRectF(0,0,100,100); - fromRect.translate(QmlItemNode(resolved.to).flowPosition()- QPoint(200, 0)); - } - - if (!resolved.joinConnection && resolved.areaNode.isValid()) { - fromRect = QmlItemNode(resolved.areaNode).instanceBoundingRect(); - fromRect.translate(QmlItemNode(resolved.from).flowPosition()); - fromRect.translate(resolved.areaNode.instancePosition()); - } - - QRectF toRect = QmlItemNode(resolved.to).instanceBoundingRect(); - if (QmlItemNode(resolved.to).isFlowDecision()) - toRect = QRectF(0, 0, flowBlockSize,flowBlockSize); - - toRect.translate(QmlItemNode(resolved.to).flowPosition()); - - if (resolved.isStartLine) { - fromRect = QRectF(0, 0, 96, 96); - fromRect.translate(QmlItemNode(resolved.to).flowPosition() + QPoint(-180, toRect.height() / 2 - 96 / 2)); - } - - toRect.translate(-pos()); - fromRect.translate(-pos()); - - ConnectionStyle style; - - style.width = 2; - - const qreal scaleFactor = viewportTransform().m11(); - - if (qmlItemNode().modelNode().hasAuxiliaryData("width")) - style.width = qmlItemNode().modelNode().auxiliaryData("width").toInt(); - - style.adjustedWidth = style.width / scaleFactor; - - if (qmlItemNode().modelNode().isSelected()) - style.width += 2; - if (m_hitTest) - style.width *= 8; - - style.color = "#e71919"; - - if (resolved.isStartLine) - style.color = "blue"; - - if (resolved.isWildcardLine) - style.color = "green"; - - if (qmlItemNode().rootModelNode().hasAuxiliaryData("transitionColor")) - style.color = qmlItemNode().rootModelNode().auxiliaryData("transitionColor").value(); - - if (qmlItemNode().modelNode().hasAuxiliaryData("color")) - style.color = qmlItemNode().modelNode().auxiliaryData("color").value(); - - style.dash = false; - - if (qmlItemNode().modelNode().hasAuxiliaryData("dash")) - style.dash = qmlItemNode().modelNode().auxiliaryData("dash").toBool(); - - style.outOffset = 0; - style.inOffset = 0; - - if (qmlItemNode().modelNode().hasAuxiliaryData("outOffset")) - style.outOffset = qmlItemNode().modelNode().auxiliaryData("outOffset").toInt(); - - if (qmlItemNode().modelNode().hasAuxiliaryData("inOffset")) - style.inOffset = qmlItemNode().modelNode().auxiliaryData("inOffset").toInt(); - - style.breakOffset = 50; - - if (qmlItemNode().modelNode().hasAuxiliaryData("breakPoint")) - style.breakOffset = qmlItemNode().modelNode().auxiliaryData("breakPoint").toInt(); - - style.radius = 8; - - if (qmlItemNode().rootModelNode().hasAuxiliaryData("transitionRadius")) - style.radius = qmlItemNode().rootModelNode().auxiliaryData("transitionRadius").toInt(); - - if (qmlItemNode().modelNode().hasAuxiliaryData("radius")) - style.radius = qmlItemNode().modelNode().auxiliaryData("radius").toInt(); - - style.bezier = 50; - - if (qmlItemNode().rootModelNode().hasAuxiliaryData("transitionBezier")) - style.bezier = qmlItemNode().rootModelNode().auxiliaryData("transitionBezier").toInt(); - - if (qmlItemNode().modelNode().hasAuxiliaryData("bezier")) - style.bezier = qmlItemNode().modelNode().auxiliaryData("bezier").toInt(); - - style.type = ConnectionType::Default; - - if (qmlItemNode().rootModelNode().hasAuxiliaryData("transitionType")) - style.type = static_cast(qmlItemNode().rootModelNode().auxiliaryData("transitionType").toInt()); - - if (qmlItemNode().modelNode().hasAuxiliaryData("type")) - style.type = static_cast(qmlItemNode().modelNode().auxiliaryData("type").toInt()); - + ConnectionConfiguration config(qmlItemNode(), resolved, viewportTransform().m11(), m_hitTest); QFont font = painter->font(); - font.setPixelSize(16 / scaleFactor); + font.setPixelSize(config.fontSize); painter->setFont(font); - QString label; +/* + // In case of one-to-many many-to-one connection change the style + if ((resolved.from.size() > 1 || resolved.to.size() > 1) && !qmlItemNode().modelNode().isSelected()) { + config.penStyle = Qt::DotLine; + config.width = 1; + } +*/ - if (qmlItemNode().modelNode().hasBindingProperty("condition")) - label = qmlItemNode().modelNode().bindingProperty("condition").expression(); + for (const auto &f : resolved.from) { + for (const auto &t : resolved.to) { + Connection connection(resolved, pos(), f, t, config); + if (!config.hitTesting) { + // The following if statement is a special treatment for n-to-n, 1-to-n and n-to-1 + // transitions. This block is setting up the connection configuration for drawing. + QPointF start = connection.path.pointAtPercent(0.0); + QPointF end = connection.path.pointAtPercent(1.0); - if (qmlItemNode().modelNode().hasVariantProperty("question")) - label = qmlItemNode().modelNode().variantProperty("question").value().toString(); + // many-to-many + if ((resolved.from.size() > 1 && resolved.to.size() > 1)) { + // TODO + } + // many-to-one + else if (resolved.from.size() > 1 && resolved.to.size() == 1) { + connection.config.joinEnd = true; + //connection.config.type = ConnectionType::Bezier; - style.labelOffset = 14 / scaleFactor; + if (qmlItemNode().modelNode().isSelected()) { + connection.config.dashPattern << 2 << 3; + } else { + QLinearGradient gradient(start, end); + QColor color = config.color; + color.setAlphaF(0); + gradient.setColorAt(0.25, color); + color.setAlphaF(1); + gradient.setColorAt(1, color); - style.labelPosition = 50.0; + connection.config.lineBrush = QBrush(gradient); + connection.config.drawStart = false; + connection.config.drawEnd = true; + connection.config.dashPattern << 1 << 6; + } + } + // one-to-many + else if (resolved.from.size() == 1 && resolved.to.size() > 1) { + //connection.config.type = ConnectionType::Bezier; - if (qmlItemNode().modelNode().hasAuxiliaryData("labelPosition")) - style.labelPosition = qmlItemNode().modelNode().auxiliaryData("labelPosition").toReal(); + if (qmlItemNode().modelNode().isSelected()) { + connection.config.dashPattern << 2 << 3; + } else { + QLinearGradient gradient(start, end); + QColor color = config.color; + color.setAlphaF(1); + gradient.setColorAt(0, color); + color.setAlphaF(0); + gradient.setColorAt(0.75, color); - style.labelFlipSide = false; + connection.config.lineBrush = QBrush(gradient); + connection.config.drawStart = true; + connection.config.drawEnd = false; + connection.config.dashPattern << 1 << 6; + } + } + } else { + connection.config.penStyle = Qt::SolidLine; + } - if (qmlItemNode().modelNode().hasAuxiliaryData("labelFlipSide")) - style.labelFlipSide = qmlItemNode().modelNode().auxiliaryData("labelFlipSide").toBool(); + paintConnection(painter, connection); - if (resolved.isStartLine) - fromRect.translate(0, style.outOffset); + if (resolved.isStartLine) { + const QString icon = Theme::getIconUnicode(Theme::startNode); - paintConnection(painter, fromRect, toRect, style, label); + QPen pen; + pen.setCosmetic(true); + pen.setColor(config.color); + painter->setPen(pen); - if (resolved.isStartLine) { - - const QString icon = Theme::getIconUnicode(Theme::startNode); - - QPen pen; - pen.setCosmetic(true); - pen.setColor(style.color); - painter->setPen(pen); - - const int iconAdjust = 48; - const int offset = 96; - const int size = fromRect.width(); - const int iconSize = size - iconAdjust; - const int x = fromRect.topRight().x() - offset; - const int y = fromRect.topRight().y(); - painter->drawRoundedRect(x, y , size - 10, size, size / 2, iconSize / 2); - drawIcon(painter, x + iconAdjust / 2, y + iconAdjust / 2, icon, iconSize, iconSize, style.color); + const int iconAdjust = 48; + const int offset = 96; + const int size = connection.fromRect.width(); + const int iconSize = size - iconAdjust; + const int x = connection.fromRect.topRight().x() - offset; + const int y = connection.fromRect.topRight().y(); + painter->drawRoundedRect(x, y , size - 10, size, size / 2, iconSize / 2); + drawIcon(painter, x + iconAdjust / 2, y + iconAdjust / 2, icon, iconSize, iconSize, config.color); + } + } } painter->restore(); @@ -1383,12 +1645,22 @@ QTransform FormEditorItem::viewportTransform() const void FormEditorFlowDecisionItem::updateGeometry() { prepareGeometryChange(); - m_selectionBoundingRect = QRectF(0,0, flowBlockSize, flowBlockSize); + m_selectionBoundingRect = QRectF(0, 0, flowBlockSize, flowBlockSize); m_paintedBoundingRect = m_selectionBoundingRect; m_boundingRect = m_paintedBoundingRect; setTransform(qmlItemNode().instanceTransformWithContentTransform()); const QPointF pos = qmlItemNode().flowPosition(); setTransform(QTransform::fromTranslate(pos.x(), pos.y())); + + // Call updateGeometry() on all related transitions + QmlFlowTargetNode flowItem(qmlItemNode()); + if (flowItem.isValid() && flowItem.flowView().isValid()) { + const auto nodes = flowItem.flowView().transitions(); + for (const ModelNode &node : nodes) { + if (FormEditorItem *item = scene()->itemForQmlItemNode(node)) + item->updateGeometry(); + } + } } void FormEditorFlowDecisionItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) diff --git a/src/plugins/qmldesigner/components/formeditor/formeditoritem.h b/src/plugins/qmldesigner/components/formeditor/formeditoritem.h index a2a491fdbf1..61ade499164 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditoritem.h +++ b/src/plugins/qmldesigner/components/formeditor/formeditoritem.h @@ -1,5 +1,4 @@ - -/**************************************************************************** +/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ @@ -47,37 +46,13 @@ namespace Internal { class MoveController; } -enum ConnectionType -{ - Default = 0, - Bezier -}; - -class ConnectionStyle -{ -public: - qreal width; - qreal adjustedWidth; - QColor color; - bool dash; - int outOffset; - int inOffset; - int breakOffset; - int radius; - int bezier; - ConnectionType type; - qreal labelOffset; - qreal labelPosition; - bool labelFlipSide; -}; - class QMLDESIGNERCORE_EXPORT FormEditorItem : public QGraphicsItem { friend class QmlDesigner::FormEditorScene; public: ~FormEditorItem() override; - void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = nullptr ) override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; bool isContainer() const; QmlItemNode qmlItemNode() const; @@ -182,7 +157,7 @@ public: QPointF instancePosition() const override; protected: - FormEditorFlowItem(const QmlItemNode &qmlItemNode, FormEditorScene* scene) + FormEditorFlowItem(const QmlItemNode &qmlItemNode, FormEditorScene *scene) : FormEditorItem(qmlItemNode, scene) {} }; @@ -191,12 +166,15 @@ class FormEditorFlowActionItem : public FormEditorItem { friend class QmlDesigner::FormEditorScene; public: - void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = nullptr ) override; + void setDataModelPosition(const QPointF &position) override; + void setDataModelPositionInBaseState(const QPointF &position) override; + void updateGeometry() override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; QTransform instanceSceneTransform() const override; QTransform instanceSceneContentItemTransform() const override; protected: - FormEditorFlowActionItem(const QmlItemNode &qmlItemNode, FormEditorScene* scene) + FormEditorFlowActionItem(const QmlItemNode &qmlItemNode, FormEditorScene *scene) : FormEditorItem(qmlItemNode, scene) {} }; @@ -210,11 +188,11 @@ public: void setDataModelPositionInBaseState(const QPointF &position) override; void updateGeometry() override; QPointF instancePosition() const override; - void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = nullptr ) override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; bool flowHitTest(const QPointF &point) const override; protected: - FormEditorTransitionItem(const QmlItemNode &qmlItemNode, FormEditorScene* scene) + FormEditorTransitionItem(const QmlItemNode &qmlItemNode, FormEditorScene *scene) : FormEditorItem(qmlItemNode, scene) {} private: @@ -227,7 +205,7 @@ class FormEditorFlowDecisionItem : FormEditorFlowItem public: void updateGeometry() override; - void paint(QPainter *painter, const QStyleOptionGraphicsItem * option, QWidget * widget = nullptr ) override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; bool flowHitTest(const QPointF &point) const override; protected: @@ -237,7 +215,7 @@ protected: }; FormEditorFlowDecisionItem(const QmlItemNode &qmlItemNode, - FormEditorScene* scene, + FormEditorScene *scene, IconType iconType = DecisionIcon) : FormEditorFlowItem(qmlItemNode, scene), m_iconType(iconType) {} @@ -249,10 +227,10 @@ class FormEditorFlowWildcardItem : FormEditorFlowDecisionItem friend class QmlDesigner::FormEditorScene; public: - void paint(QPainter *painter, const QStyleOptionGraphicsItem * option, QWidget * widget = nullptr ) override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; protected: - FormEditorFlowWildcardItem(const QmlItemNode &qmlItemNode, FormEditorScene* scene) + FormEditorFlowWildcardItem(const QmlItemNode &qmlItemNode, FormEditorScene *scene) : FormEditorFlowDecisionItem(qmlItemNode, scene, WildcardIcon) { } diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp b/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp index 58b47351f98..d43ba509144 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp @@ -36,6 +36,7 @@ #include "abstractcustomtool.h" #include +#include #include #include #include @@ -385,17 +386,37 @@ void FormEditorView::selectedNodesChanged(const QList &selectedNodeLi m_scene->update(); } -void FormEditorView::bindingPropertiesChanged(const QList &propertyList, AbstractView::PropertyChangeFlags propertyChange) +void FormEditorView::variantPropertiesChanged(const QList &propertyList, + AbstractView::PropertyChangeFlags propertyChange) +{ + Q_UNUSED(propertyChange) + for (const VariantProperty &property : propertyList) { + QmlVisualNode node(property.parentModelNode()); + if (node.isFlowTransition()) { + if (FormEditorItem *item = m_scene->itemForQmlItemNode(node.toQmlItemNode())) { + if (property.name() == "question") + item->updateGeometry(); + } + } + } +} + +void FormEditorView::bindingPropertiesChanged(const QList &propertyList, + AbstractView::PropertyChangeFlags propertyChange) { Q_UNUSED(propertyChange) for (const BindingProperty &property : propertyList) { QmlVisualNode node(property.parentModelNode()); if (node.isFlowTransition()) { - FormEditorItem *item = m_scene->itemForQmlItemNode(node.toQmlItemNode()); - if (item && node.hasNodeParent()) { - m_scene->reparentItem(node.toQmlItemNode(), node.toQmlItemNode().modelParentItem()); - m_scene->synchronizeTransformation(item); - item->update(); + if (FormEditorItem *item = m_scene->itemForQmlItemNode(node.toQmlItemNode())) { + if (property.name() == "condition" || property.name() == "question") + item->updateGeometry(); + + if (node.hasNodeParent()) { + m_scene->reparentItem(node.toQmlItemNode(), node.toQmlItemNode().modelParentItem()); + m_scene->synchronizeTransformation(item); + item->update(); + } } } else if (QmlFlowActionAreaNode::isValidQmlFlowActionAreaNode(property.parentModelNode())) { const QmlVisualNode target = property.resolveToModelNode(); @@ -502,7 +523,7 @@ void FormEditorView::changeToCustomTool() const ModelNode selectedModelNode = selectedModelNodes().constFirst(); - foreach (AbstractCustomTool *customTool, m_customToolList) { + for (AbstractCustomTool *customTool : m_customToolList) { if (customTool->wantHandleItem(selectedModelNode) > handlingRank) { handlingRank = customTool->wantHandleItem(selectedModelNode); selectedCustomTool = customTool; @@ -547,9 +568,17 @@ void FormEditorView::auxiliaryDataChanged(const ModelNode &node, const PropertyN } } else if (item.isFlowTransition() || item.isFlowActionArea() || item.isFlowDecision() || item.isFlowWildcard()) { - FormEditorItem *editorItem = m_scene->itemForQmlItemNode(item); - if (editorItem) + if (FormEditorItem *editorItem = m_scene->itemForQmlItemNode(item)) { + // Update the geomtry if one of the following auxiliary properties has changed + static const QStringList updateGeometryPropertyNames = { + "breakPoint", "bezier", "transitionBezier", "type", "tranitionType", "radius", + "transitionRadius", "labelPosition", "labelFlipSide", "inOffset", "outOffset" + }; + if (updateGeometryPropertyNames.contains(QString::fromUtf8(name))) + editorItem->updateGeometry(); + editorItem->update(); + } } else if (item.isFlowView() || item.isFlowItem()) { scene()->update(); } else if (name == "annotation" || name == "customId") { @@ -562,7 +591,7 @@ void FormEditorView::auxiliaryDataChanged(const ModelNode &node, const PropertyN void FormEditorView::instancesCompleted(const QVector &completedNodeList) { QList itemNodeList; - foreach (const ModelNode &node, completedNodeList) { + for (const ModelNode &node : completedNodeList) { const QmlItemNode qmlItemNode(node); if (qmlItemNode.isValid()) { if (FormEditorItem *item = scene()->itemForQmlItemNode(qmlItemNode)) { @@ -584,7 +613,7 @@ void FormEditorView::instanceInformationsChanged(const QMultiHashitemForQmlItemNode(qmlItemNode)) { scene()->synchronizeTransformation(item); @@ -621,7 +650,7 @@ void FormEditorView::instanceInformationsChanged(const QMultiHash &nodeList) { - foreach (const ModelNode &node, nodeList) { + for (const ModelNode &node : nodeList) { if (QmlItemNode::isValidQmlItemNode(node)) if (FormEditorItem *item = scene()->itemForQmlItemNode(QmlItemNode(node))) item->update(); @@ -632,7 +661,7 @@ void FormEditorView::instancesChildrenChanged(const QVector &nodeList { QList changedItems; - foreach (const ModelNode &node, nodeList) { + for (const ModelNode &node : nodeList) { const QmlItemNode qmlItemNode(node); if (qmlItemNode.isValid()) { if (FormEditorItem *item = scene()->itemForQmlItemNode(qmlItemNode)) { @@ -702,7 +731,7 @@ QmlItemNode findRecursiveQmlItemNode(const QmlObjectNode &firstQmlObjectNode) void FormEditorView::instancePropertyChanged(const QList > &propertyList) { QList changedItems; - foreach (auto &nodePropertyPair, propertyList) { + for (auto &nodePropertyPair : propertyList) { const QmlItemNode qmlItemNode(nodePropertyPair.first); const PropertyName propertyName = nodePropertyPair.second; if (qmlItemNode.isValid()) { diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorview.h b/src/plugins/qmldesigner/components/formeditor/formeditorview.h index 85cc5821ee4..d6a479e8472 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorview.h +++ b/src/plugins/qmldesigner/components/formeditor/formeditorview.h @@ -76,7 +76,10 @@ public: void selectedNodesChanged(const QList &selectedNodeList, const QList &lastSelectedNodeList) override; - void bindingPropertiesChanged(const QList& propertyList, + void variantPropertiesChanged(const QList &propertyList, + PropertyChangeFlags propertyChange) override; + + void bindingPropertiesChanged(const QList &propertyList, PropertyChangeFlags propertyChange) override; void documentMessagesChanged(const QList &errors, const QList &warnings) override; diff --git a/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp b/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp index ba8af364917..6597a62dde5 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp @@ -600,6 +600,7 @@ void QmlFlowActionAreaNode::assignTargetFlowItem(const QmlFlowTargetNode &flowIt QmlFlowItemNode QmlFlowActionAreaNode::flowItemParent() const { + QTC_ASSERT(modelNode().hasParentProperty(), return QmlFlowItemNode({})); return modelNode().parentProperty().parentModelNode(); }