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

source code of qtquickcontrols2/src/quicktemplates2/qquickbuttongroup.cpp