diff options
Diffstat (limited to 'src/widgets/widgets/qdialogbuttonbox.cpp')
-rw-r--r-- | src/widgets/widgets/qdialogbuttonbox.cpp | 375 |
1 files changed, 227 insertions, 148 deletions
diff --git a/src/widgets/widgets/qdialogbuttonbox.cpp b/src/widgets/widgets/qdialogbuttonbox.cpp index 560eeab433..30ace89fa8 100644 --- a/src/widgets/widgets/qdialogbuttonbox.cpp +++ b/src/widgets/widgets/qdialogbuttonbox.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtWidgets module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include <QtCore/qhash.h> #include <QtWidgets/qpushbutton.h> @@ -50,6 +14,9 @@ #include <QtGui/qaction.h> #include "qdialogbuttonbox.h" +#include "qdialogbuttonbox_p.h" + +#include <QtCore/qpointer.h> QT_BEGIN_NAMESPACE @@ -89,7 +56,7 @@ QT_BEGIN_NAMESPACE the buttons (or button texts) yourself and add them to the button box, specifying their role. - \snippet dialogs/extension/finddialog.cpp 1 + \snippet dialogs/dialogs.cpp buttonbox Alternatively, QDialogButtonBox provides several standard buttons (e.g. OK, Cancel, Save) that you can use. They exist as flags so you can OR them together in the constructor. @@ -147,39 +114,25 @@ QT_BEGIN_NAMESPACE \sa QMessageBox, QPushButton, QDialog */ - -class QDialogButtonBoxPrivate : public QWidgetPrivate +QDialogButtonBoxPrivate::QDialogButtonBoxPrivate(Qt::Orientation orient) + : orientation(orient), buttonLayout(nullptr), center(false) { - Q_DECLARE_PUBLIC(QDialogButtonBox) - -public: - QDialogButtonBoxPrivate(Qt::Orientation orient); - - QList<QAbstractButton *> buttonLists[QDialogButtonBox::NRoles]; - QHash<QPushButton *, QDialogButtonBox::StandardButton> standardButtonHash; - - Qt::Orientation orientation; - QDialogButtonBox::ButtonLayout layoutPolicy; - QBoxLayout *buttonLayout; - bool internalRemove; - bool center; + struct EventFilter : public QObject + { + EventFilter(QDialogButtonBoxPrivate *d) : d(d) {}; + + bool eventFilter(QObject *obj, QEvent *event) override + { + QAbstractButton *button = qobject_cast<QAbstractButton *>(obj); + return button ? d->handleButtonShowAndHide(button, event) : false; + } - void createStandardButtons(QDialogButtonBox::StandardButtons buttons); + private: + QDialogButtonBoxPrivate *d; - void layoutButtons(); - void initLayout(); - void resetLayout(); - QPushButton *createButton(QDialogButtonBox::StandardButton button, bool doLayout = true); - void addButton(QAbstractButton *button, QDialogButtonBox::ButtonRole role, bool doLayout = true); - void _q_handleButtonDestroyed(); - void _q_handleButtonClicked(); - void addButtonsToLayout(const QList<QAbstractButton *> &buttonList, bool reverse); - void retranslateStrings(); -}; + }; -QDialogButtonBoxPrivate::QDialogButtonBoxPrivate(Qt::Orientation orient) - : orientation(orient), buttonLayout(nullptr), internalRemove(false), center(false) -{ + filter.reset(new EventFilter(this)); } void QDialogButtonBoxPrivate::initLayout() @@ -213,7 +166,6 @@ void QDialogButtonBoxPrivate::initLayout() void QDialogButtonBoxPrivate::resetLayout() { - //delete buttonLayout; initLayout(); layoutButtons(); } @@ -221,8 +173,8 @@ void QDialogButtonBoxPrivate::resetLayout() void QDialogButtonBoxPrivate::addButtonsToLayout(const QList<QAbstractButton *> &buttonList, bool reverse) { - int start = reverse ? buttonList.count() - 1 : 0; - int end = reverse ? -1 : buttonList.count(); + int start = reverse ? buttonList.size() - 1 : 0; + int end = reverse ? -1 : buttonList.size(); int step = reverse ? -1 : 1; for (int i = start; i != end; i += step) { @@ -237,6 +189,7 @@ void QDialogButtonBoxPrivate::layoutButtons() Q_Q(QDialogButtonBox); const int MacGap = 36 - 8; // 8 is the default gap between a widget and a spacer item + QBoolBlocker blocker(ignoreShowAndHide); for (int i = buttonLayout->count() - 1; i >= 0; --i) { QLayoutItem *item = buttonLayout->takeAt(i); if (QWidget *widget = item->widget()) @@ -329,16 +282,20 @@ void QDialogButtonBoxPrivate::layoutButtons() ++currentLayout; } - QWidget *lastWidget = nullptr; - q->setFocusProxy(nullptr); + QWidgetList layoutWidgets; for (int i = 0; i < buttonLayout->count(); ++i) { - QLayoutItem *item = buttonLayout->itemAt(i); - if (QWidget *widget = item->widget()) { - if (lastWidget) - QWidget::setTabOrder(lastWidget, widget); - else - q->setFocusProxy(widget); - lastWidget = widget; + if (auto *widget = buttonLayout->itemAt(i)->widget()) + layoutWidgets << widget; + } + + q->setFocusProxy(nullptr); + if (!layoutWidgets.isEmpty()) { + QWidget *prev = layoutWidgets.constLast(); + for (QWidget *here : layoutWidgets) { + QWidget::setTabOrder(prev, here); + prev = here; + if (auto *pushButton = qobject_cast<QPushButton *>(prev); pushButton && pushButton->isDefault()) + q->setFocusProxy(pushButton); } } @@ -347,7 +304,7 @@ void QDialogButtonBoxPrivate::layoutButtons() } QPushButton *QDialogButtonBoxPrivate::createButton(QDialogButtonBox::StandardButton sbutton, - bool doLayout) + LayoutRule layoutRule) { Q_Q(QDialogButtonBox); int icon = 0; @@ -422,7 +379,7 @@ QPushButton *QDialogButtonBoxPrivate::createButton(QDialogButtonBox::StandardBut if (Q_UNLIKELY(role == QPlatformDialogHelper::InvalidRole)) qWarning("QDialogButtonBox::createButton: Invalid ButtonRole, button not added"); else - addButton(button, static_cast<QDialogButtonBox::ButtonRole>(role), doLayout); + addButton(button, static_cast<QDialogButtonBox::ButtonRole>(role), layoutRule); #if QT_CONFIG(shortcut) const QKeySequence standardShortcut = QGuiApplicationPrivate::platformTheme()->standardButtonShortcut(sbutton); if (!standardShortcut.isEmpty()) @@ -432,23 +389,36 @@ QPushButton *QDialogButtonBoxPrivate::createButton(QDialogButtonBox::StandardBut } void QDialogButtonBoxPrivate::addButton(QAbstractButton *button, QDialogButtonBox::ButtonRole role, - bool doLayout) + LayoutRule layoutRule, AddRule addRule) { - Q_Q(QDialogButtonBox); - QObject::connect(button, SIGNAL(clicked()), q, SLOT(_q_handleButtonClicked())); - QObject::connect(button, SIGNAL(destroyed()), q, SLOT(_q_handleButtonDestroyed())); buttonLists[role].append(button); - if (doLayout) + switch (addRule) { + case AddRule::Connect: + QObjectPrivate::connect(button, &QAbstractButton::clicked, + this, &QDialogButtonBoxPrivate::handleButtonClicked); + QObjectPrivate::connect(button, &QAbstractButton::destroyed, + this, &QDialogButtonBoxPrivate::handleButtonDestroyed); + button->installEventFilter(filter.get()); + break; + case AddRule::SkipConnect: + break; + } + + switch (layoutRule) { + case LayoutRule::DoLayout: layoutButtons(); + break; + case LayoutRule::SkipLayout: + break; + } } void QDialogButtonBoxPrivate::createStandardButtons(QDialogButtonBox::StandardButtons buttons) { uint i = QDialogButtonBox::FirstButton; while (i <= QDialogButtonBox::LastButton) { - if (i & buttons) { - createButton(QDialogButtonBox::StandardButton(i), false); - } + if (i & buttons) + createButton(QDialogButtonBox::StandardButton(i), LayoutRule::SkipLayout); i = i << 1; } layoutButtons(); @@ -456,13 +426,10 @@ void QDialogButtonBoxPrivate::createStandardButtons(QDialogButtonBox::StandardBu void QDialogButtonBoxPrivate::retranslateStrings() { - typedef QHash<QPushButton *, QDialogButtonBox::StandardButton>::iterator Iterator; - - const Iterator end = standardButtonHash.end(); - for (Iterator it = standardButtonHash.begin(); it != end; ++it) { - const QString text = QGuiApplicationPrivate::platformTheme()->standardButtonText(it.value()); + for (auto &&[key, value] : std::as_const(standardButtonHash).asKeyValueRange()) { + const QString text = QGuiApplicationPrivate::platformTheme()->standardButtonText(value); if (!text.isEmpty()) - it.key()->setText(text); + key->setText(text); } } @@ -518,6 +485,14 @@ QDialogButtonBox::QDialogButtonBox(StandardButtons buttons, Qt::Orientation orie */ QDialogButtonBox::~QDialogButtonBox() { + Q_D(QDialogButtonBox); + + d->ignoreShowAndHide = true; + + // QObjectPrivate::connect requires explicit disconnect in destructor + // otherwise the connection may kick in on child destruction and reach + // the parent's destroyed private object + d->disconnectAll(); } /*! @@ -672,31 +647,42 @@ void QDialogButtonBox::clear() d->standardButtonHash.clear(); for (int i = 0; i < NRoles; ++i) { QList<QAbstractButton *> &list = d->buttonLists[i]; - while (list.count()) { + while (list.size()) { QAbstractButton *button = list.takeAt(0); - QObject::disconnect(button, SIGNAL(destroyed()), this, SLOT(_q_handleButtonDestroyed())); + QObjectPrivate::disconnect(button, &QAbstractButton::destroyed, + d, &QDialogButtonBoxPrivate::handleButtonDestroyed); delete button; } } } /*! - Returns a list of all the buttons that have been added to the button box. + Returns a list of all buttons that have been added to the button box. \sa buttonRole(), addButton(), removeButton() */ QList<QAbstractButton *> QDialogButtonBox::buttons() const { Q_D(const QDialogButtonBox); + return d->allButtons(); +} + +QList<QAbstractButton *> QDialogButtonBoxPrivate::visibleButtons() const +{ QList<QAbstractButton *> finalList; - for (int i = 0; i < NRoles; ++i) { - const QList<QAbstractButton *> &list = d->buttonLists[i]; - for (int j = 0; j < list.count(); ++j) + for (int i = 0; i < QDialogButtonBox::NRoles; ++i) { + const QList<QAbstractButton *> &list = buttonLists[i]; + for (int j = 0; j < list.size(); ++j) finalList.append(list.at(j)); } return finalList; } +QList<QAbstractButton *> QDialogButtonBoxPrivate::allButtons() const +{ + return visibleButtons() << hiddenButtons.keys(); +} + /*! Returns the button role for the specified \a button. This function returns \l InvalidRole if \a button is \nullptr or has not been added to the button box. @@ -708,12 +694,12 @@ QDialogButtonBox::ButtonRole QDialogButtonBox::buttonRole(QAbstractButton *butto Q_D(const QDialogButtonBox); for (int i = 0; i < NRoles; ++i) { const QList<QAbstractButton *> &list = d->buttonLists[i]; - for (int j = 0; j < list.count(); ++j) { + for (int j = 0; j < list.size(); ++j) { if (list.at(j) == button) return ButtonRole(i); } } - return InvalidRole; + return d->hiddenButtons.value(button, InvalidRole); } /*! @@ -724,27 +710,45 @@ QDialogButtonBox::ButtonRole QDialogButtonBox::buttonRole(QAbstractButton *butto void QDialogButtonBox::removeButton(QAbstractButton *button) { Q_D(QDialogButtonBox); + d->removeButton(button, QDialogButtonBoxPrivate::RemoveReason::ManualRemove); +} +/*! + \internal + Removes \param button. + \param reason determines the behavior following the removal: + \list + \li \c ManualRemove disconnects all signals and removes the button from standardButtonHash. + \li \c HideEvent keeps connections alive, standard buttons remain in standardButtonHash. + \li \c Destroyed removes the button from standardButtonHash. Signals remain untouched, because + the button might already be only a QObject, the destructor of which handles disconnecting. + \endlist + */ +void QDialogButtonBoxPrivate::removeButton(QAbstractButton *button, RemoveReason reason) +{ if (!button) return; - // Remove it from the standard button hash first and then from the roles - d->standardButtonHash.remove(reinterpret_cast<QPushButton *>(button)); - for (int i = 0; i < NRoles; ++i) { - QList<QAbstractButton *> &list = d->buttonLists[i]; - for (int j = 0; j < list.count(); ++j) { - if (list.at(j) == button) { - list.takeAt(j); - if (!d->internalRemove) { - disconnect(button, SIGNAL(clicked()), this, SLOT(_q_handleButtonClicked())); - disconnect(button, SIGNAL(destroyed()), this, SLOT(_q_handleButtonDestroyed())); - } - break; - } - } - } - if (!d->internalRemove) + // Remove button from hidden buttons and roles + hiddenButtons.remove(button); + for (int i = 0; i < QDialogButtonBox::NRoles; ++i) + buttonLists[i].removeOne(button); + + switch (reason) { + case RemoveReason::ManualRemove: button->setParent(nullptr); + QObjectPrivate::disconnect(button, &QAbstractButton::clicked, + this, &QDialogButtonBoxPrivate::handleButtonClicked); + QObjectPrivate::disconnect(button, &QAbstractButton::destroyed, + this, &QDialogButtonBoxPrivate::handleButtonDestroyed); + button->removeEventFilter(filter.get()); + Q_FALLTHROUGH(); + case RemoveReason::Destroyed: + standardButtonHash.remove(reinterpret_cast<QPushButton *>(button)); + break; + case RemoveReason::HideEvent: + break; + } } /*! @@ -814,7 +818,7 @@ void QDialogButtonBox::setStandardButtons(StandardButtons buttons) { Q_D(QDialogButtonBox); // Clear out all the old standard buttons, then recreate them. - qDeleteAll(d->standardButtonHash.keys()); + qDeleteAll(d->standardButtonHash.keyBegin(), d->standardButtonHash.keyEnd()); d->standardButtonHash.clear(); d->createStandardButtons(buttons); @@ -856,7 +860,7 @@ QDialogButtonBox::StandardButton QDialogButtonBox::standardButton(QAbstractButto return d->standardButtonHash.value(static_cast<QPushButton *>(button)); } -void QDialogButtonBoxPrivate::_q_handleButtonClicked() +void QDialogButtonBoxPrivate::handleButtonClicked() { Q_Q(QDialogButtonBox); if (QAbstractButton *button = qobject_cast<QAbstractButton *>(q->sender())) { @@ -890,20 +894,51 @@ void QDialogButtonBoxPrivate::_q_handleButtonClicked() } } -void QDialogButtonBoxPrivate::_q_handleButtonDestroyed() +void QDialogButtonBoxPrivate::handleButtonDestroyed() +{ + Q_Q(QDialogButtonBox); + if (QObject *object = q->sender()) + removeButton(reinterpret_cast<QAbstractButton *>(object), RemoveReason::Destroyed); +} + +bool QDialogButtonBoxPrivate::handleButtonShowAndHide(QAbstractButton *button, QEvent *event) { Q_Q(QDialogButtonBox); - if (QObject *object = q->sender()) { - QBoolBlocker skippy(internalRemove); - q->removeButton(reinterpret_cast<QAbstractButton *>(object)); + + const QEvent::Type type = event->type(); + + if ((type != QEvent::HideToParent && type != QEvent::ShowToParent) || ignoreShowAndHide) + return false; + + switch (type) { + case QEvent::HideToParent: { + const QDialogButtonBox::ButtonRole role = q->buttonRole(button); + if (role != QDialogButtonBox::ButtonRole::InvalidRole) { + removeButton(button, RemoveReason::HideEvent); + hiddenButtons.insert(button, role); + layoutButtons(); + } + break; } + case QEvent::ShowToParent: + if (hiddenButtons.contains(button)) { + const auto role = hiddenButtons.take(button); + addButton(button, role, LayoutRule::DoLayout, AddRule::SkipConnect); + if (role == QDialogButtonBox::AcceptRole) + ensureFirstAcceptIsDefault(); + } + break; + default: break; + } + + return false; } /*! \property QDialogButtonBox::centerButtons \brief whether the buttons in the button box are centered - By default, this property is \c false. This behavior is appopriate + By default, this property is \c false. This behavior is appropriate for most types of dialogs. A notable exception is message boxes on most platforms (e.g. Windows), where the button box is centered horizontally. @@ -954,36 +989,80 @@ void QDialogButtonBox::changeEvent(QEvent *event) } } +void QDialogButtonBoxPrivate::ensureFirstAcceptIsDefault() +{ + Q_Q(QDialogButtonBox); + const QList<QAbstractButton *> &acceptRoleList = buttonLists[QDialogButtonBox::AcceptRole]; + QPushButton *firstAcceptButton = acceptRoleList.isEmpty() + ? nullptr + : qobject_cast<QPushButton *>(acceptRoleList.at(0)); + + if (!firstAcceptButton) + return; + + bool hasDefault = false; + QWidget *dialog = nullptr; + QWidget *p = q; + while (p && !p->isWindow()) { + p = p->parentWidget(); + if ((dialog = qobject_cast<QDialog *>(p))) + break; + } + + QWidget *parent = dialog ? dialog : q; + Q_ASSERT(parent); + + const auto pushButtons = parent->findChildren<QPushButton *>(); + for (QPushButton *pushButton : pushButtons) { + if (pushButton->isDefault() && pushButton != firstAcceptButton) { + hasDefault = true; + break; + } + } + if (!hasDefault && firstAcceptButton) { + firstAcceptButton->setDefault(true); + // When the QDialogButtonBox is focused, and it doesn't have an + // explicit focus widget, it will transfer focus to its focus + // proxy, which is the first button in the layout. This behavior, + // combined with the behavior that QPushButtons in a QDialog will + // by default have their autoDefault set to true, results in the + // focus proxy/first button stealing the default button status + // immediately when the button box is focused, which is not what + // we want. Account for this by explicitly making the firstAcceptButton + // focused as well, unless an explicit focus widget has been set, or + // a dialog child has Qt::StrongFocus. + if (dialog && !(QWidgetPrivate::get(dialog)->hasChildWithFocusPolicy(Qt::StrongFocus, q) + || dialog->focusWidget())) + firstAcceptButton->setFocus(); + } +} + +void QDialogButtonBoxPrivate::disconnectAll() +{ + Q_Q(QDialogButtonBox); + const auto buttons = q->findChildren<QAbstractButton *>(); + for (auto *button : buttons) + button->disconnect(q); +} + /*! \reimp */ bool QDialogButtonBox::event(QEvent *event) { Q_D(QDialogButtonBox); - if (event->type() == QEvent::Show) { - QList<QAbstractButton *> acceptRoleList = d->buttonLists[AcceptRole]; - QPushButton *firstAcceptButton = acceptRoleList.isEmpty() ? 0 : qobject_cast<QPushButton *>(acceptRoleList.at(0)); - bool hasDefault = false; - QWidget *dialog = nullptr; - QWidget *p = this; - while (p && !p->isWindow()) { - p = p->parentWidget(); - if ((dialog = qobject_cast<QDialog *>(p))) - break; - } + switch (event->type()) { + case QEvent::Show: + d->ensureFirstAcceptIsDefault(); + break; - const auto pbs = (dialog ? dialog : this)->findChildren<QPushButton *>(); - for (QPushButton *pb : pbs) { - if (pb->isDefault() && pb != firstAcceptButton) { - hasDefault = true; - break; - } - } - if (!hasDefault && firstAcceptButton) - firstAcceptButton->setDefault(true); - }else if (event->type() == QEvent::LanguageChange) { + case QEvent::LanguageChange: d->retranslateStrings(); + break; + + default: break; } + return QWidget::event(event); } |