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

source code of qtbase/src/widgets/widgets/qdialogbuttonbox.cpp