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(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, QQuickStatePrivate::operations_append,
271 QQuickStatePrivate::operations_count, QQuickStatePrivate::operations_at,
272 QQuickStatePrivate::operations_clear);
273}
274
275int QQuickState::operationCount() const
276{
277 Q_D(const QQuickState);
278 return d->operations.count();
279}
280
281QQuickStateOperation *QQuickState::operationAt(int index) const
282{
283 Q_D(const QQuickState);
284 return d->operations.at(index);
285}
286
287QQuickState &QQuickState::operator<<(QQuickStateOperation *op)
288{
289 Q_D(QQuickState);
290 d->operations.append(QQuickStatePrivate::OperationGuard(op, &d->operations));
291 return *this;
292}
293
294void QQuickStatePrivate::complete()
295{
296 Q_Q(QQuickState);
297
298 for (int ii = 0; ii < reverting.count(); ++ii) {
299 for (int jj = 0; jj < revertList.count(); ++jj) {
300 const QQuickRevertAction &revert = reverting.at(ii);
301 const QQuickSimpleAction &simple = revertList.at(jj);
302 if ((revert.event && simple.event() == revert.event) ||
303 simple.property() == revert.property) {
304 revertList.removeAt(jj);
305 break;
306 }
307 }
308 }
309 reverting.clear();
310
311 if (group)
312 group->stateAboutToComplete();
313 emit q->completed();
314}
315
316// Generate a list of actions for this state. This includes coelescing state
317// actions that this state "extends"
318QQuickStateOperation::ActionList
319QQuickStatePrivate::generateActionList() const
320{
321 QQuickStateOperation::ActionList applyList;
322 if (inState)
323 return applyList;
324
325 // Prevent "extends" recursion
326 inState = true;
327
328 if (!extends.isEmpty()) {
329 QList<QQuickState *> states = group ? group->states() : QList<QQuickState *>();
330 for (int ii = 0; ii < states.count(); ++ii)
331 if (states.at(ii)->name() == extends) {
332 qmlExecuteDeferred(states.at(ii));
333 applyList = static_cast<QQuickStatePrivate*>(states.at(ii)->d_func())->generateActionList();
334 }
335 }
336
337 for (QQuickStateOperation *op : operations)
338 applyList << op->actions();
339
340 inState = false;
341 return applyList;
342}
343
344QQuickStateGroup *QQuickState::stateGroup() const
345{
346 Q_D(const QQuickState);
347 return d->group;
348}
349
350void QQuickState::setStateGroup(QQuickStateGroup *group)
351{
352 Q_D(QQuickState);
353 d->group = group;
354}
355
356void QQuickState::cancel()
357{
358 Q_D(QQuickState);
359 d->transitionManager.cancel();
360}
361
362void QQuickStateAction::deleteFromBinding()
363{
364 if (fromBinding) {
365 QQmlPropertyPrivate::removeBinding(property);
366 fromBinding = nullptr;
367 }
368}
369
370bool QQuickState::containsPropertyInRevertList(QObject *target, const QString &name) const
371{
372 Q_D(const QQuickState);
373
374 if (isStateActive()) {
375 for (const QQuickSimpleAction &simpleAction : d->revertList) {
376 if (simpleAction.specifiedObject() == target && simpleAction.specifiedProperty() == name)
377 return true;
378 }
379 }
380
381 return false;
382}
383
384bool QQuickState::changeValueInRevertList(QObject *target, const QString &name, const QVariant &revertValue)
385{
386 Q_D(QQuickState);
387
388 if (isStateActive()) {
389 for (QQuickSimpleAction &simpleAction : d->revertList) {
390 if (simpleAction.specifiedObject() == target && simpleAction.specifiedProperty() == name) {
391 simpleAction.setValue(revertValue);
392 return true;
393 }
394 }
395 }
396
397 return false;
398}
399
400bool QQuickState::changeBindingInRevertList(QObject *target, const QString &name, QQmlAbstractBinding *binding)
401{
402 Q_D(QQuickState);
403
404 if (isStateActive()) {
405 for (QQuickSimpleAction &simpleAction : d->revertList) {
406 if (simpleAction.specifiedObject() == target && simpleAction.specifiedProperty() == name) {
407 simpleAction.setBinding(binding);
408 return true;
409 }
410 }
411 }
412
413 return false;
414}
415
416bool QQuickState::removeEntryFromRevertList(QObject *target, const QString &name)
417{
418 Q_D(QQuickState);
419
420 if (isStateActive()) {
421 for (auto it = d->revertList.begin(), end = d->revertList.end(); it != end; ++it) {
422 QQuickSimpleAction &simpleAction = *it;
423 if (simpleAction.property().object() == target && simpleAction.property().name() == name) {
424 QQmlPropertyPrivate::removeBinding(simpleAction.property());
425
426 simpleAction.property().write(simpleAction.value());
427 if (simpleAction.binding())
428 QQmlPropertyPrivate::setBinding(simpleAction.binding());
429
430 d->revertList.erase(it);
431 return true;
432 }
433 }
434 }
435
436 return false;
437}
438
439void QQuickState::addEntryToRevertList(const QQuickStateAction &action)
440{
441 Q_D(QQuickState);
442
443 QQuickSimpleAction simpleAction(action);
444
445 d->revertList.append(simpleAction);
446}
447
448void QQuickState::removeAllEntriesFromRevertList(QObject *target)
449{
450 Q_D(QQuickState);
451
452 if (isStateActive()) {
453 const auto actionMatchesTarget = [target](QQuickSimpleAction &simpleAction) {
454 if (simpleAction.property().object() == target) {
455 QQmlPropertyPrivate::removeBinding(simpleAction.property());
456
457 simpleAction.property().write(simpleAction.value());
458 if (simpleAction.binding())
459 QQmlPropertyPrivate::setBinding(simpleAction.binding());
460
461 return true;
462 }
463 return false;
464 };
465
466 d->revertList.erase(std::remove_if(d->revertList.begin(), d->revertList.end(),
467 actionMatchesTarget),
468 d->revertList.end());
469 }
470}
471
472void QQuickState::addEntriesToRevertList(const QList<QQuickStateAction> &actionList)
473{
474 Q_D(QQuickState);
475 if (isStateActive()) {
476 QList<QQuickSimpleAction> simpleActionList;
477 simpleActionList.reserve(actionList.count());
478
479 for (const QQuickStateAction &action : actionList) {
480 QQuickSimpleAction simpleAction(action);
481 action.property.write(action.toValue);
482 if (action.toBinding)
483 QQmlPropertyPrivate::setBinding(action.toBinding.data());
484
485 simpleActionList.append(simpleAction);
486 }
487
488 d->revertList.append(simpleActionList);
489 }
490}
491
492QVariant QQuickState::valueInRevertList(QObject *target, const QString &name) const
493{
494 Q_D(const QQuickState);
495
496 if (isStateActive()) {
497 for (const QQuickSimpleAction &simpleAction : d->revertList) {
498 if (simpleAction.specifiedObject() == target && simpleAction.specifiedProperty() == name)
499 return simpleAction.value();
500 }
501 }
502
503 return QVariant();
504}
505
506QQmlAbstractBinding *QQuickState::bindingInRevertList(QObject *target, const QString &name) const
507{
508 Q_D(const QQuickState);
509
510 if (isStateActive()) {
511 for (const QQuickSimpleAction &simpleAction : d->revertList) {
512 if (simpleAction.specifiedObject() == target && simpleAction.specifiedProperty() == name)
513 return simpleAction.binding();
514 }
515 }
516
517 return nullptr;
518}
519
520bool QQuickState::isStateActive() const
521{
522 return stateGroup() && stateGroup()->state() == name();
523}
524
525void QQuickState::apply(QQuickTransition *trans, QQuickState *revert)
526{
527 Q_D(QQuickState);
528
529 qmlExecuteDeferred(this);
530
531 cancel();
532 if (revert)
533 revert->cancel();
534 d->revertList.clear();
535 d->reverting.clear();
536
537 if (revert) {
538 QQuickStatePrivate *revertPrivate =
539 static_cast<QQuickStatePrivate*>(revert->d_func());
540 d->revertList = revertPrivate->revertList;
541 revertPrivate->revertList.clear();
542 }
543
544 // List of actions caused by this state
545 QQuickStateOperation::ActionList applyList = d->generateActionList();
546
547 // List of actions that need to be reverted to roll back (just) this state
548 QQuickStatePrivate::SimpleActionList additionalReverts;
549 // First add the reverse of all the applyList actions
550 for (int ii = 0; ii < applyList.count(); ++ii) {
551 QQuickStateAction &action = applyList[ii];
552
553 if (action.event) {
554 if (!action.event->isReversable())
555 continue;
556 bool found = false;
557 for (int jj = 0; jj < d->revertList.count(); ++jj) {
558 QQuickStateActionEvent *event = d->revertList.at(jj).event();
559 if (event && event->type() == action.event->type()) {
560 if (action.event->mayOverride(event)) {
561 found = true;
562
563 if (action.event != d->revertList.at(jj).event() && action.event->needsCopy()) {
564 action.event->copyOriginals(d->revertList.at(jj).event());
565
566 QQuickSimpleAction r(action);
567 additionalReverts << r;
568 d->revertList.removeAt(jj);
569 --jj;
570 } else if (action.event->isRewindable()) //###why needed?
571 action.event->saveCurrentValues();
572
573 break;
574 }
575 }
576 }
577 if (!found) {
578 action.event->saveOriginals();
579 // Only need to revert the applyList action if the previous
580 // state doesn't have a higher priority revert already
581 QQuickSimpleAction r(action);
582 additionalReverts << r;
583 }
584 } else {
585 bool found = false;
586 action.fromBinding = QQmlPropertyPrivate::binding(action.property);
587
588 for (int jj = 0; jj < d->revertList.count(); ++jj) {
589 if (d->revertList.at(jj).property() == action.property) {
590 found = true;
591 if (d->revertList.at(jj).binding() != action.fromBinding.data()) {
592 action.deleteFromBinding();
593 }
594 break;
595 }
596 }
597
598 if (!found) {
599 if (!action.restore) {
600 action.deleteFromBinding();;
601 } else {
602 // Only need to revert the applyList action if the previous
603 // state doesn't have a higher priority revert already
604 QQuickSimpleAction r(action);
605 additionalReverts << r;
606 }
607 }
608 }
609 }
610
611 // Any reverts from a previous state that aren't carried forth
612 // into this state need to be translated into apply actions
613 for (int ii = 0; ii < d->revertList.count(); ++ii) {
614 bool found = false;
615 if (d->revertList.at(ii).event()) {
616 QQuickStateActionEvent *event = d->revertList.at(ii).event();
617 if (!event->isReversable())
618 continue;
619 for (int jj = 0; !found && jj < applyList.count(); ++jj) {
620 const QQuickStateAction &action = applyList.at(jj);
621 if (action.event && action.event->type() == event->type()) {
622 if (action.event->mayOverride(event))
623 found = true;
624 }
625 }
626 } else {
627 for (int jj = 0; !found && jj < applyList.count(); ++jj) {
628 const QQuickStateAction &action = applyList.at(jj);
629 if (action.property == d->revertList.at(ii).property())
630 found = true;
631 }
632 }
633 if (!found) {
634 QVariant cur = d->revertList.at(ii).property().read();
635 QQmlPropertyPrivate::removeBinding(d->revertList.at(ii).property());
636
637 QQuickStateAction a;
638 a.property = d->revertList.at(ii).property();
639 a.fromValue = cur;
640 a.toValue = d->revertList.at(ii).value();
641 a.toBinding = d->revertList.at(ii).binding();
642 a.specifiedObject = d->revertList.at(ii).specifiedObject();
643 a.specifiedProperty = d->revertList.at(ii).specifiedProperty();
644 a.event = d->revertList.at(ii).event();
645 a.reverseEvent = d->revertList.at(ii).reverseEvent();
646 if (a.event && a.event->isRewindable())
647 a.event->saveCurrentValues();
648 applyList << a;
649 // Store these special reverts in the reverting list
650 if (a.event)
651 d->reverting << a.event;
652 else
653 d->reverting << a.property;
654 }
655 }
656 // All the local reverts now become part of the ongoing revertList
657 d->revertList << additionalReverts;
658
659#ifndef QT_NO_DEBUG_STREAM
660 // Output for debugging
661 if (stateChangeDebug()) {
662 for (const QQuickStateAction &action : qAsConst(applyList)) {
663 if (action.event)
664 qWarning() << " QQuickStateAction event:" << action.event->type();
665 else
666 qWarning() << " QQuickStateAction:" << action.property.object()
667 << action.property.name() << "From:" << action.fromValue
668 << "To:" << action.toValue;
669 }
670 }
671#endif
672
673 d->transitionManager.transition(applyList, trans);
674}
675
676QQuickStateOperation::ActionList QQuickStateOperation::actions()
677{
678 return ActionList();
679}
680
681QQuickState *QQuickStateOperation::state() const
682{
683 Q_D(const QQuickStateOperation);
684 return d->m_state;
685}
686
687void QQuickStateOperation::setState(QQuickState *state)
688{
689 Q_D(QQuickStateOperation);
690 d->m_state = state;
691}
692
693QT_END_NAMESPACE
694
695#include "moc_qquickstate_p.cpp"
696