// Copyright (C) 2020 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 #include "qquickactiongroup_p.h" #include #include #include #include #include "qquickaction_p.h" #include "qquickaction_p_p.h" #include QT_BEGIN_NAMESPACE /*! \qmltype ActionGroup \inherits QtObject //! \nativetype QQuickActionGroup \inqmlmodule QtQuick.Controls \since 5.10 \ingroup utilities \brief Groups actions together. ActionGroup is a non-visual group of actions. A mutually \l exclusive action group is used with actions where only one of the options can be selected at a time. The most straight-forward way to use ActionGroup is to declare actions as children of the group. \code ActionGroup { id: alignmentGroup Action { checked: true checkable: true text: qsTr("Left") } Action { checkable: true text: qsTr("Center") } Action { checkable: true text: qsTr("Right") } } \endcode Alternatively, the \l group attached property allows declaring the actions elsewhere and assigning them to a specific group. \code ActionGroup { id: alignmentGroup } Action { checked: true checkable: true text: qsTr("Left") ActionGroup.group: alignmentGroup } Action { checkable: true text: qsTr("Center") ActionGroup.group: alignmentGroup } Action { checkable: true text: qsTr("Right") ActionGroup.group: alignmentGroup } \endcode More advanced use cases can be handled using the \c addAction() and \c removeAction() methods. \sa Action, ButtonGroup */ /*! \qmlsignal QtQuick.Controls::ActionGroup::triggered(Action action) This signal is emitted when an \a action in the group has been triggered. This signal is convenient for implementing a common signal handler for all actions in the same group. \code ActionGroup { onTriggered: console.log("triggered:", action.text) Action { text: "First" } Action { text: "Second" } Action { text: "Third" } } \endcode \sa Action::triggered() */ class QQuickActionGroupPrivate : public QObjectPrivate { public: Q_DECLARE_PUBLIC(QQuickActionGroup) void clear(); void actionTriggered(); void _q_updateCurrent(); static bool changeEnabled(QQuickAction *action, bool enabled); static void actions_append(QQmlListProperty *prop, QQuickAction *obj); static qsizetype actions_count(QQmlListProperty *prop); static QQuickAction *actions_at(QQmlListProperty *prop, qsizetype index); static void actions_clear(QQmlListProperty *prop); bool enabled = true; bool exclusive = true; QPointer checkedAction; QList actions; }; void QQuickActionGroupPrivate::clear() { for (QQuickAction *action : std::as_const(actions)) { QQuickActionPrivate::get(action)->group = nullptr; QObjectPrivate::disconnect(action, &QQuickAction::triggered, this, &QQuickActionGroupPrivate::actionTriggered); QObjectPrivate::disconnect(action, &QQuickAction::checkedChanged, this, &QQuickActionGroupPrivate::_q_updateCurrent); } actions.clear(); } void QQuickActionGroupPrivate::actionTriggered() { Q_Q(QQuickActionGroup); QQuickAction *action = qobject_cast(q->sender()); if (action) emit q->triggered(action); } void QQuickActionGroupPrivate::_q_updateCurrent() { Q_Q(QQuickActionGroup); if (!exclusive) return; QQuickAction *action = qobject_cast(q->sender()); if (action && action->isChecked()) q->setCheckedAction(action); else if (!actions.contains(checkedAction)) q->setCheckedAction(nullptr); } bool QQuickActionGroupPrivate::changeEnabled(QQuickAction *action, bool enabled) { return action->isEnabled() != enabled && (!enabled || !QQuickActionPrivate::get(action)->explicitEnabled); } void QQuickActionGroupPrivate::actions_append(QQmlListProperty *prop, QQuickAction *obj) { QQuickActionGroup *q = static_cast(prop->object); q->addAction(obj); } qsizetype QQuickActionGroupPrivate::actions_count(QQmlListProperty *prop) { QQuickActionGroupPrivate *p = static_cast(prop->data); return p->actions.size(); } QQuickAction *QQuickActionGroupPrivate::actions_at(QQmlListProperty *prop, qsizetype index) { QQuickActionGroupPrivate *p = static_cast(prop->data); return p->actions.value(index); } void QQuickActionGroupPrivate::actions_clear(QQmlListProperty *prop) { QQuickActionGroupPrivate *p = static_cast(prop->data); if (!p->actions.isEmpty()) { p->clear(); QQuickActionGroup *q = static_cast(prop->object); // QTBUG-52358: don't clear the checked action immediately QMetaObject::invokeMethod(q, "_q_updateCurrent", Qt::QueuedConnection); emit q->actionsChanged(); } } QQuickActionGroup::QQuickActionGroup(QObject *parent) : QObject(*(new QQuickActionGroupPrivate), parent) { } QQuickActionGroup::~QQuickActionGroup() { Q_D(QQuickActionGroup); d->clear(); } QQuickActionGroupAttached *QQuickActionGroup::qmlAttachedProperties(QObject *object) { return new QQuickActionGroupAttached(object); } /*! \qmlproperty Action QtQuick.Controls::ActionGroup::checkedAction This property holds the currently selected action in an exclusive group, or \c null if there is none or the group is non-exclusive. By default, it is the first checked action added to an exclusive action group. \sa exclusive */ QQuickAction *QQuickActionGroup::checkedAction() const { Q_D(const QQuickActionGroup); return d->checkedAction; } void QQuickActionGroup::setCheckedAction(QQuickAction *checkedAction) { Q_D(QQuickActionGroup); if (d->checkedAction == checkedAction) return; if (d->checkedAction) d->checkedAction->setChecked(false); d->checkedAction = checkedAction; if (checkedAction) checkedAction->setChecked(true); emit checkedActionChanged(); } /*! \qmlproperty list QtQuick.Controls::ActionGroup::actions \qmldefault This property holds the list of actions in the group. \sa group */ QQmlListProperty QQuickActionGroup::actions() { Q_D(QQuickActionGroup); return QQmlListProperty(this, d, QQuickActionGroupPrivate::actions_append, QQuickActionGroupPrivate::actions_count, QQuickActionGroupPrivate::actions_at, QQuickActionGroupPrivate::actions_clear); } /*! \qmlproperty bool QtQuick.Controls::ActionGroup::exclusive This property holds whether the action group is exclusive. The default value is \c true. If this property is \c true, then only one action in the group can be checked at any given time. The user can trigger any action to check it, and that action will replace the existing one as the checked action in the group. In an exclusive group, the user cannot uncheck the currently checked action by triggering it; instead, another action in the group must be triggered to set the new checked action for that group. In a non-exclusive group, checking and unchecking actions does not affect the other actions in the group. Furthermore, the value of the \l checkedAction property is \c null. */ bool QQuickActionGroup::isExclusive() const { Q_D(const QQuickActionGroup); return d->exclusive; } void QQuickActionGroup::setExclusive(bool exclusive) { Q_D(QQuickActionGroup); if (d->exclusive == exclusive) return; d->exclusive = exclusive; emit exclusiveChanged(); } /*! \qmlproperty bool QtQuick.Controls::ActionGroup::enabled This property holds whether the action group is enabled. The default value is \c true. If this property is \c false, then all actions in the group are disabled. If this property is \c true, all actions in the group are enabled, unless explicitly disabled. */ bool QQuickActionGroup::isEnabled() const { Q_D(const QQuickActionGroup); return d->enabled; } void QQuickActionGroup::setEnabled(bool enabled) { Q_D(QQuickActionGroup); if (d->enabled == enabled) return; for (QQuickAction *action : std::as_const(d->actions)) { if (d->changeEnabled(action, enabled)) emit action->enabledChanged(enabled); } d->enabled = enabled; emit enabledChanged(); } /*! \qmlmethod void QtQuick.Controls::ActionGroup::addAction(Action action) Adds an \a action to the action group. \note Manually adding objects to a action group is typically unnecessary. The \l actions property and the \l group attached property provide a convenient and declarative syntax. \sa actions, group */ void QQuickActionGroup::addAction(QQuickAction *action) { Q_D(QQuickActionGroup); if (!action || d->actions.contains(action)) return; const bool enabledChange = d->changeEnabled(action, d->enabled); QQuickActionPrivate::get(action)->group = this; QObjectPrivate::connect(action, &QQuickAction::triggered, d, &QQuickActionGroupPrivate::actionTriggered); QObjectPrivate::connect(action, &QQuickAction::checkedChanged, d, &QQuickActionGroupPrivate::_q_updateCurrent); if (d->exclusive && action->isChecked()) setCheckedAction(action); if (enabledChange) emit action->enabledChanged(action->isEnabled()); d->actions.append(action); emit actionsChanged(); } /*! \qmlmethod void QtQuick.Controls::ActionGroup::removeAction(Action action) Removes an \a action from the action group. \note Manually removing objects from a action group is typically unnecessary. The \l actions property and the \l group attached property provide a convenient and declarative syntax. \sa actions, group */ void QQuickActionGroup::removeAction(QQuickAction *action) { Q_D(QQuickActionGroup); if (!action || !d->actions.contains(action)) return; const bool enabledChange = d->changeEnabled(action, d->enabled); QQuickActionPrivate::get(action)->group = nullptr; QObjectPrivate::disconnect(action, &QQuickAction::triggered, d, &QQuickActionGroupPrivate::actionTriggered); QObjectPrivate::disconnect(action, &QQuickAction::checkedChanged, d, &QQuickActionGroupPrivate::_q_updateCurrent); if (d->checkedAction == action) setCheckedAction(nullptr); if (enabledChange) emit action->enabledChanged(action->isEnabled()); d->actions.removeOne(action); emit actionsChanged(); } class QQuickActionGroupAttachedPrivate : public QObjectPrivate { public: QQuickActionGroup *group = nullptr; }; QQuickActionGroupAttached::QQuickActionGroupAttached(QObject *parent) : QObject(*(new QQuickActionGroupAttachedPrivate), parent) { } /*! \qmlattachedproperty ActionGroup QtQuick.Controls::ActionGroup::group This property attaches an action to an action group. \code ActionGroup { id: group } Action { checked: true text: qsTr("Option A") ActionGroup.group: group } Action { text: qsTr("Option B") ActionGroup.group: group } \endcode \sa actions */ QQuickActionGroup *QQuickActionGroupAttached::group() const { Q_D(const QQuickActionGroupAttached); return d->group; } void QQuickActionGroupAttached::setGroup(QQuickActionGroup *group) { Q_D(QQuickActionGroupAttached); if (d->group == group) return; if (d->group) d->group->removeAction(qobject_cast(parent())); d->group = group; if (group) group->addAction(qobject_cast(parent())); emit groupChanged(); } QT_END_NAMESPACE #include "moc_qquickactiongroup_p.cpp"