OSDN Git Service

It's 2011 now.
[qt-creator-jp/qt-creator-jp.git] / src / plugins / qmldesigner / designercore / model / qmlanchors.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
8 **
9 ** No Commercial Usage
10 **
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
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 **
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.
24 **
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.
28 **
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
31 **
32 **************************************************************************/
33
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"
42
43 namespace QmlDesigner {
44
45
46 static QString lineTypeToString(AnchorLine::Type lineType)
47 {
48     QString typeString;
49
50     switch (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();
61     }
62 }
63
64 bool AnchorLine::isHorizontalAnchorLine(Type anchorline)
65 {
66     return anchorline & HorizontalMask;
67 }
68
69 bool AnchorLine::isVerticalAnchorLine(Type anchorline)
70 {
71      return anchorline & VerticalMask;
72 }
73
74 static AnchorLine::Type propertyNameToLineType(const QString & string)
75 {
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;
94     }
95
96     return AnchorLine::Invalid;
97 }
98
99 static QString marginPropertyName(AnchorLine::Type lineType)
100 {
101     switch (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();
109     }
110 }
111
112 static QString anchorPropertyName(AnchorLine::Type lineType)
113 {
114     const QString typeString = lineTypeToString(lineType);
115
116     if (typeString.isEmpty())
117         return QString();
118     else
119         return QString("anchors.%1").arg(typeString);
120 }
121
122
123 QmlAnchors::QmlAnchors(const QmlItemNode &fxItemNode) : m_qmlItemNode(fxItemNode)
124 {
125 }
126
127 QmlItemNode QmlAnchors::qmlItemNode() const
128 {
129     return m_qmlItemNode;
130 }
131
132 bool QmlAnchors::isValid() const
133 {
134     return m_qmlItemNode.isValid();
135 }
136
137 void QmlAnchors::beautify()
138 {
139
140     if ((instanceHasAnchor(AnchorLine::Left) &&
141          instanceHasAnchor(AnchorLine::Right) &&
142          instanceHasAnchor(AnchorLine::Top) &&
143          instanceHasAnchor(AnchorLine::Bottom)) &&
144
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) &&
149
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())) {
156
157         if (instanceHasAnchor(AnchorLine::Fill))
158             return; //avoid recursion
159
160         QmlItemNode targetNode(instanceAnchor(AnchorLine::Left).qmlItemNode());
161         removeAnchors();
162         setAnchor(AnchorLine::Fill, targetNode, AnchorLine::Fill);
163     }
164
165     if ((instanceHasAnchor(AnchorLine::VerticalCenter) &&
166          instanceHasAnchor(AnchorLine::HorizontalCenter)) &&
167
168         (instanceAnchor(AnchorLine::VerticalCenter).type() == AnchorLine::VerticalCenter &&
169          instanceAnchor(AnchorLine::HorizontalCenter).type() == AnchorLine::HorizontalCenter) &&
170
171         (instanceAnchor(AnchorLine::VerticalCenter).qmlItemNode() ==
172          instanceAnchor(AnchorLine::HorizontalCenter).qmlItemNode())) {
173
174         if (instanceHasAnchor(AnchorLine::Center))
175             return; //avoid recursion
176
177         QmlItemNode targetNode(instanceAnchor(AnchorLine::VerticalCenter).qmlItemNode());
178         removeAnchors();
179         setAnchor(AnchorLine::Center, targetNode, AnchorLine::Center);
180     }
181 }
182
183 void QmlAnchors::setAnchor(AnchorLine::Type sourceAnchorLine,
184                           const QmlItemNode &targetQmlItemNode,
185                           AnchorLine::Type targetAnchorLine)
186 {
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);
192         }
193
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);
201     }
202     beautify();
203 }
204
205 bool detectHorizontalCycle(const ModelNode &node, QList<ModelNode> knownNodeList)
206 {
207     if (knownNodeList.contains(node))
208         return true;
209
210     knownNodeList.append(node);
211
212     static QStringList validAnchorLines(QStringList() << "right" << "left" << "horizontalCenter");
213     static QStringList anchorNames(QStringList() << "anchors.right" << "anchors.left" << "anchors.horizontalCenter");
214
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()))
220                     return true;
221
222                 if (detectHorizontalCycle(targetProperty.parentModelNode(), knownNodeList))
223                     return true;
224             }
225         }
226
227     }
228
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();
233
234             if (targetNode.isValid() && detectHorizontalCycle(targetNode, knownNodeList))
235                 return true;
236         }
237     }
238
239     return false;
240 }
241
242 bool detectVerticalCycle(const ModelNode &node, QList<ModelNode> knownNodeList)
243 {
244     if (!node.isValid())
245         return false;
246
247     if (knownNodeList.contains(node))
248         return true;
249
250     knownNodeList.append(node);
251
252     static QStringList validAnchorLines(QStringList() << "top" << "bottom" << "verticalCenter" << "baseline");
253     static QStringList anchorNames(QStringList() << "anchors.top" << "anchors.bottom" << "anchors.verticalCenter" << "anchors.baseline");
254
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()))
260                     return true;
261
262                 if (detectVerticalCycle(targetProperty.parentModelNode(), knownNodeList))
263                     return true;
264             }
265         }
266
267     }
268
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();
273
274             if (targetNode.isValid() && detectVerticalCycle(targetNode, knownNodeList))
275                 return true;
276         }
277     }
278
279     return false;
280 }
281
282 bool QmlAnchors::canAnchor(const QmlItemNode &targetModelNode) const
283 {
284     if (!qmlItemNode().isInBaseState())
285         return false;
286
287     if (targetModelNode == qmlItemNode().instanceParent())
288         return true;
289
290     if (qmlItemNode().instanceParent() == targetModelNode.instanceParent())
291         return true;
292
293     return false;
294 }
295
296 AnchorLine::Type QmlAnchors::possibleAnchorLines(AnchorLine::Type sourceAnchorLineType,
297                                                 const QmlItemNode &targetQmlItemNode) const
298 {
299     if (!canAnchor(targetQmlItemNode))
300         return AnchorLine::Invalid;
301
302     if (AnchorLine::isHorizontalAnchorLine(sourceAnchorLineType)) {
303         if (!detectHorizontalCycle(targetQmlItemNode, QList<ModelNode>() << qmlItemNode().modelNode()))
304             return AnchorLine::HorizontalMask;
305     }
306
307     if (AnchorLine::isVerticalAnchorLine(sourceAnchorLineType)) {
308         if (!detectVerticalCycle(targetQmlItemNode, QList<ModelNode>() << qmlItemNode().modelNode()))
309             return AnchorLine::VerticalMask;
310     }
311
312     return AnchorLine::Invalid;
313 }
314
315 AnchorLine QmlAnchors::instanceAnchor(AnchorLine::Type sourceAnchorLine) const
316 {
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);
324     } else {
325         targetAnchorLinePair = qmlItemNode().nodeInstance().anchor(anchorPropertyName(sourceAnchorLine));
326     }
327
328     AnchorLine::Type targetAnchorLine = propertyNameToLineType(targetAnchorLinePair.first);
329
330     if (targetAnchorLine == AnchorLine::Invalid )
331         return AnchorLine();
332
333     if (targetAnchorLinePair.second < 0) //there might be no node instance for the parent
334         return AnchorLine();
335
336     return AnchorLine(QmlItemNode(qmlItemNode().nodeForInstance(qmlItemNode().qmlModelView()->nodeInstanceView()->instanceForId(targetAnchorLinePair.second))), targetAnchorLine);
337 }
338
339 void QmlAnchors::removeAnchor(AnchorLine::Type sourceAnchorLine)
340 {
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");
350
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");
355         }
356
357         qmlItemNode().modelNode().removeProperty(propertyName);
358     }
359 }
360
361 void QmlAnchors::removeAnchors()
362 {
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");
382 }
383
384 bool QmlAnchors::instanceHasAnchor(AnchorLine::Type sourceAnchorLine) const
385 {
386     const QString propertyName = anchorPropertyName(sourceAnchorLine);
387
388     if (sourceAnchorLine & AnchorLine::Fill)
389         return qmlItemNode().nodeInstance().hasAnchor(propertyName) || qmlItemNode().nodeInstance().hasAnchor("anchors.fill");
390
391     if (sourceAnchorLine & AnchorLine::Center)
392         return qmlItemNode().nodeInstance().hasAnchor(propertyName) || qmlItemNode().nodeInstance().hasAnchor("anchors.centerIn");
393
394
395     return qmlItemNode().nodeInstance().hasAnchor(propertyName);
396 }
397
398 bool QmlAnchors::instanceHasAnchors() const
399 {
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);
407 }
408
409 void QmlAnchors::setMargin(AnchorLine::Type sourceAnchorLineType, double margin) const
410 {
411     QString propertyName = marginPropertyName(sourceAnchorLineType);
412     qmlItemNode().setVariantProperty(propertyName, qRound(margin));
413 }
414
415 bool QmlAnchors::instanceHasMargin(AnchorLine::Type sourceAnchorLineType) const
416 {
417     return !qIsNull(instanceMargin(sourceAnchorLineType));
418 }
419
420 double QmlAnchors::instanceMargin(AnchorLine::Type sourceAnchorLineType) const
421 {
422     return qmlItemNode().nodeInstance().property(marginPropertyName(sourceAnchorLineType)).toDouble();
423 }
424
425 void QmlAnchors::removeMargin(AnchorLine::Type sourceAnchorLineType)
426 {
427     if (qmlItemNode().isInBaseState()) {
428         QString propertyName = marginPropertyName(sourceAnchorLineType);
429         qmlItemNode().modelNode().removeProperty(propertyName);
430     }
431 }
432
433 void QmlAnchors::removeMargins()
434 {
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);
442 }
443
444 QmlItemNode AnchorLine::qmlItemNode() const
445 {
446     return m_qmlItemNode;
447 }
448
449 void QmlAnchors::fill()
450 {
451     if (instanceHasAnchors())
452         removeAnchors();
453
454     qmlItemNode().modelNode().bindingProperty("anchors.fill").setExpression("parent");
455 }
456
457 void QmlAnchors::centerIn()
458 {
459     if (instanceHasAnchors())
460         removeAnchors();
461
462     qmlItemNode().modelNode().bindingProperty("anchors.centerIn").setExpression("parent");
463 }
464
465 } //QmlDesigner