diff options
author | J-P Nurmi <jpnurmi@qt.io> | 2017-04-10 14:15:02 +0200 |
---|---|---|
committer | J-P Nurmi <jpnurmi@qt.io> | 2017-04-19 10:50:05 +0000 |
commit | 03607d6d3cf3a8568e74fad8f792375c24a19d0c (patch) | |
tree | e00099e97e62df864fcf6902b501a564cd3dfda6 | |
parent | 7da6884ae2f6d8631cef2d825eaa874151100b7b (diff) |
Add ActionGroup
[ChangeLog][Controls][ActionGroup] Introduced ActionGroup, a non-visual
group of actions that is mutually exclusive by default.
Task-number: QTBUG-50705
Change-Id: Ia33103173441ca8980341b7c94aba0db3264284d
Reviewed-by: Mitch Curtis <mitch.curtis@qt.io>
-rw-r--r-- | src/imports/controls/qtquickcontrols2plugin.cpp | 2 | ||||
-rw-r--r-- | src/imports/templates/qtquicktemplates2plugin.cpp | 2 | ||||
-rw-r--r-- | src/quicktemplates2/qquickaction.cpp | 9 | ||||
-rw-r--r-- | src/quicktemplates2/qquickaction_p_p.h | 2 | ||||
-rw-r--r-- | src/quicktemplates2/qquickactiongroup.cpp | 474 | ||||
-rw-r--r-- | src/quicktemplates2/qquickactiongroup_p.h | 129 | ||||
-rw-r--r-- | src/quicktemplates2/quicktemplates2.pri | 2 | ||||
-rw-r--r-- | tests/auto/controls/data/tst_actiongroup.qml | 381 |
8 files changed, 999 insertions, 2 deletions
diff --git a/src/imports/controls/qtquickcontrols2plugin.cpp b/src/imports/controls/qtquickcontrols2plugin.cpp index 2014be79..aa4eb295 100644 --- a/src/imports/controls/qtquickcontrols2plugin.cpp +++ b/src/imports/controls/qtquickcontrols2plugin.cpp @@ -48,6 +48,7 @@ #include <QtQuickControls2/private/qquicktumblerview_p.h> #endif #include <QtQuickTemplates2/private/qquickaction_p.h> +#include <QtQuickTemplates2/private/qquickactiongroup_p.h> #include <QtQuickTemplates2/private/qquickbuttongroup_p.h> #include <QtQuickTemplates2/private/qquickicon_p.h> @@ -156,6 +157,7 @@ void QtQuickControls2Plugin::registerTypes(const char *uri) // QtQuick.Controls 2.3 (new types in Qt 5.10) qmlRegisterType<QQuickAction>(uri, 2, 3, "Action"); + qmlRegisterType<QQuickActionGroup>(uri, 2, 3, "ActionGroup"); qmlRegisterType<QQuickIcon>(); } diff --git a/src/imports/templates/qtquicktemplates2plugin.cpp b/src/imports/templates/qtquicktemplates2plugin.cpp index 936d96a6..a03229a4 100644 --- a/src/imports/templates/qtquicktemplates2plugin.cpp +++ b/src/imports/templates/qtquicktemplates2plugin.cpp @@ -38,6 +38,7 @@ #include <QtQuickTemplates2/private/qquickabstractbutton_p.h> #include <QtQuickTemplates2/private/qquickaction_p.h> +#include <QtQuickTemplates2/private/qquickactiongroup_p.h> #include <QtQuickTemplates2/private/qquickapplicationwindow_p.h> #include <QtQuickTemplates2/private/qquickbusyindicator_p.h> #include <QtQuickTemplates2/private/qquickbutton_p.h> @@ -258,6 +259,7 @@ void QtQuickTemplates2Plugin::registerTypes(const char *uri) // QtQuick.Templates 2.3 (new types and revisions in Qt 5.10) qmlRegisterType<QQuickAbstractButton, 3>(uri, 2, 3, "AbstractButton"); qmlRegisterType<QQuickAction>(uri, 2, 3, "Action"); + qmlRegisterType<QQuickActionGroup>(uri, 2, 3, "ActionGroup"); qmlRegisterType<QQuickIcon>(); qmlRegisterType<QQuickRangeSlider, 3>(uri, 2, 3, "RangeSlider"); qmlRegisterType<QQuickScrollBar, 3>(uri, 2, 3, "ScrollBar"); diff --git a/src/quicktemplates2/qquickaction.cpp b/src/quicktemplates2/qquickaction.cpp index 06035455..10ba5f37 100644 --- a/src/quicktemplates2/qquickaction.cpp +++ b/src/quicktemplates2/qquickaction.cpp @@ -36,6 +36,7 @@ #include "qquickaction_p.h" #include "qquickaction_p_p.h" +#include "qquickactiongroup_p.h" #include "qquickshortcutcontext_p_p.h" #include "qquickicon_p.h" @@ -176,7 +177,8 @@ QQuickActionPrivate::QQuickActionPrivate() checked(false), checkable(false), icon(nullptr), - defaultShortcutEntry(nullptr) + defaultShortcutEntry(nullptr), + group(nullptr) { } @@ -338,6 +340,9 @@ QQuickAction::QQuickAction(QObject *parent) QQuickAction::~QQuickAction() { Q_D(QQuickAction); + if (d->group) + d->group->removeAction(this); + for (QQuickActionPrivate::ShortcutEntry *entry : qAsConst(d->shortcutEntries)) d->unwatchItem(qobject_cast<QQuickItem *>(entry->target())); @@ -391,7 +396,7 @@ QQuickIcon *QQuickAction::icon() const bool QQuickAction::isEnabled() const { Q_D(const QQuickAction); - return d->enabled; + return d->enabled && (!d->group || d->group->isEnabled()); } void QQuickAction::setEnabled(bool enabled) diff --git a/src/quicktemplates2/qquickaction_p_p.h b/src/quicktemplates2/qquickaction_p_p.h index 754de3d5..85da6b03 100644 --- a/src/quicktemplates2/qquickaction_p_p.h +++ b/src/quicktemplates2/qquickaction_p_p.h @@ -58,6 +58,7 @@ QT_BEGIN_NAMESPACE class QQuickIcon; class QShortcutEvent; +class QQuickActionGroup; class QQuickActionPrivate : public QObjectPrivate, public QQuickItemChangeListener { @@ -119,6 +120,7 @@ public: QKeySequence keySequence; ShortcutEntry *defaultShortcutEntry; QVector<ShortcutEntry *> shortcutEntries; + QQuickActionGroup *group; }; QT_END_NAMESPACE diff --git a/src/quicktemplates2/qquickactiongroup.cpp b/src/quicktemplates2/qquickactiongroup.cpp new file mode 100644 index 00000000..7a5e17ee --- /dev/null +++ b/src/quicktemplates2/qquickactiongroup.cpp @@ -0,0 +1,474 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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" + +QT_BEGIN_NAMESPACE + +/*! + \qmltype ActionGroup + \inherits QtObject + \instantiates 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 + text: qsTr("Left") + } + + Action { + text: qsTr("Center") + } + + Action { + 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 + text: qsTr("Left") + ActionGroup.group: alignmentGroup + } + + Action { + text: qsTr("Center") + ActionGroup.group: alignmentGroup + } + + Action { + 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 +{ + Q_DECLARE_PUBLIC(QQuickActionGroup) + +public: + QQuickActionGroupPrivate() + : enabled(true), + exclusive(true), + checkedAction(nullptr) + { + } + + void clear(); + void actionTriggered(); + void _q_updateCurrent(); + + static bool changeEnabled(QQuickAction *action, bool enabled); + + static void actions_append(QQmlListProperty<QQuickAction> *prop, QQuickAction *obj); + static int actions_count(QQmlListProperty<QQuickAction> *prop); + static QQuickAction *actions_at(QQmlListProperty<QQuickAction> *prop, int index); + static void actions_clear(QQmlListProperty<QQuickAction> *prop); + + bool enabled; + bool exclusive; + QQuickAction *checkedAction; + QVector<QQuickAction*> actions; +}; + +void QQuickActionGroupPrivate::clear() +{ + for (QQuickAction *action : qAsConst(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); +} + +int QQuickActionGroupPrivate::actions_count(QQmlListProperty<QQuickAction> *prop) +{ + QQuickActionGroupPrivate *p = static_cast<QQuickActionGroupPrivate *>(prop->data); + return p->actions.count(); +} + +QQuickAction *QQuickActionGroupPrivate::actions_at(QQmlListProperty<QQuickAction> *prop, int 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 + \default + + 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 : qAsConst(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: + QQuickActionGroupAttachedPrivate() : group(nullptr) { } + + QQuickActionGroup *group; +}; + +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" diff --git a/src/quicktemplates2/qquickactiongroup_p.h b/src/quicktemplates2/qquickactiongroup_p.h new file mode 100644 index 00000000..42280831 --- /dev/null +++ b/src/quicktemplates2/qquickactiongroup_p.h @@ -0,0 +1,129 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKACTIONGROUP_P_H +#define QQUICKACTIONGROUP_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qobject.h> +#include <QtQuickTemplates2/private/qtquicktemplates2global_p.h> +#include <QtQml/qqml.h> + +QT_BEGIN_NAMESPACE + +class QQuickAction; +class QQuickActionGroupPrivate; +class QQuickActionGroupAttached; +class QQuickActionGroupAttachedPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickActionGroup : public QObject +{ + Q_OBJECT + Q_PROPERTY(QQuickAction *checkedAction READ checkedAction WRITE setCheckedAction NOTIFY checkedActionChanged FINAL) + Q_PROPERTY(QQmlListProperty<QQuickAction> actions READ actions NOTIFY actionsChanged FINAL) + Q_PROPERTY(bool exclusive READ isExclusive WRITE setExclusive NOTIFY exclusiveChanged FINAL) + Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged FINAL) + +public: + explicit QQuickActionGroup(QObject *parent = nullptr); + ~QQuickActionGroup(); + + static QQuickActionGroupAttached *qmlAttachedProperties(QObject *object); + + QQuickAction *checkedAction() const; + void setCheckedAction(QQuickAction *checkedAction); + + QQmlListProperty<QQuickAction> actions(); + + bool isExclusive() const; + void setExclusive(bool exclusive); + + bool isEnabled() const; + void setEnabled(bool enabled); + +public Q_SLOTS: + void addAction(QQuickAction *action); + void removeAction(QQuickAction *action); + +Q_SIGNALS: + void checkedActionChanged(); + void actionsChanged(); + void exclusiveChanged(); + void enabledChanged(); + void triggered(QQuickAction *action); + +private: + Q_DISABLE_COPY(QQuickActionGroup) + Q_DECLARE_PRIVATE(QQuickActionGroup) + + Q_PRIVATE_SLOT(d_func(), void _q_updateCurrent()) +}; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickActionGroupAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(QQuickActionGroup *group READ group WRITE setGroup NOTIFY groupChanged FINAL) + +public: + explicit QQuickActionGroupAttached(QObject *parent = nullptr); + + QQuickActionGroup *group() const; + void setGroup(QQuickActionGroup *group); + +Q_SIGNALS: + void groupChanged(); + +private: + Q_DISABLE_COPY(QQuickActionGroupAttached) + Q_DECLARE_PRIVATE(QQuickActionGroupAttached) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickActionGroup) +QML_DECLARE_TYPEINFO(QQuickActionGroup, QML_HAS_ATTACHED_PROPERTIES) + +#endif // QQUICKACTIONGROUP_P_H diff --git a/src/quicktemplates2/quicktemplates2.pri b/src/quicktemplates2/quicktemplates2.pri index b9f9a0b9..e1a1a917 100644 --- a/src/quicktemplates2/quicktemplates2.pri +++ b/src/quicktemplates2/quicktemplates2.pri @@ -4,6 +4,7 @@ HEADERS += \ $$PWD/qquickabstractbutton_p.h \ $$PWD/qquickabstractbutton_p_p.h \ $$PWD/qquickaction_p.h \ + $$PWD/qquickactiongroup_p.h \ $$PWD/qquickapplicationwindow_p.h \ $$PWD/qquickbusyindicator_p.h \ $$PWD/qquickbutton_p.h \ @@ -85,6 +86,7 @@ HEADERS += \ SOURCES += \ $$PWD/qquickabstractbutton.cpp \ $$PWD/qquickaction.cpp \ + $$PWD/qquickactiongroup.cpp \ $$PWD/qquickapplicationwindow.cpp \ $$PWD/qquickbusyindicator.cpp \ $$PWD/qquickbutton.cpp \ diff --git a/tests/auto/controls/data/tst_actiongroup.qml b/tests/auto/controls/data/tst_actiongroup.qml new file mode 100644 index 00000000..6b31336d --- /dev/null +++ b/tests/auto/controls/data/tst_actiongroup.qml @@ -0,0 +1,381 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtTest 1.0 +import QtQuick.Controls 2.3 + +TestCase { + id: testCase + width: 200 + height: 200 + visible: true + when: windowShown + name: "ActionGroup" + + Component { + id: actionGroup + ActionGroup { } + } + + Component { + id: nonExclusiveGroup + ActionGroup { exclusive: false } + } + + Component { + id: signalSpy + SignalSpy { } + } + + function test_null() { + var group = createTemporaryObject(actionGroup, testCase) + verify(group) + + group.addAction(null) + group.removeAction(null) + } + + Component { + id: action + Action { } + } + + function test_defaults() { + var group = createTemporaryObject(actionGroup, testCase) + verify(group) + compare(group.actions.length, 0) + compare(group.checkedAction, null) + compare(group.exclusive, true) + } + + function test_current() { + var group = createTemporaryObject(actionGroup, testCase) + verify(group) + + var checkedActionSpy = createTemporaryObject(signalSpy, testCase, {target: group, signalName: "checkedActionChanged"}) + verify(checkedActionSpy.valid) + verify(!group.checkedAction) + + var action1 = createTemporaryObject(action, testCase, {checked: true}) + var action2 = createTemporaryObject(action, testCase, {checked: false}) + var action3 = createTemporaryObject(action, testCase, {checked: true, objectName: "3"}) + + // add checked + group.addAction(action1) + compare(group.checkedAction, action1) + compare(action1.checked, true) + compare(action2.checked, false) + compare(action3.checked, true) + compare(checkedActionSpy.count, 1) + + // add non-checked + group.addAction(action2) + compare(group.checkedAction, action1) + compare(action1.checked, true) + compare(action2.checked, false) + compare(action3.checked, true) + compare(checkedActionSpy.count, 1) + + // add checked + group.addAction(action3) + compare(group.checkedAction, action3) + compare(action1.checked, false) + compare(action2.checked, false) + compare(action3.checked, true) + compare(checkedActionSpy.count, 2) + + // change current + group.checkedAction = action2 + compare(group.checkedAction, action2) + compare(action1.checked, false) + compare(action2.checked, true) + compare(action3.checked, false) + compare(checkedActionSpy.count, 3) + + // check + action1.checked = true + compare(group.checkedAction, action1) + compare(action1.checked, true) + compare(action2.checked, false) + compare(action3.checked, false) + compare(checkedActionSpy.count, 4) + + // remove non-checked + group.removeAction(action2) + compare(group.checkedAction, action1) + compare(action1.checked, true) + compare(action2.checked, false) + compare(action3.checked, false) + compare(checkedActionSpy.count, 4) + + // remove checked + group.removeAction(action1) + verify(!group.checkedAction) + compare(action1.checked, false) + compare(action2.checked, false) + compare(action3.checked, false) + compare(checkedActionSpy.count, 5) + } + + function test_actions() { + var group = createTemporaryObject(actionGroup, testCase) + verify(group) + + var actionsSpy = createTemporaryObject(signalSpy, testCase, {target: group, signalName: "actionsChanged"}) + verify(actionsSpy.valid) + + compare(group.actions.length, 0) + compare(group.checkedAction, null) + + var action1 = createTemporaryObject(action, testCase, {checked: true}) + var action2 = createTemporaryObject(action, testCase, {checked: false}) + + group.actions = [action1, action2] + compare(group.actions.length, 2) + compare(group.actions[0], action1) + compare(group.actions[1], action2) + compare(group.checkedAction, action1) + compare(actionsSpy.count, 2) + + var action3 = createTemporaryObject(action, testCase, {checked: true}) + + group.addAction(action3) + compare(group.actions.length, 3) + compare(group.actions[0], action1) + compare(group.actions[1], action2) + compare(group.actions[2], action3) + compare(group.checkedAction, action3) + compare(actionsSpy.count, 3) + + group.removeAction(action1) + compare(group.actions.length, 2) + compare(group.actions[0], action2) + compare(group.actions[1], action3) + compare(group.checkedAction, action3) + compare(actionsSpy.count, 4) + + group.actions = [] + compare(group.actions.length, 0) + tryCompare(group, "checkedAction", null) + compare(actionsSpy.count, 5) + } + + function test_triggered_data() { + return [ + {tag: "exclusive", exclusive: true}, + {tag: "non-exclusive", exclusive: false} + ] + } + + function test_triggered(data) { + var group = createTemporaryObject(actionGroup, testCase, {exclusive: data.exclusive}) + verify(group) + + var triggeredSpy = createTemporaryObject(signalSpy, testCase, {target: group, signalName: "triggered"}) + verify(triggeredSpy.valid) + + var action1 = createTemporaryObject(action, testCase) + var action2 = createTemporaryObject(action, testCase) + + group.addAction(action1) + group.addAction(action2) + + action1.triggered() + compare(triggeredSpy.count, 1) + compare(triggeredSpy.signalArguments[0][0], action1) + + action2.triggered() + compare(triggeredSpy.count, 2) + compare(triggeredSpy.signalArguments[1][0], action2) + } + + Component { + id: attachedGroup + Item { + property ActionGroup group: ActionGroup { id: group } + property Action action1: Action { ActionGroup.group: group } + property Action action2: Action { ActionGroup.group: group } + property Action action3: Action { ActionGroup.group: group } + } + } + + function test_attached() { + var container = createTemporaryObject(attachedGroup, testCase) + verify(container) + + verify(!container.group.checkedAction) + + container.action1.checked = true + compare(container.group.checkedAction, container.action1) + compare(container.action1.checked, true) + compare(container.action2.checked, false) + compare(container.action3.checked, false) + + container.action2.checked = true + compare(container.group.checkedAction, container.action2) + compare(container.action1.checked, false) + compare(container.action2.checked, true) + compare(container.action3.checked, false) + + container.action3.checked = true + compare(container.group.checkedAction, container.action3) + compare(container.action1.checked, false) + compare(container.action2.checked, false) + compare(container.action3.checked, true) + } + + function test_actionDestroyed() { + var group = createTemporaryObject(actionGroup, testCase) + verify(group) + + var actionsSpy = createTemporaryObject(signalSpy, testCase, {target: group, signalName: "actionsChanged"}) + verify(actionsSpy.valid) + + var action1 = createTemporaryObject(action, testCase, {objectName: "action1", checked: true}) + + group.addAction(action1) + compare(group.actions.length, 1) + compare(group.actions[0], action1) + compare(group.checkedAction, action1) + compare(actionsSpy.count, 1) + + action1.destroy() + wait(0) + compare(group.actions.length, 0) + compare(group.checkedAction, null) + compare(actionsSpy.count, 2) + } + + function test_nonExclusive() { + var group = createTemporaryObject(nonExclusiveGroup, testCase) + verify(group) + + var action1 = createTemporaryObject(action, testCase, {checked: true}) + group.addAction(action1) + compare(action1.checked, true) + compare(group.checkedAction, null) + + var action2 = createTemporaryObject(action, testCase, {checked: true}) + group.addAction(action2) + compare(action1.checked, true) + compare(action2.checked, true) + compare(group.checkedAction, null) + + action1.checked = false + compare(action1.checked, false) + compare(action2.checked, true) + compare(group.checkedAction, null) + + action2.checked = false + compare(action1.checked, false) + compare(action2.checked, false) + compare(group.checkedAction, null) + + action1.checked = true + compare(action1.checked, true) + compare(action2.checked, false) + compare(group.checkedAction, null) + + action2.checked = true + compare(action1.checked, true) + compare(action2.checked, true) + compare(group.checkedAction, null) + } + + function test_enabled() { + var group = createTemporaryObject(actionGroup, testCase) + verify(group) + + compare(group.enabled, true) + + var action1 = createTemporaryObject(action, testCase) + var action2 = createTemporaryObject(action, testCase) + compare(action1.enabled, true) + compare(action2.enabled, true) + + var action1Spy = createTemporaryObject(signalSpy, testCase, {target: action1, signalName: "enabledChanged"}) + var action2Spy = createTemporaryObject(signalSpy, testCase, {target: action2, signalName: "enabledChanged"}) + verify(action1Spy.valid && action2Spy.valid) + + group.addAction(action1) + compare(action1.enabled, true) + compare(action2.enabled, true) + compare(action1Spy.count, 0) + compare(action2Spy.count, 0) + + group.enabled = false + compare(action1.enabled, false) + compare(action2.enabled, true) + compare(action1Spy.count, 1) + compare(action1Spy.signalArguments[0][0], false) + compare(action2Spy.count, 0) + + group.addAction(action2) + compare(action1.enabled, false) + compare(action2.enabled, false) + compare(action1Spy.count, 1) + compare(action2Spy.count, 1) + compare(action2Spy.signalArguments[0][0], false) + + action1.enabled = false + compare(action1.enabled, false) + compare(action1Spy.count, 2) + compare(action1Spy.signalArguments[1][0], false) + compare(action2Spy.count, 1) + + group.enabled = true + compare(action1.enabled, false) + compare(action2.enabled, true) + compare(action1Spy.count, 2) + compare(action2Spy.count, 2) + compare(action2Spy.signalArguments[1][0], true) + } +} |