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