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
qgroupbox.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 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
5#include "qgroupbox.h"
6
7#include "qapplication.h"
8#include "qbitmap.h"
9#include "qdrawutil.h"
10#include "qevent.h"
11#include "qlayout.h"
12#if QT_CONFIG(radiobutton)
13#include "qradiobutton.h"
14#endif
15#include "qstyle.h"
16#include "qstyleoption.h"
17#include "qstylepainter.h"
18#if QT_CONFIG(accessibility)
19#include "qaccessible.h"
20#endif
21#include <private/qwidget_p.h>
22#include <private/qguiapplication_p.h>
23#include <qpa/qplatformtheme.h>
24
25#include "qdebug.h"
26#include <QtCore/qpointer.h>
27
28QT_BEGIN_NAMESPACE
29
31{
32 Q_DECLARE_PUBLIC(QGroupBox)
33
34public:
35 void skip();
36 void init();
39 int align;
40#ifndef QT_NO_SHORTCUT
42#endif
43
44 void _q_fixFocus(Qt::FocusReason reason);
46 void click();
47 bool shouldHandleKeyEvent(const QKeyEvent *keyEvent) const;
48 bool flat;
50 bool checked;
51 bool hover;
54};
55
56/*!
57 Initialize \a option with the values from this QGroupBox. This method
58 is useful for subclasses when they need a QStyleOptionGroupBox, but don't want
59 to fill in all the information themselves.
60
61 \sa QStyleOption::initFrom()
62*/
63void QGroupBox::initStyleOption(QStyleOptionGroupBox *option) const
64{
65 if (!option)
66 return;
67
68 Q_D(const QGroupBox);
69 option->initFrom(this);
70 option->text = d->title;
71 option->lineWidth = 1;
72 option->midLineWidth = 0;
73 option->textAlignment = Qt::Alignment(d->align);
74 option->activeSubControls |= d->pressedControl;
75 option->subControls = QStyle::SC_GroupBoxFrame;
76
77 option->state.setFlag(QStyle::State_MouseOver, d->hover);
78 if (d->flat)
79 option->features |= QStyleOptionFrame::Flat;
80
81 if (d->checkable) {
82 option->subControls |= QStyle::SC_GroupBoxCheckBox;
83 option->state |= (d->checked ? QStyle::State_On : QStyle::State_Off);
84 if ((d->pressedControl == QStyle::SC_GroupBoxCheckBox
85 || d->pressedControl == QStyle::SC_GroupBoxLabel) && (d->hover || d->overCheckBox))
86 option->state |= QStyle::State_Sunken;
87 }
88
89 if (!option->palette.isBrushSet(isEnabled() ? QPalette::Active :
90 QPalette::Disabled, QPalette::WindowText))
91 option->textColor = QColor(style()->styleHint(QStyle::SH_GroupBox_TextLabelColor,
92 option, this));
93
94 if (!d->title.isEmpty())
95 option->subControls |= QStyle::SC_GroupBoxLabel;
96}
97
99{
100 Q_Q(QGroupBox);
101
102 QPointer<QGroupBox> guard(q);
103 q->setChecked(!checked);
104 if (!guard)
105 return;
106 emit q->clicked(checked);
107}
108
109/*!
110 \class QGroupBox
111 \brief The QGroupBox widget provides a group box frame with a title.
112
113 \ingroup organizers
114 \ingroup geomanagement
115 \inmodule QtWidgets
116
117 \image fusion-groupbox.png {Group box displaying several radio button items}
118
119 A group box provides a frame, a title on top, a keyboard shortcut, and
120 displays various other widgets inside itself. The keyboard shortcut moves
121 keyboard focus to one of the group box's child widgets.
122
123 QGroupBox also lets you set the \l title (normally set in the
124 constructor) and the title's \l alignment. Group boxes can be
125 \l checkable. Child widgets in checkable group boxes are enabled or
126 disabled depending on whether or not the group box is \l checked.
127
128 You can minimize the space consumption of a group box by enabling
129 the \l flat property. In most \l{QStyle}{styles}, enabling this
130 property results in the removal of the left, right and bottom
131 edges of the frame.
132
133 QGroupBox doesn't automatically lay out the child widgets (which
134 are often \l{QCheckBox}es or \l{QRadioButton}s but can be any
135 widgets). The following example shows how we can set up a
136 QGroupBox with a layout:
137
138 \snippet code/src_gui_widgets_qgroupbox.cpp Set up QGroupBox with layout
139
140 \sa QButtonGroup
141*/
142
143
144
145/*!
146 Constructs a group box widget with the given \a parent but with no title.
147*/
148
149QGroupBox::QGroupBox(QWidget *parent)
150 : QWidget(*new QGroupBoxPrivate, parent, { })
151{
152 Q_D(QGroupBox);
153 d->init();
154}
155
156/*!
157 Constructs a group box with the given \a title and \a parent.
158*/
159
160QGroupBox::QGroupBox(const QString &title, QWidget *parent)
161 : QGroupBox(parent)
162{
163 setTitle(title);
164}
165
166
167/*!
168 Destroys the group box.
169*/
170QGroupBox::~QGroupBox()
171{
172}
173
175{
176 Q_Q(QGroupBox);
177 align = Qt::AlignLeft;
178#ifndef QT_NO_SHORTCUT
179 shortcutId = 0;
180#endif
181 flat = false;
182 checkable = false;
183 checked = true;
184 hover = false;
185 overCheckBox = false;
186 pressedControl = QStyle::SC_None;
188 q->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred,
189 QSizePolicy::GroupBox));
190}
191
192void QGroupBox::setTitle(const QString &title)
193{
194 Q_D(QGroupBox);
195 if (d->title == title) // no change
196 return;
197 d->title = title;
198#ifndef QT_NO_SHORTCUT
199 releaseShortcut(d->shortcutId);
200 d->shortcutId = grabShortcut(QKeySequence::mnemonic(title));
201#endif
202 d->calculateFrame();
203
204 update();
205 updateGeometry();
206#if QT_CONFIG(accessibility)
207 QAccessibleEvent event(this, QAccessible::NameChanged);
208 QAccessible::updateAccessibility(&event);
209#endif
210}
211
212/*!
213 \property QGroupBox::title
214 \brief the group box title text
215
216 The group box title text will have a keyboard shortcut if the title
217 contains an ampersand ('&') followed by a letter.
218
219 \snippet code/src_gui_widgets_qgroupbox.cpp 0
220
221 In the example above, \uicontrol Alt+U moves the keyboard focus to the
222 group box. See the \l {QShortcut#mnemonic}{QShortcut}
223 documentation for details (to display an actual ampersand, use
224 '&&').
225
226 There is no default title text.
227
228 \sa alignment
229*/
230
231QString QGroupBox::title() const
232{
233 Q_D(const QGroupBox);
234 return d->title;
235}
236
237/*!
238 \property QGroupBox::alignment
239 \brief the alignment of the group box title.
240
241 Most styles place the title at the top of the frame. The horizontal
242 alignment of the title can be specified using single values from
243 the following list:
244
245 \list
246 \li Qt::AlignLeft aligns the title text with the left-hand side of the group box.
247 \li Qt::AlignRight aligns the title text with the right-hand side of the group box.
248 \li Qt::AlignHCenter aligns the title text with the horizontal center of the group box.
249 \endlist
250
251 The default alignment is Qt::AlignLeft.
252
253 \sa Qt::Alignment
254*/
255Qt::Alignment QGroupBox::alignment() const
256{
257 Q_D(const QGroupBox);
258 return QFlag(d->align);
259}
260
261void QGroupBox::setAlignment(int alignment)
262{
263 Q_D(QGroupBox);
264 d->align = alignment;
265 updateGeometry();
266 update();
267}
268
269/*! \reimp
270*/
271void QGroupBox::resizeEvent(QResizeEvent *e)
272{
273 QWidget::resizeEvent(e);
274}
275
276/*! \reimp
277*/
278
279void QGroupBox::paintEvent(QPaintEvent *)
280{
281 QStylePainter paint(this);
282 QStyleOptionGroupBox option;
283 initStyleOption(&option);
284 paint.drawComplexControl(QStyle::CC_GroupBox, option);
285}
286
287/*! \reimp */
288bool QGroupBox::event(QEvent *e)
289{
290 Q_D(QGroupBox);
291#ifndef QT_NO_SHORTCUT
292 if (e->type() == QEvent::Shortcut) {
293 QShortcutEvent *se = static_cast<QShortcutEvent *>(e);
294 if (se->shortcutId() == d->shortcutId) {
295 if (!isCheckable()) {
296 d->_q_fixFocus(Qt::ShortcutFocusReason);
297 } else {
298 d->click();
299 setFocus(Qt::ShortcutFocusReason);
300 }
301 return true;
302 }
303 }
304#endif
305 QStyleOptionGroupBox box;
306 initStyleOption(&box);
307 switch (e->type()) {
308 case QEvent::HoverEnter:
309 case QEvent::HoverMove: {
310 QStyle::SubControl control = style()->hitTestComplexControl(QStyle::CC_GroupBox, &box,
311 static_cast<QHoverEvent *>(e)->position().toPoint(),
312 this);
313 bool oldHover = d->hover;
314 d->hover = d->checkable && (control == QStyle::SC_GroupBoxLabel || control == QStyle::SC_GroupBoxCheckBox);
315 if (oldHover != d->hover) {
316 QRect rect = style()->subControlRect(QStyle::CC_GroupBox, &box, QStyle::SC_GroupBoxCheckBox, this)
317 | style()->subControlRect(QStyle::CC_GroupBox, &box, QStyle::SC_GroupBoxLabel, this);
318 update(rect);
319 }
320 return true;
321 }
322 case QEvent::HoverLeave:
323 d->hover = false;
324 if (d->checkable) {
325 QRect rect = style()->subControlRect(QStyle::CC_GroupBox, &box, QStyle::SC_GroupBoxCheckBox, this)
326 | style()->subControlRect(QStyle::CC_GroupBox, &box, QStyle::SC_GroupBoxLabel, this);
327 update(rect);
328 }
329 return true;
330 case QEvent::KeyPress: {
331 QKeyEvent *k = static_cast<QKeyEvent*>(e);
332 if (d->shouldHandleKeyEvent(k)) {
333 d->pressedControl = QStyle::SC_GroupBoxCheckBox;
334 update(style()->subControlRect(QStyle::CC_GroupBox, &box, QStyle::SC_GroupBoxCheckBox, this));
335 return true;
336 }
337 break;
338 }
339 case QEvent::KeyRelease: {
340 QKeyEvent *k = static_cast<QKeyEvent*>(e);
341 if (d->shouldHandleKeyEvent(k)) {
342 bool toggle = (d->pressedControl == QStyle::SC_GroupBoxLabel
343 || d->pressedControl == QStyle::SC_GroupBoxCheckBox);
344 d->pressedControl = QStyle::SC_None;
345 if (toggle)
346 d->click();
347 return true;
348 }
349 break;
350 }
351 default:
352 break;
353 }
354 return QWidget::event(e);
355}
356
357/*!\reimp */
358void QGroupBox::childEvent(QChildEvent *c)
359{
360 Q_D(QGroupBox);
361 /*
362 Children might have been enabled after being added to the group box, in which case
363 the childEvent handler ran too early, and we need to disabled children again.
364 */
365 if (!(c->added() || c->polished()) || !c->child()->isWidgetType())
366 return;
367 QWidget *w = static_cast<QWidget*>(c->child());
368 if (w->isWindow())
369 return;
370 if (d->checkable) {
371 if (d->checked) {
372 if (!w->testAttribute(Qt::WA_ForceDisabled))
373 w->setEnabled(true);
374 } else {
375 if (w->isEnabled()) {
376 w->setEnabled(false);
377 w->setAttribute(Qt::WA_ForceDisabled, false);
378 }
379 }
380 }
381}
382
383
384/*!
385 \internal
386
387 This private slot finds a widget in this group box that can accept
388 focus, and gives the focus to that widget.
389*/
390
391void QGroupBoxPrivate::_q_fixFocus(Qt::FocusReason reason)
392{
393 Q_Q(QGroupBox);
394 QWidget *fw = q->focusWidget();
395 if (!fw || fw == q) {
396 QWidget * best = nullptr;
397 QWidget * candidate = nullptr;
398 QWidget * w = q;
399 while ((w = w->nextInFocusChain()) != q) {
400 if (q->isAncestorOf(w) && (w->focusPolicy() & Qt::TabFocus) == Qt::TabFocus && w->isVisibleTo(q)) {
401#if QT_CONFIG(radiobutton)
402 if (!best && qobject_cast<QRadioButton*>(w) && ((QRadioButton*)w)->isChecked())
403 // we prefer a checked radio button or a widget that
404 // already has focus, if there is one
405 best = w;
406 else
407#endif
408 if (!candidate)
409 // but we'll accept anything that takes focus
410 candidate = w;
411 }
412 }
413 if (best)
414 fw = best;
415 else if (candidate)
416 fw = candidate;
417 }
418 if (fw)
419 fw->setFocus(reason);
420}
421
422/*
423 Sets the right frame rect depending on the title.
424*/
426{
427 Q_Q(QGroupBox);
428 QStyleOptionGroupBox box;
429 q->initStyleOption(&box);
430 QRect contentsRect = q->style()->subControlRect(QStyle::CC_GroupBox, &box, QStyle::SC_GroupBoxContents, q);
431 q->setContentsMargins(contentsRect.left() - box.rect.left(), contentsRect.top() - box.rect.top(),
432 box.rect.right() - contentsRect.right(), box.rect.bottom() - contentsRect.bottom());
433 setLayoutItemMargins(QStyle::SE_GroupBoxLayoutItem, &box);
434}
435
436/*! \reimp
437 */
438void QGroupBox::focusInEvent(QFocusEvent *fe)
439{ // note no call to super
440 Q_D(QGroupBox);
441 if (focusPolicy() == Qt::NoFocus) {
442 d->_q_fixFocus(fe->reason());
443 } else {
444 QWidget::focusInEvent(fe);
445 }
446}
447
448
449/*!
450 \reimp
451*/
452QSize QGroupBox::minimumSizeHint() const
453{
454 Q_D(const QGroupBox);
455 QStyleOptionGroupBox option;
456 initStyleOption(&option);
457
458 QFontMetrics metrics(fontMetrics());
459
460 int baseWidth = metrics.horizontalAdvance(d->title) + metrics.horizontalAdvance(u' ');
461 int baseHeight = metrics.height();
462 if (d->checkable) {
463 baseWidth += style()->pixelMetric(QStyle::PM_IndicatorWidth, &option, this);
464 baseWidth += style()->pixelMetric(QStyle::PM_CheckBoxLabelSpacing, &option, this);
465 baseHeight = qMax(baseHeight, style()->pixelMetric(QStyle::PM_IndicatorHeight, &option, this));
466 }
467
468 QSize size = style()->sizeFromContents(QStyle::CT_GroupBox, &option, QSize(baseWidth, baseHeight), this);
469 return size.expandedTo(QWidget::minimumSizeHint());
470}
471
472/*!
473 \property QGroupBox::flat
474 \brief whether the group box is painted flat or has a frame
475
476 A group box usually consists of a surrounding frame with a title
477 at the top. If this property is enabled, only the top part of the frame is
478 drawn in most styles; otherwise, the whole frame is drawn.
479
480 By default, this property is disabled, i.e., group boxes are not flat unless
481 explicitly specified.
482
483 \b{Note:} In some styles, flat and non-flat group boxes have similar
484 representations and may not be as distinguishable as they are in other
485 styles.
486
487 \sa title
488*/
489bool QGroupBox::isFlat() const
490{
491 Q_D(const QGroupBox);
492 return d->flat;
493}
494
495void QGroupBox::setFlat(bool b)
496{
497 Q_D(QGroupBox);
498 if (d->flat == b)
499 return;
500 d->flat = b;
501 updateGeometry();
502 update();
503}
504
505
506/*!
507 \property QGroupBox::checkable
508 \brief whether the group box has a checkbox in its title
509
510 If this property is \c true, the group box displays its title using
511 a checkbox in place of an ordinary label. If the checkbox is checked,
512 the group box's children are enabled; otherwise, they are disabled and
513 inaccessible.
514
515 By default, group boxes are not checkable.
516
517 If this property is enabled for a group box, it will also be initially
518 checked to ensure that its contents are enabled.
519
520 \sa checked
521*/
522void QGroupBox::setCheckable(bool checkable)
523{
524 Q_D(QGroupBox);
525
526 bool wasCheckable = d->checkable;
527 d->checkable = checkable;
528
529 if (checkable) {
530 setChecked(true);
531 if (!wasCheckable) {
532 setFocusPolicy(Qt::StrongFocus);
533 d->_q_setChildrenEnabled(true);
534 updateGeometry();
535 }
536 } else {
537 if (wasCheckable) {
538 setFocusPolicy(Qt::NoFocus);
539 d->_q_setChildrenEnabled(true);
540 updateGeometry();
541 }
542 d->_q_setChildrenEnabled(true);
543 }
544
545 if (wasCheckable != checkable) {
546 d->calculateFrame();
547 update();
548 }
549}
550
551bool QGroupBox::isCheckable() const
552{
553 Q_D(const QGroupBox);
554 return d->checkable;
555}
556
557
558bool QGroupBox::isChecked() const
559{
560 Q_D(const QGroupBox);
561 return d->checkable && d->checked;
562}
563
564
565/*!
566 \fn void QGroupBox::toggled(bool on)
567
568 If the group box is checkable, this signal is emitted when the check box
569 is toggled. \a on is true if the check box is checked; otherwise, it is false.
570
571 \sa checkable
572*/
573
574
575/*!
576 \fn void QGroupBox::clicked(bool checked)
577 \since 4.2
578
579 This signal is emitted when the check box is activated (i.e., pressed down
580 then released while the mouse cursor is inside the button), or when the
581 shortcut key is typed. Notably, this signal is \e not emitted if you call
582 setChecked().
583
584 If the check box is checked, \a checked is true; it is false if the check
585 box is unchecked.
586
587 \sa checkable, toggled(), checked
588*/
589
590/*!
591 \property QGroupBox::checked
592 \brief whether the group box is checked
593
594 If the group box is checkable, it is displayed with a check box.
595 If the check box is checked, the group box's children are enabled;
596 otherwise, the children are disabled and are inaccessible to the user.
597
598 By default, checkable group boxes are also checked.
599
600 \note The group box itself will not be disabled when the box is unchecked,
601 and you can explicitly enable individual children in a group box that is
602 unchecked. This is however not recommended, as it could create a surprising
603 experience for the end user.
604
605 \sa checkable
606*/
607void QGroupBox::setChecked(bool b)
608{
609 Q_D(QGroupBox);
610 if (d->checkable && b != d->checked) {
611 update();
612 d->checked = b;
613 d->_q_setChildrenEnabled(b);
614#if QT_CONFIG(accessibility)
615 QAccessible::State st;
616 st.checked = true;
617 QAccessibleStateChangeEvent e(this, st);
618 QAccessible::updateAccessibility(&e);
619#endif
620 emit toggled(b);
621 }
622}
623
624/*
625 sets all children of the group box except the qt_groupbox_checkbox
626 to either disabled/enabled
627*/
629{
630 Q_Q(QGroupBox);
631 for (QObject *o : q->children()) {
632 if (o->isWidgetType()) {
633 QWidget *w = static_cast<QWidget *>(o);
634 if (b) {
635 if (!w->testAttribute(Qt::WA_ForceDisabled))
636 w->setEnabled(true);
637 } else {
638 if (w->isEnabled()) {
639 w->setEnabled(false);
640 w->setAttribute(Qt::WA_ForceDisabled, false);
641 }
642 }
643 }
644 }
645}
646
647/*! \reimp */
648void QGroupBox::changeEvent(QEvent *ev)
649{
650 Q_D(QGroupBox);
651 if (ev->type() == QEvent::EnabledChange) {
652 if (d->checkable && isEnabled()) {
653 // we are being enabled - disable children
654 if (!d->checked)
655 d->_q_setChildrenEnabled(false);
656 }
657 } else if (ev->type() == QEvent::FontChange
658#ifdef Q_OS_MAC
659 || ev->type() == QEvent::MacSizeChange
660#endif
661 || ev->type() == QEvent::StyleChange) {
662 d->calculateFrame();
663 }
664 QWidget::changeEvent(ev);
665}
666
667/*! \reimp */
668void QGroupBox::mousePressEvent(QMouseEvent *event)
669{
670 if (event->button() != Qt::LeftButton) {
671 event->ignore();
672 return;
673 }
674
675 Q_D(QGroupBox);
676 QStyleOptionGroupBox box;
677 initStyleOption(&box);
678 d->pressedControl = style()->hitTestComplexControl(QStyle::CC_GroupBox, &box,
679 event->position().toPoint(), this);
680 if (d->checkable && (d->pressedControl & (QStyle::SC_GroupBoxCheckBox | QStyle::SC_GroupBoxLabel))) {
681 d->overCheckBox = true;
682 update(style()->subControlRect(QStyle::CC_GroupBox, &box, QStyle::SC_GroupBoxCheckBox, this));
683 } else {
684 event->ignore();
685 }
686}
687
688/*! \reimp */
689void QGroupBox::mouseMoveEvent(QMouseEvent *event)
690{
691 Q_D(QGroupBox);
692 QStyleOptionGroupBox box;
693 initStyleOption(&box);
694 QStyle::SubControl pressed = style()->hitTestComplexControl(QStyle::CC_GroupBox, &box,
695 event->position().toPoint(), this);
696 bool oldOverCheckBox = d->overCheckBox;
697 d->overCheckBox = (pressed == QStyle::SC_GroupBoxCheckBox || pressed == QStyle::SC_GroupBoxLabel);
698 if (d->checkable && (d->pressedControl == QStyle::SC_GroupBoxCheckBox || d->pressedControl == QStyle::SC_GroupBoxLabel)
699 && (d->overCheckBox != oldOverCheckBox))
700 update(style()->subControlRect(QStyle::CC_GroupBox, &box, QStyle::SC_GroupBoxCheckBox, this));
701
702 event->ignore();
703}
704
705/*! \reimp */
706void QGroupBox::mouseReleaseEvent(QMouseEvent *event)
707{
708 if (event->button() != Qt::LeftButton) {
709 event->ignore();
710 return;
711 }
712
713 Q_D(QGroupBox);
714 if (!d->overCheckBox) {
715 event->ignore();
716 return;
717 }
718 QStyleOptionGroupBox box;
719 initStyleOption(&box);
720 QStyle::SubControl released = style()->hitTestComplexControl(QStyle::CC_GroupBox, &box,
721 event->position().toPoint(), this);
722 bool toggle = d->checkable && (released == QStyle::SC_GroupBoxLabel
723 || released == QStyle::SC_GroupBoxCheckBox);
724 d->pressedControl = QStyle::SC_None;
725 d->overCheckBox = false;
726 if (toggle)
727 d->click();
728 else if (d->checkable)
729 update(style()->subControlRect(QStyle::CC_GroupBox, &box, QStyle::SC_GroupBoxCheckBox, this));
730}
731
732
733bool QGroupBoxPrivate::shouldHandleKeyEvent(const QKeyEvent *keyEvent) const
734{
735 Q_Q(const QGroupBox);
736
737 if (!q->isEnabled() || !q->isCheckable() || keyEvent->isAutoRepeat())
738 return false;
739
740 const QList<Qt::Key> buttonPressKeys = QGuiApplicationPrivate::platformTheme()
741 ->themeHint(QPlatformTheme::ButtonPressKeys)
742 .value<QList<Qt::Key>>();
743 return buttonPressKeys.contains(keyEvent->key());
744}
745
746
747QT_END_NAMESPACE
748
749#include "moc_qgroupbox.cpp"
QStyle::SubControl pressedControl
Definition qgroupbox.cpp:53
void _q_setChildrenEnabled(bool b)
void _q_fixFocus(Qt::FocusReason reason)
bool shouldHandleKeyEvent(const QKeyEvent *keyEvent) const