macOS: Respect QPlatformDialogHelper::ButtonLayout in native alerts

The buttons in QMessageDialogOptions do not have any order that we
can rely on. The standard buttons is just a bit mask, so any ordering
done on the QMessageBox side is lost. The custom buttons are ordered
in the same order the user added them, but this is not really the
order we want them to appear in the dialog either, as we have a
well defined order between roles provided by QPlatformDialogHelper.

We now follow the QPlatformDialogHelper::ButtonLayout order for
macOS, using the same heuristics for multiple Accept role buttons
as QDialogButtonBox.

For buttons with the same role, QMessageBox will respect the order
they were added in, but this is not something we can do for the
standard buttons, as long as they are flattened to a bit mask.

Pick-to: 6.6 6.5
Change-Id: I401f202a7c2d83dc253e988531ad145624c97580
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
bb10
Tor Arne Vestbø 2023-10-24 17:09:13 +02:00
parent b49211c125
commit 0366b554c9
1 changed files with 49 additions and 3 deletions

View File

@ -151,6 +151,8 @@ bool QCocoaMessageDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality w
// button). If an explicit default or escape button has been set, we respect these,
// and otherwise we fall back to role-based default and escape buttons.
qCDebug(lcQpaDialogs).verbosity(0) << "Adding button" << title << "with" << role;
if (!defaultButton && role == AcceptRole)
defaultButton = tag;
@ -186,19 +188,63 @@ bool QCocoaMessageDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality w
button.tag = tag;
};
// Resolve all dialog buttons from the options, both standard and custom
struct Button { QString title; int identifier; ButtonRole role; };
std::vector<Button> buttons;
const auto *platformTheme = QGuiApplicationPrivate::platformTheme();
if (auto standardButtons = options()->standardButtons()) {
for (int standardButton = FirstButton; standardButton <= LastButton; standardButton <<= 1) {
if (standardButtons & standardButton) {
auto title = platformTheme->standardButtonText(standardButton);
addButton(title, standardButton, buttonRole(StandardButton(standardButton)));
buttons.push_back({
title, standardButton, buttonRole(StandardButton(standardButton))
});
}
}
}
const auto customButtons = options()->customButtons();
for (auto customButton : customButtons)
addButton(customButton.label, customButton.id, customButton.role);
buttons.push_back({customButton.label, customButton.id, customButton.role});
// Sort them according to the QPlatformDialogHelper::ButtonLayout for macOS
// The ButtonLayout adds one additional role, AlternateRole, which is used
// for any AcceptRole beyond the first one, and should be ordered before the
// AcceptRole. Set this up by fixing the roles up front.
bool seenAccept = false;
for (auto &button : buttons) {
if (button.role == AcceptRole) {
if (!seenAccept)
seenAccept = true;
else
button.role = AlternateRole;
}
}
std::vector<Button> orderedButtons;
const int *layoutEntry = buttonLayout(Qt::Horizontal, ButtonLayout::MacLayout);
while (*layoutEntry != QPlatformDialogHelper::EOL) {
const auto role = ButtonRole(*layoutEntry & ~ButtonRole::Reverse);
const bool reverse = *layoutEntry & ButtonRole::Reverse;
auto addButton = [&](const Button &button) {
if (button.role == role)
orderedButtons.push_back(button);
};
if (reverse)
std::for_each(std::crbegin(buttons), std::crend(buttons), addButton);
else
std::for_each(std::cbegin(buttons), std::cend(buttons), addButton);
++layoutEntry;
}
// Add them to the alert in reverse order, since buttons are added right to left
for (auto button = orderedButtons.crbegin(); button != orderedButtons.crend(); ++button)
addButton(button->title, button->identifier, button->role);
// If we didn't find a an explicit or implicit default button above
// we restore the AppKit behavior of making the first button default.