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

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