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

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