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
13QT_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
142class QQuickButtonGroupPrivate : public QObjectPrivate
143{
144public:
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
166void 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
182void 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
190void 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
203void 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
218void 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
228void QQuickButtonGroupPrivate::buttons_append(QQmlListProperty<QQuickAbstractButton> *prop, QQuickAbstractButton *obj)
229{
230 QQuickButtonGroup *q = static_cast<QQuickButtonGroup *>(prop->object);
231 q->addButton(button: obj);
232}
233
234qsizetype QQuickButtonGroupPrivate::buttons_count(QQmlListProperty<QQuickAbstractButton> *prop)
235{
236 QQuickButtonGroupPrivate *p = static_cast<QQuickButtonGroupPrivate *>(prop->data);
237 return p->buttons.size();
238}
239
240QQuickAbstractButton *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
246void 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
258QQuickButtonGroup::QQuickButtonGroup(QObject *parent)
259 : QObject(*(new QQuickButtonGroupPrivate), parent)
260{
261}
262
263QQuickButtonGroup::~QQuickButtonGroup()
264{
265 Q_D(QQuickButtonGroup);
266 d->clear();
267}
268
269QQuickButtonGroupAttached *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*/
284QQuickAbstractButton *QQuickButtonGroup::checkedButton() const
285{
286 Q_D(const QQuickButtonGroup);
287 return d->checkedButton;
288}
289
290void 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*/
330QQmlListProperty<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*/
356bool QQuickButtonGroup::isExclusive() const
357{
358 Q_D(const QQuickButtonGroup);
359 return d->exclusive;
360}
361
362void 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*/
391Qt::CheckState QQuickButtonGroup::checkState() const
392{
393 Q_D(const QQuickButtonGroup);
394 return d->checkState;
395}
396
397void 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*/
426void 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*/
455void 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
473void QQuickButtonGroup::classBegin()
474{
475 Q_D(QQuickButtonGroup);
476 d->complete = false;
477}
478
479void QQuickButtonGroup::componentComplete()
480{
481 Q_D(QQuickButtonGroup);
482 d->complete = true;
483 if (!d->buttons.isEmpty())
484 d->updateCheckState();
485}
486
487class QQuickButtonGroupAttachedPrivate : public QObjectPrivate
488{
489public:
490 QQuickButtonGroup *group = nullptr;
491};
492
493QQuickButtonGroupAttached::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*/
520QQuickButtonGroup *QQuickButtonGroupAttached::group() const
521{
522 Q_D(const QQuickButtonGroupAttached);
523 return d->group;
524}
525
526void 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
541QT_END_NAMESPACE
542
543#include "moc_qquickbuttongroup_p.cpp"
544

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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