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

source code of qtdeclarative/src/quicktemplates/qquickactiongroup.cpp