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