|
1 /**************************************************************************** |
|
2 ** |
|
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). |
|
4 ** All rights reserved. |
|
5 ** Contact: Nokia Corporation (qt-info@nokia.com) |
|
6 ** |
|
7 ** This file is part of the QtDeclarative module of the Qt Toolkit. |
|
8 ** |
|
9 ** $QT_BEGIN_LICENSE:LGPL$ |
|
10 ** No Commercial Usage |
|
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 ** Alternatively, this file may be used under the terms of the GNU Lesser |
|
18 ** General Public License version 2.1 as published by the Free Software |
|
19 ** Foundation and appearing in the file LICENSE.LGPL included in the |
|
20 ** packaging of this file. Please review the following information to |
|
21 ** ensure the GNU Lesser General Public License version 2.1 requirements |
|
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
|
23 ** |
|
24 ** In addition, as a special exception, Nokia gives you certain additional |
|
25 ** rights. These rights are described in the Nokia Qt LGPL Exception |
|
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
|
27 ** |
|
28 ** If you have questions regarding the use of this file, please contact |
|
29 ** Nokia at qt-info@nokia.com. |
|
30 ** |
|
31 ** |
|
32 ** |
|
33 ** |
|
34 ** |
|
35 ** |
|
36 ** |
|
37 ** |
|
38 ** $QT_END_LICENSE$ |
|
39 ** |
|
40 ****************************************************************************/ |
|
41 |
|
42 #include "private/qdeclarativepropertychanges_p.h" |
|
43 |
|
44 #include "private/qdeclarativeopenmetaobject_p.h" |
|
45 |
|
46 #include <qdeclarativeinfo.h> |
|
47 #include <qdeclarativecustomparser_p.h> |
|
48 #include <qdeclarativeparser_p.h> |
|
49 #include <qdeclarativeexpression.h> |
|
50 #include <qdeclarativebinding_p.h> |
|
51 #include <qdeclarativecontext.h> |
|
52 #include <qdeclarativeguard_p.h> |
|
53 #include <qdeclarativeproperty_p.h> |
|
54 #include <qdeclarativecontext_p.h> |
|
55 |
|
56 #include <QtCore/qdebug.h> |
|
57 |
|
58 #include <private/qobject_p.h> |
|
59 |
|
60 QT_BEGIN_NAMESPACE |
|
61 |
|
62 /*! |
|
63 \qmlclass PropertyChanges QDeclarativePropertyChanges |
|
64 \since 4.7 |
|
65 \brief The PropertyChanges element describes new property values for a state. |
|
66 |
|
67 PropertyChanges provides a state change that modifies the properties of an item. |
|
68 |
|
69 Here is a property change that modifies the text and color of a Text element |
|
70 when it is clicked: |
|
71 |
|
72 \qml |
|
73 Text { |
|
74 id: myText |
|
75 width: 100; height: 100 |
|
76 text: "Hello" |
|
77 color: "blue" |
|
78 |
|
79 states: State { |
|
80 name: "myState" |
|
81 |
|
82 PropertyChanges { |
|
83 target: myText |
|
84 text: "Goodbye" |
|
85 color: "red" |
|
86 } |
|
87 } |
|
88 |
|
89 MouseArea { anchors.fill: parent; onClicked: myText.state = 'myState' } |
|
90 } |
|
91 \endqml |
|
92 |
|
93 State-specific script for signal handlers can also be specified: |
|
94 |
|
95 \qml |
|
96 PropertyChanges { |
|
97 target: myMouseArea |
|
98 onClicked: doSomethingDifferent() |
|
99 } |
|
100 \endqml |
|
101 |
|
102 You can reset a property in a state change by assigning \c undefined. In the following |
|
103 example we reset \c theText's width when we enter state1. This will give the text its |
|
104 natural width (which is the whole string on one line). |
|
105 |
|
106 \qml |
|
107 import Qt 4.7 |
|
108 |
|
109 Rectangle { |
|
110 width: 640 |
|
111 height: 480 |
|
112 Text { |
|
113 id: theText |
|
114 width: 50 |
|
115 wrapMode: Text.WordWrap |
|
116 text: "a text string that is longer than 50 pixels" |
|
117 } |
|
118 |
|
119 states: State { |
|
120 name: "state1" |
|
121 PropertyChanges { |
|
122 target: theText |
|
123 width: undefined |
|
124 } |
|
125 } |
|
126 } |
|
127 \endqml |
|
128 |
|
129 Anchor margins should be changed with PropertyChanges, but other anchor changes or changes to |
|
130 an Item's parent should be done using the associated change elements |
|
131 (ParentChange and AnchorChanges, respectively). |
|
132 |
|
133 \sa {qmlstate}{States}, QtDeclarative |
|
134 */ |
|
135 |
|
136 /*! |
|
137 \internal |
|
138 \class QDeclarativePropertyChanges |
|
139 \brief The QDeclarativePropertyChanges class describes new property values for a state. |
|
140 */ |
|
141 |
|
142 /*! |
|
143 \qmlproperty Object PropertyChanges::target |
|
144 This property holds the object which contains the properties to be changed. |
|
145 */ |
|
146 |
|
147 class QDeclarativeReplaceSignalHandler : public QDeclarativeActionEvent |
|
148 { |
|
149 public: |
|
150 QDeclarativeReplaceSignalHandler() : expression(0), reverseExpression(0), |
|
151 rewindExpression(0), ownedExpression(0) {} |
|
152 ~QDeclarativeReplaceSignalHandler() { |
|
153 delete ownedExpression; |
|
154 } |
|
155 |
|
156 virtual QString typeName() const { return QLatin1String("ReplaceSignalHandler"); } |
|
157 |
|
158 QDeclarativeProperty property; |
|
159 QDeclarativeExpression *expression; |
|
160 QDeclarativeExpression *reverseExpression; |
|
161 QDeclarativeExpression *rewindExpression; |
|
162 QDeclarativeGuard<QDeclarativeExpression> ownedExpression; |
|
163 |
|
164 virtual void execute(Reason) { |
|
165 ownedExpression = QDeclarativePropertyPrivate::setSignalExpression(property, expression); |
|
166 if (ownedExpression == expression) |
|
167 ownedExpression = 0; |
|
168 } |
|
169 |
|
170 virtual bool isReversable() { return true; } |
|
171 virtual void reverse(Reason) { |
|
172 ownedExpression = QDeclarativePropertyPrivate::setSignalExpression(property, reverseExpression); |
|
173 if (ownedExpression == reverseExpression) |
|
174 ownedExpression = 0; |
|
175 } |
|
176 |
|
177 virtual void saveOriginals() { |
|
178 saveCurrentValues(); |
|
179 reverseExpression = rewindExpression; |
|
180 } |
|
181 |
|
182 /*virtual void copyOriginals(QDeclarativeActionEvent *other) |
|
183 { |
|
184 QDeclarativeReplaceSignalHandler *rsh = static_cast<QDeclarativeReplaceSignalHandler*>(other); |
|
185 saveCurrentValues(); |
|
186 if (rsh == this) |
|
187 return; |
|
188 reverseExpression = rsh->reverseExpression; |
|
189 if (rsh->ownedExpression == reverseExpression) { |
|
190 ownedExpression = rsh->ownedExpression; |
|
191 rsh->ownedExpression = 0; |
|
192 } |
|
193 }*/ |
|
194 |
|
195 virtual void rewind() { |
|
196 ownedExpression = QDeclarativePropertyPrivate::setSignalExpression(property, rewindExpression); |
|
197 if (ownedExpression == rewindExpression) |
|
198 ownedExpression = 0; |
|
199 } |
|
200 virtual void saveCurrentValues() { |
|
201 rewindExpression = QDeclarativePropertyPrivate::signalExpression(property); |
|
202 } |
|
203 |
|
204 virtual bool override(QDeclarativeActionEvent*other) { |
|
205 if (other == this) |
|
206 return true; |
|
207 if (other->typeName() != typeName()) |
|
208 return false; |
|
209 if (static_cast<QDeclarativeReplaceSignalHandler*>(other)->property == property) |
|
210 return true; |
|
211 return false; |
|
212 } |
|
213 }; |
|
214 |
|
215 |
|
216 class QDeclarativePropertyChangesPrivate : public QObjectPrivate |
|
217 { |
|
218 Q_DECLARE_PUBLIC(QDeclarativePropertyChanges) |
|
219 public: |
|
220 QDeclarativePropertyChangesPrivate() : object(0), decoded(true), restore(true), |
|
221 isExplicit(false) {} |
|
222 |
|
223 QObject *object; |
|
224 QByteArray data; |
|
225 |
|
226 bool decoded : 1; |
|
227 bool restore : 1; |
|
228 bool isExplicit : 1; |
|
229 |
|
230 void decode(); |
|
231 |
|
232 QList<QPair<QByteArray, QVariant> > properties; |
|
233 QList<QPair<QByteArray, QDeclarativeExpression *> > expressions; |
|
234 QList<QDeclarativeReplaceSignalHandler*> signalReplacements; |
|
235 |
|
236 QDeclarativeProperty property(const QByteArray &); |
|
237 }; |
|
238 |
|
239 void |
|
240 QDeclarativePropertyChangesParser::compileList(QList<QPair<QByteArray, QVariant> > &list, |
|
241 const QByteArray &pre, |
|
242 const QDeclarativeCustomParserProperty &prop) |
|
243 { |
|
244 QByteArray propName = pre + prop.name(); |
|
245 |
|
246 QList<QVariant> values = prop.assignedValues(); |
|
247 for (int ii = 0; ii < values.count(); ++ii) { |
|
248 const QVariant &value = values.at(ii); |
|
249 |
|
250 if (value.userType() == qMetaTypeId<QDeclarativeCustomParserNode>()) { |
|
251 error(qvariant_cast<QDeclarativeCustomParserNode>(value), |
|
252 QDeclarativePropertyChanges::tr("PropertyChanges does not support creating state-specific objects.")); |
|
253 continue; |
|
254 } else if(value.userType() == qMetaTypeId<QDeclarativeCustomParserProperty>()) { |
|
255 |
|
256 QDeclarativeCustomParserProperty prop = |
|
257 qvariant_cast<QDeclarativeCustomParserProperty>(value); |
|
258 QByteArray pre = propName + '.'; |
|
259 compileList(list, pre, prop); |
|
260 |
|
261 } else { |
|
262 list << qMakePair(propName, value); |
|
263 } |
|
264 } |
|
265 } |
|
266 |
|
267 QByteArray |
|
268 QDeclarativePropertyChangesParser::compile(const QList<QDeclarativeCustomParserProperty> &props) |
|
269 { |
|
270 QList<QPair<QByteArray, QVariant> > data; |
|
271 for(int ii = 0; ii < props.count(); ++ii) |
|
272 compileList(data, QByteArray(), props.at(ii)); |
|
273 |
|
274 QByteArray rv; |
|
275 QDataStream ds(&rv, QIODevice::WriteOnly); |
|
276 |
|
277 ds << data.count(); |
|
278 for(int ii = 0; ii < data.count(); ++ii) { |
|
279 QDeclarativeParser::Variant v = qvariant_cast<QDeclarativeParser::Variant>(data.at(ii).second); |
|
280 QVariant var; |
|
281 bool isScript = v.isScript(); |
|
282 switch(v.type()) { |
|
283 case QDeclarativeParser::Variant::Boolean: |
|
284 var = QVariant(v.asBoolean()); |
|
285 break; |
|
286 case QDeclarativeParser::Variant::Number: |
|
287 var = QVariant(v.asNumber()); |
|
288 break; |
|
289 case QDeclarativeParser::Variant::String: |
|
290 var = QVariant(v.asString()); |
|
291 break; |
|
292 case QDeclarativeParser::Variant::Invalid: |
|
293 case QDeclarativeParser::Variant::Script: |
|
294 var = QVariant(v.asScript()); |
|
295 break; |
|
296 } |
|
297 |
|
298 ds << data.at(ii).first << isScript << var; |
|
299 } |
|
300 |
|
301 return rv; |
|
302 } |
|
303 |
|
304 void QDeclarativePropertyChangesPrivate::decode() |
|
305 { |
|
306 Q_Q(QDeclarativePropertyChanges); |
|
307 if (decoded) |
|
308 return; |
|
309 |
|
310 QDataStream ds(&data, QIODevice::ReadOnly); |
|
311 |
|
312 int count; |
|
313 ds >> count; |
|
314 for (int ii = 0; ii < count; ++ii) { |
|
315 QByteArray name; |
|
316 bool isScript; |
|
317 QVariant data; |
|
318 ds >> name; |
|
319 ds >> isScript; |
|
320 ds >> data; |
|
321 |
|
322 QDeclarativeProperty prop = property(name); //### better way to check for signal property? |
|
323 if (prop.type() & QDeclarativeProperty::SignalProperty) { |
|
324 QDeclarativeExpression *expression = new QDeclarativeExpression(qmlContext(q), object, data.toString()); |
|
325 QDeclarativeData *ddata = QDeclarativeData::get(q); |
|
326 if (ddata && ddata->outerContext && !ddata->outerContext->url.isEmpty()) |
|
327 expression->setSourceLocation(ddata->outerContext->url.toString(), ddata->lineNumber); |
|
328 QDeclarativeReplaceSignalHandler *handler = new QDeclarativeReplaceSignalHandler; |
|
329 handler->property = prop; |
|
330 handler->expression = expression; |
|
331 signalReplacements << handler; |
|
332 } else if (isScript) { |
|
333 QDeclarativeExpression *expression = new QDeclarativeExpression(qmlContext(q), object, data.toString()); |
|
334 QDeclarativeData *ddata = QDeclarativeData::get(q); |
|
335 if (ddata && ddata->outerContext && !ddata->outerContext->url.isEmpty()) |
|
336 expression->setSourceLocation(ddata->outerContext->url.toString(), ddata->lineNumber); |
|
337 expressions << qMakePair(name, expression); |
|
338 } else { |
|
339 properties << qMakePair(name, data); |
|
340 } |
|
341 } |
|
342 |
|
343 decoded = true; |
|
344 data.clear(); |
|
345 } |
|
346 |
|
347 void QDeclarativePropertyChangesParser::setCustomData(QObject *object, |
|
348 const QByteArray &data) |
|
349 { |
|
350 QDeclarativePropertyChangesPrivate *p = |
|
351 static_cast<QDeclarativePropertyChangesPrivate *>(QObjectPrivate::get(object)); |
|
352 p->data = data; |
|
353 p->decoded = false; |
|
354 } |
|
355 |
|
356 QDeclarativePropertyChanges::QDeclarativePropertyChanges() |
|
357 : QDeclarativeStateOperation(*(new QDeclarativePropertyChangesPrivate)) |
|
358 { |
|
359 } |
|
360 |
|
361 QDeclarativePropertyChanges::~QDeclarativePropertyChanges() |
|
362 { |
|
363 Q_D(QDeclarativePropertyChanges); |
|
364 for(int ii = 0; ii < d->expressions.count(); ++ii) |
|
365 delete d->expressions.at(ii).second; |
|
366 for(int ii = 0; ii < d->signalReplacements.count(); ++ii) |
|
367 delete d->signalReplacements.at(ii); |
|
368 } |
|
369 |
|
370 QObject *QDeclarativePropertyChanges::object() const |
|
371 { |
|
372 Q_D(const QDeclarativePropertyChanges); |
|
373 return d->object; |
|
374 } |
|
375 |
|
376 void QDeclarativePropertyChanges::setObject(QObject *o) |
|
377 { |
|
378 Q_D(QDeclarativePropertyChanges); |
|
379 d->object = o; |
|
380 } |
|
381 |
|
382 /*! |
|
383 \qmlproperty bool PropertyChanges::restoreEntryValues |
|
384 |
|
385 Whether or not the previous values should be restored when |
|
386 leaving the state. By default, restoreEntryValues is true. |
|
387 |
|
388 By setting restoreEntryValues to false, you can create a temporary state |
|
389 that has permanent effects on property values. |
|
390 */ |
|
391 bool QDeclarativePropertyChanges::restoreEntryValues() const |
|
392 { |
|
393 Q_D(const QDeclarativePropertyChanges); |
|
394 return d->restore; |
|
395 } |
|
396 |
|
397 void QDeclarativePropertyChanges::setRestoreEntryValues(bool v) |
|
398 { |
|
399 Q_D(QDeclarativePropertyChanges); |
|
400 d->restore = v; |
|
401 } |
|
402 |
|
403 QDeclarativeProperty |
|
404 QDeclarativePropertyChangesPrivate::property(const QByteArray &property) |
|
405 { |
|
406 Q_Q(QDeclarativePropertyChanges); |
|
407 QDeclarativeProperty prop(object, QString::fromUtf8(property), qmlContext(q)); |
|
408 if (!prop.isValid()) { |
|
409 qmlInfo(q) << QDeclarativePropertyChanges::tr("Cannot assign to non-existent property \"%1\"").arg(QString::fromUtf8(property)); |
|
410 return QDeclarativeProperty(); |
|
411 } else if (!(prop.type() & QDeclarativeProperty::SignalProperty) && !prop.isWritable()) { |
|
412 qmlInfo(q) << QDeclarativePropertyChanges::tr("Cannot assign to read-only property \"%1\"").arg(QString::fromUtf8(property)); |
|
413 return QDeclarativeProperty(); |
|
414 } |
|
415 return prop; |
|
416 } |
|
417 |
|
418 QDeclarativePropertyChanges::ActionList QDeclarativePropertyChanges::actions() |
|
419 { |
|
420 Q_D(QDeclarativePropertyChanges); |
|
421 |
|
422 d->decode(); |
|
423 |
|
424 ActionList list; |
|
425 |
|
426 for (int ii = 0; ii < d->properties.count(); ++ii) { |
|
427 |
|
428 QByteArray property = d->properties.at(ii).first; |
|
429 |
|
430 QDeclarativeAction a(d->object, QString::fromUtf8(property), |
|
431 qmlContext(this), d->properties.at(ii).second); |
|
432 |
|
433 if (a.property.isValid()) { |
|
434 a.restore = restoreEntryValues(); |
|
435 list << a; |
|
436 } |
|
437 } |
|
438 |
|
439 for (int ii = 0; ii < d->signalReplacements.count(); ++ii) { |
|
440 |
|
441 QDeclarativeReplaceSignalHandler *handler = d->signalReplacements.at(ii); |
|
442 |
|
443 if (handler->property.isValid()) { |
|
444 QDeclarativeAction a; |
|
445 a.event = handler; |
|
446 list << a; |
|
447 } |
|
448 } |
|
449 |
|
450 for (int ii = 0; ii < d->expressions.count(); ++ii) { |
|
451 |
|
452 QByteArray property = d->expressions.at(ii).first; |
|
453 QDeclarativeProperty prop = d->property(property); |
|
454 |
|
455 if (prop.isValid()) { |
|
456 QDeclarativeAction a; |
|
457 a.restore = restoreEntryValues(); |
|
458 a.property = prop; |
|
459 a.fromValue = a.property.read(); |
|
460 a.specifiedObject = d->object; |
|
461 a.specifiedProperty = QString::fromUtf8(property); |
|
462 |
|
463 if (d->isExplicit) { |
|
464 a.toValue = d->expressions.at(ii).second->evaluate(); |
|
465 } else { |
|
466 QDeclarativeExpression *e = d->expressions.at(ii).second; |
|
467 QDeclarativeBinding *newBinding = |
|
468 new QDeclarativeBinding(e->expression(), object(), qmlContext(this)); |
|
469 newBinding->setTarget(prop); |
|
470 newBinding->setSourceLocation(e->sourceFile(), e->lineNumber()); |
|
471 a.toBinding = newBinding; |
|
472 a.deletableToBinding = true; |
|
473 } |
|
474 |
|
475 list << a; |
|
476 } |
|
477 } |
|
478 |
|
479 return list; |
|
480 } |
|
481 |
|
482 /*! |
|
483 \qmlproperty bool PropertyChanges::explicit |
|
484 |
|
485 If explicit is set to true, any potential bindings will be interpreted as |
|
486 once-off assignments that occur when the state is entered. |
|
487 |
|
488 In the following example, the addition of explicit prevents myItem.width from |
|
489 being bound to parent.width. Instead, it is assigned the value of parent.width |
|
490 at the time of the state change. |
|
491 \qml |
|
492 PropertyChanges { |
|
493 target: myItem |
|
494 explicit: true |
|
495 width: parent.width |
|
496 } |
|
497 \endqml |
|
498 |
|
499 By default, explicit is false. |
|
500 */ |
|
501 bool QDeclarativePropertyChanges::isExplicit() const |
|
502 { |
|
503 Q_D(const QDeclarativePropertyChanges); |
|
504 return d->isExplicit; |
|
505 } |
|
506 |
|
507 void QDeclarativePropertyChanges::setIsExplicit(bool e) |
|
508 { |
|
509 Q_D(QDeclarativePropertyChanges); |
|
510 d->isExplicit = e; |
|
511 } |
|
512 |
|
513 QT_END_NAMESPACE |