Revision control
Copy as Markdown
// 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
// Qt-Security score:significant reason:default
#include "qquickactiongroup_p.h"
#include <QtCore/private/qobject_p.h>
#include <QtCore/qmetaobject.h>
#include <QtCore/qvariant.h>
#include <QtQml/qqmlinfo.h>
#include "qquickaction_p.h"
#include "qquickaction_p_p.h"
#include <QtCore/qpointer.h>
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<QQuickAction> *prop, QQuickAction *obj);
static qsizetype actions_count(QQmlListProperty<QQuickAction> *prop);
static QQuickAction *actions_at(QQmlListProperty<QQuickAction> *prop, qsizetype index);
static void actions_clear(QQmlListProperty<QQuickAction> *prop);
bool enabled = true;
bool exclusive = true;
QPointer<QQuickAction> checkedAction;
QList<QQuickAction*> 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<QQuickAction*>(q->sender());
if (action)
emit q->triggered(action);
}
void QQuickActionGroupPrivate::_q_updateCurrent()
{
Q_Q(QQuickActionGroup);
if (!exclusive)
return;
QQuickAction *action = qobject_cast<QQuickAction*>(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<QQuickAction> *prop, QQuickAction *obj)
{
QQuickActionGroup *q = static_cast<QQuickActionGroup *>(prop->object);
q->addAction(obj);
}
qsizetype QQuickActionGroupPrivate::actions_count(QQmlListProperty<QQuickAction> *prop)
{
QQuickActionGroupPrivate *p = static_cast<QQuickActionGroupPrivate *>(prop->data);
return p->actions.size();
}
QQuickAction *QQuickActionGroupPrivate::actions_at(QQmlListProperty<QQuickAction> *prop, qsizetype index)
{
QQuickActionGroupPrivate *p = static_cast<QQuickActionGroupPrivate *>(prop->data);
return p->actions.value(index);
}
void QQuickActionGroupPrivate::actions_clear(QQmlListProperty<QQuickAction> *prop)
{
QQuickActionGroupPrivate *p = static_cast<QQuickActionGroupPrivate *>(prop->data);
if (!p->actions.isEmpty()) {
p->clear();
QQuickActionGroup *q = static_cast<QQuickActionGroup *>(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<Action> QtQuick.Controls::ActionGroup::actions
\qmldefault
This property holds the list of actions in the group.
\sa group
*/
QQmlListProperty<QQuickAction> QQuickActionGroup::actions()
{
Q_D(QQuickActionGroup);
return QQmlListProperty<QQuickAction>(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<QQuickAction*>(parent()));
d->group = group;
if (group)
group->addAction(qobject_cast<QQuickAction*>(parent()));
emit groupChanged();
}
QT_END_NAMESPACE
#include "moc_qquickactiongroup_p.cpp"