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

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