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