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 "qquickbuttongroup_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 "qquickabstractbutton_p_p.h" |
12 | |
13 | QT_BEGIN_NAMESPACE |
14 | |
15 | /*! |
16 | \qmltype ButtonGroup |
17 | \inherits QtObject |
18 | //! \nativetype QQuickButtonGroup |
19 | \inqmlmodule QtQuick.Controls |
20 | \since 5.7 |
21 | \ingroup utilities |
22 | \brief Mutually-exclusive group of checkable buttons. |
23 | |
24 | ButtonGroup is a non-visual, mutually exclusive group of buttons. |
25 | It is used with controls such as RadioButton, where only one of the options |
26 | can be selected at a time. |
27 | |
28 | The most straight-forward way to use ButtonGroup is to assign |
29 | a list of buttons. For example, the list of children of a |
30 | \l{Item Positioners}{positioner} or a \l{Qt Quick Layouts}{layout} |
31 | that manages a group of mutually exclusive buttons. |
32 | |
33 | \code |
34 | ButtonGroup { |
35 | buttons: column.children |
36 | } |
37 | |
38 | Column { |
39 | id: column |
40 | |
41 | RadioButton { |
42 | checked: true |
43 | text: qsTr("DAB") |
44 | } |
45 | |
46 | RadioButton { |
47 | text: qsTr("FM") |
48 | } |
49 | |
50 | RadioButton { |
51 | text: qsTr("AM") |
52 | } |
53 | } |
54 | \endcode |
55 | |
56 | Mutually exclusive buttons do not always share the same parent item, |
57 | or the parent layout may sometimes contain items that should not be |
58 | included in the button group. Such cases are best handled using |
59 | the \l group attached property. |
60 | |
61 | \code |
62 | ButtonGroup { id: radioGroup } |
63 | |
64 | Column { |
65 | Label { |
66 | text: qsTr("Radio:") |
67 | } |
68 | |
69 | RadioButton { |
70 | checked: true |
71 | text: qsTr("DAB") |
72 | ButtonGroup.group: radioGroup |
73 | } |
74 | |
75 | RadioButton { |
76 | text: qsTr("FM") |
77 | ButtonGroup.group: radioGroup |
78 | } |
79 | |
80 | RadioButton { |
81 | text: qsTr("AM") |
82 | ButtonGroup.group: radioGroup |
83 | } |
84 | } |
85 | \endcode |
86 | |
87 | Another option is to filter the list of children. This is especially handy |
88 | if you're using a repeater to populate it, since the repeater will also be |
89 | a child of the parent layout: |
90 | |
91 | \code |
92 | ButtonGroup { |
93 | buttons: column.children.filter((child) => child !== repeater) |
94 | } |
95 | |
96 | Column { |
97 | id: column |
98 | |
99 | Repeater { |
100 | id: repeater |
101 | model: [ qsTr("DAB"), qsTr("AM"), qsTr("FM") ] |
102 | RadioButton { |
103 | required property string modelData |
104 | text: modelData |
105 | } |
106 | } |
107 | } |
108 | \endcode |
109 | |
110 | More advanced use cases can be handled using the \c addButton() and |
111 | \c removeButton() methods. |
112 | |
113 | \sa RadioButton, {Button Controls} |
114 | */ |
115 | |
116 | /*! |
117 | \qmlsignal QtQuick.Controls::ButtonGroup::clicked(AbstractButton button) |
118 | \since QtQuick.Controls 2.1 (Qt 5.8) |
119 | |
120 | This signal is emitted when a \a button in the group has been clicked. |
121 | |
122 | This signal is convenient for implementing a common signal handler for |
123 | all buttons in the same group. |
124 | |
125 | \code |
126 | ButtonGroup { |
127 | buttons: column.children |
128 | onClicked: console.log("clicked:", button.text) |
129 | } |
130 | |
131 | Column { |
132 | id: column |
133 | Button { text: "First" } |
134 | Button { text: "Second" } |
135 | Button { text: "Third" } |
136 | } |
137 | \endcode |
138 | |
139 | \sa AbstractButton::clicked() |
140 | */ |
141 | |
142 | class QQuickButtonGroupPrivate : public QObjectPrivate |
143 | { |
144 | public: |
145 | Q_DECLARE_PUBLIC(QQuickButtonGroup) |
146 | |
147 | void clear(); |
148 | void buttonClicked(); |
149 | void _q_updateCurrent(); |
150 | void updateCheckState(); |
151 | void setCheckState(Qt::CheckState state); |
152 | |
153 | static void buttons_append(QQmlListProperty<QQuickAbstractButton> *prop, QQuickAbstractButton *obj); |
154 | static qsizetype buttons_count(QQmlListProperty<QQuickAbstractButton> *prop); |
155 | static QQuickAbstractButton *buttons_at(QQmlListProperty<QQuickAbstractButton> *prop, qsizetype index); |
156 | static void buttons_clear(QQmlListProperty<QQuickAbstractButton> *prop); |
157 | |
158 | bool complete = true; |
159 | bool exclusive = true; |
160 | bool settingCheckState = false; |
161 | Qt::CheckState checkState = Qt::Unchecked; |
162 | QPointer<QQuickAbstractButton> checkedButton; |
163 | QList<QQuickAbstractButton*> buttons; |
164 | }; |
165 | |
166 | void QQuickButtonGroupPrivate::clear() |
167 | { |
168 | for (QQuickAbstractButton *button : std::as_const(t&: buttons)) { |
169 | auto *attached = qobject_cast<QQuickButtonGroupAttached *>( |
170 | object: qmlAttachedPropertiesObject<QQuickButtonGroup>(obj: button, create: false)); |
171 | if (attached) { |
172 | attached->setGroup(nullptr); |
173 | } else { |
174 | QQuickAbstractButtonPrivate::get(button)->group = nullptr; |
175 | QObjectPrivate::disconnect(sender: button, signal: &QQuickAbstractButton::clicked, receiverPrivate: this, slot: &QQuickButtonGroupPrivate::buttonClicked); |
176 | QObjectPrivate::disconnect(sender: button, signal: &QQuickAbstractButton::checkedChanged, receiverPrivate: this, slot: &QQuickButtonGroupPrivate::_q_updateCurrent); |
177 | } |
178 | } |
179 | buttons.clear(); |
180 | } |
181 | |
182 | void QQuickButtonGroupPrivate::buttonClicked() |
183 | { |
184 | Q_Q(QQuickButtonGroup); |
185 | QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton*>(object: q->sender()); |
186 | if (button) |
187 | emit q->clicked(button); |
188 | } |
189 | |
190 | void QQuickButtonGroupPrivate::_q_updateCurrent() |
191 | { |
192 | Q_Q(QQuickButtonGroup); |
193 | if (exclusive) { |
194 | QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton*>(object: q->sender()); |
195 | if (button && button->isChecked()) |
196 | q->setCheckedButton(button); |
197 | else if (!buttons.contains(t: checkedButton)) |
198 | q->setCheckedButton(nullptr); |
199 | } |
200 | updateCheckState(); |
201 | } |
202 | |
203 | void QQuickButtonGroupPrivate::updateCheckState() |
204 | { |
205 | if (!complete || settingCheckState) |
206 | return; |
207 | |
208 | bool anyChecked = false; |
209 | bool allChecked = !buttons.isEmpty(); |
210 | for (QQuickAbstractButton *button : std::as_const(t&: buttons)) { |
211 | const bool isChecked = button->isChecked(); |
212 | anyChecked |= isChecked; |
213 | allChecked &= isChecked; |
214 | } |
215 | setCheckState(Qt::CheckState(anyChecked + allChecked)); |
216 | } |
217 | |
218 | void QQuickButtonGroupPrivate::setCheckState(Qt::CheckState state) |
219 | { |
220 | Q_Q(QQuickButtonGroup); |
221 | if (checkState == state) |
222 | return; |
223 | |
224 | checkState = state; |
225 | emit q->checkStateChanged(); |
226 | } |
227 | |
228 | void QQuickButtonGroupPrivate::buttons_append(QQmlListProperty<QQuickAbstractButton> *prop, QQuickAbstractButton *obj) |
229 | { |
230 | QQuickButtonGroup *q = static_cast<QQuickButtonGroup *>(prop->object); |
231 | q->addButton(button: obj); |
232 | } |
233 | |
234 | qsizetype QQuickButtonGroupPrivate::buttons_count(QQmlListProperty<QQuickAbstractButton> *prop) |
235 | { |
236 | QQuickButtonGroupPrivate *p = static_cast<QQuickButtonGroupPrivate *>(prop->data); |
237 | return p->buttons.size(); |
238 | } |
239 | |
240 | QQuickAbstractButton *QQuickButtonGroupPrivate::buttons_at(QQmlListProperty<QQuickAbstractButton> *prop, qsizetype index) |
241 | { |
242 | QQuickButtonGroupPrivate *p = static_cast<QQuickButtonGroupPrivate *>(prop->data); |
243 | return p->buttons.value(i: index); |
244 | } |
245 | |
246 | void QQuickButtonGroupPrivate::buttons_clear(QQmlListProperty<QQuickAbstractButton> *prop) |
247 | { |
248 | QQuickButtonGroupPrivate *p = static_cast<QQuickButtonGroupPrivate *>(prop->data); |
249 | if (!p->buttons.isEmpty()) { |
250 | p->clear(); |
251 | QQuickButtonGroup *q = static_cast<QQuickButtonGroup *>(prop->object); |
252 | // QTBUG-52358: don't clear the checked button immediately |
253 | QMetaObject::invokeMethod(obj: q, member: "_q_updateCurrent", c: Qt::QueuedConnection); |
254 | emit q->buttonsChanged(); |
255 | } |
256 | } |
257 | |
258 | QQuickButtonGroup::QQuickButtonGroup(QObject *parent) |
259 | : QObject(*(new QQuickButtonGroupPrivate), parent) |
260 | { |
261 | } |
262 | |
263 | QQuickButtonGroup::~QQuickButtonGroup() |
264 | { |
265 | Q_D(QQuickButtonGroup); |
266 | d->clear(); |
267 | } |
268 | |
269 | QQuickButtonGroupAttached *QQuickButtonGroup::qmlAttachedProperties(QObject *object) |
270 | { |
271 | return new QQuickButtonGroupAttached(object); |
272 | } |
273 | |
274 | /*! |
275 | \qmlproperty AbstractButton QtQuick.Controls::ButtonGroup::checkedButton |
276 | |
277 | This property holds the currently selected button in an exclusive group, |
278 | or \c null if there is none or the group is non-exclusive. |
279 | |
280 | By default, it is the first checked button added to an exclusive button group. |
281 | |
282 | \sa exclusive |
283 | */ |
284 | QQuickAbstractButton *QQuickButtonGroup::checkedButton() const |
285 | { |
286 | Q_D(const QQuickButtonGroup); |
287 | return d->checkedButton; |
288 | } |
289 | |
290 | void QQuickButtonGroup::setCheckedButton(QQuickAbstractButton *checkedButton) |
291 | { |
292 | Q_D(QQuickButtonGroup); |
293 | if (d->checkedButton == checkedButton) |
294 | return; |
295 | |
296 | if (d->checkedButton) |
297 | d->checkedButton->setChecked(false); |
298 | d->checkedButton = checkedButton; |
299 | if (checkedButton) |
300 | checkedButton->setChecked(true); |
301 | emit checkedButtonChanged(); |
302 | } |
303 | |
304 | /*! |
305 | \qmlproperty list<AbstractButton> QtQuick.Controls::ButtonGroup::buttons |
306 | |
307 | This property holds the list of buttons. |
308 | |
309 | \code |
310 | ButtonGroup { |
311 | buttons: column.children |
312 | } |
313 | |
314 | Column { |
315 | id: column |
316 | |
317 | RadioButton { |
318 | checked: true |
319 | text: qsTr("Option A") |
320 | } |
321 | |
322 | RadioButton { |
323 | text: qsTr("Option B") |
324 | } |
325 | } |
326 | \endcode |
327 | |
328 | \sa group |
329 | */ |
330 | QQmlListProperty<QQuickAbstractButton> QQuickButtonGroup::buttons() |
331 | { |
332 | Q_D(QQuickButtonGroup); |
333 | return QQmlListProperty<QQuickAbstractButton>(this, d, |
334 | QQuickButtonGroupPrivate::buttons_append, |
335 | QQuickButtonGroupPrivate::buttons_count, |
336 | QQuickButtonGroupPrivate::buttons_at, |
337 | QQuickButtonGroupPrivate::buttons_clear); |
338 | } |
339 | |
340 | /*! |
341 | \since QtQuick.Controls 2.3 (Qt 5.10) |
342 | \qmlproperty bool QtQuick.Controls::ButtonGroup::exclusive |
343 | |
344 | This property holds whether the button group is exclusive. The default value is \c true. |
345 | |
346 | If this property is \c true, then only one button in the group can be checked at any given time. |
347 | The user can click on any button to check it, and that button will replace the existing one as |
348 | the checked button in the group. |
349 | |
350 | In an exclusive group, the user cannot uncheck the currently checked button by clicking on it; |
351 | instead, another button in the group must be clicked to set the new checked button for that group. |
352 | |
353 | In a non-exclusive group, checking and unchecking buttons does not affect the other buttons in |
354 | the group. Furthermore, the value of the \l checkedButton property is \c null. |
355 | */ |
356 | bool QQuickButtonGroup::isExclusive() const |
357 | { |
358 | Q_D(const QQuickButtonGroup); |
359 | return d->exclusive; |
360 | } |
361 | |
362 | void QQuickButtonGroup::setExclusive(bool exclusive) |
363 | { |
364 | Q_D(QQuickButtonGroup); |
365 | if (d->exclusive == exclusive) |
366 | return; |
367 | |
368 | d->exclusive = exclusive; |
369 | emit exclusiveChanged(); |
370 | } |
371 | |
372 | /*! |
373 | \since QtQuick.Controls 2.4 (Qt 5.11) |
374 | \qmlproperty enumeration QtQuick.Controls::ButtonGroup::checkState |
375 | |
376 | This property holds the combined check state of the button group. |
377 | |
378 | Available states: |
379 | \value Qt.Unchecked None of the buttons are checked. |
380 | \value Qt.PartiallyChecked Some of the buttons are checked. |
381 | \value Qt.Checked All of the buttons are checked. |
382 | |
383 | Setting the check state of a non-exclusive button group to \c Qt.Unchecked |
384 | or \c Qt.Checked unchecks or checks all buttons in the group, respectively. |
385 | \c Qt.PartiallyChecked is ignored. |
386 | |
387 | Setting the check state of an exclusive button group to \c Qt.Unchecked |
388 | unchecks the \l checkedButton. \c Qt.Checked and \c Qt.PartiallyChecked |
389 | are ignored. |
390 | */ |
391 | Qt::CheckState QQuickButtonGroup::checkState() const |
392 | { |
393 | Q_D(const QQuickButtonGroup); |
394 | return d->checkState; |
395 | } |
396 | |
397 | void QQuickButtonGroup::setCheckState(Qt::CheckState state) |
398 | { |
399 | Q_D(QQuickButtonGroup); |
400 | if (d->checkState == state || state == Qt::PartiallyChecked) |
401 | return; |
402 | |
403 | d->settingCheckState = true; |
404 | if (d->exclusive) { |
405 | if (d->checkedButton && state == Qt::Unchecked) |
406 | setCheckedButton(nullptr); |
407 | } else { |
408 | for (QQuickAbstractButton *button : std::as_const(t&: d->buttons)) |
409 | button->setChecked(state == Qt::Checked); |
410 | } |
411 | d->settingCheckState = false; |
412 | d->setCheckState(state); |
413 | } |
414 | |
415 | /*! |
416 | \qmlmethod void QtQuick.Controls::ButtonGroup::addButton(AbstractButton button) |
417 | |
418 | Adds a \a button to the button group. |
419 | |
420 | \note Manually adding objects to a button group is typically unnecessary. |
421 | The \l buttons property and the \l group attached property provide a |
422 | convenient and declarative syntax. |
423 | |
424 | \sa buttons, group |
425 | */ |
426 | void QQuickButtonGroup::addButton(QQuickAbstractButton *button) |
427 | { |
428 | Q_D(QQuickButtonGroup); |
429 | if (!button || d->buttons.contains(t: button)) |
430 | return; |
431 | |
432 | QQuickAbstractButtonPrivate::get(button)->group = this; |
433 | QObjectPrivate::connect(sender: button, signal: &QQuickAbstractButton::clicked, receiverPrivate: d, slot: &QQuickButtonGroupPrivate::buttonClicked); |
434 | QObjectPrivate::connect(sender: button, signal: &QQuickAbstractButton::checkedChanged, receiverPrivate: d, slot: &QQuickButtonGroupPrivate::_q_updateCurrent); |
435 | |
436 | if (d->exclusive && button->isChecked()) |
437 | setCheckedButton(button); |
438 | |
439 | d->buttons.append(t: button); |
440 | d->updateCheckState(); |
441 | emit buttonsChanged(); |
442 | } |
443 | |
444 | /*! |
445 | \qmlmethod void QtQuick.Controls::ButtonGroup::removeButton(AbstractButton button) |
446 | |
447 | Removes a \a button from the button group. |
448 | |
449 | \note Manually removing objects from a button group is typically unnecessary. |
450 | The \l buttons property and the \l group attached property provide a |
451 | convenient and declarative syntax. |
452 | |
453 | \sa buttons, group |
454 | */ |
455 | void QQuickButtonGroup::removeButton(QQuickAbstractButton *button) |
456 | { |
457 | Q_D(QQuickButtonGroup); |
458 | if (!button || !d->buttons.contains(t: button)) |
459 | return; |
460 | |
461 | QQuickAbstractButtonPrivate::get(button)->group = nullptr; |
462 | QObjectPrivate::disconnect(sender: button, signal: &QQuickAbstractButton::clicked, receiverPrivate: d, slot: &QQuickButtonGroupPrivate::buttonClicked); |
463 | QObjectPrivate::disconnect(sender: button, signal: &QQuickAbstractButton::checkedChanged, receiverPrivate: d, slot: &QQuickButtonGroupPrivate::_q_updateCurrent); |
464 | |
465 | if (d->checkedButton == button) |
466 | setCheckedButton(nullptr); |
467 | |
468 | d->buttons.removeOne(t: button); |
469 | d->updateCheckState(); |
470 | emit buttonsChanged(); |
471 | } |
472 | |
473 | void QQuickButtonGroup::classBegin() |
474 | { |
475 | Q_D(QQuickButtonGroup); |
476 | d->complete = false; |
477 | } |
478 | |
479 | void QQuickButtonGroup::componentComplete() |
480 | { |
481 | Q_D(QQuickButtonGroup); |
482 | d->complete = true; |
483 | if (!d->buttons.isEmpty()) |
484 | d->updateCheckState(); |
485 | } |
486 | |
487 | class QQuickButtonGroupAttachedPrivate : public QObjectPrivate |
488 | { |
489 | public: |
490 | QQuickButtonGroup *group = nullptr; |
491 | }; |
492 | |
493 | QQuickButtonGroupAttached::QQuickButtonGroupAttached(QObject *parent) |
494 | : QObject(*(new QQuickButtonGroupAttachedPrivate), parent) |
495 | { |
496 | } |
497 | |
498 | /*! |
499 | \qmlattachedproperty ButtonGroup QtQuick.Controls::ButtonGroup::group |
500 | |
501 | This property attaches a button to a button group. |
502 | |
503 | \code |
504 | ButtonGroup { id: group } |
505 | |
506 | RadioButton { |
507 | checked: true |
508 | text: qsTr("Option A") |
509 | ButtonGroup.group: group |
510 | } |
511 | |
512 | RadioButton { |
513 | text: qsTr("Option B") |
514 | ButtonGroup.group: group |
515 | } |
516 | \endcode |
517 | |
518 | \sa buttons |
519 | */ |
520 | QQuickButtonGroup *QQuickButtonGroupAttached::group() const |
521 | { |
522 | Q_D(const QQuickButtonGroupAttached); |
523 | return d->group; |
524 | } |
525 | |
526 | void QQuickButtonGroupAttached::setGroup(QQuickButtonGroup *group) |
527 | { |
528 | Q_D(QQuickButtonGroupAttached); |
529 | if (d->group == group) |
530 | return; |
531 | |
532 | auto *button = qobject_cast<QQuickAbstractButton *>(object: parent()); |
533 | if (d->group) |
534 | d->group->removeButton(button); |
535 | d->group = group; |
536 | if (group) |
537 | group->addButton(button); |
538 | emit groupChanged(); |
539 | } |
540 | |
541 | QT_END_NAMESPACE |
542 | |
543 | #include "moc_qquickbuttongroup_p.cpp" |
544 |
Definitions
- QQuickButtonGroupPrivate
- clear
- buttonClicked
- _q_updateCurrent
- updateCheckState
- setCheckState
- buttons_append
- buttons_count
- buttons_at
- buttons_clear
- QQuickButtonGroup
- ~QQuickButtonGroup
- qmlAttachedProperties
- checkedButton
- setCheckedButton
- buttons
- isExclusive
- setExclusive
- checkState
- setCheckState
- addButton
- removeButton
- classBegin
- componentComplete
- QQuickButtonGroupAttachedPrivate
- QQuickButtonGroupAttached
- group
Learn Advanced QML with KDAB
Find out more