1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights. These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
32 **************************************************************************/
34 #include "qmlanchors.h"
35 #include "variantproperty.h"
36 #include "bindingproperty.h"
37 #include "nodeabstractproperty.h"
38 #include "nodeinstance.h"
39 #include "rewritertransaction.h"
40 #include "qmlmodelview.h"
41 #include "mathutils.h"
43 namespace QmlDesigner {
46 static QString lineTypeToString(AnchorLine::Type lineType)
51 case AnchorLine::Left: return QLatin1String("left");
52 case AnchorLine::Top: return QLatin1String("top");
53 case AnchorLine::Right: return QLatin1String("right");
54 case AnchorLine::Bottom: return QLatin1String("bottom");
55 case AnchorLine::HorizontalCenter: return QLatin1String("horizontalCenter");
56 case AnchorLine::VerticalCenter: return QLatin1String("verticalCenter");
57 case AnchorLine::Baseline: return QLatin1String("baseline");
58 case AnchorLine::Fill: return QLatin1String("fill");
59 case AnchorLine::Center: return QLatin1String("centerIn");
60 default: return QString();
64 bool AnchorLine::isHorizontalAnchorLine(Type anchorline)
66 return anchorline & HorizontalMask;
69 bool AnchorLine::isVerticalAnchorLine(Type anchorline)
71 return anchorline & VerticalMask;
74 static AnchorLine::Type propertyNameToLineType(const QString & string)
76 if (string == QLatin1String("left")) {
77 return AnchorLine::Left;
78 } else if (string == QLatin1String("top")) {
79 return AnchorLine::Top;
80 } else if (string == QLatin1String("right")) {
81 return AnchorLine::Right;
82 } else if (string == QLatin1String("bottom")) {
83 return AnchorLine::Bottom;
84 } else if (string == QLatin1String("horizontalCenter")) {
85 return AnchorLine::HorizontalCenter;
86 } else if (string == QLatin1String("verticalCenter")) {
87 return AnchorLine::VerticalCenter;
88 } else if (string == QLatin1String("baseline")) {
89 return AnchorLine::VerticalCenter;
90 } else if (string == QLatin1String("centerIn")) {
91 return AnchorLine::Center;
92 } else if (string == QLatin1String("fill")) {
93 return AnchorLine::Fill;
96 return AnchorLine::Invalid;
99 static QString marginPropertyName(AnchorLine::Type lineType)
102 case AnchorLine::Left: return QLatin1String("anchors.leftMargin");
103 case AnchorLine::Top: return QLatin1String("anchors.topMargin");
104 case AnchorLine::Right: return QLatin1String("anchors.rightMargin");
105 case AnchorLine::Bottom: return QLatin1String("anchors.bottomMargin");
106 case AnchorLine::HorizontalCenter: return QLatin1String("anchors.horizontalCenterOffset");
107 case AnchorLine::VerticalCenter: return QLatin1String("anchors.verticalCenterOffset");
108 default: return QString();
112 static QString anchorPropertyName(AnchorLine::Type lineType)
114 const QString typeString = lineTypeToString(lineType);
116 if (typeString.isEmpty())
119 return QString("anchors.%1").arg(typeString);
123 QmlAnchors::QmlAnchors(const QmlItemNode &fxItemNode) : m_qmlItemNode(fxItemNode)
127 QmlItemNode QmlAnchors::qmlItemNode() const
129 return m_qmlItemNode;
132 bool QmlAnchors::isValid() const
134 return m_qmlItemNode.isValid();
137 void QmlAnchors::beautify()
140 if ((instanceHasAnchor(AnchorLine::Left) &&
141 instanceHasAnchor(AnchorLine::Right) &&
142 instanceHasAnchor(AnchorLine::Top) &&
143 instanceHasAnchor(AnchorLine::Bottom)) &&
145 (instanceAnchor(AnchorLine::Left).type() == AnchorLine::Left &&
146 instanceAnchor(AnchorLine::Right).type() == AnchorLine::Right &&
147 instanceAnchor(AnchorLine::Top).type() == AnchorLine::Top &&
148 instanceAnchor(AnchorLine::Bottom).type() == AnchorLine::Bottom) &&
150 (instanceAnchor(AnchorLine::Left).qmlItemNode() ==
151 instanceAnchor(AnchorLine::Right).qmlItemNode() &&
152 instanceAnchor(AnchorLine::Top).qmlItemNode() ==
153 instanceAnchor(AnchorLine::Bottom).qmlItemNode()) &&
154 (instanceAnchor(AnchorLine::Left).qmlItemNode() ==
155 instanceAnchor(AnchorLine::Bottom).qmlItemNode())) {
157 if (instanceHasAnchor(AnchorLine::Fill))
158 return; //avoid recursion
160 QmlItemNode targetNode(instanceAnchor(AnchorLine::Left).qmlItemNode());
162 setAnchor(AnchorLine::Fill, targetNode, AnchorLine::Fill);
165 if ((instanceHasAnchor(AnchorLine::VerticalCenter) &&
166 instanceHasAnchor(AnchorLine::HorizontalCenter)) &&
168 (instanceAnchor(AnchorLine::VerticalCenter).type() == AnchorLine::VerticalCenter &&
169 instanceAnchor(AnchorLine::HorizontalCenter).type() == AnchorLine::HorizontalCenter) &&
171 (instanceAnchor(AnchorLine::VerticalCenter).qmlItemNode() ==
172 instanceAnchor(AnchorLine::HorizontalCenter).qmlItemNode())) {
174 if (instanceHasAnchor(AnchorLine::Center))
175 return; //avoid recursion
177 QmlItemNode targetNode(instanceAnchor(AnchorLine::VerticalCenter).qmlItemNode());
179 setAnchor(AnchorLine::Center, targetNode, AnchorLine::Center);
183 void QmlAnchors::setAnchor(AnchorLine::Type sourceAnchorLine,
184 const QmlItemNode &targetQmlItemNode,
185 AnchorLine::Type targetAnchorLine)
187 RewriterTransaction transaction = qmlItemNode().qmlModelView()->beginRewriterTransaction();
188 if (qmlItemNode().isInBaseState()) {
189 if ((qmlItemNode().nodeInstance().hasAnchor("anchors.fill") && (sourceAnchorLine & AnchorLine::Fill))
190 || ((qmlItemNode().nodeInstance().hasAnchor("anchors.centerIn") && (sourceAnchorLine & AnchorLine::Center)))) {
191 removeAnchor(sourceAnchorLine);
194 const QString propertyName = anchorPropertyName(sourceAnchorLine);
195 QString targetExpression = targetQmlItemNode.modelNode().validId();
196 if (targetQmlItemNode.modelNode() == qmlItemNode().modelNode().parentProperty().parentModelNode())
197 targetExpression = "parent";
198 if (sourceAnchorLine != AnchorLine::Center && sourceAnchorLine != AnchorLine::Fill)
199 targetExpression = targetExpression + QLatin1Char('.') + lineTypeToString(targetAnchorLine);
200 qmlItemNode().modelNode().bindingProperty(propertyName).setExpression(targetExpression);
205 bool detectHorizontalCycle(const ModelNode &node, QList<ModelNode> knownNodeList)
207 if (knownNodeList.contains(node))
210 knownNodeList.append(node);
212 static QStringList validAnchorLines(QStringList() << "right" << "left" << "horizontalCenter");
213 static QStringList anchorNames(QStringList() << "anchors.right" << "anchors.left" << "anchors.horizontalCenter");
215 foreach (const QString &anchorName, anchorNames) {
216 if (node.hasBindingProperty(anchorName)) {
217 AbstractProperty targetProperty = node.bindingProperty(anchorName).resolveToProperty();
218 if (targetProperty.isValid()) {
219 if (!validAnchorLines.contains(targetProperty.name()))
222 if (detectHorizontalCycle(targetProperty.parentModelNode(), knownNodeList))
229 static QStringList anchorShortcutNames(QStringList() << "anchors.fill" << "anchors.centerIn");
230 foreach (const QString &anchorName, anchorShortcutNames) {
231 if (node.hasBindingProperty(anchorName)) {
232 ModelNode targetNode = node.bindingProperty(anchorName).resolveToModelNode();
234 if (targetNode.isValid() && detectHorizontalCycle(targetNode, knownNodeList))
242 bool detectVerticalCycle(const ModelNode &node, QList<ModelNode> knownNodeList)
247 if (knownNodeList.contains(node))
250 knownNodeList.append(node);
252 static QStringList validAnchorLines(QStringList() << "top" << "bottom" << "verticalCenter" << "baseline");
253 static QStringList anchorNames(QStringList() << "anchors.top" << "anchors.bottom" << "anchors.verticalCenter" << "anchors.baseline");
255 foreach (const QString &anchorName, anchorNames) {
256 if (node.hasBindingProperty(anchorName)) {
257 AbstractProperty targetProperty = node.bindingProperty(anchorName).resolveToProperty();
258 if (targetProperty.isValid()) {
259 if (!validAnchorLines.contains(targetProperty.name()))
262 if (detectVerticalCycle(targetProperty.parentModelNode(), knownNodeList))
269 static QStringList anchorShortcutNames(QStringList() << "anchors.fill" << "anchors.centerIn");
270 foreach (const QString &anchorName, anchorShortcutNames) {
271 if (node.hasBindingProperty(anchorName)) {
272 ModelNode targetNode = node.bindingProperty(anchorName).resolveToModelNode();
274 if (targetNode.isValid() && detectVerticalCycle(targetNode, knownNodeList))
282 bool QmlAnchors::canAnchor(const QmlItemNode &targetModelNode) const
284 if (!qmlItemNode().isInBaseState())
287 if (targetModelNode == qmlItemNode().instanceParent())
290 if (qmlItemNode().instanceParent() == targetModelNode.instanceParent())
296 AnchorLine::Type QmlAnchors::possibleAnchorLines(AnchorLine::Type sourceAnchorLineType,
297 const QmlItemNode &targetQmlItemNode) const
299 if (!canAnchor(targetQmlItemNode))
300 return AnchorLine::Invalid;
302 if (AnchorLine::isHorizontalAnchorLine(sourceAnchorLineType)) {
303 if (!detectHorizontalCycle(targetQmlItemNode, QList<ModelNode>() << qmlItemNode().modelNode()))
304 return AnchorLine::HorizontalMask;
307 if (AnchorLine::isVerticalAnchorLine(sourceAnchorLineType)) {
308 if (!detectVerticalCycle(targetQmlItemNode, QList<ModelNode>() << qmlItemNode().modelNode()))
309 return AnchorLine::VerticalMask;
312 return AnchorLine::Invalid;
315 AnchorLine QmlAnchors::instanceAnchor(AnchorLine::Type sourceAnchorLine) const
317 QPair<QString, qint32> targetAnchorLinePair;
318 if (qmlItemNode().nodeInstance().hasAnchor("anchors.fill") && (sourceAnchorLine & AnchorLine::Fill)) {
319 targetAnchorLinePair = qmlItemNode().nodeInstance().anchor("anchors.fill");
320 targetAnchorLinePair.first = lineTypeToString(sourceAnchorLine); // TODO: looks wrong
321 } else if (qmlItemNode().nodeInstance().hasAnchor("anchors.centerIn") && (sourceAnchorLine & AnchorLine::Center)) {
322 targetAnchorLinePair = qmlItemNode().nodeInstance().anchor("anchors.centerIn");
323 targetAnchorLinePair.first = lineTypeToString(sourceAnchorLine);
325 targetAnchorLinePair = qmlItemNode().nodeInstance().anchor(anchorPropertyName(sourceAnchorLine));
328 AnchorLine::Type targetAnchorLine = propertyNameToLineType(targetAnchorLinePair.first);
330 if (targetAnchorLine == AnchorLine::Invalid )
333 if (targetAnchorLinePair.second < 0) //there might be no node instance for the parent
336 return AnchorLine(QmlItemNode(qmlItemNode().nodeForInstance(qmlItemNode().qmlModelView()->nodeInstanceView()->instanceForId(targetAnchorLinePair.second))), targetAnchorLine);
339 void QmlAnchors::removeAnchor(AnchorLine::Type sourceAnchorLine)
341 RewriterTransaction transaction = qmlItemNode().qmlModelView()->beginRewriterTransaction();
342 if (qmlItemNode().isInBaseState()) {
343 const QString propertyName = anchorPropertyName(sourceAnchorLine);
344 if (qmlItemNode().nodeInstance().hasAnchor("anchors.fill") && (sourceAnchorLine & AnchorLine::Fill)) {
345 qmlItemNode().modelNode().removeProperty("anchors.fill");
346 qmlItemNode().modelNode().bindingProperty("anchors.top").setExpression("parent.top");
347 qmlItemNode().modelNode().bindingProperty("anchors.left").setExpression("parent.left");
348 qmlItemNode().modelNode().bindingProperty("anchors.bottom").setExpression("parent.bottom");
349 qmlItemNode().modelNode().bindingProperty("anchors.right").setExpression("parent.right");
351 } else if (qmlItemNode().nodeInstance().hasAnchor("anchors.centerIn") && (sourceAnchorLine & AnchorLine::Center)) {
352 qmlItemNode().modelNode().removeProperty("anchors.centerIn");
353 qmlItemNode().modelNode().bindingProperty("anchors.horizontalCenter").setExpression("parent.horizontalCenter");
354 qmlItemNode().modelNode().bindingProperty("anchors.verticalCenter").setExpression("parent.verticalCenter");
357 qmlItemNode().modelNode().removeProperty(propertyName);
361 void QmlAnchors::removeAnchors()
363 RewriterTransaction transaction = qmlItemNode().qmlModelView()->beginRewriterTransaction();
364 if (qmlItemNode().nodeInstance().hasAnchor("anchors.fill"))
365 qmlItemNode().modelNode().removeProperty("anchors.fill");
366 if (qmlItemNode().nodeInstance().hasAnchor("anchors.centerIn"))
367 qmlItemNode().modelNode().removeProperty("anchors.centerIn");
368 if (qmlItemNode().nodeInstance().hasAnchor("anchors.top"))
369 qmlItemNode().modelNode().removeProperty("anchors.top");
370 if (qmlItemNode().nodeInstance().hasAnchor("anchors.left"))
371 qmlItemNode().modelNode().removeProperty("anchors.left");
372 if (qmlItemNode().nodeInstance().hasAnchor("anchors.right"))
373 qmlItemNode().modelNode().removeProperty("anchors.right");
374 if (qmlItemNode().nodeInstance().hasAnchor("anchors.bottom"))
375 qmlItemNode().modelNode().removeProperty("anchors.bottom");
376 if (qmlItemNode().nodeInstance().hasAnchor("anchors.horizontalCenter"))
377 qmlItemNode().modelNode().removeProperty("anchors.horizontalCenter");
378 if (qmlItemNode().nodeInstance().hasAnchor("anchors.verticalCenter"))
379 qmlItemNode().modelNode().removeProperty("anchors.verticalCenter");
380 if (qmlItemNode().nodeInstance().hasAnchor("anchors.baseline"))
381 qmlItemNode().modelNode().removeProperty("anchors.baseline");
384 bool QmlAnchors::instanceHasAnchor(AnchorLine::Type sourceAnchorLine) const
386 const QString propertyName = anchorPropertyName(sourceAnchorLine);
388 if (sourceAnchorLine & AnchorLine::Fill)
389 return qmlItemNode().nodeInstance().hasAnchor(propertyName) || qmlItemNode().nodeInstance().hasAnchor("anchors.fill");
391 if (sourceAnchorLine & AnchorLine::Center)
392 return qmlItemNode().nodeInstance().hasAnchor(propertyName) || qmlItemNode().nodeInstance().hasAnchor("anchors.centerIn");
395 return qmlItemNode().nodeInstance().hasAnchor(propertyName);
398 bool QmlAnchors::instanceHasAnchors() const
400 return instanceHasAnchor(AnchorLine::Left) ||
401 instanceHasAnchor(AnchorLine::Right) ||
402 instanceHasAnchor(AnchorLine::Top) ||
403 instanceHasAnchor(AnchorLine::Bottom) ||
404 instanceHasAnchor(AnchorLine::HorizontalCenter) ||
405 instanceHasAnchor(AnchorLine::VerticalCenter) ||
406 instanceHasAnchor(AnchorLine::Baseline);
409 void QmlAnchors::setMargin(AnchorLine::Type sourceAnchorLineType, double margin) const
411 QString propertyName = marginPropertyName(sourceAnchorLineType);
412 qmlItemNode().setVariantProperty(propertyName, qRound(margin));
415 bool QmlAnchors::instanceHasMargin(AnchorLine::Type sourceAnchorLineType) const
417 return !qIsNull(instanceMargin(sourceAnchorLineType));
420 double QmlAnchors::instanceMargin(AnchorLine::Type sourceAnchorLineType) const
422 return qmlItemNode().nodeInstance().property(marginPropertyName(sourceAnchorLineType)).toDouble();
425 void QmlAnchors::removeMargin(AnchorLine::Type sourceAnchorLineType)
427 if (qmlItemNode().isInBaseState()) {
428 QString propertyName = marginPropertyName(sourceAnchorLineType);
429 qmlItemNode().modelNode().removeProperty(propertyName);
433 void QmlAnchors::removeMargins()
435 RewriterTransaction transaction = qmlItemNode().qmlModelView()->beginRewriterTransaction();
436 removeMargin(AnchorLine::Left);
437 removeMargin(AnchorLine::Right);
438 removeMargin(AnchorLine::Top);
439 removeMargin(AnchorLine::Bottom);
440 removeMargin(AnchorLine::HorizontalCenter);
441 removeMargin(AnchorLine::VerticalCenter);
444 QmlItemNode AnchorLine::qmlItemNode() const
446 return m_qmlItemNode;
449 void QmlAnchors::fill()
451 if (instanceHasAnchors())
454 qmlItemNode().modelNode().bindingProperty("anchors.fill").setExpression("parent");
457 void QmlAnchors::centerIn()
459 if (instanceHasAnchors())
462 qmlItemNode().modelNode().bindingProperty("anchors.centerIn").setExpression("parent");