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
74 static void append_transition(QQmlListProperty<QQuickTransition> *list, QQuickTransition *state);
75 static int count_transitions(QQmlListProperty<QQuickTransition> *list);
76 static QQuickTransition *at_transition(QQmlListProperty<QQuickTransition> *list, int index);
77 static void clear_transitions(QQmlListProperty<QQuickTransition> *list);
78
79 QList<QQuickState *> states;
80 QList<QQuickTransition *> transitions;
81
82 bool componentComplete;
83 bool ignoreTrans;
84 bool applyingState;
85 int unnamedCount;
86
87 QQuickTransition *findTransition(const QString &from, const QString &to);
88 void setCurrentStateInternal(const QString &state, bool = false);
89 bool updateAutoState();
90};
91
92/*!
93 \qmltype StateGroup
94 \instantiates QQuickStateGroup
95 \inqmlmodule QtQuick
96 \ingroup qtquick-states
97 \brief Provides built-in state support for non-Item types.
98
99 Item (and all derived types) provides built in support for states and transitions
100 via its \l{Item::state}{state}, \l{Item::states}{states} and \l{Item::transitions}{transitions} properties. StateGroup provides an easy way to
101 use this support in other (non-Item-derived) types.
102
103 \qml
104 MyCustomObject {
105 StateGroup {
106 id: myStateGroup
107 states: State {
108 name: "state1"
109 // ...
110 }
111 transitions: Transition {
112 // ...
113 }
114 }
115
116 onSomethingHappened: myStateGroup.state = "state1";
117 }
118 \endqml
119
120 \sa {Qt Quick States}{Qt Quick States}, {Animation and Transitions in Qt Quick}{Transitions}, {Qt QML}
121*/
122
123QQuickStateGroup::QQuickStateGroup(QObject *parent)
124 : QObject(*(new QQuickStateGroupPrivate), parent)
125{
126}
127
128QQuickStateGroup::~QQuickStateGroup()
129{
130 Q_D(const QQuickStateGroup);
131 for (int i = 0; i < d->states.count(); ++i)
132 d->states.at(i)->setStateGroup(nullptr);
133 if (d->nullState)
134 d->nullState->setStateGroup(nullptr);
135}
136
137QList<QQuickState *> QQuickStateGroup::states() const
138{
139 Q_D(const QQuickStateGroup);
140 return d->states;
141}
142
143/*!
144 \qmlproperty list<State> QtQuick::StateGroup::states
145 This property holds a list of states defined by the state group.
146
147 \qml
148 StateGroup {
149 states: [
150 State {
151 // State definition...
152 },
153 State {
154 // ...
155 }
156 // Other states...
157 ]
158 }
159 \endqml
160
161 \sa {Qt Quick States}{Qt Quick States}
162*/
163QQmlListProperty<QQuickState> QQuickStateGroup::statesProperty()
164{
165 Q_D(QQuickStateGroup);
166 return QQmlListProperty<QQuickState>(this, &d->states, &QQuickStateGroupPrivate::append_state,
167 &QQuickStateGroupPrivate::count_state,
168 &QQuickStateGroupPrivate::at_state,
169 &QQuickStateGroupPrivate::clear_states);
170}
171
172void QQuickStateGroupPrivate::append_state(QQmlListProperty<QQuickState> *list, QQuickState *state)
173{
174 QQuickStateGroup *_this = static_cast<QQuickStateGroup *>(list->object);
175 if (state) {
176 _this->d_func()->states.append(state);
177 state->setStateGroup(_this);
178 }
179
180}
181
182int QQuickStateGroupPrivate::count_state(QQmlListProperty<QQuickState> *list)
183{
184 QQuickStateGroup *_this = static_cast<QQuickStateGroup *>(list->object);
185 return _this->d_func()->states.count();
186}
187
188QQuickState *QQuickStateGroupPrivate::at_state(QQmlListProperty<QQuickState> *list, int index)
189{
190 QQuickStateGroup *_this = static_cast<QQuickStateGroup *>(list->object);
191 return _this->d_func()->states.at(index);
192}
193
194void QQuickStateGroupPrivate::clear_states(QQmlListProperty<QQuickState> *list)
195{
196 QQuickStateGroup *_this = static_cast<QQuickStateGroup *>(list->object);
197 _this->d_func()->setCurrentStateInternal(QString(), true);
198 for (int i = 0; i < _this->d_func()->states.count(); ++i) {
199 _this->d_func()->states.at(i)->setStateGroup(nullptr);
200 }
201 _this->d_func()->states.clear();
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(trans);
238}
239
240int QQuickStateGroupPrivate::count_transitions(QQmlListProperty<QQuickTransition> *list)
241{
242 QQuickStateGroup *_this = static_cast<QQuickStateGroup *>(list->object);
243 return _this->d_func()->transitions.count();
244}
245
246QQuickTransition *QQuickStateGroupPrivate::at_transition(QQmlListProperty<QQuickTransition> *list, int index)
247{
248 QQuickStateGroup *_this = static_cast<QQuickStateGroup *>(list->object);
249 return _this->d_func()->transitions.at(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 \js
267 function toggle() {
268 if (button.state == 'On')
269 button.state = 'Off';
270 else
271 button.state = 'On';
272 }
273 \endjs
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(d->states.count());
309 for (int ii = 0; ii < d->states.count(); ++ii) {
310 QQuickState *state = d->states.at(ii);
311 if (!state->isNamed())
312 state->setName(QLatin1String("anonymousState") + QString::number(++d->unnamedCount));
313
314 const QString stateName = state->name();
315 if (names.contains(stateName)) {
316 qmlWarning(state->parent()) << "Found duplicate state name: " << stateName;
317 } else {
318 names.append(std::move(stateName));
319 }
320 }
321
322 if (d->updateAutoState()) {
323 return;
324 } else if (!d->currentState.isEmpty()) {
325 QString cs = d->currentState;
326 d->currentState.clear();
327 d->setCurrentStateInternal(cs, true);
328 }
329}
330
331/*!
332 Returns true if the state was changed, otherwise false.
333*/
334bool QQuickStateGroup::updateAutoState()
335{
336 Q_D(QQuickStateGroup);
337 return d->updateAutoState();
338}
339
340bool QQuickStateGroupPrivate::updateAutoState()
341{
342 Q_Q(QQuickStateGroup);
343 if (!componentComplete)
344 return false;
345
346 bool revert = false;
347 for (int ii = 0; ii < states.count(); ++ii) {
348 QQuickState *state = states.at(ii);
349 if (state->isWhenKnown()) {
350 if (state->isNamed()) {
351 if (state->when()) {
352 if (stateChangeDebug())
353 qWarning() << "Setting auto state due to expression";
354 if (currentState != state->name()) {
355 q->setState(state->name());
356 return true;
357 } else {
358 return false;
359 }
360 } else if (state->name() == currentState) {
361 revert = true;
362 }
363 }
364 }
365 }
366 if (revert) {
367 bool rv = !currentState.isEmpty();
368 q->setState(QString());
369 return rv;
370 } else {
371 return false;
372 }
373}
374
375QQuickTransition *QQuickStateGroupPrivate::findTransition(const QString &from, const QString &to)
376{
377 QQuickTransition *highest = nullptr;
378 int score = 0;
379 bool reversed = false;
380 bool done = false;
381
382 for (int ii = 0; !done && ii < transitions.count(); ++ii) {
383 QQuickTransition *t = transitions.at(ii);
384 if (!t->enabled())
385 continue;
386 for (int ii = 0; ii < 2; ++ii)
387 {
388 if (ii && (!t->reversible() ||
389 (t->fromState() == QLatin1String("*") &&
390 t->toState() == QLatin1String("*"))))
391 break;
392 const QString fromStateStr = t->fromState();
393 const QString toStateStr = t->toState();
394
395 QVector<QStringRef> fromState = fromStateStr.splitRef(QLatin1Char(','));
396 for (int jj = 0; jj < fromState.count(); ++jj)
397 fromState[jj] = fromState.at(jj).trimmed();
398 QVector<QStringRef> toState = toStateStr.splitRef(QLatin1Char(','));
399 for (int jj = 0; jj < toState.count(); ++jj)
400 toState[jj] = toState.at(jj).trimmed();
401 if (ii == 1)
402 qSwap(fromState, toState);
403 int tScore = 0;
404 const QString asterisk = QStringLiteral("*");
405 if (fromState.contains(QStringRef(&from)))
406 tScore += 2;
407 else if (fromState.contains(QStringRef(&asterisk)))
408 tScore += 1;
409 else
410 continue;
411
412 if (toState.contains(QStringRef(&to)))
413 tScore += 2;
414 else if (toState.contains(QStringRef(&asterisk)))
415 tScore += 1;
416 else
417 continue;
418
419 if (ii == 1)
420 reversed = true;
421 else
422 reversed = false;
423
424 if (tScore == 4) {
425 highest = t;
426 done = true;
427 break;
428 } else if (tScore > score) {
429 score = tScore;
430 highest = t;
431 }
432 }
433 }
434
435 if (highest)
436 highest->setReversed(reversed);
437
438 return highest;
439}
440
441void QQuickStateGroupPrivate::setCurrentStateInternal(const QString &state,
442 bool ignoreTrans)
443{
444 Q_Q(QQuickStateGroup);
445 if (!componentComplete) {
446 currentState = state;
447 return;
448 }
449
450 if (applyingState) {
451 qmlWarning(q) << "Can't apply a state change as part of a state definition.";
452 return;
453 }
454
455 applyingState = true;
456
457 QQuickTransition *transition = ignoreTrans ? nullptr : findTransition(currentState, state);
458 if (stateChangeDebug()) {
459 qWarning() << this << "Changing state. From" << currentState << ". To" << state;
460 if (transition)
461 qWarning() << " using transition" << transition->fromState()
462 << transition->toState();
463 }
464
465 QQuickState *oldState = nullptr;
466 if (!currentState.isEmpty()) {
467 for (int ii = 0; ii < states.count(); ++ii) {
468 if (states.at(ii)->name() == currentState) {
469 oldState = states.at(ii);
470 break;
471 }
472 }
473 }
474
475 currentState = state;
476 emit q->stateChanged(currentState);
477
478 QQuickState *newState = nullptr;
479 for (int ii = 0; ii < states.count(); ++ii) {
480 if (states.at(ii)->name() == currentState) {
481 newState = states.at(ii);
482 break;
483 }
484 }
485
486 if (oldState == nullptr || newState == nullptr) {
487 if (!nullState) {
488 nullState = new QQuickState;
489 QQml_setParent_noEvent(nullState, q);
490 nullState->setStateGroup(q);
491 }
492 if (!oldState) oldState = nullState;
493 if (!newState) newState = nullState;
494 }
495
496 newState->apply(transition, oldState);
497 applyingState = false; //### consider removing this (don't allow state changes in transition)
498}
499
500QQuickState *QQuickStateGroup::findState(const QString &name) const
501{
502 Q_D(const QQuickStateGroup);
503 for (int i = 0; i < d->states.count(); ++i) {
504 QQuickState *state = d->states.at(i);
505 if (state->name() == name)
506 return state;
507 }
508
509 return nullptr;
510}
511
512void QQuickStateGroup::removeState(QQuickState *state)
513{
514 Q_D(QQuickStateGroup);
515 d->states.removeOne(state);
516}
517
518void QQuickStateGroup::stateAboutToComplete()
519{
520 Q_D(QQuickStateGroup);
521 d->applyingState = false;
522}
523
524QT_END_NAMESPACE
525
526
527#include "moc_qquickstategroup_p.cpp"
528