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
14QT_BEGIN_NAMESPACE
15
16Q_LOGGING_CATEGORY(lcStates, "qt.qml.states")
17
18QQuickStateAction::QQuickStateAction()
19: restore(true), actionDone(false), reverseEvent(false), deletableToBinding(false), fromBinding(nullptr), event(nullptr),
20 specifiedObject(nullptr)
21{
22}
23
24QQuickStateAction::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
35QQuickStateAction::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
46QQuickStateActionEvent::~QQuickStateActionEvent()
47{
48}
49
50void QQuickStateActionEvent::execute()
51{
52}
53
54bool QQuickStateActionEvent::isReversable()
55{
56 return false;
57}
58
59void QQuickStateActionEvent::reverse()
60{
61}
62
63bool QQuickStateActionEvent::changesBindings()
64{
65 return false;
66}
67
68void QQuickStateActionEvent::clearBindings()
69{
70}
71
72bool QQuickStateActionEvent::mayOverride(QQuickStateActionEvent *other)
73{
74 Q_UNUSED(other);
75 return false;
76}
77
78QQuickStateOperation::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*/
117QQuickState::QQuickState(QObject *parent)
118: QObject(*(new QQuickStatePrivate), parent)
119{
120 Q_D(QQuickState);
121 d->transitionManager.setState(this);
122}
123
124QQuickState::~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*/
137QString QQuickState::name() const
138{
139 Q_D(const QQuickState);
140 return d->name;
141}
142
143void QQuickState::setName(const QString &n)
144{
145 Q_D(QQuickState);
146 d->name = n;
147 d->named = true;
148}
149
150bool QQuickState::isNamed() const
151{
152 Q_D(const QQuickState);
153 return d->named;
154}
155
156bool 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*/
186bool QQuickState::when() const
187{
188 Q_D(const QQuickState);
189 return d->when;
190}
191
192void 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*/
210QString QQuickState::extends() const
211{
212 Q_D(const QQuickState);
213 return d->extends;
214}
215
216void 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*/
231QQmlListProperty<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
243int QQuickState::operationCount() const
244{
245 Q_D(const QQuickState);
246 return d->operations.size();
247}
248
249QQuickStateOperation *QQuickState::operationAt(int index) const
250{
251 Q_D(const QQuickState);
252 return d->operations.at(i: index);
253}
254
255QQuickState &QQuickState::operator<<(QQuickStateOperation *op)
256{
257 Q_D(QQuickState);
258 d->operations.append(t: QQuickStatePrivate::OperationGuard(op, &d->operations));
259 return *this;
260}
261
262void 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"
286QQuickStateOperation::ActionList
287QQuickStatePrivate::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
312QQuickStateGroup *QQuickState::stateGroup() const
313{
314 Q_D(const QQuickState);
315 return d->group;
316}
317
318void QQuickState::setStateGroup(QQuickStateGroup *group)
319{
320 Q_D(QQuickState);
321 d->group = group;
322}
323
324void QQuickState::cancel()
325{
326 Q_D(QQuickState);
327 d->transitionManager.cancel();
328}
329
330void QQuickStateAction::deleteFromBinding()
331{
332 if (fromBinding) {
333 if (fromBinding.isAbstractPropertyBinding()) {
334 QQmlPropertyPrivate::removeBinding(that: property);
335 fromBinding = nullptr;
336 }
337 }
338}
339
340bool 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
354bool 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
370bool 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
386bool 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
411void QQuickState::addEntryToRevertList(const QQuickStateAction &action)
412{
413 Q_D(QQuickState);
414
415 QQuickSimpleAction simpleAction(action);
416
417 d->revertList.append(t: simpleAction);
418}
419
420void 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
445void 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
465QVariant 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
479QQmlAnyBinding 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
493bool QQuickState::isStateActive() const
494{
495 return stateGroup() && stateGroup()->state() == name();
496}
497
498void 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
652QQuickStateOperation::ActionList QQuickStateOperation::actions()
653{
654 return ActionList();
655}
656
657QQuickState *QQuickStateOperation::state() const
658{
659 Q_D(const QQuickStateOperation);
660 return d->m_state;
661}
662
663void QQuickStateOperation::setState(QQuickState *state)
664{
665 Q_D(QQuickStateOperation);
666 d->m_state = state;
667}
668
669QT_END_NAMESPACE
670
671#include "moc_qquickstate_p.cpp"
672

source code of qtdeclarative/src/quick/util/qquickstate.cpp