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