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

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