1 /****************************************************************************
2 **
3 ** Copyright (C) 2017 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL3$
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 http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free
28 ** Software Foundation and appearing in the file LICENSE.GPL included in
29 ** the packaging of this file. Please review the following information to
30 ** ensure the GNU General Public License version 2.0 requirements will be
31 ** met: http://www.gnu.org/licenses/gpl-2.0.html.
32 **
33 ** $QT_END_LICENSE$
34 **
35 ****************************************************************************/
36 
37 #include "qquickactiongroup_p.h"
38 
39 #include <QtCore/private/qobject_p.h>
40 #include <QtCore/qmetaobject.h>
41 #include <QtCore/qvariant.h>
42 #include <QtQml/qqmlinfo.h>
43 
44 #include "qquickaction_p.h"
45 #include "qquickaction_p_p.h"
46 
47 QT_BEGIN_NAMESPACE
48 
49 /*!
50     \qmltype ActionGroup
51     \inherits QtObject
52 //!     \instantiates QQuickActionGroup
53     \inqmlmodule QtQuick.Controls
54     \since 5.10
55     \ingroup utilities
56     \brief Groups actions together.
57 
58     ActionGroup is a non-visual group of actions. A mutually \l exclusive
59     action group is used with actions where only one of the options can be
60     selected at a time.
61 
62     The most straight-forward way to use ActionGroup is to declare actions
63     as children of the group.
64 
65     \code
66     ActionGroup {
67         id: alignmentGroup
68 
69         Action {
70             checked: true
71             checkable: true
72             text: qsTr("Left")
73         }
74 
75         Action {
76             checkable: true
77             text: qsTr("Center")
78         }
79 
80         Action {
81             checkable: true
82             text: qsTr("Right")
83         }
84     }
85     \endcode
86 
87     Alternatively, the \l group attached property allows declaring the actions
88     elsewhere and assigning them to a specific group.
89 
90     \code
91     ActionGroup { id: alignmentGroup }
92 
93     Action {
94         checked: true
95         checkable: true
96         text: qsTr("Left")
97         ActionGroup.group: alignmentGroup
98     }
99 
100     Action {
101         checkable: true
102         text: qsTr("Center")
103         ActionGroup.group: alignmentGroup
104     }
105 
106     Action {
107         checkable: true
108         text: qsTr("Right")
109         ActionGroup.group: alignmentGroup
110     }
111     \endcode
112 
113     More advanced use cases can be handled using the \c addAction() and
114     \c removeAction() methods.
115 
116     \sa Action, ButtonGroup
117 */
118 
119 /*!
120     \qmlsignal QtQuick.Controls::ActionGroup::triggered(Action action)
121 
122     This signal is emitted when an \a action in the group has been triggered.
123 
124     This signal is convenient for implementing a common signal handler for
125     all actions in the same group.
126 
127     \code
128     ActionGroup {
129         onTriggered: console.log("triggered:", action.text)
130 
131         Action { text: "First" }
132         Action { text: "Second" }
133         Action { text: "Third" }
134     }
135     \endcode
136 
137     \sa Action::triggered()
138 */
139 
140 class QQuickActionGroupPrivate : public QObjectPrivate
141 {
142     Q_DECLARE_PUBLIC(QQuickActionGroup)
143 
144 public:
145     void clear();
146     void actionTriggered();
147     void _q_updateCurrent();
148 
149     static bool changeEnabled(QQuickAction *action, bool enabled);
150 
151     static void actions_append(QQmlListProperty<QQuickAction> *prop, QQuickAction *obj);
152     static int actions_count(QQmlListProperty<QQuickAction> *prop);
153     static QQuickAction *actions_at(QQmlListProperty<QQuickAction> *prop, int index);
154     static void actions_clear(QQmlListProperty<QQuickAction> *prop);
155 
156     bool enabled = true;
157     bool exclusive = true;
158     QPointer<QQuickAction> checkedAction;
159     QVector<QQuickAction*> actions;
160 };
161 
clear()162 void QQuickActionGroupPrivate::clear()
163 {
164     for (QQuickAction *action : qAsConst(actions)) {
165         QQuickActionPrivate::get(action)->group = nullptr;
166         QObjectPrivate::disconnect(action, &QQuickAction::triggered, this, &QQuickActionGroupPrivate::actionTriggered);
167         QObjectPrivate::disconnect(action, &QQuickAction::checkedChanged, this, &QQuickActionGroupPrivate::_q_updateCurrent);
168     }
169     actions.clear();
170 }
171 
actionTriggered()172 void QQuickActionGroupPrivate::actionTriggered()
173 {
174     Q_Q(QQuickActionGroup);
175     QQuickAction *action = qobject_cast<QQuickAction*>(q->sender());
176     if (action)
177         emit q->triggered(action);
178 }
179 
_q_updateCurrent()180 void QQuickActionGroupPrivate::_q_updateCurrent()
181 {
182     Q_Q(QQuickActionGroup);
183     if (!exclusive)
184         return;
185     QQuickAction *action = qobject_cast<QQuickAction*>(q->sender());
186     if (action && action->isChecked())
187         q->setCheckedAction(action);
188     else if (!actions.contains(checkedAction))
189         q->setCheckedAction(nullptr);
190 }
191 
changeEnabled(QQuickAction * action,bool enabled)192 bool QQuickActionGroupPrivate::changeEnabled(QQuickAction *action, bool enabled)
193 {
194     return action->isEnabled() != enabled && (!enabled || !QQuickActionPrivate::get(action)->explicitEnabled);
195 }
196 
actions_append(QQmlListProperty<QQuickAction> * prop,QQuickAction * obj)197 void QQuickActionGroupPrivate::actions_append(QQmlListProperty<QQuickAction> *prop, QQuickAction *obj)
198 {
199     QQuickActionGroup *q = static_cast<QQuickActionGroup *>(prop->object);
200     q->addAction(obj);
201 }
202 
actions_count(QQmlListProperty<QQuickAction> * prop)203 int QQuickActionGroupPrivate::actions_count(QQmlListProperty<QQuickAction> *prop)
204 {
205     QQuickActionGroupPrivate *p = static_cast<QQuickActionGroupPrivate *>(prop->data);
206     return p->actions.count();
207 }
208 
actions_at(QQmlListProperty<QQuickAction> * prop,int index)209 QQuickAction *QQuickActionGroupPrivate::actions_at(QQmlListProperty<QQuickAction> *prop, int index)
210 {
211     QQuickActionGroupPrivate *p = static_cast<QQuickActionGroupPrivate *>(prop->data);
212     return p->actions.value(index);
213 }
214 
actions_clear(QQmlListProperty<QQuickAction> * prop)215 void QQuickActionGroupPrivate::actions_clear(QQmlListProperty<QQuickAction> *prop)
216 {
217     QQuickActionGroupPrivate *p = static_cast<QQuickActionGroupPrivate *>(prop->data);
218     if (!p->actions.isEmpty()) {
219         p->clear();
220         QQuickActionGroup *q = static_cast<QQuickActionGroup *>(prop->object);
221         // QTBUG-52358: don't clear the checked action immediately
222         QMetaObject::invokeMethod(q, "_q_updateCurrent", Qt::QueuedConnection);
223         emit q->actionsChanged();
224     }
225 }
226 
QQuickActionGroup(QObject * parent)227 QQuickActionGroup::QQuickActionGroup(QObject *parent)
228     : QObject(*(new QQuickActionGroupPrivate), parent)
229 {
230 }
231 
~QQuickActionGroup()232 QQuickActionGroup::~QQuickActionGroup()
233 {
234     Q_D(QQuickActionGroup);
235     d->clear();
236 }
237 
qmlAttachedProperties(QObject * object)238 QQuickActionGroupAttached *QQuickActionGroup::qmlAttachedProperties(QObject *object)
239 {
240     return new QQuickActionGroupAttached(object);
241 }
242 
243 /*!
244     \qmlproperty Action QtQuick.Controls::ActionGroup::checkedAction
245 
246     This property holds the currently selected action in an exclusive group,
247     or \c null if there is none or the group is non-exclusive.
248 
249     By default, it is the first checked action added to an exclusive action group.
250 
251     \sa exclusive
252 */
checkedAction() const253 QQuickAction *QQuickActionGroup::checkedAction() const
254 {
255     Q_D(const QQuickActionGroup);
256     return d->checkedAction;
257 }
258 
setCheckedAction(QQuickAction * checkedAction)259 void QQuickActionGroup::setCheckedAction(QQuickAction *checkedAction)
260 {
261     Q_D(QQuickActionGroup);
262     if (d->checkedAction == checkedAction)
263         return;
264 
265     if (d->checkedAction)
266         d->checkedAction->setChecked(false);
267     d->checkedAction = checkedAction;
268     if (checkedAction)
269         checkedAction->setChecked(true);
270     emit checkedActionChanged();
271 }
272 
273 /*!
274     \qmlproperty list<Action> QtQuick.Controls::ActionGroup::actions
275     \default
276 
277     This property holds the list of actions in the group.
278 
279     \sa group
280 */
actions()281 QQmlListProperty<QQuickAction> QQuickActionGroup::actions()
282 {
283     Q_D(QQuickActionGroup);
284     return QQmlListProperty<QQuickAction>(this, d,
285         QQuickActionGroupPrivate::actions_append,
286         QQuickActionGroupPrivate::actions_count,
287         QQuickActionGroupPrivate::actions_at,
288         QQuickActionGroupPrivate::actions_clear);
289 }
290 
291 /*!
292     \qmlproperty bool QtQuick.Controls::ActionGroup::exclusive
293 
294     This property holds whether the action group is exclusive. The default value is \c true.
295 
296     If this property is \c true, then only one action in the group can be checked at any given time.
297     The user can trigger any action to check it, and that action will replace the existing one as
298     the checked action in the group.
299 
300     In an exclusive group, the user cannot uncheck the currently checked action by triggering it;
301     instead, another action in the group must be triggered to set the new checked action for that
302     group.
303 
304     In a non-exclusive group, checking and unchecking actions does not affect the other actions in
305     the group. Furthermore, the value of the \l checkedAction property is \c null.
306 */
isExclusive() const307 bool QQuickActionGroup::isExclusive() const
308 {
309     Q_D(const QQuickActionGroup);
310     return d->exclusive;
311 }
312 
setExclusive(bool exclusive)313 void QQuickActionGroup::setExclusive(bool exclusive)
314 {
315     Q_D(QQuickActionGroup);
316     if (d->exclusive == exclusive)
317         return;
318 
319     d->exclusive = exclusive;
320     emit exclusiveChanged();
321 }
322 
323 /*!
324     \qmlproperty bool QtQuick.Controls::ActionGroup::enabled
325 
326     This property holds whether the action group is enabled. The default value is \c true.
327 
328     If this property is \c false, then all actions in the group are disabled. If this property
329     is \c true, all actions in the group are enabled, unless explicitly disabled.
330 */
isEnabled() const331 bool QQuickActionGroup::isEnabled() const
332 {
333     Q_D(const QQuickActionGroup);
334     return d->enabled;
335 }
336 
setEnabled(bool enabled)337 void QQuickActionGroup::setEnabled(bool enabled)
338 {
339     Q_D(QQuickActionGroup);
340     if (d->enabled == enabled)
341         return;
342 
343     for (QQuickAction *action : qAsConst(d->actions)) {
344         if (d->changeEnabled(action, enabled))
345             emit action->enabledChanged(enabled);
346     }
347 
348     d->enabled = enabled;
349     emit enabledChanged();
350 }
351 
352 /*!
353     \qmlmethod void QtQuick.Controls::ActionGroup::addAction(Action action)
354 
355     Adds an \a action to the action group.
356 
357     \note Manually adding objects to a action group is typically unnecessary.
358           The \l actions property and the \l group attached property provide a
359           convenient and declarative syntax.
360 
361     \sa actions, group
362 */
addAction(QQuickAction * action)363 void QQuickActionGroup::addAction(QQuickAction *action)
364 {
365     Q_D(QQuickActionGroup);
366     if (!action || d->actions.contains(action))
367         return;
368 
369     const bool enabledChange = d->changeEnabled(action, d->enabled);
370 
371     QQuickActionPrivate::get(action)->group = this;
372     QObjectPrivate::connect(action, &QQuickAction::triggered, d, &QQuickActionGroupPrivate::actionTriggered);
373     QObjectPrivate::connect(action, &QQuickAction::checkedChanged, d, &QQuickActionGroupPrivate::_q_updateCurrent);
374 
375     if (d->exclusive && action->isChecked())
376         setCheckedAction(action);
377     if (enabledChange)
378         emit action->enabledChanged(action->isEnabled());
379 
380     d->actions.append(action);
381     emit actionsChanged();
382 }
383 
384 /*!
385     \qmlmethod void QtQuick.Controls::ActionGroup::removeAction(Action action)
386 
387     Removes an \a action from the action group.
388 
389     \note Manually removing objects from a action group is typically unnecessary.
390           The \l actions property and the \l group attached property provide a
391           convenient and declarative syntax.
392 
393     \sa actions, group
394 */
removeAction(QQuickAction * action)395 void QQuickActionGroup::removeAction(QQuickAction *action)
396 {
397     Q_D(QQuickActionGroup);
398     if (!action || !d->actions.contains(action))
399         return;
400 
401     const bool enabledChange = d->changeEnabled(action, d->enabled);
402 
403     QQuickActionPrivate::get(action)->group = nullptr;
404     QObjectPrivate::disconnect(action, &QQuickAction::triggered, d, &QQuickActionGroupPrivate::actionTriggered);
405     QObjectPrivate::disconnect(action, &QQuickAction::checkedChanged, d, &QQuickActionGroupPrivate::_q_updateCurrent);
406 
407     if (d->checkedAction == action)
408         setCheckedAction(nullptr);
409     if (enabledChange)
410         emit action->enabledChanged(action->isEnabled());
411 
412     d->actions.removeOne(action);
413     emit actionsChanged();
414 }
415 
416 class QQuickActionGroupAttachedPrivate : public QObjectPrivate
417 {
418 public:
419     QQuickActionGroup *group = nullptr;
420 };
421 
QQuickActionGroupAttached(QObject * parent)422 QQuickActionGroupAttached::QQuickActionGroupAttached(QObject *parent)
423     : QObject(*(new QQuickActionGroupAttachedPrivate), parent)
424 {
425 }
426 
427 /*!
428     \qmlattachedproperty ActionGroup QtQuick.Controls::ActionGroup::group
429 
430     This property attaches an action to an action group.
431 
432     \code
433     ActionGroup { id: group }
434 
435     Action {
436         checked: true
437         text: qsTr("Option A")
438         ActionGroup.group: group
439     }
440 
441     Action {
442         text: qsTr("Option B")
443         ActionGroup.group: group
444     }
445     \endcode
446 
447     \sa actions
448 */
group() const449 QQuickActionGroup *QQuickActionGroupAttached::group() const
450 {
451     Q_D(const QQuickActionGroupAttached);
452     return d->group;
453 }
454 
setGroup(QQuickActionGroup * group)455 void QQuickActionGroupAttached::setGroup(QQuickActionGroup *group)
456 {
457     Q_D(QQuickActionGroupAttached);
458     if (d->group == group)
459         return;
460 
461     if (d->group)
462         d->group->removeAction(qobject_cast<QQuickAction*>(parent()));
463     d->group = group;
464     if (group)
465         group->addAction(qobject_cast<QQuickAction*>(parent()));
466     emit groupChanged();
467 }
468 
469 QT_END_NAMESPACE
470 
471 #include "moc_qquickactiongroup_p.cpp"
472