Customize buttons on MessageBox and confirm before recycling (#2376)

* Add confirmation prompt before moving groups to the recycling bin

Spawn a yes/no QMessage box when "Delete Group" is selected on a group
that is not already in the recycle bin (note: the prompt for deletion
from the recycle bin was already implemented).  This follows the same
pattern and language as entry deletion.

Fixes #2125

* Make prompts for destructive operations use action words on buttons

Replace yes/no, yes/cancel (and other such buttons on prompts that cause
data to be destroyed) use language that indicates the action that it is
going to take. This makes destructive/unsafe and/or irreversible operations
more clear to the user.

Address feedback on PR #2376

* Refactor MessageBox class to allow for custom buttons

Replaces arguments and return values of type QMessageBox::StandardButton(s)
with MessageBox::Button(s), which reimplements the entire set of
QMessageBox::StandardButton and allows for custom KeePassXC buttons,
such as "Skip". Modifies all calls to MessageBox functions to use
MessageBox::Button(s).

Addresses feedback on #2376

* Remove MessageBox::addButton in favor of map lookup

Replaced the switch statement mechanism in MessageBox::addButton with
a map lookup to address CodeFactor Complex Method issue. This has a
side-effect of a small performance/cleanliness increase, as an
extra QPushButton is no longer created/destroyed (to obtain it's label
text) everytime a MessageBox button based on QMessageBox::StandardButton
is created; now the text is obtained once, at application start up.
This commit is contained in:
Kyle Kneitinger 2018-12-19 20:14:11 -08:00 committed by Jonathan White
parent 8ac9d0a131
commit 4d4c839afa
15 changed files with 400 additions and 232 deletions

View File

@ -34,6 +34,7 @@
#include "core/Metadata.h"
#include "core/PasswordGenerator.h"
#include "gui/MainWindow.h"
#include "gui/MessageBox.h"
const char BrowserService::KEEPASSXCBROWSER_NAME[] = "KeePassXC-Browser Settings";
const char BrowserService::KEEPASSXCBROWSER_OLD_NAME[] = "keepassxc-browser Settings";
@ -157,7 +158,7 @@ QString BrowserService::storeKey(const QString& key)
}
bool contains;
QMessageBox::StandardButton dialogResult = QMessageBox::No;
MessageBox::Button dialogResult = MessageBox::Cancel;
do {
QInputDialog keyDialog;
@ -180,14 +181,15 @@ QString BrowserService::storeKey(const QString& key)
contains = db->metadata()->customData()->contains(QLatin1String(ASSOCIATE_KEY_PREFIX) + id);
if (contains) {
dialogResult = QMessageBox::warning(nullptr,
tr("KeePassXC: Overwrite existing key?"),
tr("A shared encryption key with the name \"%1\" "
"already exists.\nDo you want to overwrite it?")
.arg(id),
QMessageBox::Yes | QMessageBox::No);
dialogResult = MessageBox::warning(nullptr,
tr("KeePassXC: Overwrite existing key?"),
tr("A shared encryption key with the name \"%1\" "
"already exists.\nDo you want to overwrite it?")
.arg(id),
MessageBox::Overwrite | MessageBox::Cancel,
MessageBox::Cancel);
}
} while (contains && dialogResult == QMessageBox::No);
} while (contains && dialogResult == MessageBox::Cancel);
db->metadata()->customData()->set(QLatin1String(ASSOCIATE_KEY_PREFIX) + id, key);
return id;
@ -367,21 +369,17 @@ void BrowserService::updateEntry(const QString& id,
if (username.compare(login, Qt::CaseSensitive) != 0
|| entry->password().compare(password, Qt::CaseSensitive) != 0) {
int dialogResult = QMessageBox::No;
MessageBox::Button dialogResult = MessageBox::No;
if (!browserSettings()->alwaysAllowUpdate()) {
QMessageBox msgBox;
msgBox.setWindowTitle(tr("KeePassXC: Update Entry"));
msgBox.setText(tr("Do you want to update the information in %1 - %2?").arg(QUrl(url).host(), username));
msgBox.setStandardButtons(QMessageBox::Yes);
msgBox.addButton(QMessageBox::No);
msgBox.setDefaultButton(QMessageBox::No);
msgBox.setWindowFlags(Qt::WindowStaysOnTopHint);
msgBox.activateWindow();
msgBox.raise();
dialogResult = msgBox.exec();
dialogResult = MessageBox::question(nullptr,
tr("KeePassXC: Update Entry"),
tr("Do you want to update the information in %1 - %2?")
.arg(QUrl(url).host(), username),
MessageBox::Save | MessageBox::Cancel,
MessageBox::Cancel);
}
if (browserSettings()->alwaysAllowUpdate() || dialogResult == QMessageBox::Yes) {
if (browserSettings()->alwaysAllowUpdate() || dialogResult == MessageBox::Save) {
entry->beginUpdate();
entry->setUsername(login);
entry->setPassword(password);
@ -497,24 +495,24 @@ void BrowserService::convertAttributesToCustomData(QSharedPointer<Database> curr
progress.reset();
if (counter > 0) {
QMessageBox::information(nullptr,
tr("KeePassXC: Converted KeePassHTTP attributes"),
tr("Successfully converted attributes from %1 entry(s).\n"
"Moved %2 keys to custom data.",
"")
.arg(counter)
.arg(keyCounter),
QMessageBox::Ok);
MessageBox::information(nullptr,
tr("KeePassXC: Converted KeePassHTTP attributes"),
tr("Successfully converted attributes from %1 entry(s).\n"
"Moved %2 keys to custom data.",
"")
.arg(counter)
.arg(keyCounter),
MessageBox::Ok);
} else if (counter == 0 && keyCounter > 0) {
QMessageBox::information(nullptr,
tr("KeePassXC: Converted KeePassHTTP attributes"),
tr("Successfully moved %n keys to custom data.", "", keyCounter),
QMessageBox::Ok);
MessageBox::information(nullptr,
tr("KeePassXC: Converted KeePassHTTP attributes"),
tr("Successfully moved %n keys to custom data.", "", keyCounter),
MessageBox::Ok);
} else {
QMessageBox::information(nullptr,
tr("KeePassXC: No entry with KeePassHTTP attributes found!"),
tr("The active database does not contain an entry with KeePassHTTP attributes."),
QMessageBox::Ok);
MessageBox::information(nullptr,
tr("KeePassXC: No entry with KeePassHTTP attributes found!"),
tr("The active database does not contain an entry with KeePassHTTP attributes."),
MessageBox::Ok);
}
// Rename password groupName
@ -901,13 +899,14 @@ bool BrowserService::checkLegacySettings()
return false;
}
auto dialogResult = QMessageBox::warning(nullptr,
tr("KeePassXC: Legacy browser integration settings detected"),
tr("Legacy browser integration settings have been detected.\n"
"Do you want to upgrade the settings to the latest standard?\n"
"This is necessary to maintain compatibility with the browser plugin."),
QMessageBox::Yes | QMessageBox::No);
return dialogResult == QMessageBox::Yes;
auto dialogResult = MessageBox::warning(nullptr,
tr("KeePassXC: Legacy browser integration settings detected"),
tr("Legacy browser integration settings have been detected.\n"
"Do you want to upgrade the settings to the latest standard?\n"
"This is necessary to maintain compatibility with the browser plugin."),
MessageBox::Yes | MessageBox::No);
return dialogResult == MessageBox::Yes;
}
void BrowserService::databaseLocked(DatabaseWidget* dbWidget)

View File

@ -18,10 +18,12 @@
#include "Bootstrap.h"
#include "core/Config.h"
#include "core/Translator.h"
#include "gui/MessageBox.h"
#ifdef Q_OS_WIN
#include <aclapi.h> // for createWindowsDACL()
#include <windows.h> // for Sleep(), SetDllDirectoryA(), SetSearchPathMode(), ...
#undef MessageBox
#endif
namespace Bootstrap
@ -56,6 +58,7 @@ namespace Bootstrap
setupSearchPaths();
applyEarlyQNetworkAccessManagerWorkaround();
Translator::installTranslators();
MessageBox::initializeButtonDefs();
#ifdef Q_OS_MACOS
// Don't show menu icons on OSX

View File

@ -98,8 +98,8 @@ QSharedPointer<Database> DatabaseTabWidget::execNewDatabaseWizard()
tr("Database creation error"),
tr("The created database has no key or KDF, refusing to save it.\n"
"This is definitely a bug, please report it to the developers."),
QMessageBox::Ok,
QMessageBox::Ok);
MessageBox::Ok,
MessageBox::Ok);
return {};
}

View File

@ -445,6 +445,7 @@ void DatabaseWidget::deleteEntries()
bool inRecycleBin = recycleBin && recycleBin->findEntryByUuid(selectedEntries.first()->uuid());
if (inRecycleBin || !m_db->metadata()->recycleBinEnabled()) {
QString prompt;
refreshSearch();
if (selected.size() == 1) {
prompt = tr("Do you really want to delete the entry \"%1\" for good?")
.arg(selectedEntries.first()->title().toHtmlEscaped());
@ -452,12 +453,16 @@ void DatabaseWidget::deleteEntries()
prompt = tr("Do you really want to delete %n entry(s) for good?", "", selected.size());
}
QMessageBox::StandardButton result = MessageBox::question(
this, tr("Delete entry(s)?", "", selected.size()), prompt, QMessageBox::Yes | QMessageBox::No);
auto answer = MessageBox::question(this,
tr("Delete entry(s)?", "", selected.size()),
prompt,
MessageBox::Delete | MessageBox::Cancel,
MessageBox::Cancel);
if (result == QMessageBox::Yes) {
if (answer == MessageBox::Delete) {
for (Entry* entry : asConst(selectedEntries)) {
delete entry;
refreshSearch();
}
refreshSearch();
}
@ -470,10 +475,13 @@ void DatabaseWidget::deleteEntries()
prompt = tr("Do you really want to move %n entry(s) to the recycle bin?", "", selected.size());
}
QMessageBox::StandardButton result = MessageBox::question(
this, tr("Move entry(s) to recycle bin?", "", selected.size()), prompt, QMessageBox::Yes | QMessageBox::No);
auto answer = MessageBox::question(this,
tr("Move entry(s) to recycle bin?", "", selected.size()),
prompt,
MessageBox::Move | MessageBox::Cancel,
MessageBox::Cancel);
if (result == QMessageBox::No) {
if (answer == MessageBox::Cancel) {
return;
}
@ -650,16 +658,27 @@ void DatabaseWidget::deleteGroup()
bool isRecycleBin = recycleBin && (currentGroup == recycleBin);
bool isRecycleBinSubgroup = recycleBin && currentGroup->findGroupByUuid(recycleBin->uuid());
if (inRecycleBin || isRecycleBin || isRecycleBinSubgroup || !m_db->metadata()->recycleBinEnabled()) {
QMessageBox::StandardButton result = MessageBox::question(
this,
tr("Delete group?"),
tr("Do you really want to delete the group \"%1\" for good?").arg(currentGroup->name().toHtmlEscaped()),
QMessageBox::Yes | QMessageBox::No);
if (result == QMessageBox::Yes) {
auto result = MessageBox::question(this,
tr("Delete group"),
tr("Do you really want to delete the group \"%1\" for good?")
.arg(currentGroup->name().toHtmlEscaped()),
MessageBox::Delete | MessageBox::Cancel,
MessageBox::Cancel);
if (result == MessageBox::Delete) {
delete currentGroup;
}
} else {
m_db->recycleGroup(currentGroup);
auto result = MessageBox::question(this,
tr("Move group to recycle bin?"),
tr("Do you really want to move the group "
"\"%1\" to the recycle bin?")
.arg(currentGroup->name().toHtmlEscaped()),
MessageBox::Move | MessageBox::Cancel,
MessageBox::Cancel);
if (result == MessageBox::Move) {
m_db->recycleGroup(currentGroup);
}
}
}
@ -1127,8 +1146,8 @@ bool DatabaseWidget::lock()
if (currentMode() == DatabaseWidget::Mode::EditMode) {
auto result = MessageBox::question(this, tr("Lock Database?"),
tr("You are editing an entry. Discard changes and lock anyway?"),
QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Cancel);
if (result == QMessageBox::Cancel) {
MessageBox::Discard | MessageBox::Cancel, MessageBox::Cancel);
if (result == MessageBox::Cancel) {
return false;
}
}
@ -1146,10 +1165,10 @@ bool DatabaseWidget::lock()
msg = tr("Database was modified.\nSave changes?");
}
auto result = MessageBox::question(this, tr("Save changes?"), msg,
QMessageBox::Yes | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Yes);
if (result == QMessageBox::Yes && !m_db->save(nullptr, false, false)) {
MessageBox::Save | MessageBox::Discard | MessageBox::Cancel, MessageBox::Save);
if (result == MessageBox::Save && !m_db->save(nullptr, false, false)) {
return false;
} else if (result == QMessageBox::Cancel) {
} else if (result == MessageBox::Cancel) {
return false;
}
}
@ -1240,9 +1259,9 @@ void DatabaseWidget::reloadDatabaseFile()
auto result = MessageBox::question(this,
tr("File has changed"),
tr("The database file has changed. Do you want to load the changes?"),
QMessageBox::Yes | QMessageBox::No);
MessageBox::Yes | MessageBox::No);
if (result == QMessageBox::No) {
if (result == MessageBox::No) {
// Notify everyone the database does not match the file
m_db->markAsModified();
// Rewatch the database file
@ -1259,9 +1278,10 @@ void DatabaseWidget::reloadDatabaseFile()
auto result = MessageBox::question(this,
tr("Merge Request"),
tr("The database file has changed and you have unsaved changes.\nDo you want to merge your changes?"),
QMessageBox::Yes | QMessageBox::No);
MessageBox::Merge | MessageBox::Cancel,
MessageBox::Merge);
if (result == QMessageBox::Yes) {
if (result == MessageBox::Merge) {
// Merge the old database into the new one
Merger merger(m_db.data(), db.data());
merger.merge();
@ -1447,14 +1467,14 @@ bool DatabaseWidget::save(int attempt)
if (attempt > 2 && useAtomicSaves) {
// Saving failed 3 times, issue a warning and attempt to resolve
auto choice = MessageBox::question(this,
auto result = MessageBox::question(this,
tr("Disable safe saves?"),
tr("KeePassXC has failed to save the database multiple times. "
"This is likely caused by file sync services holding a lock on "
"the save file.\nDisable safe saves and try again?"),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::Yes);
if (choice == QMessageBox::Yes) {
MessageBox::Disable | MessageBox::Cancel,
MessageBox::Disable);
if (result == MessageBox::Disable) {
config()->set("UseAtomicSaves", false);
return save(attempt + 1);
}
@ -1529,13 +1549,13 @@ void DatabaseWidget::emptyRecycleBin()
return;
}
QMessageBox::StandardButton result =
MessageBox::question(this,
tr("Empty recycle bin?"),
tr("Are you sure you want to permanently delete everything from your recycle bin?"),
QMessageBox::Yes | QMessageBox::No);
auto result = MessageBox::question(this,
tr("Empty recycle bin?"),
tr("Are you sure you want to permanently delete everything from your recycle bin?"),
MessageBox::Empty | MessageBox::Cancel,
MessageBox::Cancel);
if (result == QMessageBox::Yes) {
if (result == MessageBox::Empty) {
m_db->emptyRecycleBin();
refreshSearch();
}

View File

@ -416,16 +416,18 @@ void EditWidgetIcons::removeCustomIcon()
int iconUseCount = entriesWithSameIcon.size() + groupsWithSameIcon.size();
if (iconUseCount > 0) {
QMessageBox::StandardButton ans =
MessageBox::question(this,
tr("Confirm Delete"),
tr("This icon is used by %n entry(s), and will be replaced "
"by the default icon. Are you sure you want to delete it?",
"",
iconUseCount),
QMessageBox::Yes | QMessageBox::No);
if (ans == QMessageBox::No) {
auto result = MessageBox::question(this,
tr("Confirm Delete"),
tr("This icon is used by %n entry(s), and will be replaced "
"by the default icon. Are you sure you want to delete it?",
"",
iconUseCount),
MessageBox::Delete | MessageBox::Cancel,
MessageBox::Cancel);
if (result == MessageBox::Cancel) {
// Early out, nothing is changed
return;
} else {

View File

@ -68,13 +68,14 @@ const CustomData* EditWidgetProperties::customData() const
void EditWidgetProperties::removeSelectedPluginData()
{
if (QMessageBox::Yes
!= MessageBox::question(this,
tr("Delete plugin data?"),
tr("Do you really want to delete the selected plugin data?\n"
"This may cause the affected plugins to malfunction."),
QMessageBox::Yes | QMessageBox::Cancel,
QMessageBox::Cancel)) {
auto result = MessageBox::question(this,
tr("Delete plugin data?"),
tr("Do you really want to delete the selected plugin data?\n"
"This may cause the affected plugins to malfunction."),
MessageBox::Delete | MessageBox::Cancel,
MessageBox::Cancel);
if (result == MessageBox::Cancel) {
return;
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2013 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -17,69 +18,139 @@
#include "MessageBox.h"
QMessageBox::StandardButton MessageBox::m_nextAnswer(QMessageBox::NoButton);
MessageBox::Button MessageBox::m_nextAnswer(MessageBox::NoButton);
QMessageBox::StandardButton MessageBox::critical(QWidget* parent,
const QString& title,
const QString& text,
QMessageBox::StandardButtons buttons,
QMessageBox::StandardButton defaultButton)
QMap<QAbstractButton*, MessageBox::Button>
MessageBox::m_addedButtonLookup =
QMap<QAbstractButton*, MessageBox::Button>();
QMap<MessageBox::Button, std::pair<QString, QMessageBox::ButtonRole>>
MessageBox::m_buttonDefs =
QMap<MessageBox::Button, std::pair<QString, QMessageBox::ButtonRole>>();
void MessageBox::initializeButtonDefs()
{
if (m_nextAnswer == QMessageBox::NoButton) {
return QMessageBox::critical(parent, title, text, buttons, defaultButton);
m_buttonDefs =
QMap<Button, std::pair<QString, QMessageBox::ButtonRole>>
{
// Reimplementation of Qt StandardButtons
{Ok, {stdButtonText(QMessageBox::Ok), QMessageBox::ButtonRole::AcceptRole}},
{Open, {stdButtonText(QMessageBox::Open), QMessageBox::ButtonRole::AcceptRole}},
{Save, {stdButtonText(QMessageBox::Save), QMessageBox::ButtonRole::AcceptRole}},
{Cancel, {stdButtonText(QMessageBox::Cancel), QMessageBox::ButtonRole::RejectRole}},
{Close, {stdButtonText(QMessageBox::Close), QMessageBox::ButtonRole::RejectRole}},
{Discard, {stdButtonText(QMessageBox::Discard), QMessageBox::ButtonRole::DestructiveRole}},
{Apply, {stdButtonText(QMessageBox::Apply), QMessageBox::ButtonRole::ApplyRole}},
{Reset, {stdButtonText(QMessageBox::Reset), QMessageBox::ButtonRole::ResetRole}},
{RestoreDefaults, {stdButtonText(QMessageBox::RestoreDefaults), QMessageBox::ButtonRole::ResetRole}},
{Help, {stdButtonText(QMessageBox::Help), QMessageBox::ButtonRole::HelpRole}},
{SaveAll, {stdButtonText(QMessageBox::SaveAll), QMessageBox::ButtonRole::AcceptRole}},
{Yes, {stdButtonText(QMessageBox::Yes), QMessageBox::ButtonRole::YesRole}},
{YesToAll, {stdButtonText(QMessageBox::YesToAll), QMessageBox::ButtonRole::YesRole}},
{No, {stdButtonText(QMessageBox::No), QMessageBox::ButtonRole::NoRole}},
{NoToAll, {stdButtonText(QMessageBox::NoToAll), QMessageBox::ButtonRole::NoRole}},
{Abort, {stdButtonText(QMessageBox::Abort), QMessageBox::ButtonRole::RejectRole}},
{Retry, {stdButtonText(QMessageBox::Retry), QMessageBox::ButtonRole::AcceptRole}},
{Ignore, {stdButtonText(QMessageBox::Ignore), QMessageBox::ButtonRole::AcceptRole}},
// KeePassXC Buttons
{Overwrite, {QMessageBox::tr("Overwrite"), QMessageBox::ButtonRole::AcceptRole}},
{Delete, {QMessageBox::tr("Delete"), QMessageBox::ButtonRole::AcceptRole}},
{Move, {QMessageBox::tr("Move"), QMessageBox::ButtonRole::AcceptRole}},
{Empty, {QMessageBox::tr("Empty"), QMessageBox::ButtonRole::AcceptRole}},
{Remove, {QMessageBox::tr("Remove"), QMessageBox::ButtonRole::AcceptRole}},
{Skip, {QMessageBox::tr("Skip"), QMessageBox::ButtonRole::AcceptRole}},
{Disable, {QMessageBox::tr("Disable"), QMessageBox::ButtonRole::AcceptRole}},
{Merge, {QMessageBox::tr("Merge"), QMessageBox::ButtonRole::AcceptRole}},
};
}
QString MessageBox::stdButtonText(QMessageBox::StandardButton button)
{
QMessageBox buttonHost;
return buttonHost.addButton(button)->text();
}
MessageBox::Button MessageBox::messageBox(QWidget* parent,
QMessageBox::Icon icon,
const QString& title,
const QString& text,
MessageBox::Buttons buttons,
MessageBox::Button defaultButton)
{
if (m_nextAnswer == MessageBox::NoButton) {
QMessageBox msgBox(parent);
msgBox.setIcon(icon);
msgBox.setWindowTitle(title);
msgBox.setText(text);
for (uint64_t b = First; b <= Last; b <<= 1) {
if (b & buttons) {
QString text = m_buttonDefs[static_cast<Button>(b)].first;
QMessageBox::ButtonRole role = m_buttonDefs[static_cast<Button>(b)].second;
auto buttonPtr = msgBox.addButton(text, role);
m_addedButtonLookup.insert(buttonPtr, static_cast<Button>(b));
}
}
if (defaultButton != MessageBox::NoButton) {
QList<QAbstractButton*> defPtrList = m_addedButtonLookup.keys(defaultButton);
if (defPtrList.count() > 0) {
msgBox.setDefaultButton(static_cast<QPushButton*>(defPtrList[0]));
}
}
msgBox.exec();
Button returnButton = m_addedButtonLookup[msgBox.clickedButton()];
m_addedButtonLookup.clear();
return returnButton;
} else {
QMessageBox::StandardButton returnButton = m_nextAnswer;
m_nextAnswer = QMessageBox::NoButton;
MessageBox::Button returnButton = m_nextAnswer;
m_nextAnswer = MessageBox::NoButton;
return returnButton;
}
}
QMessageBox::StandardButton MessageBox::information(QWidget* parent,
const QString& title,
const QString& text,
QMessageBox::StandardButtons buttons,
QMessageBox::StandardButton defaultButton)
MessageBox::Button MessageBox::critical(QWidget* parent,
const QString& title,
const QString& text,
MessageBox::Buttons buttons,
MessageBox::Button defaultButton)
{
if (m_nextAnswer == QMessageBox::NoButton) {
return QMessageBox::information(parent, title, text, buttons, defaultButton);
} else {
QMessageBox::StandardButton returnButton = m_nextAnswer;
m_nextAnswer = QMessageBox::NoButton;
return returnButton;
}
return messageBox(parent, QMessageBox::Critical, title, text, buttons, defaultButton);
}
QMessageBox::StandardButton MessageBox::question(QWidget* parent,
const QString& title,
const QString& text,
QMessageBox::StandardButtons buttons,
QMessageBox::StandardButton defaultButton)
MessageBox::Button MessageBox::information(QWidget* parent,
const QString& title,
const QString& text,
MessageBox::Buttons buttons,
MessageBox::Button defaultButton)
{
if (m_nextAnswer == QMessageBox::NoButton) {
return QMessageBox::question(parent, title, text, buttons, defaultButton);
} else {
QMessageBox::StandardButton returnButton = m_nextAnswer;
m_nextAnswer = QMessageBox::NoButton;
return returnButton;
}
return messageBox(parent, QMessageBox::Information, title, text, buttons, defaultButton);
}
QMessageBox::StandardButton MessageBox::warning(QWidget* parent,
const QString& title,
const QString& text,
QMessageBox::StandardButtons buttons,
QMessageBox::StandardButton defaultButton)
MessageBox::Button MessageBox::question(QWidget* parent,
const QString& title,
const QString& text,
MessageBox::Buttons buttons,
MessageBox::Button defaultButton)
{
if (m_nextAnswer == QMessageBox::NoButton) {
return QMessageBox::warning(parent, title, text, buttons, defaultButton);
} else {
QMessageBox::StandardButton returnButton = m_nextAnswer;
m_nextAnswer = QMessageBox::NoButton;
return returnButton;
}
return messageBox(parent, QMessageBox::Question, title, text, buttons, defaultButton);
}
void MessageBox::setNextAnswer(QMessageBox::StandardButton button)
MessageBox::Button MessageBox::warning(QWidget* parent,
const QString& title,
const QString& text,
MessageBox::Buttons buttons,
MessageBox::Button defaultButton)
{
return messageBox(parent, QMessageBox::Warning, title, text, buttons, defaultButton);
}
void MessageBox::setNextAnswer(MessageBox::Button button)
{
m_nextAnswer = button;
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2013 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -19,35 +20,91 @@
#define KEEPASSX_MESSAGEBOX_H
#include <QMessageBox>
#include <QPushButton>
#include <QMap>
class MessageBox
{
public:
static QMessageBox::StandardButton critical(QWidget* parent,
const QString& title,
const QString& text,
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
static QMessageBox::StandardButton information(QWidget* parent,
const QString& title,
const QString& text,
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
static QMessageBox::StandardButton question(QWidget* parent,
const QString& title,
const QString& text,
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
static QMessageBox::StandardButton warning(QWidget* parent,
const QString& title,
const QString& text,
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
enum Button : uint64_t {
// Reimplementation of Qt StandardButtons
NoButton = 0,
Ok = 1 << 1,
Open = 1 << 2,
Save = 1 << 3,
Cancel = 1 << 4,
Close = 1 << 5,
Discard = 1 << 6,
Apply = 1 << 7,
Reset = 1 << 8,
RestoreDefaults = 1 << 9,
Help = 1 << 10,
SaveAll = 1 << 11,
Yes = 1 << 12,
YesToAll = 1 << 13,
No = 1 << 14,
NoToAll = 1 << 15,
Abort = 1 << 16,
Retry = 1 << 17,
Ignore = 1 << 18,
static void setNextAnswer(QMessageBox::StandardButton button);
// KeePassXC Buttons
Overwrite = 1 << 19,
Delete = 1 << 20,
Move = 1 << 21,
Empty = 1 << 22,
Remove = 1 << 23,
Skip = 1 << 24,
Disable = 1 << 25,
Merge = 1 << 26,
// Internal loop markers. Update Last when new KeePassXC button is added
First = Ok,
Last = Merge,
};
typedef uint64_t Buttons;
static void initializeButtonDefs();
static void setNextAnswer(Button button);
static Button critical(QWidget* parent,
const QString& title,
const QString& text,
Buttons buttons = MessageBox::Ok,
Button defaultButton = MessageBox::NoButton);
static Button information(QWidget* parent,
const QString& title,
const QString& text,
Buttons buttons = MessageBox::Ok,
Button defaultButton = MessageBox::NoButton);
static Button question(QWidget* parent,
const QString& title,
const QString& text,
Buttons buttons = MessageBox::Ok,
Button defaultButton = MessageBox::NoButton);
static Button warning(QWidget* parent,
const QString& title,
const QString& text,
Buttons buttons = MessageBox::Ok,
Button defaultButton = MessageBox::NoButton);
private:
static QMessageBox::StandardButton m_nextAnswer;
static Button m_nextAnswer;
static QMap<QAbstractButton*, Button> m_addedButtonLookup;
static QMap<Button, std::pair<QString, QMessageBox::ButtonRole>> m_buttonDefs;
static Button messageBox(QWidget* parent,
QMessageBox::Icon icon,
const QString& title,
const QString& text,
Buttons buttons = MessageBox::Ok,
Button defaultButton = MessageBox::NoButton);
static QString stdButtonText(QMessageBox::StandardButton button);
};
#endif // KEEPASSX_MESSAGEBOX_H

View File

@ -278,8 +278,8 @@ void CsvImportWidget::writeDatabase()
MessageBox::warning(this,
tr("Error"),
tr("CSV import: writer has errors:\n%1").arg(writer.errorString()),
QMessageBox::Ok,
QMessageBox::Ok);
MessageBox::Ok,
MessageBox::Ok);
}
emit editFinished(true);
}

View File

@ -91,13 +91,13 @@ bool DatabaseSettingsWidgetBrowser::save()
void DatabaseSettingsWidgetBrowser::removeSelectedKey()
{
if (QMessageBox::Yes
if (MessageBox::Yes
!= MessageBox::question(this,
tr("Delete the selected key?"),
tr("Do you really want to delete the selected key?\n"
"This may prevent connection to the browser plugin."),
QMessageBox::Yes | QMessageBox::Cancel,
QMessageBox::Cancel)) {
MessageBox::Yes | MessageBox::Cancel,
MessageBox::Cancel)) {
return;
}
@ -156,13 +156,13 @@ void DatabaseSettingsWidgetBrowser::settingsWarning()
void DatabaseSettingsWidgetBrowser::removeSharedEncryptionKeys()
{
if (QMessageBox::Yes
if (MessageBox::Yes
!= MessageBox::question(this,
tr("Disconnect all browsers"),
tr("Do you really want to disconnect all browsers?\n"
"This may prevent connection to the browser plugin."),
QMessageBox::Yes | QMessageBox::Cancel,
QMessageBox::Cancel)) {
MessageBox::Yes | MessageBox::Cancel,
MessageBox::Cancel)) {
return;
}
@ -174,10 +174,10 @@ void DatabaseSettingsWidgetBrowser::removeSharedEncryptionKeys()
}
if (keysToRemove.isEmpty()) {
QMessageBox::information(this,
MessageBox::information(this,
tr("KeePassXC: No keys found"),
tr("No shared encryption keys found in KeePassXC settings."),
QMessageBox::Ok);
MessageBox::Ok);
return;
}
@ -186,21 +186,21 @@ void DatabaseSettingsWidgetBrowser::removeSharedEncryptionKeys()
}
const int count = keysToRemove.count();
QMessageBox::information(this,
MessageBox::information(this,
tr("KeePassXC: Removed keys from database"),
tr("Successfully removed %n encryption key(s) from KeePassXC settings.", "", count),
QMessageBox::Ok);
MessageBox::Ok);
}
void DatabaseSettingsWidgetBrowser::removeStoredPermissions()
{
if (QMessageBox::Yes
if (MessageBox::Yes
!= MessageBox::question(this,
tr("Forget all site-specific settings on entries"),
tr("Do you really want forget all site-specific settings on every entry?\n"
"Permissions to access entries will be revoked."),
QMessageBox::Yes | QMessageBox::Cancel,
QMessageBox::Cancel)) {
MessageBox::Yes | MessageBox::Cancel,
MessageBox::Cancel)) {
return;
}
@ -226,28 +226,28 @@ void DatabaseSettingsWidgetBrowser::removeStoredPermissions()
progress.reset();
if (counter > 0) {
QMessageBox::information(this,
MessageBox::information(this,
tr("KeePassXC: Removed permissions"),
tr("Successfully removed permissions from %n entry(s).", "", counter),
QMessageBox::Ok);
MessageBox::Ok);
} else {
QMessageBox::information(this,
MessageBox::information(this,
tr("KeePassXC: No entry with permissions found!"),
tr("The active database does not contain an entry with permissions."),
QMessageBox::Ok);
MessageBox::Ok);
}
}
void DatabaseSettingsWidgetBrowser::convertAttributesToCustomData()
{
if (QMessageBox::Yes
if (MessageBox::Yes
!= MessageBox::question(
this,
tr("Move KeePassHTTP attributes to custom data"),
tr("Do you really want to move all legacy browser integration data to the latest standard?\n"
"This is necessary to maintain compatibility with the browser plugin."),
QMessageBox::Yes | QMessageBox::Cancel,
QMessageBox::Cancel)) {
MessageBox::Yes | MessageBox::Cancel,
MessageBox::Cancel)) {
return;
}

View File

@ -172,8 +172,8 @@ bool DatabaseSettingsWidgetMasterKey::save()
MessageBox::critical(this,
tr("No encryption key added"),
tr("You must add at least one encryption key to secure your database!"),
QMessageBox::Ok,
QMessageBox::Ok);
MessageBox::Ok,
MessageBox::Ok);
return false;
}
@ -183,9 +183,9 @@ bool DatabaseSettingsWidgetMasterKey::save()
tr("WARNING! You have not set a password. Using a database without "
"a password is strongly discouraged!\n\n"
"Are you sure you want to continue without a password?"),
QMessageBox::Yes | QMessageBox::Cancel,
QMessageBox::Cancel);
if (answer != QMessageBox::Yes) {
MessageBox::Yes | MessageBox::Cancel,
MessageBox::Cancel);
if (answer != MessageBox::Yes) {
return false;
}
}
@ -221,7 +221,7 @@ bool DatabaseSettingsWidgetMasterKey::addToCompositeKey(KeyComponentWidget* widg
if (widget->visiblePage() == KeyComponentWidget::Edit) {
QString error = tr("Unknown error");
if (!widget->validate(error) || !widget->addToCompositeKey(newKey)) {
QMessageBox::critical(this, tr("Failed to change master key"), error, QMessageBox::Ok);
MessageBox::critical(this, tr("Failed to change master key"), error, MessageBox::Ok);
return false;
}
} else if (widget->visiblePage() == KeyComponentWidget::LeaveOrRemove) {
@ -238,7 +238,7 @@ bool DatabaseSettingsWidgetMasterKey::addToCompositeKey(KeyComponentWidget* widg
if (widget->visiblePage() == KeyComponentWidget::Edit) {
QString error = tr("Unknown error");
if (!widget->validate(error) || !widget->addToCompositeKey(newKey)) {
QMessageBox::critical(this, tr("Failed to change master key"), error, QMessageBox::Ok);
MessageBox::critical(this, tr("Failed to change master key"), error, MessageBox::Ok);
return false;
}
} else if (widget->visiblePage() == KeyComponentWidget::LeaveOrRemove) {

View File

@ -821,9 +821,9 @@ bool EditEntryWidget::commitEntry()
auto answer = MessageBox::question(this,
tr("Apply generated password?"),
tr("Do you want to apply the generated password to this entry?"),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::Yes);
if (answer == QMessageBox::Yes) {
MessageBox::Yes | MessageBox::No,
MessageBox::Yes);
if (answer == MessageBox::Yes) {
m_mainUi->passwordGenerator->applyPassword();
}
}
@ -947,13 +947,13 @@ void EditEntryWidget::cancel()
auto result = MessageBox::question(this,
QString(),
tr("Entry has unsaved changes"),
QMessageBox::Cancel | QMessageBox::Save | QMessageBox::Discard,
QMessageBox::Cancel);
if (result == QMessageBox::Cancel) {
MessageBox::Cancel | MessageBox::Save | MessageBox::Discard,
MessageBox::Cancel);
if (result == MessageBox::Cancel) {
m_mainUi->passwordGenerator->reset();
return;
}
if (result == QMessageBox::Save) {
if (result == MessageBox::Save) {
commitEntry();
m_saved = true;
}
@ -1058,11 +1058,14 @@ void EditEntryWidget::removeCurrentAttribute()
QModelIndex index = m_advancedUi->attributesView->currentIndex();
if (index.isValid()) {
if (MessageBox::question(this,
tr("Confirm Remove"),
tr("Are you sure you want to remove this attribute?"),
QMessageBox::Yes | QMessageBox::No)
== QMessageBox::Yes) {
auto result = MessageBox::question(this,
tr("Confirm Removal"),
tr("Are you sure you want to remove this attribute?"),
MessageBox::Remove | MessageBox::Cancel,
MessageBox::Cancel);
if (result == MessageBox::Remove) {
m_entryAttributes->remove(m_attributesModel->keyByIndex(index));
setUnsavedChanges(true);
}

View File

@ -165,10 +165,13 @@ void EntryAttachmentsWidget::removeSelectedAttachments()
return;
}
const QString question = tr("Are you sure you want to remove %n attachment(s)?", "", indexes.count());
QMessageBox::StandardButton answer =
MessageBox::question(this, tr("Confirm remove"), question, QMessageBox::Yes | QMessageBox::No);
if (answer == QMessageBox::Yes) {
auto result = MessageBox::question(this,
tr("Confirm remove"),
tr("Are you sure you want to remove %n attachment(s)?", "", indexes.count()),
MessageBox::Remove | MessageBox::Cancel,
MessageBox::Cancel);
if (result == MessageBox::Remove) {
QStringList keys;
for (const QModelIndex& index : indexes) {
keys.append(m_attachmentsModel->keyByIndex(index));
@ -211,15 +214,24 @@ void EntryAttachmentsWidget::saveSelectedAttachments()
const QString attachmentPath = saveDir.absoluteFilePath(filename);
if (QFileInfo::exists(attachmentPath)) {
const QString question(
MessageBox::Buttons buttons = MessageBox::Overwrite | MessageBox::Cancel;
if (indexes.length() > 1) {
buttons |= MessageBox::Skip;
}
const QString questionText(
tr("Are you sure you want to overwrite the existing file \"%1\" with the attachment?"));
auto answer = MessageBox::question(this,
auto result = MessageBox::question(this,
tr("Confirm overwrite"),
question.arg(filename),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
if (answer == QMessageBox::No) {
questionText.arg(filename),
buttons,
MessageBox::Cancel);
if (result == MessageBox::Skip) {
continue;
} else if (answer == QMessageBox::Cancel) {
} else if (result == MessageBox::Cancel) {
return;
}
}

View File

@ -148,7 +148,7 @@ void TestAutoType::testSingleAutoType()
void TestAutoType::testGlobalAutoTypeWithNoMatch()
{
m_test->setActiveWindowTitle("nomatch");
MessageBox::setNextAnswer(QMessageBox::Ok);
MessageBox::setNextAnswer(MessageBox::Ok);
m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString());
@ -195,7 +195,7 @@ void TestAutoType::testGlobalAutoTypeUrlSubdomainMatch()
void TestAutoType::testGlobalAutoTypeTitleMatchDisabled()
{
m_test->setActiveWindowTitle("An Entry Title!");
MessageBox::setNextAnswer(QMessageBox::Ok);
MessageBox::setNextAnswer(MessageBox::Ok);
m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString());
@ -379,4 +379,4 @@ void TestAutoType::testAutoTypeEffectiveSequences()
QCOMPARE(entry5->effectiveAutoTypeSequence(), QString());
QCOMPARE(entry6->defaultAutoTypeSequence(), sequenceOrphan);
QCOMPARE(entry6->effectiveAutoTypeSequence(), QString());
}
}

View File

@ -134,10 +134,10 @@ void TestGui::cleanup()
{
// DO NOT save the database
m_db->markAsClean();
MessageBox::setNextAnswer(QMessageBox::No);
MessageBox::setNextAnswer(MessageBox::No);
triggerAction("actionDatabaseClose");
QApplication::processEvents();
MessageBox::setNextAnswer(QMessageBox::NoButton);
MessageBox::setNextAnswer(MessageBox::NoButton);
if (m_dbWidget) {
delete m_dbWidget;
@ -204,7 +204,7 @@ void TestGui::testCreateDatabase()
QCOMPARE(m_db->key()->rawKey(), compositeKey->rawKey());
// close the new database
MessageBox::setNextAnswer(QMessageBox::No);
MessageBox::setNextAnswer(MessageBox::No);
triggerAction("actionDatabaseClose");
}
@ -338,7 +338,7 @@ void TestGui::testAutoreloadDatabase()
mergeDbFile.close();
// Test accepting new file in autoreload
MessageBox::setNextAnswer(QMessageBox::Yes);
MessageBox::setNextAnswer(MessageBox::Yes);
// Overwrite the current database with the temp data
QVERIFY(m_dbFile->open());
QVERIFY(m_dbFile->write(unmodifiedMergeDatabase, static_cast<qint64>(unmodifiedMergeDatabase.size())));
@ -356,7 +356,7 @@ void TestGui::testAutoreloadDatabase()
init();
// Test rejecting new file in autoreload
MessageBox::setNextAnswer(QMessageBox::No);
MessageBox::setNextAnswer(MessageBox::No);
// Overwrite the current temp database with a new file
m_dbFile->open();
QVERIFY(m_dbFile->write(unmodifiedMergeDatabase, static_cast<qint64>(unmodifiedMergeDatabase.size())));
@ -380,7 +380,7 @@ void TestGui::testAutoreloadDatabase()
testEditEntry();
// This is saying yes to merging the entries
MessageBox::setNextAnswer(QMessageBox::Yes);
MessageBox::setNextAnswer(MessageBox::Yes);
// Overwrite the current database with the temp data
QVERIFY(m_dbFile->open());
QVERIFY(m_dbFile->write(unmodifiedMergeDatabase, static_cast<qint64>(unmodifiedMergeDatabase.size())));
@ -604,7 +604,7 @@ void TestGui::testAddEntry()
// Add entry "something 5" but click cancel button (does NOT add entry)
QTest::mouseClick(entryNewWidget, Qt::LeftButton);
QTest::keyClicks(titleEdit, "something 5");
MessageBox::setNextAnswer(QMessageBox::Discard);
MessageBox::setNextAnswer(MessageBox::Discard);
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Cancel), Qt::LeftButton);
QApplication::processEvents();
@ -944,7 +944,7 @@ void TestGui::testDeleteEntry()
QVERIFY(entryDeleteWidget->isEnabled());
QVERIFY(!m_db->metadata()->recycleBin());
MessageBox::setNextAnswer(QMessageBox::Yes);
MessageBox::setNextAnswer(MessageBox::Move);
QTest::mouseClick(entryDeleteWidget, Qt::LeftButton);
QCOMPARE(entryView->model()->rowCount(), 3);
@ -954,12 +954,12 @@ void TestGui::testDeleteEntry()
clickIndex(entryView->model()->index(2, 1), entryView, Qt::LeftButton, Qt::ControlModifier);
QCOMPARE(entryView->selectionModel()->selectedRows().size(), 2);
MessageBox::setNextAnswer(QMessageBox::No);
MessageBox::setNextAnswer(MessageBox::Cancel);
QTest::mouseClick(entryDeleteWidget, Qt::LeftButton);
QCOMPARE(entryView->model()->rowCount(), 3);
QCOMPARE(m_db->metadata()->recycleBin()->entries().size(), 1);
MessageBox::setNextAnswer(QMessageBox::Yes);
MessageBox::setNextAnswer(MessageBox::Move);
QTest::mouseClick(entryDeleteWidget, Qt::LeftButton);
QCOMPARE(entryView->model()->rowCount(), 1);
QCOMPARE(m_db->metadata()->recycleBin()->entries().size(), 3);
@ -972,19 +972,19 @@ void TestGui::testDeleteEntry()
QCOMPARE(groupView->currentGroup()->name(), m_db->metadata()->recycleBin()->name());
clickIndex(entryView->model()->index(0, 1), entryView, Qt::LeftButton);
MessageBox::setNextAnswer(QMessageBox::No);
MessageBox::setNextAnswer(MessageBox::Cancel);
QTest::mouseClick(entryDeleteWidget, Qt::LeftButton);
QCOMPARE(entryView->model()->rowCount(), 3);
QCOMPARE(m_db->metadata()->recycleBin()->entries().size(), 3);
MessageBox::setNextAnswer(QMessageBox::Yes);
MessageBox::setNextAnswer(MessageBox::Delete);
QTest::mouseClick(entryDeleteWidget, Qt::LeftButton);
QCOMPARE(entryView->model()->rowCount(), 2);
QCOMPARE(m_db->metadata()->recycleBin()->entries().size(), 2);
clickIndex(entryView->model()->index(0, 1), entryView, Qt::LeftButton);
clickIndex(entryView->model()->index(1, 1), entryView, Qt::LeftButton, Qt::ControlModifier);
MessageBox::setNextAnswer(QMessageBox::Yes);
MessageBox::setNextAnswer(MessageBox::Delete);
QTest::mouseClick(entryDeleteWidget, Qt::LeftButton);
QCOMPARE(entryView->model()->rowCount(), 0);
QCOMPARE(m_db->metadata()->recycleBin()->entries().size(), 0);
@ -1183,7 +1183,7 @@ void TestGui::testKeePass1Import()
QTRY_COMPARE(m_tabWidget->tabName(m_tabWidget->currentIndex()), QString("basic [New Database]*"));
// Close the KeePass1 Database
MessageBox::setNextAnswer(QMessageBox::No);
MessageBox::setNextAnswer(MessageBox::No);
triggerAction("actionDatabaseClose");
QApplication::processEvents();
}
@ -1192,7 +1192,7 @@ void TestGui::testDatabaseLocking()
{
QString origDbName = m_tabWidget->tabText(0);
MessageBox::setNextAnswer(QMessageBox::Cancel);
MessageBox::setNextAnswer(MessageBox::Cancel);
triggerAction("actionLockDatabases");
QCOMPARE(m_tabWidget->tabName(0), origDbName + " [Locked]");
@ -1248,7 +1248,7 @@ void TestGui::testDragAndDropKdbxFiles()
QCOMPARE(m_tabWidget->count(), openedDatabasesCount + 1);
MessageBox::setNextAnswer(QMessageBox::No);
MessageBox::setNextAnswer(MessageBox::No);
triggerAction("actionDatabaseClose");
QTRY_COMPARE(m_tabWidget->count(), openedDatabasesCount);