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(QDialogButtonBox::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 = QDialogButtonBox::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 = QDialogButtonBox::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
893/*!
894 Returns the display text for the standard \a button.
895
896 \since 6.12
897 \sa StandardButton
898*/
899QString QDialogButtonBox::standardButtonText(StandardButton button)
900{
901 return QGuiApplicationPrivate::platformTheme()->standardButtonText(button);
902}
903
904/*!
905 Returns the shortcut for the standard \a button.
906
907 \since 6.12
908 \sa StandardButton
909*/
910QKeySequence QDialogButtonBox::standardButtonShortcut(StandardButton button)
911{
912 return QGuiApplicationPrivate::platformTheme()->standardButtonShortcut(button);
913}
914
915void QDialogButtonBoxPrivate::handleButtonClicked()
916{
917 Q_Q(QDialogButtonBox);
918 if (QAbstractButton *button = qobject_cast<QAbstractButton *>(q->sender())) {
919 // Can't fetch this *after* emitting clicked, as clicked may destroy the button
920 // or change its role. Now changing the role is not possible yet, but arguably
921 // both clicked and accepted/rejected/etc. should be emitted "atomically"
922 // depending on whatever role the button had at the time of the click.
923 const QDialogButtonBox::ButtonRole buttonRole = q->buttonRole(button);
924 QPointer<QDialogButtonBox> guard(q);
925
926 emit q->clicked(button);
927
928 if (!guard)
929 return;
930
931 switch (QPlatformDialogHelper::ButtonRole(buttonRole)) {
932 case QPlatformDialogHelper::AcceptRole:
933 case QPlatformDialogHelper::YesRole:
934 emit q->accepted();
935 break;
936 case QPlatformDialogHelper::RejectRole:
937 case QPlatformDialogHelper::NoRole:
938 emit q->rejected();
939 break;
940 case QPlatformDialogHelper::HelpRole:
941 emit q->helpRequested();
942 break;
943 default:
944 break;
945 }
946 }
947}
948
949void QDialogButtonBoxPrivate::handleButtonDestroyed()
950{
951 Q_Q(QDialogButtonBox);
952 if (QObject *object = q->sender())
953 removeButton(reinterpret_cast<QAbstractButton *>(object), RemoveReason::Destroyed);
954}
955
956bool QDialogButtonBoxPrivate::handleButtonShowAndHide(QAbstractButton *button, QEvent *event)
957{
958 Q_Q(QDialogButtonBox);
959
960 const QEvent::Type type = event->type();
961
962 if ((type != QEvent::HideToParent && type != QEvent::ShowToParent) || ignoreShowAndHide)
963 return false;
964
965 switch (type) {
966 case QEvent::HideToParent: {
967 const QDialogButtonBox::ButtonRole role = q->buttonRole(button);
968 if (role != QDialogButtonBox::ButtonRole::InvalidRole) {
969 removeButton(button, RemoveReason::HideEvent);
970 hiddenButtons.insert(button, role);
971 layoutButtons();
972 }
973 break;
974 }
975 case QEvent::ShowToParent:
976 if (hiddenButtons.contains(button)) {
977 const auto role = hiddenButtons.take(button);
978 addButton(button, role, LayoutRule::DoLayout, AddRule::SkipConnect);
979 if (role == QDialogButtonBox::AcceptRole)
980 ensureFirstAcceptIsDefault();
981 }
982 break;
983 default: break;
984 }
985
986 return false;
987}
988
989/*!
990 \property QDialogButtonBox::centerButtons
991 \brief whether the buttons in the button box are centered
992
993 By default, this property is \c false. This behavior is appropriate
994 for most types of dialogs. A notable exception is message boxes
995 on most platforms (e.g. Windows), where the button box is
996 centered horizontally.
997
998 \sa QMessageBox
999*/
1000void QDialogButtonBox::setCenterButtons(bool center)
1001{
1002 Q_D(QDialogButtonBox);
1003 if (d->center != center) {
1004 d->center = center;
1005 d->resetLayout();
1006 }
1007}
1008
1009bool QDialogButtonBox::centerButtons() const
1010{
1011 Q_D(const QDialogButtonBox);
1012 return d->center;
1013}
1014
1015/*!
1016 \reimp
1017*/
1018void QDialogButtonBox::changeEvent(QEvent *event)
1019{
1020 Q_D(QDialogButtonBox);
1021 switch (event->type()) {
1022 case QEvent::StyleChange: // Propagate style
1023 if (!d->standardButtonMap.empty()) {
1024 QStyle *newStyle = style();
1025 for (auto key : d->standardButtonMap.keys())
1026 key->setStyle(newStyle);
1027 }
1028#ifdef Q_OS_MACOS
1029 Q_FALLTHROUGH();
1030 case QEvent::MacSizeChange:
1031#endif
1032 d->resetLayout();
1033 QWidget::changeEvent(event);
1034 break;
1035 default:
1036 QWidget::changeEvent(event);
1037 break;
1038 }
1039}
1040
1041void QDialogButtonBoxPrivate::ensureFirstAcceptIsDefault()
1042{
1043 Q_Q(QDialogButtonBox);
1044 const QList<QAbstractButton *> &acceptRoleList = buttonLists[QDialogButtonBox::AcceptRole];
1045 QPushButton *firstAcceptButton = acceptRoleList.isEmpty()
1046 ? nullptr
1047 : qobject_cast<QPushButton *>(acceptRoleList.at(0));
1048
1049 if (!firstAcceptButton)
1050 return;
1051
1052 bool hasDefault = false;
1053 QWidget *dialog = nullptr;
1054 QWidget *p = q;
1055 while (p && !p->isWindow()) {
1056 p = p->parentWidget();
1057 if ((dialog = qobject_cast<QDialog *>(p)))
1058 break;
1059 }
1060
1061 QWidget *parent = dialog ? dialog : q;
1062 Q_ASSERT(parent);
1063
1064 const auto pushButtons = parent->findChildren<QPushButton *>();
1065 for (QPushButton *pushButton : pushButtons) {
1066 if (pushButton->isDefault() && pushButton != firstAcceptButton) {
1067 hasDefault = true;
1068 break;
1069 }
1070 }
1071 if (!hasDefault && firstAcceptButton) {
1072 firstAcceptButton->setDefault(true);
1073 // When the QDialogButtonBox is focused, and it doesn't have an
1074 // explicit focus widget, it will transfer focus to its focus
1075 // proxy, which is the first button in the layout. This behavior,
1076 // combined with the behavior that QPushButtons in a QDialog will
1077 // by default have their autoDefault set to true, results in the
1078 // focus proxy/first button stealing the default button status
1079 // immediately when the button box is focused, which is not what
1080 // we want. Account for this by explicitly making the firstAcceptButton
1081 // focused as well, unless an explicit focus widget has been set, or
1082 // a dialog child has Qt::StrongFocus.
1083 if (dialog && !(QWidgetPrivate::get(dialog)->hasChildWithFocusPolicy(Qt::StrongFocus, q)
1084 || dialog->focusWidget()))
1085 firstAcceptButton->setFocus();
1086 }
1087}
1088
1089void QDialogButtonBoxPrivate::disconnectAll()
1090{
1091 Q_Q(QDialogButtonBox);
1092 const auto buttons = q->findChildren<QAbstractButton *>();
1093 for (auto *button : buttons)
1094 button->disconnect(q);
1095}
1096
1097/*!
1098 \reimp
1099*/
1100bool QDialogButtonBox::event(QEvent *event)
1101{
1102 Q_D(QDialogButtonBox);
1103 switch (event->type()) {
1104 case QEvent::Show:
1105 d->ensureFirstAcceptIsDefault();
1106 break;
1107
1108 case QEvent::LanguageChange:
1109 d->retranslateStrings();
1110 break;
1111
1112 default: break;
1113 }
1114
1115 return QWidget::event(event);
1116}
1117
1118QT_END_NAMESPACE
1119
1120#include "moc_qdialogbuttonbox.cpp"
Combined button and popup list for selecting options.