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 
46 QT_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 
152 class QQuickButtonGroupPrivate : public QObjectPrivate
153 {
154     Q_DECLARE_PUBLIC(QQuickButtonGroup)
155 
156 public:
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 
clear()176 void QQuickButtonGroupPrivate::clear()
177 {
178     for (QQuickAbstractButton *button : qAsConst(buttons)) {
179         QQuickAbstractButtonPrivate::get(button)->group = nullptr;
180         QObjectPrivate::disconnect(button, &QQuickAbstractButton::clicked, this, &QQuickButtonGroupPrivate::buttonClicked);
181         QObjectPrivate::disconnect(button, &QQuickAbstractButton::checkedChanged, this, &QQuickButtonGroupPrivate::_q_updateCurrent);
182     }
183     buttons.clear();
184 }
185 
buttonClicked()186 void QQuickButtonGroupPrivate::buttonClicked()
187 {
188     Q_Q(QQuickButtonGroup);
189     QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton*>(q->sender());
190     if (button)
191         emit q->clicked(button);
192 }
193 
_q_updateCurrent()194 void QQuickButtonGroupPrivate::_q_updateCurrent()
195 {
196     Q_Q(QQuickButtonGroup);
197     if (exclusive) {
198         QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton*>(q->sender());
199         if (button && button->isChecked())
200             q->setCheckedButton(button);
201         else if (!buttons.contains(checkedButton))
202             q->setCheckedButton(nullptr);
203     }
204     updateCheckState();
205 }
206 
updateCheckState()207 void QQuickButtonGroupPrivate::updateCheckState()
208 {
209     if (!complete || settingCheckState)
210         return;
211 
212     bool anyChecked = false;
213     bool allChecked = !buttons.isEmpty();
214     for (QQuickAbstractButton *button : qAsConst(buttons)) {
215         const bool isChecked = button->isChecked();
216         anyChecked |= isChecked;
217         allChecked &= isChecked;
218     }
219     setCheckState(Qt::CheckState(anyChecked + allChecked));
220 }
221 
setCheckState(Qt::CheckState state)222 void 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 
buttons_append(QQmlListProperty<QQuickAbstractButton> * prop,QQuickAbstractButton * obj)232 void QQuickButtonGroupPrivate::buttons_append(QQmlListProperty<QQuickAbstractButton> *prop, QQuickAbstractButton *obj)
233 {
234     QQuickButtonGroup *q = static_cast<QQuickButtonGroup *>(prop->object);
235     q->addButton(obj);
236 }
237 
buttons_count(QQmlListProperty<QQuickAbstractButton> * prop)238 int QQuickButtonGroupPrivate::buttons_count(QQmlListProperty<QQuickAbstractButton> *prop)
239 {
240     QQuickButtonGroupPrivate *p = static_cast<QQuickButtonGroupPrivate *>(prop->data);
241     return p->buttons.count();
242 }
243 
buttons_at(QQmlListProperty<QQuickAbstractButton> * prop,int index)244 QQuickAbstractButton *QQuickButtonGroupPrivate::buttons_at(QQmlListProperty<QQuickAbstractButton> *prop, int index)
245 {
246     QQuickButtonGroupPrivate *p = static_cast<QQuickButtonGroupPrivate *>(prop->data);
247     return p->buttons.value(index);
248 }
249 
buttons_clear(QQmlListProperty<QQuickAbstractButton> * prop)250 void 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(q, "_q_updateCurrent", Qt::QueuedConnection);
258         emit q->buttonsChanged();
259     }
260 }
261 
QQuickButtonGroup(QObject * parent)262 QQuickButtonGroup::QQuickButtonGroup(QObject *parent)
263     : QObject(*(new QQuickButtonGroupPrivate), parent)
264 {
265 }
266 
~QQuickButtonGroup()267 QQuickButtonGroup::~QQuickButtonGroup()
268 {
269     Q_D(QQuickButtonGroup);
270     d->clear();
271 }
272 
qmlAttachedProperties(QObject * object)273 QQuickButtonGroupAttached *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 */
checkedButton() const288 QQuickAbstractButton *QQuickButtonGroup::checkedButton() const
289 {
290     Q_D(const QQuickButtonGroup);
291     return d->checkedButton;
292 }
293 
setCheckedButton(QQuickAbstractButton * checkedButton)294 void 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 */
buttons()335 QQmlListProperty<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 */
isExclusive() const361 bool QQuickButtonGroup::isExclusive() const
362 {
363     Q_D(const QQuickButtonGroup);
364     return d->exclusive;
365 }
366 
setExclusive(bool exclusive)367 void 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 */
checkState() const396 Qt::CheckState QQuickButtonGroup::checkState() const
397 {
398     Q_D(const QQuickButtonGroup);
399     return d->checkState;
400 }
401 
setCheckState(Qt::CheckState state)402 void 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(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 */
addButton(QQuickAbstractButton * button)431 void QQuickButtonGroup::addButton(QQuickAbstractButton *button)
432 {
433     Q_D(QQuickButtonGroup);
434     if (!button || d->buttons.contains(button))
435         return;
436 
437     QQuickAbstractButtonPrivate::get(button)->group = this;
438     QObjectPrivate::connect(button, &QQuickAbstractButton::clicked, d, &QQuickButtonGroupPrivate::buttonClicked);
439     QObjectPrivate::connect(button, &QQuickAbstractButton::checkedChanged, d, &QQuickButtonGroupPrivate::_q_updateCurrent);
440 
441     if (d->exclusive && button->isChecked())
442         setCheckedButton(button);
443 
444     d->buttons.append(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 */
removeButton(QQuickAbstractButton * button)460 void QQuickButtonGroup::removeButton(QQuickAbstractButton *button)
461 {
462     Q_D(QQuickButtonGroup);
463     if (!button || !d->buttons.contains(button))
464         return;
465 
466     QQuickAbstractButtonPrivate::get(button)->group = nullptr;
467     QObjectPrivate::disconnect(button, &QQuickAbstractButton::clicked, d, &QQuickButtonGroupPrivate::buttonClicked);
468     QObjectPrivate::disconnect(button, &QQuickAbstractButton::checkedChanged, d, &QQuickButtonGroupPrivate::_q_updateCurrent);
469 
470     if (d->checkedButton == button)
471         setCheckedButton(nullptr);
472 
473     d->buttons.removeOne(button);
474     d->updateCheckState();
475     emit buttonsChanged();
476 }
477 
classBegin()478 void QQuickButtonGroup::classBegin()
479 {
480     Q_D(QQuickButtonGroup);
481     d->complete = false;
482 }
483 
componentComplete()484 void QQuickButtonGroup::componentComplete()
485 {
486     Q_D(QQuickButtonGroup);
487     d->complete = true;
488     if (!d->buttons.isEmpty())
489         d->updateCheckState();
490 }
491 
492 class QQuickButtonGroupAttachedPrivate : public QObjectPrivate
493 {
494 public:
495     QQuickButtonGroup *group = nullptr;
496 };
497 
QQuickButtonGroupAttached(QObject * parent)498 QQuickButtonGroupAttached::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 */
group() const525 QQuickButtonGroup *QQuickButtonGroupAttached::group() const
526 {
527     Q_D(const QQuickButtonGroupAttached);
528     return d->group;
529 }
530 
setGroup(QQuickButtonGroup * group)531 void 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(qobject_cast<QQuickAbstractButton*>(parent()));
539     d->group = group;
540     if (group)
541         group->addButton(qobject_cast<QQuickAbstractButton*>(parent()));
542     emit groupChanged();
543 }
544 
545 QT_END_NAMESPACE
546 
547 #include "moc_qquickbuttongroup_p.cpp"
548