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 | |
176 | void 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 | |
186 | void 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 | |
194 | void 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 | |
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(t&: buttons)) { |
215 | const bool isChecked = button->isChecked(); |
216 | anyChecked |= isChecked; |
217 | allChecked &= isChecked; |
218 | } |
219 | setCheckState(Qt::CheckState(anyChecked + allChecked)); |
220 | } |
221 | |
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 | |
232 | void QQuickButtonGroupPrivate::buttons_append(QQmlListProperty<QQuickAbstractButton> *prop, QQuickAbstractButton *obj) |
233 | { |
234 | QQuickButtonGroup *q = static_cast<QQuickButtonGroup *>(prop->object); |
235 | q->addButton(button: obj); |
236 | } |
237 | |
238 | int QQuickButtonGroupPrivate::buttons_count(QQmlListProperty<QQuickAbstractButton> *prop) |
239 | { |
240 | QQuickButtonGroupPrivate *p = static_cast<QQuickButtonGroupPrivate *>(prop->data); |
241 | return p->buttons.count(); |
242 | } |
243 | |
244 | QQuickAbstractButton *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 | |
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(obj: q, member: "_q_updateCurrent" , type: Qt::QueuedConnection); |
258 | emit q->buttonsChanged(); |
259 | } |
260 | } |
261 | |
262 | QQuickButtonGroup::QQuickButtonGroup(QObject *parent) |
263 | : QObject(*(new QQuickButtonGroupPrivate), parent) |
264 | { |
265 | } |
266 | |
267 | QQuickButtonGroup::~QQuickButtonGroup() |
268 | { |
269 | Q_D(QQuickButtonGroup); |
270 | d->clear(); |
271 | } |
272 | |
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 | */ |
288 | QQuickAbstractButton *QQuickButtonGroup::checkedButton() const |
289 | { |
290 | Q_D(const QQuickButtonGroup); |
291 | return d->checkedButton; |
292 | } |
293 | |
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 | */ |
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 | */ |
361 | bool QQuickButtonGroup::isExclusive() const |
362 | { |
363 | Q_D(const QQuickButtonGroup); |
364 | return d->exclusive; |
365 | } |
366 | |
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 | */ |
396 | Qt::CheckState QQuickButtonGroup::checkState() const |
397 | { |
398 | Q_D(const QQuickButtonGroup); |
399 | return d->checkState; |
400 | } |
401 | |
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(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 | */ |
431 | void 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 | */ |
460 | void 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 | |
478 | void QQuickButtonGroup::classBegin() |
479 | { |
480 | Q_D(QQuickButtonGroup); |
481 | d->complete = false; |
482 | } |
483 | |
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 | |
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 | */ |
525 | QQuickButtonGroup *QQuickButtonGroupAttached::group() const |
526 | { |
527 | Q_D(const QQuickButtonGroupAttached); |
528 | return d->group; |
529 | } |
530 | |
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(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 | |
545 | QT_END_NAMESPACE |
546 | |
547 | #include "moc_qquickbuttongroup_p.cpp" |
548 | |