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 | |
162 | void QQuickActionGroupPrivate::clear() |
163 | { |
164 | for (QQuickAction *action : qAsConst(t&: actions)) { |
165 | QQuickActionPrivate::get(action)->group = nullptr; |
166 | QObjectPrivate::disconnect(sender: action, signal: &QQuickAction::triggered, receiverPrivate: this, slot: &QQuickActionGroupPrivate::actionTriggered); |
167 | QObjectPrivate::disconnect(sender: action, signal: &QQuickAction::checkedChanged, receiverPrivate: this, slot: &QQuickActionGroupPrivate::_q_updateCurrent); |
168 | } |
169 | actions.clear(); |
170 | } |
171 | |
172 | void QQuickActionGroupPrivate::actionTriggered() |
173 | { |
174 | Q_Q(QQuickActionGroup); |
175 | QQuickAction *action = qobject_cast<QQuickAction*>(object: q->sender()); |
176 | if (action) |
177 | emit q->triggered(action); |
178 | } |
179 | |
180 | void QQuickActionGroupPrivate::_q_updateCurrent() |
181 | { |
182 | Q_Q(QQuickActionGroup); |
183 | if (!exclusive) |
184 | return; |
185 | QQuickAction *action = qobject_cast<QQuickAction*>(object: q->sender()); |
186 | if (action && action->isChecked()) |
187 | q->setCheckedAction(action); |
188 | else if (!actions.contains(t: checkedAction)) |
189 | q->setCheckedAction(nullptr); |
190 | } |
191 | |
192 | bool QQuickActionGroupPrivate::changeEnabled(QQuickAction *action, bool enabled) |
193 | { |
194 | return action->isEnabled() != enabled && (!enabled || !QQuickActionPrivate::get(action)->explicitEnabled); |
195 | } |
196 | |
197 | void QQuickActionGroupPrivate::actions_append(QQmlListProperty<QQuickAction> *prop, QQuickAction *obj) |
198 | { |
199 | QQuickActionGroup *q = static_cast<QQuickActionGroup *>(prop->object); |
200 | q->addAction(action: obj); |
201 | } |
202 | |
203 | int QQuickActionGroupPrivate::actions_count(QQmlListProperty<QQuickAction> *prop) |
204 | { |
205 | QQuickActionGroupPrivate *p = static_cast<QQuickActionGroupPrivate *>(prop->data); |
206 | return p->actions.count(); |
207 | } |
208 | |
209 | QQuickAction *QQuickActionGroupPrivate::actions_at(QQmlListProperty<QQuickAction> *prop, int index) |
210 | { |
211 | QQuickActionGroupPrivate *p = static_cast<QQuickActionGroupPrivate *>(prop->data); |
212 | return p->actions.value(i: index); |
213 | } |
214 | |
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(obj: q, member: "_q_updateCurrent" , type: Qt::QueuedConnection); |
223 | emit q->actionsChanged(); |
224 | } |
225 | } |
226 | |
227 | QQuickActionGroup::QQuickActionGroup(QObject *parent) |
228 | : QObject(*(new QQuickActionGroupPrivate), parent) |
229 | { |
230 | } |
231 | |
232 | QQuickActionGroup::~QQuickActionGroup() |
233 | { |
234 | Q_D(QQuickActionGroup); |
235 | d->clear(); |
236 | } |
237 | |
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 | */ |
253 | QQuickAction *QQuickActionGroup::checkedAction() const |
254 | { |
255 | Q_D(const QQuickActionGroup); |
256 | return d->checkedAction; |
257 | } |
258 | |
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 | */ |
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 | */ |
307 | bool QQuickActionGroup::isExclusive() const |
308 | { |
309 | Q_D(const QQuickActionGroup); |
310 | return d->exclusive; |
311 | } |
312 | |
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 | */ |
331 | bool QQuickActionGroup::isEnabled() const |
332 | { |
333 | Q_D(const QQuickActionGroup); |
334 | return d->enabled; |
335 | } |
336 | |
337 | void QQuickActionGroup::setEnabled(bool enabled) |
338 | { |
339 | Q_D(QQuickActionGroup); |
340 | if (d->enabled == enabled) |
341 | return; |
342 | |
343 | for (QQuickAction *action : qAsConst(t&: 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 | */ |
363 | void QQuickActionGroup::addAction(QQuickAction *action) |
364 | { |
365 | Q_D(QQuickActionGroup); |
366 | if (!action || d->actions.contains(t: action)) |
367 | return; |
368 | |
369 | const bool enabledChange = d->changeEnabled(action, enabled: d->enabled); |
370 | |
371 | QQuickActionPrivate::get(action)->group = this; |
372 | QObjectPrivate::connect(sender: action, signal: &QQuickAction::triggered, receiverPrivate: d, slot: &QQuickActionGroupPrivate::actionTriggered); |
373 | QObjectPrivate::connect(sender: action, signal: &QQuickAction::checkedChanged, receiverPrivate: d, slot: &QQuickActionGroupPrivate::_q_updateCurrent); |
374 | |
375 | if (d->exclusive && action->isChecked()) |
376 | setCheckedAction(action); |
377 | if (enabledChange) |
378 | emit action->enabledChanged(enabled: action->isEnabled()); |
379 | |
380 | d->actions.append(t: 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 | */ |
395 | void QQuickActionGroup::removeAction(QQuickAction *action) |
396 | { |
397 | Q_D(QQuickActionGroup); |
398 | if (!action || !d->actions.contains(t: action)) |
399 | return; |
400 | |
401 | const bool enabledChange = d->changeEnabled(action, enabled: d->enabled); |
402 | |
403 | QQuickActionPrivate::get(action)->group = nullptr; |
404 | QObjectPrivate::disconnect(sender: action, signal: &QQuickAction::triggered, receiverPrivate: d, slot: &QQuickActionGroupPrivate::actionTriggered); |
405 | QObjectPrivate::disconnect(sender: action, signal: &QQuickAction::checkedChanged, receiverPrivate: d, slot: &QQuickActionGroupPrivate::_q_updateCurrent); |
406 | |
407 | if (d->checkedAction == action) |
408 | setCheckedAction(nullptr); |
409 | if (enabledChange) |
410 | emit action->enabledChanged(enabled: action->isEnabled()); |
411 | |
412 | d->actions.removeOne(t: action); |
413 | emit actionsChanged(); |
414 | } |
415 | |
416 | class QQuickActionGroupAttachedPrivate : public QObjectPrivate |
417 | { |
418 | public: |
419 | QQuickActionGroup *group = nullptr; |
420 | }; |
421 | |
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 | */ |
449 | QQuickActionGroup *QQuickActionGroupAttached::group() const |
450 | { |
451 | Q_D(const QQuickActionGroupAttached); |
452 | return d->group; |
453 | } |
454 | |
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(action: qobject_cast<QQuickAction*>(object: parent())); |
463 | d->group = group; |
464 | if (group) |
465 | group->addAction(action: qobject_cast<QQuickAction*>(object: parent())); |
466 | emit groupChanged(); |
467 | } |
468 | |
469 | QT_END_NAMESPACE |
470 | |
471 | #include "moc_qquickactiongroup_p.cpp" |
472 | |