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