diff options
Diffstat (limited to 'src/widgets/widgets/qdialogbuttonbox.cpp')
-rw-r--r-- | src/widgets/widgets/qdialogbuttonbox.cpp | 361 |
1 files changed, 240 insertions, 121 deletions
diff --git a/src/widgets/widgets/qdialogbuttonbox.cpp b/src/widgets/widgets/qdialogbuttonbox.cpp index 5afecc8d0a..0b6a4df41a 100644 --- a/src/widgets/widgets/qdialogbuttonbox.cpp +++ b/src/widgets/widgets/qdialogbuttonbox.cpp @@ -14,6 +14,9 @@ #include <QtGui/qaction.h> #include "qdialogbuttonbox.h" +#include "qdialogbuttonbox_p.h" + +#include <QtCore/qpointer.h> QT_BEGIN_NAMESPACE @@ -111,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() @@ -177,7 +166,6 @@ void QDialogButtonBoxPrivate::initLayout() void QDialogButtonBoxPrivate::resetLayout() { - //delete buttonLayout; initLayout(); layoutButtons(); } @@ -201,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()) @@ -293,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); } } @@ -311,7 +304,7 @@ void QDialogButtonBoxPrivate::layoutButtons() } QPushButton *QDialogButtonBoxPrivate::createButton(QDialogButtonBox::StandardButton sbutton, - bool doLayout) + LayoutRule layoutRule) { Q_Q(QDialogButtonBox); int icon = 0; @@ -381,12 +374,12 @@ QPushButton *QDialogButtonBoxPrivate::createButton(QDialogButtonBox::StandardBut button->setIcon(style->standardIcon(QStyle::StandardPixmap(icon), nullptr, q)); if (style != QApplication::style()) // Propagate style button->setStyle(style); - standardButtonHash.insert(button, sbutton); + standardButtonMap.insert(button, sbutton); QPlatformDialogHelper::ButtonRole role = QPlatformDialogHelper::buttonRole(static_cast<QPlatformDialogHelper::StandardButton>(sbutton)); 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()) @@ -396,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(); @@ -420,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 (const auto &it : std::as_const(standardButtonMap)) { + const QString text = QGuiApplicationPrivate::platformTheme()->standardButtonText(it.second); if (!text.isEmpty()) - it.key()->setText(text); + it.first->setText(text); } } @@ -482,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(); } /*! @@ -633,34 +644,49 @@ void QDialogButtonBox::clear() Q_D(QDialogButtonBox); // Remove the created standard buttons, they should be in the other lists, which will // do the deletion - d->standardButtonHash.clear(); + d->standardButtonMap.clear(); for (int i = 0; i < NRoles; ++i) { QList<QAbstractButton *> &list = d->buttonLists[i]; - while (list.size()) { - QAbstractButton *button = list.takeAt(0); - QObject::disconnect(button, SIGNAL(destroyed()), this, SLOT(_q_handleButtonDestroyed())); + for (auto button : std::as_const(list)) { + QObjectPrivate::disconnect(button, &QAbstractButton::destroyed, + d, &QDialogButtonBoxPrivate::handleButtonDestroyed); delete button; } + list.clear(); } } /*! - 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 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 +{ + QList<QAbstractButton *> ret(visibleButtons()); + ret.reserve(ret.size() + hiddenButtons.size()); + for (const auto &it : hiddenButtons) + ret.push_back(it.first); + return ret; +} + /*! 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. @@ -677,7 +703,7 @@ QDialogButtonBox::ButtonRole QDialogButtonBox::buttonRole(QAbstractButton *butto return ButtonRole(i); } } - return InvalidRole; + return d->hiddenButtons.value(button, InvalidRole); } /*! @@ -688,27 +714,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 standardButtonMap. + \li \c HideEvent keeps connections alive, standard buttons remain in standardButtonMap. + \li \c Destroyed removes the button from standardButtonMap. 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.size(); ++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: + standardButtonMap.remove(reinterpret_cast<QPushButton *>(button)); + break; + case RemoveReason::HideEvent: + break; + } } /*! @@ -778,8 +822,9 @@ void QDialogButtonBox::setStandardButtons(StandardButtons buttons) { Q_D(QDialogButtonBox); // Clear out all the old standard buttons, then recreate them. - qDeleteAll(d->standardButtonHash.keys()); - d->standardButtonHash.clear(); + const auto oldButtons = d->standardButtonMap.keys(); + d->standardButtonMap.clear(); + qDeleteAll(oldButtons); d->createStandardButtons(buttons); } @@ -788,11 +833,8 @@ QDialogButtonBox::StandardButtons QDialogButtonBox::standardButtons() const { Q_D(const QDialogButtonBox); StandardButtons standardButtons = NoButton; - QHash<QPushButton *, StandardButton>::const_iterator it = d->standardButtonHash.constBegin(); - while (it != d->standardButtonHash.constEnd()) { - standardButtons |= it.value(); - ++it; - } + for (const auto value : d->standardButtonMap.values()) + standardButtons |= value; return standardButtons; } @@ -805,7 +847,12 @@ QDialogButtonBox::StandardButtons QDialogButtonBox::standardButtons() const QPushButton *QDialogButtonBox::button(StandardButton which) const { Q_D(const QDialogButtonBox); - return d->standardButtonHash.key(which); + + for (const auto &it : std::as_const(d->standardButtonMap)) { + if (it.second == which) + return it.first; + } + return nullptr; } /*! @@ -817,10 +864,10 @@ QPushButton *QDialogButtonBox::button(StandardButton which) const QDialogButtonBox::StandardButton QDialogButtonBox::standardButton(QAbstractButton *button) const { Q_D(const QDialogButtonBox); - return d->standardButtonHash.value(static_cast<QPushButton *>(button)); + return d->standardButtonMap.value(static_cast<QPushButton *>(button)); } -void QDialogButtonBoxPrivate::_q_handleButtonClicked() +void QDialogButtonBoxPrivate::handleButtonClicked() { Q_Q(QDialogButtonBox); if (QAbstractButton *button = qobject_cast<QAbstractButton *>(q->sender())) { @@ -854,13 +901,44 @@ 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; } /*! @@ -894,16 +972,13 @@ bool QDialogButtonBox::centerButtons() const */ void QDialogButtonBox::changeEvent(QEvent *event) { - typedef QHash<QPushButton *, QDialogButtonBox::StandardButton> StandardButtonHash; - Q_D(QDialogButtonBox); switch (event->type()) { case QEvent::StyleChange: // Propagate style - if (!d->standardButtonHash.empty()) { + if (!d->standardButtonMap.empty()) { QStyle *newStyle = style(); - const StandardButtonHash::iterator end = d->standardButtonHash.end(); - for (StandardButtonHash::iterator it = d->standardButtonHash.begin(); it != end; ++it) - it.key()->setStyle(newStyle); + for (auto key : d->standardButtonMap.keys()) + key->setStyle(newStyle); } #ifdef Q_OS_MAC Q_FALLTHROUGH(); @@ -918,36 +993,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); } |