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 | |
14 | QT_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 | |
107 | class QQuickActionGroupPrivate : public QObjectPrivate |
108 | { |
109 | public: |
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 | |
129 | void 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 | |
139 | void 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 | |
147 | void 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 | |
159 | bool QQuickActionGroupPrivate::changeEnabled(QQuickAction *action, bool enabled) |
160 | { |
161 | return action->isEnabled() != enabled && (!enabled || !QQuickActionPrivate::get(action)->explicitEnabled); |
162 | } |
163 | |
164 | void QQuickActionGroupPrivate::actions_append(QQmlListProperty<QQuickAction> *prop, QQuickAction *obj) |
165 | { |
166 | QQuickActionGroup *q = static_cast<QQuickActionGroup *>(prop->object); |
167 | q->addAction(action: obj); |
168 | } |
169 | |
170 | qsizetype QQuickActionGroupPrivate::actions_count(QQmlListProperty<QQuickAction> *prop) |
171 | { |
172 | QQuickActionGroupPrivate *p = static_cast<QQuickActionGroupPrivate *>(prop->data); |
173 | return p->actions.size(); |
174 | } |
175 | |
176 | QQuickAction *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 | |
182 | void 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 | |
194 | QQuickActionGroup::QQuickActionGroup(QObject *parent) |
195 | : QObject(*(new QQuickActionGroupPrivate), parent) |
196 | { |
197 | } |
198 | |
199 | QQuickActionGroup::~QQuickActionGroup() |
200 | { |
201 | Q_D(QQuickActionGroup); |
202 | d->clear(); |
203 | } |
204 | |
205 | QQuickActionGroupAttached *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 | */ |
220 | QQuickAction *QQuickActionGroup::checkedAction() const |
221 | { |
222 | Q_D(const QQuickActionGroup); |
223 | return d->checkedAction; |
224 | } |
225 | |
226 | void 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 | */ |
248 | QQmlListProperty<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 | */ |
274 | bool QQuickActionGroup::isExclusive() const |
275 | { |
276 | Q_D(const QQuickActionGroup); |
277 | return d->exclusive; |
278 | } |
279 | |
280 | void 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 | */ |
298 | bool QQuickActionGroup::isEnabled() const |
299 | { |
300 | Q_D(const QQuickActionGroup); |
301 | return d->enabled; |
302 | } |
303 | |
304 | void 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 | */ |
330 | void 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 | */ |
362 | void 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 | |
383 | class QQuickActionGroupAttachedPrivate : public QObjectPrivate |
384 | { |
385 | public: |
386 | QQuickActionGroup *group = nullptr; |
387 | }; |
388 | |
389 | QQuickActionGroupAttached::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 | */ |
416 | QQuickActionGroup *QQuickActionGroupAttached::group() const |
417 | { |
418 | Q_D(const QQuickActionGroupAttached); |
419 | return d->group; |
420 | } |
421 | |
422 | void 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 | |
436 | QT_END_NAMESPACE |
437 | |
438 | #include "moc_qquickactiongroup_p.cpp" |
439 | |