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 "qquickstategroup_p.h"
5
6#include "qquicktransition_p.h"
7
8#include <private/qqmlbinding_p.h>
9#include <private/qqmlglobal_p.h>
10
11#include <QtCore/qstringlist.h>
12#include <QtCore/qdebug.h>
13#include <QtCore/qvector.h>
14
15#include <private/qobject_p.h>
16#include <qqmlinfo.h>
17
18QT_BEGIN_NAMESPACE
19
20using namespace Qt::StringLiterals;
21
22class QQuickStateGroupPrivate : public QObjectPrivate
23{
24 Q_DECLARE_PUBLIC(QQuickStateGroup)
25public:
26 QQuickStateGroupPrivate()
27 : nullState(nullptr), componentComplete(true),
28 ignoreTrans(false), applyingState(false), unnamedCount(0) {}
29
30 QString currentState;
31 QQuickState *nullState;
32
33 static void append_state(QQmlListProperty<QQuickState> *list, QQuickState *state);
34 static qsizetype count_state(QQmlListProperty<QQuickState> *list);
35 static QQuickState *at_state(QQmlListProperty<QQuickState> *list, qsizetype index);
36 static void clear_states(QQmlListProperty<QQuickState> *list);
37 static void replace_states(QQmlListProperty<QQuickState> *list, qsizetype index, QQuickState *state);
38 static void removeLast_states(QQmlListProperty<QQuickState> *list);
39
40 static void append_transition(QQmlListProperty<QQuickTransition> *list, QQuickTransition *state);
41 static qsizetype count_transitions(QQmlListProperty<QQuickTransition> *list);
42 static QQuickTransition *at_transition(QQmlListProperty<QQuickTransition> *list, qsizetype index);
43 static void clear_transitions(QQmlListProperty<QQuickTransition> *list);
44
45 QList<QQuickState *> states;
46 QList<QQuickTransition *> transitions;
47
48 bool componentComplete;
49 bool ignoreTrans;
50 bool applyingState;
51 int unnamedCount;
52
53 QQuickTransition *findTransition(const QString &from, const QString &to);
54 void setCurrentStateInternal(const QString &state, bool = false);
55 bool updateAutoState();
56};
57
58/*!
59 \qmltype StateGroup
60 \nativetype QQuickStateGroup
61 \inqmlmodule QtQuick
62 \ingroup qtquick-states
63 \brief Provides built-in state support for non-Item types.
64
65 Item (and all derived types) provides built in support for states and transitions
66 via its \l{Item::state}{state}, \l{Item::states}{states} and \l{Item::transitions}{transitions} properties. StateGroup provides an easy way to
67 use this support in other (non-Item-derived) types.
68
69 \qml
70 MyCustomObject {
71 StateGroup {
72 id: myStateGroup
73 states: State {
74 name: "state1"
75 // ...
76 }
77 transitions: Transition {
78 // ...
79 }
80 }
81
82 onSomethingHappened: myStateGroup.state = "state1";
83 }
84 \endqml
85
86 \sa {Qt Quick States}{Qt Quick States}, {Animation and Transitions in Qt Quick}{Transitions}, {Qt Qml}
87*/
88
89QQuickStateGroup::QQuickStateGroup(QObject *parent)
90 : QObject(*(new QQuickStateGroupPrivate), parent)
91{
92}
93
94QQuickStateGroup::~QQuickStateGroup()
95{
96 Q_D(const QQuickStateGroup);
97 for (QQuickState *state : std::as_const(t: d->states)) {
98 if (state)
99 state->setStateGroup(nullptr);
100 }
101 if (d->nullState)
102 d->nullState->setStateGroup(nullptr);
103}
104
105QList<QQuickState *> QQuickStateGroup::states() const
106{
107 Q_D(const QQuickStateGroup);
108 return d->states;
109}
110
111/*!
112 \qmlproperty list<State> QtQuick::StateGroup::states
113 This property holds a list of states defined by the state group.
114
115 \qml
116 StateGroup {
117 states: [
118 State {
119 // State definition...
120 },
121 State {
122 // ...
123 }
124 // Other states...
125 ]
126 }
127 \endqml
128
129 \sa {Qt Quick States}{Qt Quick States}
130*/
131QQmlListProperty<QQuickState> QQuickStateGroup::statesProperty()
132{
133 Q_D(QQuickStateGroup);
134 return QQmlListProperty<QQuickState>(this, &d->states,
135 &QQuickStateGroupPrivate::append_state,
136 &QQuickStateGroupPrivate::count_state,
137 &QQuickStateGroupPrivate::at_state,
138 &QQuickStateGroupPrivate::clear_states,
139 &QQuickStateGroupPrivate::replace_states,
140 &QQuickStateGroupPrivate::removeLast_states);
141}
142
143void QQuickStateGroupPrivate::append_state(QQmlListProperty<QQuickState> *list, QQuickState *state)
144{
145 QQuickStateGroup *_this = static_cast<QQuickStateGroup *>(list->object);
146 _this->d_func()->states.append(t: state);
147 if (state)
148 state->setStateGroup(_this);
149}
150
151qsizetype QQuickStateGroupPrivate::count_state(QQmlListProperty<QQuickState> *list)
152{
153 QQuickStateGroup *_this = static_cast<QQuickStateGroup *>(list->object);
154 return _this->d_func()->states.size();
155}
156
157QQuickState *QQuickStateGroupPrivate::at_state(QQmlListProperty<QQuickState> *list, qsizetype index)
158{
159 QQuickStateGroup *_this = static_cast<QQuickStateGroup *>(list->object);
160 return _this->d_func()->states.at(i: index);
161}
162
163void QQuickStateGroupPrivate::clear_states(QQmlListProperty<QQuickState> *list)
164{
165 QQuickStateGroupPrivate *d = static_cast<QQuickStateGroup *>(list->object)->d_func();
166 d->setCurrentStateInternal(state: QString(), true);
167 for (QQuickState *state : std::as_const(t&: d->states)) {
168 if (state)
169 state->setStateGroup(nullptr);
170 }
171 d->states.clear();
172}
173
174void QQuickStateGroupPrivate::replace_states(QQmlListProperty<QQuickState> *list, qsizetype index, QQuickState *state)
175{
176 auto *self = static_cast<QQuickStateGroup *>(list->object);
177 auto *d = self->d_func();
178 auto *oldState = d->states.at(i: index);
179 if (oldState != state) {
180 if (oldState)
181 oldState->setStateGroup(nullptr);
182
183 if (state)
184 state->setStateGroup(self);
185 d->states.replace(i: index, t: state);
186 if (!oldState || d->currentState == oldState->name())
187 d->setCurrentStateInternal(state: state ? state->name() : QString(), true);
188 }
189}
190
191void QQuickStateGroupPrivate::removeLast_states(QQmlListProperty<QQuickState> *list)
192{
193 auto *d = static_cast<QQuickStateGroup *>(list->object)->d_func();
194 if (QQuickState *last = d->states.last()) {
195 if (d->currentState == last->name()) {
196 QQuickState *first = d->states.size() > 1 ? d->states.first() : nullptr;
197 d->setCurrentStateInternal(state: first ? first->name() : QString(), true);
198 }
199 last->setStateGroup(nullptr);
200 }
201 d->states.removeLast();
202}
203
204/*!
205 \qmlproperty list<Transition> QtQuick::StateGroup::transitions
206 This property holds a list of transitions defined by the state group.
207
208 \qml
209 StateGroup {
210 transitions: [
211 Transition {
212 // ...
213 },
214 Transition {
215 // ...
216 }
217 // ...
218 ]
219 }
220 \endqml
221
222 \sa {Animation and Transitions in Qt Quick}{Transitions}
223*/
224QQmlListProperty<QQuickTransition> QQuickStateGroup::transitionsProperty()
225{
226 Q_D(QQuickStateGroup);
227 return QQmlListProperty<QQuickTransition>(this, &d->transitions, &QQuickStateGroupPrivate::append_transition,
228 &QQuickStateGroupPrivate::count_transitions,
229 &QQuickStateGroupPrivate::at_transition,
230 &QQuickStateGroupPrivate::clear_transitions);
231}
232
233void QQuickStateGroupPrivate::append_transition(QQmlListProperty<QQuickTransition> *list, QQuickTransition *trans)
234{
235 QQuickStateGroup *_this = static_cast<QQuickStateGroup *>(list->object);
236 if (trans)
237 _this->d_func()->transitions.append(t: trans);
238}
239
240qsizetype QQuickStateGroupPrivate::count_transitions(QQmlListProperty<QQuickTransition> *list)
241{
242 QQuickStateGroup *_this = static_cast<QQuickStateGroup *>(list->object);
243 return _this->d_func()->transitions.size();
244}
245
246QQuickTransition *QQuickStateGroupPrivate::at_transition(QQmlListProperty<QQuickTransition> *list, qsizetype index)
247{
248 QQuickStateGroup *_this = static_cast<QQuickStateGroup *>(list->object);
249 return _this->d_func()->transitions.at(i: index);
250}
251
252void QQuickStateGroupPrivate::clear_transitions(QQmlListProperty<QQuickTransition> *list)
253{
254 QQuickStateGroup *_this = static_cast<QQuickStateGroup *>(list->object);
255 _this->d_func()->transitions.clear();
256}
257
258/*!
259 \qmlproperty string QtQuick::StateGroup::state
260
261 This property holds the name of the current state of the state group.
262
263 This property is often used in scripts to change between states. For
264 example:
265
266 \qml
267 function toggle() {
268 if (button.state == 'On')
269 button.state = 'Off';
270 else
271 button.state = 'On';
272 }
273 \endqml
274
275 If the state group is in its base state (i.e. no explicit state has been
276 set), \c state will be a blank string. Likewise, you can return a
277 state group to its base state by setting its current state to \c ''.
278
279 \sa {Qt Quick States}{Qt Quick States}
280*/
281QString QQuickStateGroup::state() const
282{
283 Q_D(const QQuickStateGroup);
284 return d->currentState;
285}
286
287void QQuickStateGroup::setState(const QString &state)
288{
289 Q_D(QQuickStateGroup);
290 if (d->currentState == state)
291 return;
292
293 d->setCurrentStateInternal(state);
294}
295
296void QQuickStateGroup::classBegin()
297{
298 Q_D(QQuickStateGroup);
299 d->componentComplete = false;
300}
301
302void QQuickStateGroup::componentComplete()
303{
304 Q_D(QQuickStateGroup);
305 d->componentComplete = true;
306
307 QVarLengthArray<QString, 4> names;
308 names.reserve(sz: d->states.size());
309 for (QQuickState *state : std::as_const(t&: d->states)) {
310 if (!state)
311 continue;
312
313 if (!state->isNamed())
314 state->setName(QLatin1String("anonymousState") + QString::number(++d->unnamedCount));
315
316 QString stateName = state->name();
317 if (names.contains(t: stateName))
318 qmlWarning(me: state->parent()) << "Found duplicate state name: " << stateName;
319 else
320 names.append(t: std::move(stateName));
321 }
322
323 if (d->updateAutoState()) {
324 return;
325 } else if (!d->currentState.isEmpty()) {
326 QString cs = d->currentState;
327 d->currentState.clear();
328 d->setCurrentStateInternal(state: cs, true);
329 }
330}
331
332/*!
333 Returns true if the state was changed, otherwise false.
334*/
335bool QQuickStateGroup::updateAutoState()
336{
337 Q_D(QQuickStateGroup);
338 return d->updateAutoState();
339}
340
341bool QQuickStateGroupPrivate::updateAutoState()
342{
343 Q_Q(QQuickStateGroup);
344 if (!componentComplete)
345 return false;
346
347 bool revert = false;
348 for (QQuickState *state : std::as_const(t&: states)) {
349 if (!state || !state->isWhenKnown() || !state->isNamed())
350 continue;
351
352 bool whenValue = state->when();
353 const QQmlPropertyIndex whenIndex(state->metaObject()->indexOfProperty(name: "when"));
354 const auto potentialWhenBinding = QQmlAnyBinding::ofProperty(object: state, index: whenIndex);
355 Q_ASSERT(!potentialWhenBinding.isUntypedPropertyBinding());
356
357 // if there is a binding, the value in when might not be up-to-date at this point
358 // so we manually re-evaluate the binding
359 QQmlAbstractBinding *abstractBinding = potentialWhenBinding.asAbstractBinding();
360 if (abstractBinding && abstractBinding->kind() == QQmlAbstractBinding::QmlBinding) {
361 QQmlBinding *binding = static_cast<QQmlBinding *>(abstractBinding);
362 if (binding->hasValidContext()) {
363 const auto boolType = QMetaType::fromType<bool>();
364 const bool isUndefined = !binding->evaluate(result: &whenValue, type: boolType);
365 if (isUndefined)
366 whenValue = false;
367 }
368 }
369
370 if (whenValue) {
371 qCDebug(lcStates) << "Setting auto state due to expression";
372 if (currentState != state->name()) {
373 q->setState(state->name());
374 return true;
375 } else {
376 return false;
377 }
378 } else if (state->name() == currentState) {
379 revert = true;
380 }
381 }
382 if (revert) {
383 bool rv = !currentState.isEmpty();
384 q->setState(QString());
385 return rv;
386 } else {
387 return false;
388 }
389}
390
391QQuickTransition *QQuickStateGroupPrivate::findTransition(const QString &from, const QString &to)
392{
393 QQuickTransition *highest = nullptr;
394 int score = 0;
395 bool reversed = false;
396 bool done = false;
397
398 for (int ii = 0; !done && ii < transitions.size(); ++ii) {
399 QQuickTransition *t = transitions.at(i: ii);
400 if (!t->enabled())
401 continue;
402 for (int ii = 0; ii < 2; ++ii)
403 {
404 if (ii && (!t->reversible() ||
405 (t->fromState() == QLatin1String("*") &&
406 t->toState() == QLatin1String("*"))))
407 break;
408 const QString fromStateStr = t->fromState();
409 const QString toStateStr = t->toState();
410
411 auto fromState = QStringView{fromStateStr}.split(sep: QLatin1Char(','));
412 for (int jj = 0; jj < fromState.size(); ++jj)
413 fromState[jj] = fromState.at(i: jj).trimmed();
414 auto toState = QStringView{toStateStr}.split(sep: QLatin1Char(','));
415 for (int jj = 0; jj < toState.size(); ++jj)
416 toState[jj] = toState.at(i: jj).trimmed();
417 if (ii == 1)
418 qSwap(value1&: fromState, value2&: toState);
419 int tScore = 0;
420 const QString asterisk = QStringLiteral("*");
421 if (fromState.contains(t: QStringView(from)))
422 tScore += 2;
423 else if (fromState.contains(t: QStringView(asterisk)))
424 tScore += 1;
425 else
426 continue;
427
428 if (toState.contains(t: QStringView(to)))
429 tScore += 2;
430 else if (toState.contains(t: QStringView(asterisk)))
431 tScore += 1;
432 else
433 continue;
434
435 if (ii == 1)
436 reversed = true;
437 else
438 reversed = false;
439
440 if (tScore == 4) {
441 highest = t;
442 done = true;
443 break;
444 } else if (tScore > score) {
445 score = tScore;
446 highest = t;
447 }
448 }
449 }
450
451 if (highest)
452 highest->setReversed(reversed);
453
454 return highest;
455}
456
457void QQuickStateGroupPrivate::setCurrentStateInternal(const QString &state,
458 bool ignoreTrans)
459{
460 Q_Q(QQuickStateGroup);
461 if (!componentComplete) {
462 currentState = state;
463 return;
464 }
465
466 if (applyingState) {
467 qmlWarning(me: q) << "Can't apply a state change as part of a state definition.";
468 return;
469 }
470
471 applyingState = true;
472
473 QQuickTransition *transition = ignoreTrans ? nullptr : findTransition(from: currentState, to: state);
474 if (lcStates().isDebugEnabled()) {
475 qCDebug(lcStates) << this << "changing state from:" << currentState << "to:" << state;
476 if (transition)
477 qCDebug(lcStates) << " using transition" << transition->fromState()
478 << transition->toState();
479 }
480
481 QQuickState *oldState = nullptr;
482 if (!currentState.isEmpty()) {
483 for (QQuickState *state : std::as_const(t&: states)) {
484 if (state && state->name() == currentState) {
485 oldState = state;
486 break;
487 }
488 }
489 }
490
491 currentState = state;
492 emit q->stateChanged(currentState);
493
494 QQuickState *newState = nullptr;
495 for (QQuickState *state : std::as_const(t&: states)) {
496 if (state && state->name() == currentState) {
497 newState = state;
498 break;
499 }
500 }
501
502 if (oldState == nullptr || newState == nullptr) {
503 if (!nullState) {
504 nullState = new QQuickState;
505 QQml_setParent_noEvent(object: nullState, parent: q);
506 nullState->setStateGroup(q);
507 }
508 if (!oldState) oldState = nullState;
509 if (!newState) newState = nullState;
510 }
511
512 newState->apply(transition, revert: oldState);
513 applyingState = false; //### consider removing this (don't allow state changes in transition)
514}
515
516QQuickState *QQuickStateGroup::findState(const QString &name) const
517{
518 Q_D(const QQuickStateGroup);
519 for (QQuickState *state : std::as_const(t: d->states)) {
520 if (state && state->name() == name)
521 return state;
522 }
523
524 return nullptr;
525}
526
527void QQuickStateGroup::removeState(QQuickState *state)
528{
529 Q_D(QQuickStateGroup);
530 d->states.removeOne(t: state);
531}
532
533void QQuickStateGroup::stateAboutToComplete()
534{
535 Q_D(QQuickStateGroup);
536 d->applyingState = false;
537}
538
539QT_END_NAMESPACE
540
541
542#include "moc_qquickstategroup_p.cpp"
543

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