/**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ****************************************************************************/ #include "qmlanchors.h" #include "variantproperty.h" #include "bindingproperty.h" #include "nodeabstractproperty.h" #include "nodeinstance.h" #include "rewritertransaction.h" #include "qmlmodelview.h" #include "mathutils.h" namespace QmlDesigner { static QString lineTypeToString(AnchorLine::Type lineType) { QString typeString; switch (lineType) { case AnchorLine::Left: return QLatin1String("left"); case AnchorLine::Top: return QLatin1String("top"); case AnchorLine::Right: return QLatin1String("right"); case AnchorLine::Bottom: return QLatin1String("bottom"); case AnchorLine::HorizontalCenter: return QLatin1String("horizontalCenter"); case AnchorLine::VerticalCenter: return QLatin1String("verticalCenter"); case AnchorLine::Baseline: return QLatin1String("baseline"); case AnchorLine::Fill: return QLatin1String("fill"); case AnchorLine::Center: return QLatin1String("centerIn"); default: return QString(); } } bool AnchorLine::isHorizontalAnchorLine(Type anchorline) { return anchorline & HorizontalMask; } bool AnchorLine::isVerticalAnchorLine(Type anchorline) { return anchorline & VerticalMask; } static AnchorLine::Type propertyNameToLineType(const QString & string) { if (string == QLatin1String("left")) { return AnchorLine::Left; } else if (string == QLatin1String("top")) { return AnchorLine::Top; } else if (string == QLatin1String("right")) { return AnchorLine::Right; } else if (string == QLatin1String("bottom")) { return AnchorLine::Bottom; } else if (string == QLatin1String("horizontalCenter")) { return AnchorLine::HorizontalCenter; } else if (string == QLatin1String("verticalCenter")) { return AnchorLine::VerticalCenter; } else if (string == QLatin1String("baseline")) { return AnchorLine::VerticalCenter; } else if (string == QLatin1String("centerIn")) { return AnchorLine::Center; } else if (string == QLatin1String("fill")) { return AnchorLine::Fill; } return AnchorLine::Invalid; } static QString marginPropertyName(AnchorLine::Type lineType) { switch (lineType) { case AnchorLine::Left: return QLatin1String("anchors.leftMargin"); case AnchorLine::Top: return QLatin1String("anchors.topMargin"); case AnchorLine::Right: return QLatin1String("anchors.rightMargin"); case AnchorLine::Bottom: return QLatin1String("anchors.bottomMargin"); case AnchorLine::HorizontalCenter: return QLatin1String("anchors.horizontalCenterOffset"); case AnchorLine::VerticalCenter: return QLatin1String("anchors.verticalCenterOffset"); default: return QString(); } } static QString anchorPropertyName(AnchorLine::Type lineType) { const QString typeString = lineTypeToString(lineType); if (typeString.isEmpty()) return QString(); else return QString("anchors.%1").arg(typeString); } QmlAnchors::QmlAnchors(const QmlItemNode &fxItemNode) : m_qmlItemNode(fxItemNode) { } QmlItemNode QmlAnchors::qmlItemNode() const { return m_qmlItemNode; } bool QmlAnchors::isValid() const { return m_qmlItemNode.isValid(); } void QmlAnchors::beautify() { return; if ((instanceHasAnchor(AnchorLine::Left) && instanceHasAnchor(AnchorLine::Right) && instanceHasAnchor(AnchorLine::Top) && instanceHasAnchor(AnchorLine::Bottom)) && (instanceAnchor(AnchorLine::Left).type() == AnchorLine::Left && instanceAnchor(AnchorLine::Right).type() == AnchorLine::Right && instanceAnchor(AnchorLine::Top).type() == AnchorLine::Top && instanceAnchor(AnchorLine::Bottom).type() == AnchorLine::Bottom) && (instanceAnchor(AnchorLine::Left).qmlItemNode() == instanceAnchor(AnchorLine::Right).qmlItemNode() && instanceAnchor(AnchorLine::Top).qmlItemNode() == instanceAnchor(AnchorLine::Bottom).qmlItemNode()) && (instanceAnchor(AnchorLine::Left).qmlItemNode() == instanceAnchor(AnchorLine::Bottom).qmlItemNode())) { if (instanceHasAnchor(AnchorLine::Fill)) return; //avoid recursion QmlItemNode targetNode(instanceAnchor(AnchorLine::Left).qmlItemNode()); removeAnchors(); setAnchor(AnchorLine::Fill, targetNode, AnchorLine::Fill); } if ((instanceHasAnchor(AnchorLine::VerticalCenter) && instanceHasAnchor(AnchorLine::HorizontalCenter)) && (instanceAnchor(AnchorLine::VerticalCenter).type() == AnchorLine::VerticalCenter && instanceAnchor(AnchorLine::HorizontalCenter).type() == AnchorLine::HorizontalCenter) && (instanceAnchor(AnchorLine::VerticalCenter).qmlItemNode() == instanceAnchor(AnchorLine::HorizontalCenter).qmlItemNode())) { if (instanceHasAnchor(AnchorLine::Center)) return; //avoid recursion QmlItemNode targetNode(instanceAnchor(AnchorLine::VerticalCenter).qmlItemNode()); removeAnchors(); setAnchor(AnchorLine::Center, targetNode, AnchorLine::Center); } } void QmlAnchors::setAnchor(AnchorLine::Type sourceAnchorLine, const QmlItemNode &targetQmlItemNode, AnchorLine::Type targetAnchorLine) { RewriterTransaction transaction = qmlItemNode().qmlModelView()->beginRewriterTransaction(); if (qmlItemNode().isInBaseState()) { if ((qmlItemNode().nodeInstance().hasAnchor("anchors.fill") && (sourceAnchorLine & AnchorLine::Fill)) || ((qmlItemNode().nodeInstance().hasAnchor("anchors.centerIn") && (sourceAnchorLine & AnchorLine::Center)))) { removeAnchor(sourceAnchorLine); } const QString propertyName = anchorPropertyName(sourceAnchorLine); QString targetExpression = targetQmlItemNode.modelNode().validId(); if (targetQmlItemNode.modelNode() == qmlItemNode().modelNode().parentProperty().parentModelNode()) targetExpression = "parent"; if (sourceAnchorLine != AnchorLine::Center && sourceAnchorLine != AnchorLine::Fill) targetExpression = targetExpression + QLatin1Char('.') + lineTypeToString(targetAnchorLine); qmlItemNode().modelNode().bindingProperty(propertyName).setExpression(targetExpression); } beautify(); } bool detectHorizontalCycle(const ModelNode &node, QList knownNodeList) { if (knownNodeList.contains(node)) return true; knownNodeList.append(node); static QStringList validAnchorLines(QStringList() << "right" << "left" << "horizontalCenter"); static QStringList anchorNames(QStringList() << "anchors.right" << "anchors.left" << "anchors.horizontalCenter"); foreach (const QString &anchorName, anchorNames) { if (node.hasBindingProperty(anchorName)) { AbstractProperty targetProperty = node.bindingProperty(anchorName).resolveToProperty(); if (targetProperty.isValid()) { if (!validAnchorLines.contains(targetProperty.name())) return true; if (detectHorizontalCycle(targetProperty.parentModelNode(), knownNodeList)) return true; } } } static QStringList anchorShortcutNames(QStringList() << "anchors.fill" << "anchors.centerIn"); foreach (const QString &anchorName, anchorShortcutNames) { if (node.hasBindingProperty(anchorName)) { ModelNode targetNode = node.bindingProperty(anchorName).resolveToModelNode(); if (targetNode.isValid() && detectHorizontalCycle(targetNode, knownNodeList)) return true; } } return false; } bool detectVerticalCycle(const ModelNode &node, QList knownNodeList) { if (!node.isValid()) return false; if (knownNodeList.contains(node)) return true; knownNodeList.append(node); static QStringList validAnchorLines(QStringList() << "top" << "bottom" << "verticalCenter" << "baseline"); static QStringList anchorNames(QStringList() << "anchors.top" << "anchors.bottom" << "anchors.verticalCenter" << "anchors.baseline"); foreach (const QString &anchorName, anchorNames) { if (node.hasBindingProperty(anchorName)) { AbstractProperty targetProperty = node.bindingProperty(anchorName).resolveToProperty(); if (targetProperty.isValid()) { if (!validAnchorLines.contains(targetProperty.name())) return true; if (detectVerticalCycle(targetProperty.parentModelNode(), knownNodeList)) return true; } } } static QStringList anchorShortcutNames(QStringList() << "anchors.fill" << "anchors.centerIn"); foreach (const QString &anchorName, anchorShortcutNames) { if (node.hasBindingProperty(anchorName)) { ModelNode targetNode = node.bindingProperty(anchorName).resolveToModelNode(); if (targetNode.isValid() && detectVerticalCycle(targetNode, knownNodeList)) return true; } } return false; } bool QmlAnchors::canAnchor(const QmlItemNode &targetModelNode) const { if (!qmlItemNode().isInBaseState()) return false; if (targetModelNode == qmlItemNode().instanceParent()) return true; if (qmlItemNode().instanceParent() == targetModelNode.instanceParent()) return true; return false; } AnchorLine::Type QmlAnchors::possibleAnchorLines(AnchorLine::Type sourceAnchorLineType, const QmlItemNode &targetQmlItemNode) const { if (!canAnchor(targetQmlItemNode)) return AnchorLine::Invalid; if (AnchorLine::isHorizontalAnchorLine(sourceAnchorLineType)) { if (!detectHorizontalCycle(targetQmlItemNode, QList() << qmlItemNode().modelNode())) return AnchorLine::HorizontalMask; } if (AnchorLine::isVerticalAnchorLine(sourceAnchorLineType)) { if (!detectVerticalCycle(targetQmlItemNode, QList() << qmlItemNode().modelNode())) return AnchorLine::VerticalMask; } return AnchorLine::Invalid; } AnchorLine QmlAnchors::instanceAnchor(AnchorLine::Type sourceAnchorLine) const { QPair targetAnchorLinePair; if (qmlItemNode().nodeInstance().hasAnchor("anchors.fill") && (sourceAnchorLine & AnchorLine::Fill)) { targetAnchorLinePair = qmlItemNode().nodeInstance().anchor("anchors.fill"); targetAnchorLinePair.first = lineTypeToString(sourceAnchorLine); // TODO: looks wrong } else if (qmlItemNode().nodeInstance().hasAnchor("anchors.centerIn") && (sourceAnchorLine & AnchorLine::Center)) { targetAnchorLinePair = qmlItemNode().nodeInstance().anchor("anchors.centerIn"); targetAnchorLinePair.first = lineTypeToString(sourceAnchorLine); } else { targetAnchorLinePair = qmlItemNode().nodeInstance().anchor(anchorPropertyName(sourceAnchorLine)); } AnchorLine::Type targetAnchorLine = propertyNameToLineType(targetAnchorLinePair.first); if (targetAnchorLine == AnchorLine::Invalid ) return AnchorLine(); if (targetAnchorLinePair.second < 0) //there might be no node instance for the parent return AnchorLine(); return AnchorLine(QmlItemNode(qmlItemNode().nodeForInstance(qmlItemNode().qmlModelView()->nodeInstanceView()->instanceForId(targetAnchorLinePair.second))), targetAnchorLine); } void QmlAnchors::removeAnchor(AnchorLine::Type sourceAnchorLine) { RewriterTransaction transaction = qmlItemNode().qmlModelView()->beginRewriterTransaction(); if (qmlItemNode().isInBaseState()) { const QString propertyName = anchorPropertyName(sourceAnchorLine); if (qmlItemNode().nodeInstance().hasAnchor("anchors.fill") && (sourceAnchorLine & AnchorLine::Fill)) { qmlItemNode().modelNode().removeProperty("anchors.fill"); qmlItemNode().modelNode().bindingProperty("anchors.top").setExpression("parent.top"); qmlItemNode().modelNode().bindingProperty("anchors.left").setExpression("parent.left"); qmlItemNode().modelNode().bindingProperty("anchors.bottom").setExpression("parent.bottom"); qmlItemNode().modelNode().bindingProperty("anchors.right").setExpression("parent.right"); } else if (qmlItemNode().nodeInstance().hasAnchor("anchors.centerIn") && (sourceAnchorLine & AnchorLine::Center)) { qmlItemNode().modelNode().removeProperty("anchors.centerIn"); qmlItemNode().modelNode().bindingProperty("anchors.horizontalCenter").setExpression("parent.horizontalCenter"); qmlItemNode().modelNode().bindingProperty("anchors.verticalCenter").setExpression("parent.verticalCenter"); } qmlItemNode().modelNode().removeProperty(propertyName); } } void QmlAnchors::removeAnchors() { RewriterTransaction transaction = qmlItemNode().qmlModelView()->beginRewriterTransaction(); if (qmlItemNode().nodeInstance().hasAnchor("anchors.fill")) qmlItemNode().modelNode().removeProperty("anchors.fill"); if (qmlItemNode().nodeInstance().hasAnchor("anchors.centerIn")) qmlItemNode().modelNode().removeProperty("anchors.centerIn"); if (qmlItemNode().nodeInstance().hasAnchor("anchors.top")) qmlItemNode().modelNode().removeProperty("anchors.top"); if (qmlItemNode().nodeInstance().hasAnchor("anchors.left")) qmlItemNode().modelNode().removeProperty("anchors.left"); if (qmlItemNode().nodeInstance().hasAnchor("anchors.right")) qmlItemNode().modelNode().removeProperty("anchors.right"); if (qmlItemNode().nodeInstance().hasAnchor("anchors.bottom")) qmlItemNode().modelNode().removeProperty("anchors.bottom"); if (qmlItemNode().nodeInstance().hasAnchor("anchors.horizontalCenter")) qmlItemNode().modelNode().removeProperty("anchors.horizontalCenter"); if (qmlItemNode().nodeInstance().hasAnchor("anchors.verticalCenter")) qmlItemNode().modelNode().removeProperty("anchors.verticalCenter"); if (qmlItemNode().nodeInstance().hasAnchor("anchors.baseline")) qmlItemNode().modelNode().removeProperty("anchors.baseline"); } bool QmlAnchors::instanceHasAnchor(AnchorLine::Type sourceAnchorLine) const { const QString propertyName = anchorPropertyName(sourceAnchorLine); if (sourceAnchorLine & AnchorLine::Fill) return qmlItemNode().nodeInstance().hasAnchor(propertyName) || qmlItemNode().nodeInstance().hasAnchor("anchors.fill"); if (sourceAnchorLine & AnchorLine::Center) return qmlItemNode().nodeInstance().hasAnchor(propertyName) || qmlItemNode().nodeInstance().hasAnchor("anchors.centerIn"); return qmlItemNode().nodeInstance().hasAnchor(propertyName); } bool QmlAnchors::instanceHasAnchors() const { return instanceHasAnchor(AnchorLine::Left) || instanceHasAnchor(AnchorLine::Right) || instanceHasAnchor(AnchorLine::Top) || instanceHasAnchor(AnchorLine::Bottom) || instanceHasAnchor(AnchorLine::HorizontalCenter) || instanceHasAnchor(AnchorLine::VerticalCenter) || instanceHasAnchor(AnchorLine::Baseline); } void QmlAnchors::setMargin(AnchorLine::Type sourceAnchorLineType, double margin) const { QString propertyName = marginPropertyName(sourceAnchorLineType); qmlItemNode().setVariantProperty(propertyName, qRound(margin)); } bool QmlAnchors::instanceHasMargin(AnchorLine::Type sourceAnchorLineType) const { return !qIsNull(instanceMargin(sourceAnchorLineType)); } double QmlAnchors::instanceMargin(AnchorLine::Type sourceAnchorLineType) const { return qmlItemNode().nodeInstance().property(marginPropertyName(sourceAnchorLineType)).toDouble(); } void QmlAnchors::removeMargin(AnchorLine::Type sourceAnchorLineType) { if (qmlItemNode().isInBaseState()) { QString propertyName = marginPropertyName(sourceAnchorLineType); qmlItemNode().modelNode().removeProperty(propertyName); } } void QmlAnchors::removeMargins() { RewriterTransaction transaction = qmlItemNode().qmlModelView()->beginRewriterTransaction(); removeMargin(AnchorLine::Left); removeMargin(AnchorLine::Right); removeMargin(AnchorLine::Top); removeMargin(AnchorLine::Bottom); removeMargin(AnchorLine::HorizontalCenter); removeMargin(AnchorLine::VerticalCenter); } QmlItemNode AnchorLine::qmlItemNode() const { return m_qmlItemNode; } void QmlAnchors::fill() { if (instanceHasAnchors()) removeAnchors(); qmlItemNode().modelNode().bindingProperty("anchors.fill").setExpression("parent"); } void QmlAnchors::centerIn() { if (instanceHasAnchors()) removeAnchors(); qmlItemNode().modelNode().bindingProperty("anchors.centerIn").setExpression("parent"); } } //QmlDesigner