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
qdialogbuttonbox.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 <QtCore/qhash.h>
6#include <QtWidgets/qpushbutton.h>
7#include <QtWidgets/qstyle.h>
8#include <QtWidgets/qlayout.h>
9#include <QtWidgets/qdialog.h>
10#include <QtWidgets/qapplication.h>
11#include <private/qwidget_p.h>
12#include <private/qguiapplication_p.h>
13#include <QtGui/qpa/qplatformdialoghelper.h>
14#include <QtGui/qpa/qplatformtheme.h>
15#include <QtGui/qaction.h>
16
19
20#include <QtCore/qpointer.h>
21
23
24/*!
25 \class QDialogButtonBox
26 \since 4.2
27 \brief The QDialogButtonBox class is a widget that presents buttons in a
28 layout that is appropriate to the current widget style.
29
30 \ingroup dialog-classes
31 \inmodule QtWidgets
32
33 Dialogs and message boxes typically present buttons in a layout that
34 conforms to the interface guidelines for that platform. Invariably,
35 different platforms have different layouts for their dialogs.
36 QDialogButtonBox allows a developer to add buttons to it and will
37 automatically use the appropriate layout for the user's desktop
38 environment.
39
40 Most buttons for a dialog follow certain roles. Such roles include:
41
42 \list
43 \li Accepting or rejecting the dialog.
44 \li Asking for help.
45 \li Performing actions on the dialog itself (such as resetting fields or
46 applying changes).
47 \endlist
48
49 There can also be alternate ways of dismissing the dialog which may cause
50 destructive results.
51
52 Most dialogs have buttons that can almost be considered standard (e.g.
53 \uicontrol OK and \uicontrol Cancel buttons). It is sometimes convenient to create these
54 buttons in a standard way.
55
56 There are a couple ways of using QDialogButtonBox. One ways is to create
57 the buttons (or button texts) yourself and add them to the button box,
58 specifying their role.
59
60 \snippet dialogs/dialogs.cpp buttonbox
61
62 Alternatively, QDialogButtonBox provides several standard buttons (e.g. OK, Cancel, Save)
63 that you can use. They exist as flags so you can OR them together in the constructor.
64
65 \snippet dialogs/tabdialog/tabdialog.cpp 2
66
67 You can mix and match normal buttons and standard buttons.
68
69 Currently the buttons are laid out in the following way if the button box is horizontal:
70 \table
71 \row \li \inlineimage buttonbox-gnomelayout-horizontal.png
72 {Several buttons using the GnomeLayout horizontal layout}
73 GnomeLayout Horizontal
74 \li Button box laid out in horizontal GnomeLayout
75 \row \li \inlineimage buttonbox-kdelayout-horizontal.png
76 {Several buttons using the KdeLayout horizontal layout}
77 KdeLayout Horizontal
78 \li Button box laid out in horizontal KdeLayout
79 \row \li \inlineimage buttonbox-maclayout-horizontal.png
80 {Several buttons using the MacLayout horizontal layout}
81 MacLayout Horizontal
82 \li Button box laid out in horizontal MacLayout
83 \row \li \inlineimage buttonbox-winlayout-horizontal.png
84 {Several buttons using the WinLayout horizontal layout}
85 WinLayout Horizontal
86 \li Button box laid out in horizontal WinLayout
87 \endtable
88
89 The buttons are laid out the following way if the button box is vertical:
90
91 \table
92 \row \li GnomeLayout
93 \li KdeLayout
94 \li MacLayout
95 \li WinLayout
96 \row \li \inlineimage buttonbox-gnomelayout-vertical.png
97 {Several buttons using the GnomeLayout vertical layout}
98 GnomeLayout Vertical
99 \li \inlineimage buttonbox-kdelayout-vertical.png
100 {Several buttons using the KdeLayout vertical layout}
101 KdeLayout Vertical
102 \li \inlineimage buttonbox-maclayout-vertical.png
103 {Several buttons using the MacLayout vertical layout}
104 MacLayout Vertical
105 \li \inlineimage buttonbox-winlayout-vertical.png
106 {Several buttons using the WinLayout vertical layout}
107 WinLayout Vertical
108 \endtable
109
110 Additionally, button boxes that contain only buttons with ActionRole or
111 HelpRole can be considered modeless and have an alternate look on \macos:
112
113 \table
114 \row \li modeless horizontal MacLayout
115 \li \inlineimage buttonbox-mac-modeless-horizontal.png
116 {Several buttons using the horizontal modeless style on macOS}
117 Screenshot of modeless horizontal MacLayout
118 \row \li modeless vertical MacLayout
119 \li \inlineimage buttonbox-mac-modeless-vertical.png
120 {Several buttons using the horizontal modeless style on macOS}
121 Screenshot of modeless vertical MacLayout
122 \endtable
123
124 When a button is clicked in the button box, the clicked() signal is emitted
125 for the actual button is that is pressed. For convenience, if the button
126 has an AcceptRole, RejectRole, or HelpRole, the accepted(), rejected(), or
127 helpRequested() signals are emitted respectively.
128
129 If you want a specific button to be default you need to call
130 QPushButton::setDefault() on it yourself. However, if there is no default
131 button set and to preserve which button is the default button across
132 platforms when using the QPushButton::autoDefault property, the first push
133 button with the accept role is made the default button when the
134 QDialogButtonBox is shown,
135
136 \sa QMessageBox, QPushButton, QDialog
137*/
138QDialogButtonBoxPrivate::QDialogButtonBoxPrivate(Qt::Orientation orient)
139 : orientation(orient), buttonLayout(nullptr), center(false)
140{
141 struct EventFilter : public QObject
142 {
143 EventFilter(QDialogButtonBoxPrivate *d) : d(d) {};
144
145 bool eventFilter(QObject *obj, QEvent *event) override
146 {
147 QAbstractButton *button = qobject_cast<QAbstractButton *>(obj);
148 return button ? d->handleButtonShowAndHide(button, event) : false;
149 }
150
151 private:
152 QDialogButtonBoxPrivate *d;
153
154 };
155
156 filter.reset(new EventFilter(this));
157}
158
159void QDialogButtonBoxPrivate::initLayout()
160{
161 Q_Q(QDialogButtonBox);
162 layoutPolicy = QDialogButtonBox::ButtonLayout(q->style()->styleHint(QStyle::SH_DialogButtonLayout, nullptr, q));
163 bool createNewLayout = buttonLayout == nullptr
164 || (orientation == Qt::Horizontal && qobject_cast<QVBoxLayout *>(buttonLayout) != 0)
165 || (orientation == Qt::Vertical && qobject_cast<QHBoxLayout *>(buttonLayout) != 0);
166 if (createNewLayout) {
167 delete buttonLayout;
168 if (orientation == Qt::Horizontal)
169 buttonLayout = new QHBoxLayout(q);
170 else
171 buttonLayout = new QVBoxLayout(q);
172 }
173
174 int left, top, right, bottom;
175 setLayoutItemMargins(QStyle::SE_PushButtonLayoutItem);
176 getLayoutItemMargins(&left, &top, &right, &bottom);
177 buttonLayout->setContentsMargins(-left, -top, -right, -bottom);
178
179 if (!q->testAttribute(Qt::WA_WState_OwnSizePolicy)) {
180 QSizePolicy sp(QSizePolicy::Expanding, QSizePolicy::Fixed, QSizePolicy::ButtonBox);
181 if (orientation == Qt::Vertical)
182 sp.transpose();
183 q->setSizePolicy(sp);
184 q->setAttribute(Qt::WA_WState_OwnSizePolicy, false);
185 }
186}
187
188void QDialogButtonBoxPrivate::resetLayout()
189{
190 initLayout();
191 layoutButtons();
192}
193
194void QDialogButtonBoxPrivate::addButtonsToLayout(const QList<QAbstractButton *> &buttonList,
195 bool reverse)
196{
197 int start = reverse ? buttonList.size() - 1 : 0;
198 int end = reverse ? -1 : buttonList.size();
199 int step = reverse ? -1 : 1;
200
201 for (int i = start; i != end; i += step) {
202 QAbstractButton *button = buttonList.at(i);
203 buttonLayout->addWidget(button);
204 button->show();
205 }
206}
207
208void QDialogButtonBoxPrivate::layoutButtons()
209{
210 Q_Q(QDialogButtonBox);
211 const int MacGap = 36 - 8; // 8 is the default gap between a widget and a spacer item
212
213 QScopedValueRollback blocker(ignoreShowAndHide, true);
214 for (int i = buttonLayout->count() - 1; i >= 0; --i) {
215 QLayoutItem *item = buttonLayout->takeAt(i);
216 if (QWidget *widget = item->widget())
217 widget->hide();
218 delete item;
219 }
220
221 int tmpPolicy = layoutPolicy;
222
223 static const int M = 5;
224 static const int ModalRoles[M] = { QPlatformDialogHelper::AcceptRole, QPlatformDialogHelper::RejectRole,
225 QPlatformDialogHelper::DestructiveRole, QPlatformDialogHelper::YesRole, QPlatformDialogHelper::NoRole };
226 if (tmpPolicy == QDialogButtonBox::MacLayout) {
227 bool hasModalButton = false;
228 for (int i = 0; i < M; ++i) {
229 if (!buttonLists[ModalRoles[i]].isEmpty()) {
230 hasModalButton = true;
231 break;
232 }
233 }
234 if (!hasModalButton)
235 tmpPolicy = 4; // Mac modeless
236 }
237
238 const int *currentLayout = QPlatformDialogHelper::buttonLayout(
239 orientation, static_cast<QPlatformDialogHelper::ButtonLayout>(tmpPolicy));
240
241 if (center)
242 buttonLayout->addStretch();
243
244 const QList<QAbstractButton *> &acceptRoleList = buttonLists[QPlatformDialogHelper::AcceptRole];
245
246 while (*currentLayout != QPlatformDialogHelper::EOL) {
247 int role = (*currentLayout & ~QPlatformDialogHelper::Reverse);
248 bool reverse = (*currentLayout & QPlatformDialogHelper::Reverse);
249
250 switch (role) {
251 case QPlatformDialogHelper::Stretch:
252 if (!center)
253 buttonLayout->addStretch();
254 break;
255 case QPlatformDialogHelper::AcceptRole: {
256 if (acceptRoleList.isEmpty())
257 break;
258 // Only the first one
259 QAbstractButton *button = acceptRoleList.first();
260 buttonLayout->addWidget(button);
261 button->show();
262 }
263 break;
264 case QPlatformDialogHelper::AlternateRole:
265 if (acceptRoleList.size() > 1)
266 addButtonsToLayout(acceptRoleList.mid(1), reverse);
267 break;
268 case QPlatformDialogHelper::DestructiveRole:
269 {
270 const QList<QAbstractButton *> &list = buttonLists[role];
271
272 /*
273 Mac: Insert a gap on the left of the destructive
274 buttons to ensure that they don't get too close to
275 the help and action buttons (but only if there are
276 some buttons to the left of the destructive buttons
277 (and the stretch, whence buttonLayout->count() > 1
278 and not 0)).
279 */
280 if (tmpPolicy == QDialogButtonBox::MacLayout
281 && !list.isEmpty() && buttonLayout->count() > 1)
282 buttonLayout->addSpacing(MacGap);
283
284 addButtonsToLayout(list, reverse);
285
286 /*
287 Insert a gap between the destructive buttons and the
288 accept and reject buttons.
289 */
290 if (tmpPolicy == QDialogButtonBox::MacLayout && !list.isEmpty())
291 buttonLayout->addSpacing(MacGap);
292 }
293 break;
294 case QPlatformDialogHelper::RejectRole:
295 case QPlatformDialogHelper::ActionRole:
296 case QPlatformDialogHelper::HelpRole:
297 case QPlatformDialogHelper::YesRole:
298 case QPlatformDialogHelper::NoRole:
299 case QPlatformDialogHelper::ApplyRole:
300 case QPlatformDialogHelper::ResetRole:
301 addButtonsToLayout(buttonLists[role], reverse);
302 }
303 ++currentLayout;
304 }
305
306 QWidgetList layoutWidgets;
307 for (int i = 0; i < buttonLayout->count(); ++i) {
308 if (auto *widget = buttonLayout->itemAt(i)->widget())
309 layoutWidgets << widget;
310 }
311
312 q->setFocusProxy(nullptr);
313 if (!layoutWidgets.isEmpty()) {
314 QWidget *prev = layoutWidgets.constLast();
315 for (QWidget *here : layoutWidgets) {
316 QWidget::setTabOrder(prev, here);
317 prev = here;
318 if (auto *pushButton = qobject_cast<QPushButton *>(prev); pushButton && pushButton->isDefault())
319 q->setFocusProxy(pushButton);
320 }
321 }
322
323 if (center)
324 buttonLayout->addStretch();
325}
326
327QPushButton *QDialogButtonBoxPrivate::createButton(QDialogButtonBox::StandardButton sbutton,
328 LayoutRule layoutRule)
329{
330 Q_Q(QDialogButtonBox);
331 int icon = 0;
332
333 switch (sbutton) {
334 case QDialogButtonBox::Ok:
335 icon = QStyle::SP_DialogOkButton;
336 break;
337 case QDialogButtonBox::Save:
338 icon = QStyle::SP_DialogSaveButton;
339 break;
340 case QDialogButtonBox::Open:
341 icon = QStyle::SP_DialogOpenButton;
342 break;
343 case QDialogButtonBox::Cancel:
344 icon = QStyle::SP_DialogCancelButton;
345 break;
346 case QDialogButtonBox::Close:
347 icon = QStyle::SP_DialogCloseButton;
348 break;
349 case QDialogButtonBox::Apply:
350 icon = QStyle::SP_DialogApplyButton;
351 break;
352 case QDialogButtonBox::Reset:
353 icon = QStyle::SP_DialogResetButton;
354 break;
355 case QDialogButtonBox::Help:
356 icon = QStyle::SP_DialogHelpButton;
357 break;
358 case QDialogButtonBox::Discard:
359 icon = QStyle::SP_DialogDiscardButton;
360 break;
361 case QDialogButtonBox::Yes:
362 icon = QStyle::SP_DialogYesButton;
363 break;
364 case QDialogButtonBox::No:
365 icon = QStyle::SP_DialogNoButton;
366 break;
367 case QDialogButtonBox::YesToAll:
368 icon = QStyle::SP_DialogYesToAllButton;
369 break;
370 case QDialogButtonBox::NoToAll:
371 icon = QStyle::SP_DialogNoToAllButton;
372 break;
373 case QDialogButtonBox::SaveAll:
374 icon = QStyle::SP_DialogSaveAllButton;
375 break;
376 case QDialogButtonBox::Abort:
377 icon = QStyle::SP_DialogAbortButton;
378 break;
379 case QDialogButtonBox::Retry:
380 icon = QStyle::SP_DialogRetryButton;
381 break;
382 case QDialogButtonBox::Ignore:
383 icon = QStyle::SP_DialogIgnoreButton;
384 break;
385 case QDialogButtonBox::RestoreDefaults:
386 icon = QStyle::SP_RestoreDefaultsButton;
387 break;
388 case QDialogButtonBox::NoButton:
389 return nullptr;
390 ;
391 }
392 QPushButton *button = new QPushButton(QGuiApplicationPrivate::platformTheme()->standardButtonText(sbutton), q);
393 QStyle *style = q->style();
394 if (style->styleHint(QStyle::SH_DialogButtonBox_ButtonsHaveIcons, nullptr, q) && icon != 0)
395 button->setIcon(style->standardIcon(QStyle::StandardPixmap(icon), nullptr, q));
396 if (style != QApplication::style()) // Propagate style
397 button->setStyle(style);
398 standardButtonMap.insert(button, sbutton);
399 QPlatformDialogHelper::ButtonRole role = QPlatformDialogHelper::buttonRole(static_cast<QPlatformDialogHelper::StandardButton>(sbutton));
400 if (Q_UNLIKELY(role == QPlatformDialogHelper::InvalidRole))
401 qWarning("QDialogButtonBox::createButton: Invalid ButtonRole, button not added");
402 else
403 addButton(button, static_cast<QDialogButtonBox::ButtonRole>(role), layoutRule);
404#if QT_CONFIG(shortcut)
405 const QKeySequence standardShortcut = QGuiApplicationPrivate::platformTheme()->standardButtonShortcut(sbutton);
406 if (!standardShortcut.isEmpty())
407 button->setShortcut(standardShortcut);
408#endif
409 return button;
410}
411
412void QDialogButtonBoxPrivate::addButton(QAbstractButton *button, QDialogButtonBox::ButtonRole role,
413 LayoutRule layoutRule, AddRule addRule)
414{
415 buttonLists[role].append(button);
416 switch (addRule) {
417 case AddRule::Connect:
418 QObjectPrivate::connect(button, &QAbstractButton::clicked,
419 this, &QDialogButtonBoxPrivate::handleButtonClicked);
420 QObjectPrivate::connect(button, &QAbstractButton::destroyed,
421 this, &QDialogButtonBoxPrivate::handleButtonDestroyed);
422 button->installEventFilter(filter.get());
423 break;
424 case AddRule::SkipConnect:
425 break;
426 }
427
428 switch (layoutRule) {
429 case LayoutRule::DoLayout:
430 layoutButtons();
431 break;
432 case LayoutRule::SkipLayout:
433 break;
434 }
435}
436
437void QDialogButtonBoxPrivate::createStandardButtons(QDialogButtonBox::StandardButtons buttons)
438{
439 uint i = QDialogButtonBox::FirstButton;
440 while (i <= QDialogButtonBox::LastButton) {
441 if (i & buttons)
442 createButton(QDialogButtonBox::StandardButton(i), LayoutRule::SkipLayout);
443 i = i << 1;
444 }
445 layoutButtons();
446}
447
448void QDialogButtonBoxPrivate::retranslateStrings()
449{
450 for (const auto &it : std::as_const(standardButtonMap)) {
451 const QString text = QGuiApplicationPrivate::platformTheme()->standardButtonText(it.second);
452 if (!text.isEmpty())
453 it.first->setText(text);
454 }
455}
456
457/*!
458 Constructs an empty, horizontal button box with the given \a parent.
459
460 \sa orientation, addButton()
461*/
462QDialogButtonBox::QDialogButtonBox(QWidget *parent)
463 : QDialogButtonBox(Qt::Horizontal, parent)
464{
465}
466
467/*!
468 Constructs an empty button box with the given \a orientation and \a parent.
469
470 \sa orientation, addButton()
471*/
472QDialogButtonBox::QDialogButtonBox(Qt::Orientation orientation, QWidget *parent)
473 : QWidget(*new QDialogButtonBoxPrivate(orientation), parent, { })
474{
475 d_func()->initLayout();
476}
477
478/*!
479 \since 5.2
480
481 Constructs a horizontal button box with the given \a parent, containing
482 the standard buttons specified by \a buttons.
483
484 \sa orientation, addButton()
485*/
486QDialogButtonBox::QDialogButtonBox(StandardButtons buttons, QWidget *parent)
487 : QDialogButtonBox(buttons, Qt::Horizontal, parent)
488{
489}
490
491/*!
492 Constructs a button box with the given \a orientation and \a parent, containing
493 the standard buttons specified by \a buttons.
494
495 \sa orientation, addButton()
496*/
497QDialogButtonBox::QDialogButtonBox(StandardButtons buttons, Qt::Orientation orientation,
498 QWidget *parent)
499 : QDialogButtonBox(orientation, parent)
500{
501 d_func()->createStandardButtons(buttons);
502}
503
504/*!
505 Destroys the button box.
506*/
507QDialogButtonBox::~QDialogButtonBox()
508{
509 Q_D(QDialogButtonBox);
510
511 d->ignoreShowAndHide = true;
512
513 // QObjectPrivate::connect requires explicit disconnect in destructor
514 // otherwise the connection may kick in on child destruction and reach
515 // the parent's destroyed private object
516 d->disconnectAll();
517 // ditto event filter:
518 d->filter.reset();
519}
520
521/*!
522 \enum QDialogButtonBox::ButtonRole
523
524//! [buttonrole-enum]
525 This enum describes the roles that can be used to describe buttons in
526 the button box. Combinations of these roles are as flags used to
527 describe different aspects of their behavior.
528
529 \value InvalidRole The button is invalid.
530 \value AcceptRole Clicking the button causes the dialog to be accepted
531 (e.g. OK).
532 \value RejectRole Clicking the button causes the dialog to be rejected
533 (e.g. Cancel).
534 \value DestructiveRole Clicking the button causes a destructive change
535 (e.g. for Discarding Changes) and closes the dialog.
536 \value ActionRole Clicking the button causes changes to the elements within
537 the dialog.
538 \value HelpRole The button can be clicked to request help.
539 \value YesRole The button is a "Yes"-like button.
540 \value NoRole The button is a "No"-like button.
541 \value ApplyRole The button applies current changes.
542 \value ResetRole The button resets the dialog's fields to default values.
543
544 \omitvalue NRoles
545
546 \sa StandardButton
547//! [buttonrole-enum]
548*/
549
550/*!
551 \enum QDialogButtonBox::StandardButton
552
553 These enums describe flags for standard buttons. Each button has a
554 defined \l ButtonRole.
555
556 \value Ok An "OK" button defined with the \l AcceptRole.
557 \value Open An "Open" button defined with the \l AcceptRole.
558 \value Save A "Save" button defined with the \l AcceptRole.
559 \value Cancel A "Cancel" button defined with the \l RejectRole.
560 \value Close A "Close" button defined with the \l RejectRole.
561 \value Discard A "Discard" or "Don't Save" button, depending on the platform,
562 defined with the \l DestructiveRole.
563 \value Apply An "Apply" button defined with the \l ApplyRole.
564 \value Reset A "Reset" button defined with the \l ResetRole.
565 \value RestoreDefaults A "Restore Defaults" button defined with the \l ResetRole.
566 \value Help A "Help" button defined with the \l HelpRole.
567 \value SaveAll A "Save All" button defined with the \l AcceptRole.
568 \value Yes A "Yes" button defined with the \l YesRole.
569 \value YesToAll A "Yes to All" button defined with the \l YesRole.
570 \value No A "No" button defined with the \l NoRole.
571 \value NoToAll A "No to All" button defined with the \l NoRole.
572 \value Abort An "Abort" button defined with the \l RejectRole.
573 \value Retry A "Retry" button defined with the \l AcceptRole.
574 \value Ignore An "Ignore" button defined with the \l AcceptRole.
575
576 \value NoButton An invalid button.
577
578 \omitvalue FirstButton
579 \omitvalue LastButton
580
581 \sa ButtonRole, standardButtons
582*/
583
584/*!
585 \enum QDialogButtonBox::ButtonLayout
586
587 This enum describes the layout policy to be used when arranging the buttons
588 contained in the button box.
589
590 \value WinLayout Use a policy appropriate for applications on Windows.
591 \value MacLayout Use a policy appropriate for applications on \macos.
592 \value KdeLayout Use a policy appropriate for applications on KDE.
593 \value GnomeLayout Use a policy appropriate for applications on GNOME.
594 \value AndroidLayout Use a policy appropriate for applications on Android.
595 This enum value was added in Qt 5.10.
596
597 The button layout is specified by the \l{style()}{current style}. However,
598 on the X11 platform, it may be influenced by the desktop environment.
599*/
600
601/*!
602 \fn void QDialogButtonBox::clicked(QAbstractButton *button)
603
604 This signal is emitted when a button inside the button box is clicked. The
605 specific button that was pressed is specified by \a button.
606
607 \sa accepted(), rejected(), helpRequested()
608*/
609
610/*!
611 \fn void QDialogButtonBox::accepted()
612
613 This signal is emitted when a button inside the button box is clicked, as long
614 as it was defined with the \l AcceptRole or \l YesRole.
615
616 \sa rejected(), clicked(), helpRequested()
617*/
618
619/*!
620 \fn void QDialogButtonBox::rejected()
621
622 This signal is emitted when a button inside the button box is clicked, as long
623 as it was defined with the \l RejectRole or \l NoRole.
624
625 \sa accepted(), helpRequested(), clicked()
626*/
627
628/*!
629 \fn void QDialogButtonBox::helpRequested()
630
631 This signal is emitted when a button inside the button box is clicked, as long
632 as it was defined with the \l HelpRole.
633
634 \sa accepted(), rejected(), clicked()
635*/
636
637/*!
638 \property QDialogButtonBox::orientation
639 \brief the orientation of the button box
640
641 By default, the orientation is horizontal (i.e. the buttons are laid out
642 side by side). The possible orientations are Qt::Horizontal and
643 Qt::Vertical.
644*/
645Qt::Orientation QDialogButtonBox::orientation() const
646{
647 return d_func()->orientation;
648}
649
650void QDialogButtonBox::setOrientation(Qt::Orientation orientation)
651{
652 Q_D(QDialogButtonBox);
653 if (orientation == d->orientation)
654 return;
655
656 d->orientation = orientation;
657 d->resetLayout();
658}
659
660/*!
661 Clears the button box, deleting all buttons within it.
662
663 \sa removeButton(), addButton()
664*/
665void QDialogButtonBox::clear()
666{
667 Q_D(QDialogButtonBox);
668 // Remove the created standard buttons, they should be in the other lists, which will
669 // do the deletion
670 d->standardButtonMap.clear();
671 for (int i = 0; i < NRoles; ++i) {
672 QList<QAbstractButton *> &list = d->buttonLists[i];
673 for (auto button : std::as_const(list)) {
674 QObjectPrivate::disconnect(button, &QAbstractButton::destroyed,
675 d, &QDialogButtonBoxPrivate::handleButtonDestroyed);
676 delete button;
677 }
678 list.clear();
679 }
680}
681
682/*!
683 Returns a list of all buttons that have been added to the button box.
684
685 \sa buttonRole(), addButton(), removeButton()
686*/
687QList<QAbstractButton *> QDialogButtonBox::buttons() const
688{
689 Q_D(const QDialogButtonBox);
690 return d->allButtons();
691}
692
693QList<QAbstractButton *> QDialogButtonBoxPrivate::visibleButtons() const
694{
695 QList<QAbstractButton *> finalList;
696 for (int i = 0; i < QDialogButtonBox::NRoles; ++i) {
697 const QList<QAbstractButton *> &list = buttonLists[i];
698 for (int j = 0; j < list.size(); ++j)
699 finalList.append(list.at(j));
700 }
701 return finalList;
702}
703
704QList<QAbstractButton *> QDialogButtonBoxPrivate::allButtons() const
705{
706 QList<QAbstractButton *> ret(visibleButtons());
707 ret.reserve(ret.size() + hiddenButtons.size());
708 for (const auto &it : hiddenButtons)
709 ret.push_back(it.first);
710 return ret;
711}
712
713/*!
714 Returns the button role for the specified \a button. This function returns
715 \l InvalidRole if \a button is \nullptr or has not been added to the button box.
716
717 \sa buttons(), addButton()
718*/
719QDialogButtonBox::ButtonRole QDialogButtonBox::buttonRole(QAbstractButton *button) const
720{
721 Q_D(const QDialogButtonBox);
722 for (int i = 0; i < NRoles; ++i) {
723 const QList<QAbstractButton *> &list = d->buttonLists[i];
724 for (int j = 0; j < list.size(); ++j) {
725 if (list.at(j) == button)
726 return ButtonRole(i);
727 }
728 }
729 return d->hiddenButtons.value(button, InvalidRole);
730}
731
732/*!
733 Removes \a button from the button box without deleting it and sets its parent to zero.
734
735 \sa clear(), buttons(), addButton()
736*/
737void QDialogButtonBox::removeButton(QAbstractButton *button)
738{
739 Q_D(QDialogButtonBox);
740 d->removeButton(button, QDialogButtonBoxPrivate::RemoveReason::ManualRemove);
741}
742
743/*!
744 \internal
745 Removes \param button.
746 \param reason determines the behavior following the removal:
747 \list
748 \li \c ManualRemove disconnects all signals and removes the button from standardButtonMap.
749 \li \c HideEvent keeps connections alive, standard buttons remain in standardButtonMap.
750 \li \c Destroyed removes the button from standardButtonMap. Signals remain untouched, because
751 the button might already be only a QObject, the destructor of which handles disconnecting.
752 \endlist
753 */
754void QDialogButtonBoxPrivate::removeButton(QAbstractButton *button, RemoveReason reason)
755{
756 if (!button)
757 return;
758
759 // Remove button from hidden buttons and roles
760 hiddenButtons.remove(button);
761 for (int i = 0; i < QDialogButtonBox::NRoles; ++i)
762 buttonLists[i].removeOne(button);
763
764 switch (reason) {
765 case RemoveReason::ManualRemove:
766 button->setParent(nullptr);
767 QObjectPrivate::disconnect(button, &QAbstractButton::clicked,
768 this, &QDialogButtonBoxPrivate::handleButtonClicked);
769 QObjectPrivate::disconnect(button, &QAbstractButton::destroyed,
770 this, &QDialogButtonBoxPrivate::handleButtonDestroyed);
771 button->removeEventFilter(filter.get());
772 Q_FALLTHROUGH();
773 case RemoveReason::Destroyed:
774 standardButtonMap.remove(reinterpret_cast<QPushButton *>(button));
775 break;
776 case RemoveReason::HideEvent:
777 break;
778 }
779}
780
781/*!
782 Adds the given \a button to the button box with the specified \a role.
783 If the role is invalid, the button is not added.
784
785 If the button has already been added, it is removed and added again with the
786 new role.
787
788 \note The button box takes ownership of the button.
789
790 \sa removeButton(), clear()
791*/
792void QDialogButtonBox::addButton(QAbstractButton *button, ButtonRole role)
793{
794 Q_D(QDialogButtonBox);
795 if (Q_UNLIKELY(role <= InvalidRole || role >= NRoles)) {
796 qWarning("QDialogButtonBox::addButton: Invalid ButtonRole, button not added");
797 return;
798 }
799 removeButton(button);
800 button->setParent(this);
801 d->addButton(button, role);
802}
803
804/*!
805 Creates a push button with the given \a text, adds it to the button box for the
806 specified \a role, and returns the corresponding push button. If \a role is
807 invalid, no button is created, and zero is returned.
808
809 \sa removeButton(), clear()
810*/
811QPushButton *QDialogButtonBox::addButton(const QString &text, ButtonRole role)
812{
813 Q_D(QDialogButtonBox);
814 if (Q_UNLIKELY(role <= InvalidRole || role >= NRoles)) {
815 qWarning("QDialogButtonBox::addButton: Invalid ButtonRole, button not added");
816 return nullptr;
817 }
818 QPushButton *button = new QPushButton(text, this);
819 d->addButton(button, role);
820 return button;
821}
822
823/*!
824 Adds a standard \a button to the button box if it is valid to do so, and returns
825 a push button. If \a button is invalid, it is not added to the button box, and
826 zero is returned.
827
828 \sa removeButton(), clear()
829*/
830QPushButton *QDialogButtonBox::addButton(StandardButton button)
831{
832 Q_D(QDialogButtonBox);
833 return d->createButton(button);
834}
835
836/*!
837 \property QDialogButtonBox::standardButtons
838 \brief collection of standard buttons in the button box
839
840 This property controls which standard buttons are used by the button box.
841
842 \sa addButton()
843*/
844void QDialogButtonBox::setStandardButtons(StandardButtons buttons)
845{
846 Q_D(QDialogButtonBox);
847 // Clear out all the old standard buttons, then recreate them.
848 const auto oldButtons = d->standardButtonMap.keys();
849 d->standardButtonMap.clear();
850 qDeleteAll(oldButtons);
851
852 d->createStandardButtons(buttons);
853}
854
855QDialogButtonBox::StandardButtons QDialogButtonBox::standardButtons() const
856{
857 Q_D(const QDialogButtonBox);
858 StandardButtons standardButtons = NoButton;
859 for (const auto value : d->standardButtonMap.values())
860 standardButtons |= value;
861 return standardButtons;
862}
863
864/*!
865 Returns the QPushButton corresponding to the standard button \a which,
866 or \nullptr if the standard button doesn't exist in this button box.
867
868 \sa standardButton(), standardButtons(), buttons()
869*/
870QPushButton *QDialogButtonBox::button(StandardButton which) const
871{
872 Q_D(const QDialogButtonBox);
873
874 for (const auto &it : std::as_const(d->standardButtonMap)) {
875 if (it.second == which)
876 return it.first;
877 }
878 return nullptr;
879}
880
881/*!
882 Returns the standard button enum value corresponding to the given \a button,
883 or NoButton if the given \a button isn't a standard button.
884
885 \sa button(), buttons(), standardButtons()
886*/
887QDialogButtonBox::StandardButton QDialogButtonBox::standardButton(QAbstractButton *button) const
888{
889 Q_D(const QDialogButtonBox);
890 return d->standardButtonMap.value(static_cast<QPushButton *>(button));
891}
892
893void QDialogButtonBoxPrivate::handleButtonClicked()
894{
895 Q_Q(QDialogButtonBox);
896 if (QAbstractButton *button = qobject_cast<QAbstractButton *>(q->sender())) {
897 // Can't fetch this *after* emitting clicked, as clicked may destroy the button
898 // or change its role. Now changing the role is not possible yet, but arguably
899 // both clicked and accepted/rejected/etc. should be emitted "atomically"
900 // depending on whatever role the button had at the time of the click.
901 const QDialogButtonBox::ButtonRole buttonRole = q->buttonRole(button);
902 QPointer<QDialogButtonBox> guard(q);
903
904 emit q->clicked(button);
905
906 if (!guard)
907 return;
908
909 switch (QPlatformDialogHelper::ButtonRole(buttonRole)) {
910 case QPlatformDialogHelper::AcceptRole:
911 case QPlatformDialogHelper::YesRole:
912 emit q->accepted();
913 break;
914 case QPlatformDialogHelper::RejectRole:
915 case QPlatformDialogHelper::NoRole:
916 emit q->rejected();
917 break;
918 case QPlatformDialogHelper::HelpRole:
919 emit q->helpRequested();
920 break;
921 default:
922 break;
923 }
924 }
925}
926
927void QDialogButtonBoxPrivate::handleButtonDestroyed()
928{
929 Q_Q(QDialogButtonBox);
930 if (QObject *object = q->sender())
931 removeButton(reinterpret_cast<QAbstractButton *>(object), RemoveReason::Destroyed);
932}
933
934bool QDialogButtonBoxPrivate::handleButtonShowAndHide(QAbstractButton *button, QEvent *event)
935{
936 Q_Q(QDialogButtonBox);
937
938 const QEvent::Type type = event->type();
939
940 if ((type != QEvent::HideToParent && type != QEvent::ShowToParent) || ignoreShowAndHide)
941 return false;
942
943 switch (type) {
944 case QEvent::HideToParent: {
945 const QDialogButtonBox::ButtonRole role = q->buttonRole(button);
946 if (role != QDialogButtonBox::ButtonRole::InvalidRole) {
947 removeButton(button, RemoveReason::HideEvent);
948 hiddenButtons.insert(button, role);
949 layoutButtons();
950 }
951 break;
952 }
953 case QEvent::ShowToParent:
954 if (hiddenButtons.contains(button)) {
955 const auto role = hiddenButtons.take(button);
956 addButton(button, role, LayoutRule::DoLayout, AddRule::SkipConnect);
957 if (role == QDialogButtonBox::AcceptRole)
958 ensureFirstAcceptIsDefault();
959 }
960 break;
961 default: break;
962 }
963
964 return false;
965}
966
967/*!
968 \property QDialogButtonBox::centerButtons
969 \brief whether the buttons in the button box are centered
970
971 By default, this property is \c false. This behavior is appropriate
972 for most types of dialogs. A notable exception is message boxes
973 on most platforms (e.g. Windows), where the button box is
974 centered horizontally.
975
976 \sa QMessageBox
977*/
978void QDialogButtonBox::setCenterButtons(bool center)
979{
980 Q_D(QDialogButtonBox);
981 if (d->center != center) {
982 d->center = center;
983 d->resetLayout();
984 }
985}
986
987bool QDialogButtonBox::centerButtons() const
988{
989 Q_D(const QDialogButtonBox);
990 return d->center;
991}
992
993/*!
994 \reimp
995*/
996void QDialogButtonBox::changeEvent(QEvent *event)
997{
998 Q_D(QDialogButtonBox);
999 switch (event->type()) {
1000 case QEvent::StyleChange: // Propagate style
1001 if (!d->standardButtonMap.empty()) {
1002 QStyle *newStyle = style();
1003 for (auto key : d->standardButtonMap.keys())
1004 key->setStyle(newStyle);
1005 }
1006#ifdef Q_OS_MAC
1007 Q_FALLTHROUGH();
1008 case QEvent::MacSizeChange:
1009#endif
1010 d->resetLayout();
1011 QWidget::changeEvent(event);
1012 break;
1013 default:
1014 QWidget::changeEvent(event);
1015 break;
1016 }
1017}
1018
1019void QDialogButtonBoxPrivate::ensureFirstAcceptIsDefault()
1020{
1021 Q_Q(QDialogButtonBox);
1022 const QList<QAbstractButton *> &acceptRoleList = buttonLists[QDialogButtonBox::AcceptRole];
1023 QPushButton *firstAcceptButton = acceptRoleList.isEmpty()
1024 ? nullptr
1025 : qobject_cast<QPushButton *>(acceptRoleList.at(0));
1026
1027 if (!firstAcceptButton)
1028 return;
1029
1030 bool hasDefault = false;
1031 QWidget *dialog = nullptr;
1032 QWidget *p = q;
1033 while (p && !p->isWindow()) {
1034 p = p->parentWidget();
1035 if ((dialog = qobject_cast<QDialog *>(p)))
1036 break;
1037 }
1038
1039 QWidget *parent = dialog ? dialog : q;
1040 Q_ASSERT(parent);
1041
1042 const auto pushButtons = parent->findChildren<QPushButton *>();
1043 for (QPushButton *pushButton : pushButtons) {
1044 if (pushButton->isDefault() && pushButton != firstAcceptButton) {
1045 hasDefault = true;
1046 break;
1047 }
1048 }
1049 if (!hasDefault && firstAcceptButton) {
1050 firstAcceptButton->setDefault(true);
1051 // When the QDialogButtonBox is focused, and it doesn't have an
1052 // explicit focus widget, it will transfer focus to its focus
1053 // proxy, which is the first button in the layout. This behavior,
1054 // combined with the behavior that QPushButtons in a QDialog will
1055 // by default have their autoDefault set to true, results in the
1056 // focus proxy/first button stealing the default button status
1057 // immediately when the button box is focused, which is not what
1058 // we want. Account for this by explicitly making the firstAcceptButton
1059 // focused as well, unless an explicit focus widget has been set, or
1060 // a dialog child has Qt::StrongFocus.
1061 if (dialog && !(QWidgetPrivate::get(dialog)->hasChildWithFocusPolicy(Qt::StrongFocus, q)
1062 || dialog->focusWidget()))
1063 firstAcceptButton->setFocus();
1064 }
1065}
1066
1067void QDialogButtonBoxPrivate::disconnectAll()
1068{
1069 Q_Q(QDialogButtonBox);
1070 const auto buttons = q->findChildren<QAbstractButton *>();
1071 for (auto *button : buttons)
1072 button->disconnect(q);
1073}
1074
1075/*!
1076 \reimp
1077*/
1078bool QDialogButtonBox::event(QEvent *event)
1079{
1080 Q_D(QDialogButtonBox);
1081 switch (event->type()) {
1082 case QEvent::Show:
1083 d->ensureFirstAcceptIsDefault();
1084 break;
1085
1086 case QEvent::LanguageChange:
1087 d->retranslateStrings();
1088 break;
1089
1090 default: break;
1091 }
1092
1093 return QWidget::event(event);
1094}
1095
1096QT_END_NAMESPACE
1097
1098#include "moc_qdialogbuttonbox.cpp"
Combined button and popup list for selecting options.