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 | |
54 | QT_BEGIN_NAMESPACE |
55 | |
56 | DEFINE_BOOL_CONFIG_OPTION(stateChangeDebug, STATECHANGE_DEBUG); |
57 | |
58 | class QQuickStateGroupPrivate : public QObjectPrivate |
59 | { |
60 | Q_DECLARE_PUBLIC(QQuickStateGroup) |
61 | public: |
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 | |
125 | QQuickStateGroup::QQuickStateGroup(QObject *parent) |
126 | : QObject(*(new QQuickStateGroupPrivate), parent) |
127 | { |
128 | } |
129 | |
130 | QQuickStateGroup::~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 | |
139 | QList<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 | */ |
165 | QQmlListProperty<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 | |
177 | void 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 | |
187 | int QQuickStateGroupPrivate::count_state(QQmlListProperty<QQuickState> *list) |
188 | { |
189 | QQuickStateGroup *_this = static_cast<QQuickStateGroup *>(list->object); |
190 | return _this->d_func()->states.count(); |
191 | } |
192 | |
193 | QQuickState *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 | |
199 | void 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 | |
209 | void 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 | |
223 | void 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 | */ |
252 | QQmlListProperty<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 | |
261 | void 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 | |
268 | int QQuickStateGroupPrivate::count_transitions(QQmlListProperty<QQuickTransition> *list) |
269 | { |
270 | QQuickStateGroup *_this = static_cast<QQuickStateGroup *>(list->object); |
271 | return _this->d_func()->transitions.count(); |
272 | } |
273 | |
274 | QQuickTransition *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 | |
280 | void 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 | */ |
309 | QString QQuickStateGroup::state() const |
310 | { |
311 | Q_D(const QQuickStateGroup); |
312 | return d->currentState; |
313 | } |
314 | |
315 | void QQuickStateGroup::setState(const QString &state) |
316 | { |
317 | Q_D(QQuickStateGroup); |
318 | if (d->currentState == state) |
319 | return; |
320 | |
321 | d->setCurrentStateInternal(state); |
322 | } |
323 | |
324 | void QQuickStateGroup::classBegin() |
325 | { |
326 | Q_D(QQuickStateGroup); |
327 | d->componentComplete = false; |
328 | } |
329 | |
330 | void 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 | */ |
362 | bool QQuickStateGroup::updateAutoState() |
363 | { |
364 | Q_D(QQuickStateGroup); |
365 | return d->updateAutoState(); |
366 | } |
367 | |
368 | bool 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 | |
410 | QQuickTransition *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 | |
476 | void 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 | |
535 | QQuickState *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 | |
547 | void QQuickStateGroup::removeState(QQuickState *state) |
548 | { |
549 | Q_D(QQuickStateGroup); |
550 | d->states.removeOne(t: state); |
551 | } |
552 | |
553 | void QQuickStateGroup::stateAboutToComplete() |
554 | { |
555 | Q_D(QQuickStateGroup); |
556 | d->applyingState = false; |
557 | } |
558 | |
559 | QT_END_NAMESPACE |
560 | |
561 | |
562 | #include "moc_qquickstategroup_p.cpp" |
563 | |