1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qquickstate_p_p.h" |
5 | #include "qquickstate_p.h" |
6 | |
7 | #include "qquickstategroup_p.h" |
8 | #include "qquickstatechangescript_p.h" |
9 | |
10 | #include <private/qqmlglobal_p.h> |
11 | |
12 | #include <QtCore/qdebug.h> |
13 | |
14 | QT_BEGIN_NAMESPACE |
15 | |
16 | Q_LOGGING_CATEGORY(lcStates, "qt.qml.states" ) |
17 | |
18 | QQuickStateAction::QQuickStateAction() |
19 | : restore(true), actionDone(false), reverseEvent(false), deletableToBinding(false), fromBinding(nullptr), event(nullptr), |
20 | specifiedObject(nullptr) |
21 | { |
22 | } |
23 | |
24 | QQuickStateAction::QQuickStateAction(QObject *target, const QString &propertyName, |
25 | const QVariant &value) |
26 | : restore(true), actionDone(false), reverseEvent(false), deletableToBinding(false), |
27 | property(target, propertyName, qmlEngine(target)), toValue(value), |
28 | fromBinding(nullptr), event(nullptr), |
29 | specifiedObject(target), specifiedProperty(propertyName) |
30 | { |
31 | if (property.isValid()) |
32 | fromValue = property.read(); |
33 | } |
34 | |
35 | QQuickStateAction::QQuickStateAction(QObject *target, const QQmlProperty &property, const QString &propertyName, const QVariant &value) |
36 | : restore(true), actionDone(false), reverseEvent(false), deletableToBinding(false), |
37 | property(property), toValue(value), |
38 | fromBinding(nullptr), event(nullptr), |
39 | specifiedObject(target), specifiedProperty(propertyName) |
40 | { |
41 | if (property.isValid()) |
42 | fromValue = property.read(); |
43 | } |
44 | |
45 | |
46 | QQuickStateActionEvent::~QQuickStateActionEvent() |
47 | { |
48 | } |
49 | |
50 | void QQuickStateActionEvent::execute() |
51 | { |
52 | } |
53 | |
54 | bool QQuickStateActionEvent::isReversable() |
55 | { |
56 | return false; |
57 | } |
58 | |
59 | void QQuickStateActionEvent::reverse() |
60 | { |
61 | } |
62 | |
63 | bool QQuickStateActionEvent::changesBindings() |
64 | { |
65 | return false; |
66 | } |
67 | |
68 | void QQuickStateActionEvent::clearBindings() |
69 | { |
70 | } |
71 | |
72 | bool QQuickStateActionEvent::mayOverride(QQuickStateActionEvent *other) |
73 | { |
74 | Q_UNUSED(other); |
75 | return false; |
76 | } |
77 | |
78 | QQuickStateOperation::QQuickStateOperation(QObjectPrivate &dd, QObject *parent) |
79 | : QObject(dd, parent) |
80 | { |
81 | } |
82 | |
83 | /*! |
84 | \qmltype State |
85 | \instantiates QQuickState |
86 | \inqmlmodule QtQuick |
87 | \ingroup qtquick-states |
88 | \brief Defines configurations of objects and properties. |
89 | |
90 | A \e state is a set of batched changes from the default configuration. |
91 | |
92 | All items have a default state that defines the default configuration of objects |
93 | and property values. New states can be defined by adding State items to the \l {Item::states}{states} property to |
94 | allow items to switch between different configurations. These configurations |
95 | can, for example, be used to apply different sets of property values or execute |
96 | different scripts. |
97 | |
98 | The following example displays a single \l Rectangle. In the default state, the rectangle |
99 | is colored black. In the "clicked" state, a PropertyChanges object changes the |
100 | rectangle's color to red. Clicking within the MouseArea toggles the rectangle's state |
101 | between the default state and the "clicked" state, thus toggling the color of the |
102 | rectangle between black and red. |
103 | |
104 | \snippet qml/state.qml 0 |
105 | |
106 | Notice the default state is referred to using an empty string (""). |
107 | |
108 | States are commonly used together with \l{Animation and Transitions in Qt Quick}{Transitions} to provide |
109 | animations when state changes occur. |
110 | |
111 | \note Setting the state of an object from within another state of the same object is |
112 | not allowed. |
113 | |
114 | \sa {Qt Quick Examples - Animation#States}{States example}, {Qt Quick States}, |
115 | {Animation and Transitions in Qt Quick}{Transitions}, {Qt QML} |
116 | */ |
117 | QQuickState::QQuickState(QObject *parent) |
118 | : QObject(*(new QQuickStatePrivate), parent) |
119 | { |
120 | Q_D(QQuickState); |
121 | d->transitionManager.setState(this); |
122 | } |
123 | |
124 | QQuickState::~QQuickState() |
125 | { |
126 | Q_D(QQuickState); |
127 | if (d->group) |
128 | d->group->removeState(state: this); |
129 | } |
130 | |
131 | /*! |
132 | \qmlproperty string QtQuick::State::name |
133 | This property holds the name of the state. |
134 | |
135 | Each state should have a unique name within its item. |
136 | */ |
137 | QString QQuickState::name() const |
138 | { |
139 | Q_D(const QQuickState); |
140 | return d->name; |
141 | } |
142 | |
143 | void QQuickState::setName(const QString &n) |
144 | { |
145 | Q_D(QQuickState); |
146 | d->name = n; |
147 | d->named = true; |
148 | } |
149 | |
150 | bool QQuickState::isNamed() const |
151 | { |
152 | Q_D(const QQuickState); |
153 | return d->named; |
154 | } |
155 | |
156 | bool QQuickState::isWhenKnown() const |
157 | { |
158 | Q_D(const QQuickState); |
159 | return d->whenKnown; |
160 | } |
161 | |
162 | /*! |
163 | \qmlproperty bool QtQuick::State::when |
164 | This property holds when the state should be applied. |
165 | |
166 | This should be set to an expression that evaluates to \c true when you want the state to |
167 | be applied. For example, the following \l Rectangle changes in and out of the "hidden" |
168 | state when the \l MouseArea is pressed: |
169 | |
170 | \snippet qml/state-when.qml 0 |
171 | |
172 | If multiple states in a group have \c when clauses that evaluate to \c true |
173 | at the same time, the first matching state will be applied. For example, in |
174 | the following snippet \c state1 will always be selected rather than |
175 | \c state2 when sharedCondition becomes \c true. |
176 | \qml |
177 | Item { |
178 | states: [ |
179 | State { name: "state1"; when: sharedCondition }, |
180 | State { name: "state2"; when: sharedCondition } |
181 | ] |
182 | // ... |
183 | } |
184 | \endqml |
185 | */ |
186 | bool QQuickState::when() const |
187 | { |
188 | Q_D(const QQuickState); |
189 | return d->when; |
190 | } |
191 | |
192 | void QQuickState::setWhen(bool when) |
193 | { |
194 | Q_D(QQuickState); |
195 | d->whenKnown = true; |
196 | d->when = when; |
197 | if (d->group) |
198 | d->group->updateAutoState(); |
199 | } |
200 | |
201 | /*! |
202 | \qmlproperty string QtQuick::State::extend |
203 | This property holds the state that this state extends. |
204 | |
205 | When a state extends another state, it inherits all the changes of that state. |
206 | |
207 | The state being extended is treated as the base state in regards to |
208 | the changes specified by the extending state. |
209 | */ |
210 | QString QQuickState::extends() const |
211 | { |
212 | Q_D(const QQuickState); |
213 | return d->extends; |
214 | } |
215 | |
216 | void QQuickState::setExtends(const QString &extends) |
217 | { |
218 | Q_D(QQuickState); |
219 | d->extends = extends; |
220 | } |
221 | |
222 | /*! |
223 | \qmlproperty list<Change> QtQuick::State::changes |
224 | This property holds the changes to apply for this state |
225 | \qmldefault |
226 | |
227 | By default these changes are applied against the default state. If the state |
228 | extends another state, then the changes are applied against the state being |
229 | extended. |
230 | */ |
231 | QQmlListProperty<QQuickStateOperation> QQuickState::changes() |
232 | { |
233 | Q_D(QQuickState); |
234 | return QQmlListProperty<QQuickStateOperation>(this, &d->operations, |
235 | QQuickStatePrivate::operations_append, |
236 | QQuickStatePrivate::operations_count, |
237 | QQuickStatePrivate::operations_at, |
238 | QQuickStatePrivate::operations_clear, |
239 | QQuickStatePrivate::operations_replace, |
240 | QQuickStatePrivate::operations_removeLast); |
241 | } |
242 | |
243 | int QQuickState::operationCount() const |
244 | { |
245 | Q_D(const QQuickState); |
246 | return d->operations.size(); |
247 | } |
248 | |
249 | QQuickStateOperation *QQuickState::operationAt(int index) const |
250 | { |
251 | Q_D(const QQuickState); |
252 | return d->operations.at(i: index); |
253 | } |
254 | |
255 | QQuickState &QQuickState::operator<<(QQuickStateOperation *op) |
256 | { |
257 | Q_D(QQuickState); |
258 | d->operations.append(t: QQuickStatePrivate::OperationGuard(op, &d->operations)); |
259 | return *this; |
260 | } |
261 | |
262 | void QQuickStatePrivate::complete() |
263 | { |
264 | Q_Q(QQuickState); |
265 | |
266 | for (int ii = 0; ii < reverting.size(); ++ii) { |
267 | for (int jj = 0; jj < revertList.size(); ++jj) { |
268 | const QQuickRevertAction &revert = reverting.at(i: ii); |
269 | const QQuickSimpleAction &simple = revertList.at(i: jj); |
270 | if ((revert.event && simple.event() == revert.event) || |
271 | simple.property() == revert.property) { |
272 | revertList.removeAt(i: jj); |
273 | break; |
274 | } |
275 | } |
276 | } |
277 | reverting.clear(); |
278 | |
279 | if (group) |
280 | group->stateAboutToComplete(); |
281 | emit q->completed(); |
282 | } |
283 | |
284 | // Generate a list of actions for this state. This includes coelescing state |
285 | // actions that this state "extends" |
286 | QQuickStateOperation::ActionList |
287 | QQuickStatePrivate::generateActionList() const |
288 | { |
289 | QQuickStateOperation::ActionList applyList; |
290 | if (inState) |
291 | return applyList; |
292 | |
293 | // Prevent "extends" recursion |
294 | inState = true; |
295 | |
296 | if (!extends.isEmpty()) { |
297 | QList<QQuickState *> states = group ? group->states() : QList<QQuickState *>(); |
298 | for (int ii = 0; ii < states.size(); ++ii) |
299 | if (states.at(i: ii)->name() == extends) { |
300 | qmlExecuteDeferred(states.at(i: ii)); |
301 | applyList = static_cast<QQuickStatePrivate*>(states.at(i: ii)->d_func())->generateActionList(); |
302 | } |
303 | } |
304 | |
305 | for (QQuickStateOperation *op : operations) |
306 | applyList << op->actions(); |
307 | |
308 | inState = false; |
309 | return applyList; |
310 | } |
311 | |
312 | QQuickStateGroup *QQuickState::stateGroup() const |
313 | { |
314 | Q_D(const QQuickState); |
315 | return d->group; |
316 | } |
317 | |
318 | void QQuickState::setStateGroup(QQuickStateGroup *group) |
319 | { |
320 | Q_D(QQuickState); |
321 | d->group = group; |
322 | } |
323 | |
324 | void QQuickState::cancel() |
325 | { |
326 | Q_D(QQuickState); |
327 | d->transitionManager.cancel(); |
328 | } |
329 | |
330 | void QQuickStateAction::deleteFromBinding() |
331 | { |
332 | if (fromBinding) { |
333 | if (fromBinding.isAbstractPropertyBinding()) { |
334 | QQmlPropertyPrivate::removeBinding(that: property); |
335 | fromBinding = nullptr; |
336 | } |
337 | } |
338 | } |
339 | |
340 | bool QQuickState::containsPropertyInRevertList(QObject *target, const QString &name) const |
341 | { |
342 | Q_D(const QQuickState); |
343 | |
344 | if (isStateActive()) { |
345 | for (const QQuickSimpleAction &simpleAction : d->revertList) { |
346 | if (simpleAction.specifiedObject() == target && simpleAction.specifiedProperty() == name) |
347 | return true; |
348 | } |
349 | } |
350 | |
351 | return false; |
352 | } |
353 | |
354 | bool QQuickState::changeValueInRevertList(QObject *target, const QString &name, const QVariant &revertValue) |
355 | { |
356 | Q_D(QQuickState); |
357 | |
358 | if (isStateActive()) { |
359 | for (QQuickSimpleAction &simpleAction : d->revertList) { |
360 | if (simpleAction.specifiedObject() == target && simpleAction.specifiedProperty() == name) { |
361 | simpleAction.setValue(revertValue); |
362 | return true; |
363 | } |
364 | } |
365 | } |
366 | |
367 | return false; |
368 | } |
369 | |
370 | bool QQuickState::changeBindingInRevertList(QObject *target, const QString &name, QQmlAnyBinding binding) |
371 | { |
372 | Q_D(QQuickState); |
373 | |
374 | if (isStateActive()) { |
375 | for (QQuickSimpleAction &simpleAction : d->revertList) { |
376 | if (simpleAction.specifiedObject() == target && simpleAction.specifiedProperty() == name) { |
377 | simpleAction.setBinding(binding); |
378 | return true; |
379 | } |
380 | } |
381 | } |
382 | |
383 | return false; |
384 | } |
385 | |
386 | bool QQuickState::removeEntryFromRevertList(QObject *target, const QString &name) |
387 | { |
388 | Q_D(QQuickState); |
389 | |
390 | if (isStateActive()) { |
391 | for (auto it = d->revertList.begin(), end = d->revertList.end(); it != end; ++it) { |
392 | QQuickSimpleAction &simpleAction = *it; |
393 | if (simpleAction.property().object() == target && simpleAction.property().name() == name) { |
394 | QQmlPropertyPrivate::removeBinding(that: simpleAction.property()); |
395 | |
396 | simpleAction.property().write(simpleAction.value()); |
397 | if (auto binding = simpleAction.binding(); binding) { |
398 | QQmlProperty prop = simpleAction.property(); |
399 | binding.installOn(target: prop); |
400 | } |
401 | |
402 | d->revertList.erase(pos: it); |
403 | return true; |
404 | } |
405 | } |
406 | } |
407 | |
408 | return false; |
409 | } |
410 | |
411 | void QQuickState::addEntryToRevertList(const QQuickStateAction &action) |
412 | { |
413 | Q_D(QQuickState); |
414 | |
415 | QQuickSimpleAction simpleAction(action); |
416 | |
417 | d->revertList.append(t: simpleAction); |
418 | } |
419 | |
420 | void QQuickState::removeAllEntriesFromRevertList(QObject *target) |
421 | { |
422 | Q_D(QQuickState); |
423 | |
424 | if (isStateActive()) { |
425 | const auto actionMatchesTarget = [target](QQuickSimpleAction &simpleAction) { |
426 | if (simpleAction.property().object() == target) { |
427 | QQmlPropertyPrivate::removeBinding(that: simpleAction.property()); |
428 | simpleAction.property().write(simpleAction.value()); |
429 | if (auto binding = simpleAction.binding()) { |
430 | QQmlProperty prop = simpleAction.property(); |
431 | binding.installOn(target: prop); |
432 | } |
433 | |
434 | return true; |
435 | } |
436 | return false; |
437 | }; |
438 | |
439 | d->revertList.erase(begin: std::remove_if(first: d->revertList.begin(), last: d->revertList.end(), |
440 | pred: actionMatchesTarget), |
441 | end: d->revertList.end()); |
442 | } |
443 | } |
444 | |
445 | void QQuickState::addEntriesToRevertList(const QList<QQuickStateAction> &actionList) |
446 | { |
447 | Q_D(QQuickState); |
448 | if (isStateActive()) { |
449 | QList<QQuickSimpleAction> simpleActionList; |
450 | simpleActionList.reserve(size: actionList.size()); |
451 | |
452 | for (const QQuickStateAction &action : actionList) { |
453 | QQuickSimpleAction simpleAction(action); |
454 | action.property.write(action.toValue); |
455 | if (auto binding = action.toBinding; binding) |
456 | binding.installOn(target: action.property); |
457 | |
458 | simpleActionList.append(t: simpleAction); |
459 | } |
460 | |
461 | d->revertList.append(l: simpleActionList); |
462 | } |
463 | } |
464 | |
465 | QVariant QQuickState::valueInRevertList(QObject *target, const QString &name) const |
466 | { |
467 | Q_D(const QQuickState); |
468 | |
469 | if (isStateActive()) { |
470 | for (const QQuickSimpleAction &simpleAction : d->revertList) { |
471 | if (simpleAction.specifiedObject() == target && simpleAction.specifiedProperty() == name) |
472 | return simpleAction.value(); |
473 | } |
474 | } |
475 | |
476 | return QVariant(); |
477 | } |
478 | |
479 | QQmlAnyBinding QQuickState::bindingInRevertList(QObject *target, const QString &name) const |
480 | { |
481 | Q_D(const QQuickState); |
482 | |
483 | if (isStateActive()) { |
484 | for (const QQuickSimpleAction &simpleAction : d->revertList) { |
485 | if (simpleAction.specifiedObject() == target && simpleAction.specifiedProperty() == name) |
486 | return simpleAction.binding(); |
487 | } |
488 | } |
489 | |
490 | return nullptr; |
491 | } |
492 | |
493 | bool QQuickState::isStateActive() const |
494 | { |
495 | return stateGroup() && stateGroup()->state() == name(); |
496 | } |
497 | |
498 | void QQuickState::apply(QQuickTransition *trans, QQuickState *revert) |
499 | { |
500 | Q_D(QQuickState); |
501 | |
502 | qmlExecuteDeferred(this); |
503 | |
504 | cancel(); |
505 | if (revert) |
506 | revert->cancel(); |
507 | d->revertList.clear(); |
508 | d->reverting.clear(); |
509 | |
510 | if (revert) { |
511 | QQuickStatePrivate *revertPrivate = |
512 | static_cast<QQuickStatePrivate*>(revert->d_func()); |
513 | d->revertList = revertPrivate->revertList; |
514 | revertPrivate->revertList.clear(); |
515 | } |
516 | |
517 | // List of actions caused by this state |
518 | QQuickStateOperation::ActionList applyList = d->generateActionList(); |
519 | |
520 | // List of actions that need to be reverted to roll back (just) this state |
521 | QQuickStatePrivate::SimpleActionList additionalReverts; |
522 | // First add the reverse of all the applyList actions |
523 | for (int ii = 0; ii < applyList.size(); ++ii) { |
524 | QQuickStateAction &action = applyList[ii]; |
525 | |
526 | if (action.event) { |
527 | if (!action.event->isReversable()) |
528 | continue; |
529 | bool found = false; |
530 | for (int jj = 0; jj < d->revertList.size(); ++jj) { |
531 | QQuickStateActionEvent *event = d->revertList.at(i: jj).event(); |
532 | if (event && event->type() == action.event->type()) { |
533 | if (action.event->mayOverride(other: event)) { |
534 | found = true; |
535 | |
536 | if (action.event != d->revertList.at(i: jj).event() && action.event->needsCopy()) { |
537 | action.event->copyOriginals(d->revertList.at(i: jj).event()); |
538 | |
539 | QQuickSimpleAction r(action); |
540 | additionalReverts << r; |
541 | d->revertList.removeAt(i: jj); |
542 | --jj; |
543 | } else if (action.event->isRewindable()) //###why needed? |
544 | action.event->saveCurrentValues(); |
545 | |
546 | break; |
547 | } |
548 | } |
549 | } |
550 | if (!found) { |
551 | action.event->saveOriginals(); |
552 | // Only need to revert the applyList action if the previous |
553 | // state doesn't have a higher priority revert already |
554 | QQuickSimpleAction r(action); |
555 | additionalReverts << r; |
556 | } |
557 | } else { |
558 | bool found = false; |
559 | action.fromBinding = QQmlAnyBinding::ofProperty(prop: action.property); |
560 | |
561 | for (int jj = 0; jj < d->revertList.size(); ++jj) { |
562 | if (d->revertList.at(i: jj).property() == action.property) { |
563 | found = true; |
564 | if (d->revertList.at(i: jj).binding() != action.fromBinding) { |
565 | action.deleteFromBinding(); |
566 | } |
567 | break; |
568 | } |
569 | } |
570 | |
571 | if (!found) { |
572 | if (!action.restore) { |
573 | action.deleteFromBinding();; |
574 | } else { |
575 | // Only need to revert the applyList action if the previous |
576 | // state doesn't have a higher priority revert already |
577 | QQuickSimpleAction r(action); |
578 | additionalReverts << r; |
579 | } |
580 | } |
581 | } |
582 | } |
583 | |
584 | // Any reverts from a previous state that aren't carried forth |
585 | // into this state need to be translated into apply actions |
586 | for (int ii = 0; ii < d->revertList.size(); ++ii) { |
587 | bool found = false; |
588 | if (d->revertList.at(i: ii).event()) { |
589 | QQuickStateActionEvent *event = d->revertList.at(i: ii).event(); |
590 | if (!event->isReversable()) |
591 | continue; |
592 | for (int jj = 0; !found && jj < applyList.size(); ++jj) { |
593 | const QQuickStateAction &action = applyList.at(i: jj); |
594 | if (action.event && action.event->type() == event->type()) { |
595 | if (action.event->mayOverride(other: event)) |
596 | found = true; |
597 | } |
598 | } |
599 | } else { |
600 | for (int jj = 0; !found && jj < applyList.size(); ++jj) { |
601 | const QQuickStateAction &action = applyList.at(i: jj); |
602 | if (action.property == d->revertList.at(i: ii).property()) |
603 | found = true; |
604 | } |
605 | } |
606 | if (!found) { |
607 | // If revert list contains bindings assigned to deleted objects, we need to |
608 | // prevent reverting properties of those objects. |
609 | if (d->revertList.at(i: ii).binding() && !d->revertList.at(i: ii).property().object()) { |
610 | continue; |
611 | } |
612 | QVariant cur = d->revertList.at(i: ii).property().read(); |
613 | QQmlProperty prop = d->revertList.at(i: ii).property(); |
614 | QQmlAnyBinding::removeBindingFrom(prop); |
615 | |
616 | QQuickStateAction a; |
617 | a.property = d->revertList.at(i: ii).property(); |
618 | a.fromValue = cur; |
619 | a.toValue = d->revertList.at(i: ii).value(); |
620 | a.toBinding = d->revertList.at(i: ii).binding(); |
621 | a.specifiedObject = d->revertList.at(i: ii).specifiedObject(); |
622 | a.specifiedProperty = d->revertList.at(i: ii).specifiedProperty(); |
623 | a.event = d->revertList.at(i: ii).event(); |
624 | a.reverseEvent = d->revertList.at(i: ii).reverseEvent(); |
625 | if (a.event && a.event->isRewindable()) |
626 | a.event->saveCurrentValues(); |
627 | applyList << a; |
628 | // Store these special reverts in the reverting list |
629 | if (a.event) |
630 | d->reverting << a.event; |
631 | else |
632 | d->reverting << a.property; |
633 | } |
634 | } |
635 | // All the local reverts now become part of the ongoing revertList |
636 | d->revertList << additionalReverts; |
637 | |
638 | if (lcStates().isDebugEnabled()) { |
639 | for (const QQuickStateAction &action : std::as_const(t&: applyList)) { |
640 | if (action.event) |
641 | qCDebug(lcStates) << "QQuickStateAction event:" << action.event->type(); |
642 | else |
643 | qCDebug(lcStates) << "QQuickStateAction on" << action.property.object() |
644 | << action.property.name() << "from:" << action.fromValue |
645 | << "to:" << action.toValue; |
646 | } |
647 | } |
648 | |
649 | d->transitionManager.transition(applyList, transition: trans); |
650 | } |
651 | |
652 | QQuickStateOperation::ActionList QQuickStateOperation::actions() |
653 | { |
654 | return ActionList(); |
655 | } |
656 | |
657 | QQuickState *QQuickStateOperation::state() const |
658 | { |
659 | Q_D(const QQuickStateOperation); |
660 | return d->m_state; |
661 | } |
662 | |
663 | void QQuickStateOperation::setState(QQuickState *state) |
664 | { |
665 | Q_D(QQuickStateOperation); |
666 | d->m_state = state; |
667 | } |
668 | |
669 | QT_END_NAMESPACE |
670 | |
671 | #include "moc_qquickstate_p.cpp" |
672 | |