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 \nativetype 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 (QQuickState *state : std::as_const(t: d->states)) {
100 if (state)
101 state->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 QQuickStateGroupPrivate *d = static_cast<QQuickStateGroup *>(list->object)->d_func();
168 d->setCurrentStateInternal(state: QString(), true);
169 for (QQuickState *state : std::as_const(t&: d->states)) {
170 if (state)
171 state->setStateGroup(nullptr);
172 }
173 d->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
185 if (state)
186 state->setStateGroup(self);
187 d->states.replace(i: index, t: state);
188 if (!oldState || d->currentState == oldState->name())
189 d->setCurrentStateInternal(state: state ? state->name() : QString(), true);
190 }
191}
192
193void QQuickStateGroupPrivate::removeLast_states(QQmlListProperty<QQuickState> *list)
194{
195 auto *d = static_cast<QQuickStateGroup *>(list->object)->d_func();
196 if (QQuickState *last = d->states.last()) {
197 if (d->currentState == last->name()) {
198 QQuickState *first = d->states.size() > 1 ? d->states.first() : nullptr;
199 d->setCurrentStateInternal(state: first ? first->name() : QString(), true);
200 }
201 last->setStateGroup(nullptr);
202 }
203 d->states.removeLast();
204}
205
206/*!
207 \qmlproperty list<Transition> QtQuick::StateGroup::transitions
208 This property holds a list of transitions defined by the state group.
209
210 \qml
211 StateGroup {
212 transitions: [
213 Transition {
214 // ...
215 },
216 Transition {
217 // ...
218 }
219 // ...
220 ]
221 }
222 \endqml
223
224 \sa {Animation and Transitions in Qt Quick}{Transitions}
225*/
226QQmlListProperty<QQuickTransition> QQuickStateGroup::transitionsProperty()
227{
228 Q_D(QQuickStateGroup);
229 return QQmlListProperty<QQuickTransition>(this, &d->transitions, &QQuickStateGroupPrivate::append_transition,
230 &QQuickStateGroupPrivate::count_transitions,
231 &QQuickStateGroupPrivate::at_transition,
232 &QQuickStateGroupPrivate::clear_transitions);
233}
234
235void QQuickStateGroupPrivate::append_transition(QQmlListProperty<QQuickTransition> *list, QQuickTransition *trans)
236{
237 QQuickStateGroup *_this = static_cast<QQuickStateGroup *>(list->object);
238 if (trans)
239 _this->d_func()->transitions.append(t: trans);
240}
241
242qsizetype QQuickStateGroupPrivate::count_transitions(QQmlListProperty<QQuickTransition> *list)
243{
244 QQuickStateGroup *_this = static_cast<QQuickStateGroup *>(list->object);
245 return _this->d_func()->transitions.size();
246}
247
248QQuickTransition *QQuickStateGroupPrivate::at_transition(QQmlListProperty<QQuickTransition> *list, qsizetype index)
249{
250 QQuickStateGroup *_this = static_cast<QQuickStateGroup *>(list->object);
251 return _this->d_func()->transitions.at(i: index);
252}
253
254void QQuickStateGroupPrivate::clear_transitions(QQmlListProperty<QQuickTransition> *list)
255{
256 QQuickStateGroup *_this = static_cast<QQuickStateGroup *>(list->object);
257 _this->d_func()->transitions.clear();
258}
259
260/*!
261 \qmlproperty string QtQuick::StateGroup::state
262
263 This property holds the name of the current state of the state group.
264
265 This property is often used in scripts to change between states. For
266 example:
267
268 \qml
269 function toggle() {
270 if (button.state == 'On')
271 button.state = 'Off';
272 else
273 button.state = 'On';
274 }
275 \endqml
276
277 If the state group is in its base state (i.e. no explicit state has been
278 set), \c state will be a blank string. Likewise, you can return a
279 state group to its base state by setting its current state to \c ''.
280
281 \sa {Qt Quick States}{Qt Quick States}
282*/
283QString QQuickStateGroup::state() const
284{
285 Q_D(const QQuickStateGroup);
286 return d->currentState;
287}
288
289void QQuickStateGroup::setState(const QString &state)
290{
291 Q_D(QQuickStateGroup);
292 if (d->currentState == state)
293 return;
294
295 d->setCurrentStateInternal(state);
296}
297
298void QQuickStateGroup::classBegin()
299{
300 Q_D(QQuickStateGroup);
301 d->componentComplete = false;
302}
303
304void QQuickStateGroup::componentComplete()
305{
306 Q_D(QQuickStateGroup);
307 d->componentComplete = true;
308
309 QVarLengthArray<QString, 4> names;
310 names.reserve(sz: d->states.size());
311 for (QQuickState *state : std::as_const(t&: d->states)) {
312 if (!state)
313 continue;
314
315 if (!state->isNamed())
316 state->setName(QLatin1String("anonymousState") + QString::number(++d->unnamedCount));
317
318 QString stateName = state->name();
319 if (names.contains(t: stateName))
320 qmlWarning(me: state->parent()) << "Found duplicate state name: " << stateName;
321 else
322 names.append(t: std::move(stateName));
323 }
324
325 if (d->updateAutoState()) {
326 return;
327 } else if (!d->currentState.isEmpty()) {
328 QString cs = d->currentState;
329 d->currentState.clear();
330 d->setCurrentStateInternal(state: cs, true);
331 }
332}
333
334/*!
335 Returns true if the state was changed, otherwise false.
336*/
337bool QQuickStateGroup::updateAutoState()
338{
339 Q_D(QQuickStateGroup);
340 return d->updateAutoState();
341}
342
343bool QQuickStateGroupPrivate::updateAutoState()
344{
345 Q_Q(QQuickStateGroup);
346 if (!componentComplete)
347 return false;
348
349 bool revert = false;
350 for (QQuickState *state : std::as_const(t&: states)) {
351 if (!state || !state->isWhenKnown() || !state->isNamed())
352 continue;
353
354 bool whenValue = state->when();
355 const QQmlPropertyIndex whenIndex(state->metaObject()->indexOfProperty(name: "when"));
356 const auto potentialWhenBinding = QQmlAnyBinding::ofProperty(object: state, index: whenIndex);
357 Q_ASSERT(!potentialWhenBinding.isUntypedPropertyBinding());
358
359 // if there is a binding, the value in when might not be up-to-date at this point
360 // so we manually re-evaluate the binding
361 QQmlAbstractBinding *abstractBinding = potentialWhenBinding.asAbstractBinding();
362 if (abstractBinding && abstractBinding->kind() == QQmlAbstractBinding::QmlBinding) {
363 QQmlBinding *binding = static_cast<QQmlBinding *>(abstractBinding);
364 if (binding->hasValidContext()) {
365 const auto boolType = QMetaType::fromType<bool>();
366 const bool isUndefined = !binding->evaluate(result: &whenValue, type: boolType);
367 if (isUndefined)
368 whenValue = false;
369 }
370 }
371
372 if (whenValue) {
373 qCDebug(lcStates) << "Setting auto state due to expression";
374 if (currentState != state->name()) {
375 q->setState(state->name());
376 return true;
377 } else {
378 return false;
379 }
380 } else if (state->name() == currentState) {
381 revert = true;
382 }
383 }
384 if (revert) {
385 bool rv = !currentState.isEmpty();
386 q->setState(QString());
387 return rv;
388 } else {
389 return false;
390 }
391}
392
393QQuickTransition *QQuickStateGroupPrivate::findTransition(const QString &from, const QString &to)
394{
395 QQuickTransition *highest = nullptr;
396 int score = 0;
397 bool reversed = false;
398 bool done = false;
399
400 for (int ii = 0; !done && ii < transitions.size(); ++ii) {
401 QQuickTransition *t = transitions.at(i: ii);
402 if (!t->enabled())
403 continue;
404 for (int ii = 0; ii < 2; ++ii)
405 {
406 if (ii && (!t->reversible() ||
407 (t->fromState() == QLatin1String("*") &&
408 t->toState() == QLatin1String("*"))))
409 break;
410 const QString fromStateStr = t->fromState();
411 const QString toStateStr = t->toState();
412
413 auto fromState = QStringView{fromStateStr}.split(sep: QLatin1Char(','));
414 for (int jj = 0; jj < fromState.size(); ++jj)
415 fromState[jj] = fromState.at(i: jj).trimmed();
416 auto toState = QStringView{toStateStr}.split(sep: QLatin1Char(','));
417 for (int jj = 0; jj < toState.size(); ++jj)
418 toState[jj] = toState.at(i: jj).trimmed();
419 if (ii == 1)
420 qSwap(value1&: fromState, value2&: toState);
421 int tScore = 0;
422 const QString asterisk = QStringLiteral("*");
423 if (fromState.contains(t: QStringView(from)))
424 tScore += 2;
425 else if (fromState.contains(t: QStringView(asterisk)))
426 tScore += 1;
427 else
428 continue;
429
430 if (toState.contains(t: QStringView(to)))
431 tScore += 2;
432 else if (toState.contains(t: QStringView(asterisk)))
433 tScore += 1;
434 else
435 continue;
436
437 if (ii == 1)
438 reversed = true;
439 else
440 reversed = false;
441
442 if (tScore == 4) {
443 highest = t;
444 done = true;
445 break;
446 } else if (tScore > score) {
447 score = tScore;
448 highest = t;
449 }
450 }
451 }
452
453 if (highest)
454 highest->setReversed(reversed);
455
456 return highest;
457}
458
459void QQuickStateGroupPrivate::setCurrentStateInternal(const QString &state,
460 bool ignoreTrans)
461{
462 Q_Q(QQuickStateGroup);
463 if (!componentComplete) {
464 currentState = state;
465 return;
466 }
467
468 if (applyingState) {
469 qmlWarning(me: q) << "Can't apply a state change as part of a state definition.";
470 return;
471 }
472
473 applyingState = true;
474
475 QQuickTransition *transition = ignoreTrans ? nullptr : findTransition(from: currentState, to: state);
476 if (lcStates().isDebugEnabled()) {
477 qCDebug(lcStates) << this << "changing state from:" << currentState << "to:" << state;
478 if (transition)
479 qCDebug(lcStates) << " using transition" << transition->fromState()
480 << transition->toState();
481 }
482
483 QQuickState *oldState = nullptr;
484 if (!currentState.isEmpty()) {
485 for (QQuickState *state : std::as_const(t&: states)) {
486 if (state && state->name() == currentState) {
487 oldState = state;
488 break;
489 }
490 }
491 }
492
493 currentState = state;
494 emit q->stateChanged(currentState);
495
496 QQuickState *newState = nullptr;
497 for (QQuickState *state : std::as_const(t&: states)) {
498 if (state && state->name() == currentState) {
499 newState = state;
500 break;
501 }
502 }
503
504 if (oldState == nullptr || newState == nullptr) {
505 if (!nullState) {
506 nullState = new QQuickState;
507 QQml_setParent_noEvent(object: nullState, parent: q);
508 nullState->setStateGroup(q);
509 }
510 if (!oldState) oldState = nullState;
511 if (!newState) newState = nullState;
512 }
513
514 newState->apply(transition, revert: oldState);
515 applyingState = false; //### consider removing this (don't allow state changes in transition)
516}
517
518QQuickState *QQuickStateGroup::findState(const QString &name) const
519{
520 Q_D(const QQuickStateGroup);
521 for (QQuickState *state : std::as_const(t: d->states)) {
522 if (state && state->name() == name)
523 return state;
524 }
525
526 return nullptr;
527}
528
529void QQuickStateGroup::removeState(QQuickState *state)
530{
531 Q_D(QQuickStateGroup);
532 d->states.removeOne(t: state);
533}
534
535void QQuickStateGroup::stateAboutToComplete()
536{
537 Q_D(QQuickStateGroup);
538 d->applyingState = false;
539}
540
541QT_END_NAMESPACE
542
543
544#include "moc_qquickstategroup_p.cpp"
545

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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