Revision control
++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++ #include "qquickstate_p_p.h"
++ #include "qquickstate_p.h"
++
++ #include "qquickstategroup_p.h"
++ #include "qquickstatechangescript_p.h"
++
++ #include <private/qqmlglobal_p.h>
++
++ #include <QtCore/qdebug.h>
++
++ QT_BEGIN_NAMESPACE
++
++ Q_LOGGING_CATEGORY(lcStates, "qt.qml.states")
++
++ QQuickStateAction::QQuickStateAction()
++ : restore(true), actionDone(false), reverseEvent(false), deletableToBinding(false), fromBinding(nullptr), event(nullptr),
++ specifiedObject(nullptr)
++ {
++ }
++
++ QQuickStateAction::QQuickStateAction(QObject *target, const QString &propertyName,
++ const QVariant &value)
++ : restore(true), actionDone(false), reverseEvent(false), deletableToBinding(false),
++ property(target, propertyName, qmlEngine(target)), toValue(value),
++ fromBinding(nullptr), event(nullptr),
++ specifiedObject(target), specifiedProperty(propertyName)
++ {
++ if (property.isValid())
++ fromValue = property.read();
++ }
++
++ QQuickStateAction::QQuickStateAction(QObject *target, const QQmlProperty &property, const QString &propertyName, const QVariant &value)
++ : restore(true), actionDone(false), reverseEvent(false), deletableToBinding(false),
++ property(property), toValue(value),
++ fromBinding(nullptr), event(nullptr),
++ specifiedObject(target), specifiedProperty(propertyName)
++ {
++ if (property.isValid())
++ fromValue = property.read();
++ }
++
++
++ QQuickStateActionEvent::~QQuickStateActionEvent()
++ {
++ }
++
++ void QQuickStateActionEvent::execute()
++ {
++ }
++
++ bool QQuickStateActionEvent::isReversable()
++ {
++ return false;
++ }
++
++ void QQuickStateActionEvent::reverse()
++ {
++ }
++
++ bool QQuickStateActionEvent::changesBindings()
++ {
++ return false;
++ }
++
++ void QQuickStateActionEvent::clearBindings()
++ {
++ }
++
++ bool QQuickStateActionEvent::mayOverride(QQuickStateActionEvent *other)
++ {
++ Q_UNUSED(other);
++ return false;
++ }
++
++ QQuickStateOperation::QQuickStateOperation(QObjectPrivate &dd, QObject *parent)
++ : QObject(dd, parent)
++ {
++ }
++
++ /*!
++ \qmltype State
++ \nativetype QQuickState
++ \inqmlmodule QtQuick
++ \ingroup qtquick-states
++ \brief Defines configurations of objects and properties.
++
++ A \e state is a set of batched changes from the default configuration.
++
++ All items have a default state that defines the default configuration of objects
++ and property values. New states can be defined by adding State items to the \l {Item::states}{states} property to
++ allow items to switch between different configurations. These configurations
++ can, for example, be used to apply different sets of property values or execute
++ different scripts.
++
++ The following example displays a single \l Rectangle. In the default state, the rectangle
++ is colored black. In the "clicked" state, a PropertyChanges object changes the
++ rectangle's color to red. Clicking within the MouseArea toggles the rectangle's state
++ between the default state and the "clicked" state, thus toggling the color of the
++ rectangle between black and red.
++
++ \snippet qml/state.qml 0
++
++ Notice the default state is referred to using an empty string ("").
++
++ States are commonly used together with \l{Animation and Transitions in Qt Quick}{Transitions} to provide
++ animations when state changes occur.
++
++ \note Setting the state of an object from within another state of the same object is
++ not allowed.
++
++ \sa {Qt Quick Examples - Animation#States}{States example}, {Qt Quick States},
++ {Animation and Transitions in Qt Quick}{Transitions}, {Qt Qml}
++ */
++ QQuickState::QQuickState(QObject *parent)
++ : QObject(*(new QQuickStatePrivate), parent)
++ {
++ Q_D(QQuickState);
++ d->transitionManager.setState(this);
++ }
++
++ QQuickState::~QQuickState()
++ {
++ Q_D(QQuickState);
++ if (d->group)
++ d->group->removeState(this);
++ }
++
++ /*!
++ \qmlproperty string QtQuick::State::name
++ This property holds the name of the state.
++
++ Each state should have a unique name within its item.
++ */
++ QString QQuickState::name() const
++ {
++ Q_D(const QQuickState);
++ return d->name;
++ }
++
++ void QQuickState::setName(const QString &n)
++ {
++ Q_D(QQuickState);
++ d->name = n;
++ d->named = true;
++ }
++
++ bool QQuickState::isNamed() const
++ {
++ Q_D(const QQuickState);
++ return d->named;
++ }
++
++ bool QQuickState::isWhenKnown() const
++ {
++ Q_D(const QQuickState);
++ return d->whenKnown;
++ }
++
++ /*!
++ \qmlproperty bool QtQuick::State::when
++ This property holds when the state should be applied.
++
++ This should be set to an expression that evaluates to \c true when you want the state to
++ be applied. For example, the following \l Rectangle changes in and out of the "hidden"
++ state when the \l MouseArea is pressed:
++
++ \snippet qml/state-when.qml 0
++
++ If multiple states in a group have \c when clauses that evaluate to \c true
++ at the same time, the first matching state will be applied. For example, in
++ the following snippet \c state1 will always be selected rather than
++ \c state2 when sharedCondition becomes \c true.
++ \qml
++ Item {
++ states: [
++ State { name: "state1"; when: sharedCondition },
++ State { name: "state2"; when: sharedCondition }
++ ]
++ // ...
++ }
++ \endqml
++ */
++ bool QQuickState::when() const
++ {
++ Q_D(const QQuickState);
++ return d->when;
++ }
++
++ void QQuickState::setWhen(bool when)
++ {
++ Q_D(QQuickState);
++ d->whenKnown = true;
++ d->when = when;
++ if (d->group)
++ d->group->updateAutoState();
++ }
++
++ /*!
++ \qmlproperty string QtQuick::State::extend
++ This property holds the state that this state extends.
++
++ When a state extends another state, it inherits all the changes of that state.
++
++ The state being extended is treated as the base state in regards to
++ the changes specified by the extending state.
++ */
++ QString QQuickState::extends() const
++ {
++ Q_D(const QQuickState);
++ return d->extends;
++ }
++
++ void QQuickState::setExtends(const QString &extends)
++ {
++ Q_D(QQuickState);
++ d->extends = extends;
++ }
++
++ /*!
++ \qmlproperty list<Change> QtQuick::State::changes
++ This property holds the changes to apply for this state
++ \qmldefault
++
++ By default these changes are applied against the default state. If the state
++ extends another state, then the changes are applied against the state being
++ extended.
++ */
++ QQmlListProperty<QQuickStateOperation> QQuickState::changes()
++ {
++ Q_D(QQuickState);
++ return QQmlListProperty<QQuickStateOperation>(this, &d->operations,
++ QQuickStatePrivate::operations_append,
++ QQuickStatePrivate::operations_count,
++ QQuickStatePrivate::operations_at,
++ QQuickStatePrivate::operations_clear,
++ QQuickStatePrivate::operations_replace,
++ QQuickStatePrivate::operations_removeLast);
++ }
++
++ int QQuickState::operationCount() const
++ {
++ Q_D(const QQuickState);
++ return d->operations.size();
++ }
++
++ QQuickStateOperation *QQuickState::operationAt(int index) const
++ {
++ Q_D(const QQuickState);
++ return d->operations.at(index);
++ }
++
++ QQuickState &QQuickState::operator<<(QQuickStateOperation *op)
++ {
++ Q_D(QQuickState);
++ d->operations.append(QQuickStatePrivate::OperationGuard(op, &d->operations));
++ return *this;
++ }
++
++ void QQuickStatePrivate::complete()
++ {
++ Q_Q(QQuickState);
++
++ for (int ii = 0; ii < reverting.size(); ++ii) {
++ for (int jj = 0; jj < revertList.size(); ++jj) {
++ const QQuickRevertAction &revert = reverting.at(ii);
++ const QQuickSimpleAction &simple = revertList.at(jj);
++ if ((revert.event && simple.event() == revert.event) ||
++ simple.property() == revert.property) {
++ revertList.removeAt(jj);
++ break;
++ }
++ }
++ }
++ reverting.clear();
++
++ if (group)
++ group->stateAboutToComplete();
++ emit q->completed();
++ }
++
++ // Generate a list of actions for this state. This includes coelescing state
++ // actions that this state "extends"
++ QQuickStateOperation::ActionList
++ QQuickStatePrivate::generateActionList() const
++ {
++ QQuickStateOperation::ActionList applyList;
++ if (inState)
++ return applyList;
++
++ // Prevent "extends" recursion
++ inState = true;
++
++ if (!extends.isEmpty()) {
++ QList<QQuickState *> states = group ? group->states() : QList<QQuickState *>();
++ for (int ii = 0; ii < states.size(); ++ii)
++ if (states.at(ii)->name() == extends) {
++ qmlExecuteDeferred(states.at(ii));
++ applyList = static_cast<QQuickStatePrivate*>(states.at(ii)->d_func())->generateActionList();
++ }
++ }
++
++ for (QQuickStateOperation *op : operations)
++ applyList << op->actions();
++
++ inState = false;
++ return applyList;
++ }
++
++ QQuickStateGroup *QQuickState::stateGroup() const
++ {
++ Q_D(const QQuickState);
++ return d->group;
++ }
++
++ void QQuickState::setStateGroup(QQuickStateGroup *group)
++ {
++ Q_D(QQuickState);
++ d->group = group;
++ }
++
++ void QQuickState::cancel()
++ {
++ Q_D(QQuickState);
++ d->transitionManager.cancel();
++ }
++
++ void QQuickStateAction::deleteFromBinding()
++ {
++ if (fromBinding) {
++ if (fromBinding.isAbstractPropertyBinding()) {
++ QQmlPropertyPrivate::removeBinding(property);
++ fromBinding = nullptr;
++ }
++ }
++ }
++
++ bool QQuickState::containsPropertyInRevertList(QObject *target, const QString &name) const
++ {
++ Q_D(const QQuickState);
++
++ if (isStateActive()) {
++ for (const QQuickSimpleAction &simpleAction : d->revertList) {
++ if (simpleAction.specifiedObject() == target && simpleAction.specifiedProperty() == name)
++ return true;
++ }
++ }
++
++ return false;
++ }
++
++ bool QQuickState::changeValueInRevertList(QObject *target, const QString &name, const QVariant &revertValue)
++ {
++ Q_D(QQuickState);
++
++ if (isStateActive()) {
++ for (QQuickSimpleAction &simpleAction : d->revertList) {
++ if (simpleAction.specifiedObject() == target && simpleAction.specifiedProperty() == name) {
++ simpleAction.setValue(revertValue);
++ return true;
++ }
++ }
++ }
++
++ return false;
++ }
++
++ bool QQuickState::changeBindingInRevertList(QObject *target, const QString &name, QQmlAnyBinding binding)
++ {
++ Q_D(QQuickState);
++
++ if (isStateActive()) {
++ for (QQuickSimpleAction &simpleAction : d->revertList) {
++ if (simpleAction.specifiedObject() == target && simpleAction.specifiedProperty() == name) {
++ simpleAction.setBinding(binding);
++ return true;
++ }
++ }
++ }
++
++ return false;
++ }
++
++ bool QQuickState::removeEntryFromRevertList(QObject *target, const QString &name)
++ {
++ Q_D(QQuickState);
++
++ if (isStateActive()) {
++ for (auto it = d->revertList.begin(), end = d->revertList.end(); it != end; ++it) {
++ QQuickSimpleAction &simpleAction = *it;
++ if (simpleAction.property().object() == target && simpleAction.property().name() == name) {
++ QQmlPropertyPrivate::removeBinding(simpleAction.property());
++
++ simpleAction.property().write(simpleAction.value());
++ if (auto binding = simpleAction.binding(); binding) {
++ QQmlProperty prop = simpleAction.property();
++ binding.installOn(prop);
++ }
++
++ d->revertList.erase(it);
++ return true;
++ }
++ }
++ }
++
++ return false;
++ }
++
++ void QQuickState::addEntryToRevertList(const QQuickStateAction &action)
++ {
++ Q_D(QQuickState);
++
++ QQuickSimpleAction simpleAction(action);
++
++ d->revertList.append(simpleAction);
++ }
++
++ void QQuickState::removeAllEntriesFromRevertList(QObject *target)
++ {
++ Q_D(QQuickState);
++
++ if (isStateActive()) {
++ const auto actionMatchesTarget = [target](const QQuickSimpleAction &simpleAction) {
++ if (simpleAction.property().object() == target) {
++ QQmlPropertyPrivate::removeBinding(simpleAction.property());
++ simpleAction.property().write(simpleAction.value());
++ if (auto binding = simpleAction.binding()) {
++ QQmlProperty prop = simpleAction.property();
++ binding.installOn(prop);
++ }
++
++ return true;
++ }
++ return false;
++ };
++
++ d->revertList.removeIf(actionMatchesTarget);
++ }
++ }
++
++ void QQuickState::addEntriesToRevertList(const QList<QQuickStateAction> &actionList)
++ {
++ Q_D(QQuickState);
++ if (isStateActive()) {
++ QList<QQuickSimpleAction> simpleActionList;
++ simpleActionList.reserve(actionList.size());
++
++ for (const QQuickStateAction &action : actionList) {
++ QQuickSimpleAction simpleAction(action);
++ action.property.write(action.toValue);
++ if (auto binding = action.toBinding; binding)
++ binding.installOn(action.property);
++
++ simpleActionList.append(simpleAction);
++ }
++
++ d->revertList.append(simpleActionList);
++ }
++ }
++
++ QVariant QQuickState::valueInRevertList(QObject *target, const QString &name) const
++ {
++ Q_D(const QQuickState);
++
++ if (isStateActive()) {
++ for (const QQuickSimpleAction &simpleAction : d->revertList) {
++ if (simpleAction.specifiedObject() == target && simpleAction.specifiedProperty() == name)
++ return simpleAction.value();
++ }
++ }
++
++ return QVariant();
++ }
++
++ QQmlAnyBinding QQuickState::bindingInRevertList(QObject *target, const QString &name) const
++ {
++ Q_D(const QQuickState);
++
++ if (isStateActive()) {
++ for (const QQuickSimpleAction &simpleAction : d->revertList) {
++ if (simpleAction.specifiedObject() == target && simpleAction.specifiedProperty() == name)
++ return simpleAction.binding();
++ }
++ }
++
++ return nullptr;
++ }
++
++ bool QQuickState::isStateActive() const
++ {
++ return stateGroup() && stateGroup()->state() == name();
++ }
++
++ void QQuickState::apply(QQuickTransition *trans, QQuickState *revert)
++ {
++ Q_D(QQuickState);
++
++ qmlExecuteDeferred(this);
++
++ cancel();
++ if (revert)
++ revert->cancel();
++ d->revertList.clear();
++ d->reverting.clear();
++
++ if (revert) {
++ QQuickStatePrivate *revertPrivate =
++ static_cast<QQuickStatePrivate*>(revert->d_func());
++ d->revertList = revertPrivate->revertList;
++ revertPrivate->revertList.clear();
++ }
++
++ // List of actions caused by this state
++ QQuickStateOperation::ActionList applyList = d->generateActionList();
++
++ // List of actions that need to be reverted to roll back (just) this state
++ QQuickStatePrivate::SimpleActionList additionalReverts;
++ // First add the reverse of all the applyList actions
++ for (int ii = 0; ii < applyList.size(); ++ii) {
++ QQuickStateAction &action = applyList[ii];
++
++ if (action.event) {
++ if (!action.event->isReversable())
++ continue;
++ bool found = false;
++ for (int jj = 0; jj < d->revertList.size(); ++jj) {
++ QQuickStateActionEvent *event = d->revertList.at(jj).event();
++ if (event && event->type() == action.event->type()) {
++ if (action.event->mayOverride(event)) {
++ found = true;
++
++ if (action.event != d->revertList.at(jj).event() && action.event->needsCopy()) {
++ action.event->copyOriginals(d->revertList.at(jj).event());
++
++ QQuickSimpleAction r(action);
++ additionalReverts << r;
++ d->revertList.removeAt(jj);
++ --jj;
++ } else if (action.event->isRewindable()) //###why needed?
++ action.event->saveCurrentValues();
++
++ break;
++ }
++ }
++ }
++ if (!found) {
++ action.event->saveOriginals();
++ // Only need to revert the applyList action if the previous
++ // state doesn't have a higher priority revert already
++ QQuickSimpleAction r(action);
++ additionalReverts << r;
++ }
++ } else {
++ bool found = false;
++ action.fromBinding = QQmlAnyBinding::ofProperty(action.property);
++
++ for (int jj = 0; jj < d->revertList.size(); ++jj) {
++ if (d->revertList.at(jj).property() == action.property) {
++ found = true;
++ if (d->revertList.at(jj).binding() != action.fromBinding) {
++ action.deleteFromBinding();
++ }
++ break;
++ }
++ }
++
++ if (!found) {
++ if (!action.restore) {
++ action.deleteFromBinding();;
++ } else {
++ // Only need to revert the applyList action if the previous
++ // state doesn't have a higher priority revert already
++ QQuickSimpleAction r(action);
++ additionalReverts << r;
++ }
++ }
++ }
++ }
++
++ // Any reverts from a previous state that aren't carried forth
++ // into this state need to be translated into apply actions
++ for (int ii = 0; ii < d->revertList.size(); ++ii) {
++ bool found = false;
++ if (d->revertList.at(ii).event()) {
++ QQuickStateActionEvent *event = d->revertList.at(ii).event();
++ if (!event->isReversable())
++ continue;
++ for (int jj = 0; !found && jj < applyList.size(); ++jj) {
++ const QQuickStateAction &action = applyList.at(jj);
++ if (action.event && action.event->type() == event->type()) {
++ if (action.event->mayOverride(event))
++ found = true;
++ }
++ }
++ } else {
++ for (int jj = 0; !found && jj < applyList.size(); ++jj) {
++ const QQuickStateAction &action = applyList.at(jj);
++ if (action.property == d->revertList.at(ii).property())
++ found = true;
++ }
++ }
++ if (!found) {
++ // If revert list contains bindings assigned to deleted objects, we need to
++ // prevent reverting properties of those objects.
++ if (d->revertList.at(ii).binding() && !d->revertList.at(ii).property().object()) {
++ continue;
++ }
++ QVariant cur = d->revertList.at(ii).property().read();
++ QQmlProperty prop = d->revertList.at(ii).property();
++ QQmlAnyBinding::removeBindingFrom(prop);
++
++ QQuickStateAction a;
++ a.property = d->revertList.at(ii).property();
++ a.fromValue = cur;
++ a.toValue = d->revertList.at(ii).value();
++ a.toBinding = d->revertList.at(ii).binding();
++ a.specifiedObject = d->revertList.at(ii).specifiedObject();
++ a.specifiedProperty = d->revertList.at(ii).specifiedProperty();
++ a.event = d->revertList.at(ii).event();
++ a.reverseEvent = d->revertList.at(ii).reverseEvent();
++ if (a.event && a.event->isRewindable())
++ a.event->saveCurrentValues();
++ applyList << a;
++ // Store these special reverts in the reverting list
++ if (a.event)
++ d->reverting << a.event;
++ else
++ d->reverting << a.property;
++ }
++ }
++ // All the local reverts now become part of the ongoing revertList
++ d->revertList << additionalReverts;
++
++ if (lcStates().isDebugEnabled()) {
++ for (const QQuickStateAction &action : std::as_const(applyList)) {
++ if (action.event)
++ qCDebug(lcStates) << "QQuickStateAction event:" << action.event->type();
++ else
++ qCDebug(lcStates) << "QQuickStateAction on" << action.property.object()
++ << action.property.name() << "from:" << action.fromValue
++ << "to:" << action.toValue;
++ }
++ }
++
++ d->transitionManager.transition(applyList, trans);
++ }
++
++ QQuickStateOperation::ActionList QQuickStateOperation::actions()
++ {
++ return ActionList();
++ }
++
++ QQuickState *QQuickStateOperation::state() const
++ {
++ Q_D(const QQuickStateOperation);
++ return d->m_state;
++ }
++
++ void QQuickStateOperation::setState(QQuickState *state)
++ {
++ Q_D(QQuickStateOperation);
++ d->m_state = state;
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qquickstate_p.cpp"