diff --git a/share/translations/keepassxc_en.ts b/share/translations/keepassxc_en.ts
index 1e938c8fd..4c0d486b1 100644
--- a/share/translations/keepassxc_en.ts
+++ b/share/translations/keepassxc_en.ts
@@ -816,6 +816,10 @@ Ctrl+4 - Use Virtual Keyboard (Windows Only)</p>
Disable for this site
+
+ Undo
+
+
BrowserEntrySaveDialog
diff --git a/src/browser/BrowserAccessControlDialog.cpp b/src/browser/BrowserAccessControlDialog.cpp
index 3fce10c5c..f3a29c993 100644
--- a/src/browser/BrowserAccessControlDialog.cpp
+++ b/src/browser/BrowserAccessControlDialog.cpp
@@ -1,6 +1,6 @@
/*
+ * Copyright (C) 2023 KeePassXC Team
* Copyright (C) 2013 Francois Ferrand
- * Copyright (C) 2022 KeePassXC Team
*
* 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
@@ -21,12 +21,11 @@
#include
#include "core/Entry.h"
-#include
+#include "gui/Icons.h"
BrowserAccessControlDialog::BrowserAccessControlDialog(QWidget* parent)
: QDialog(parent)
, m_ui(new Ui::BrowserAccessControlDialog())
- , m_entriesAccepted(false)
{
setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
@@ -34,13 +33,22 @@ BrowserAccessControlDialog::BrowserAccessControlDialog(QWidget* parent)
connect(m_ui->allowButton, SIGNAL(clicked()), SLOT(accept()));
connect(m_ui->denyButton, SIGNAL(clicked()), SLOT(reject()));
+ connect(m_ui->itemsTable, SIGNAL(cellDoubleClicked(int, int)), this, SLOT(accept()));
+ connect(m_ui->itemsTable->selectionModel(),
+ SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
+ this,
+ SLOT(selectionChanged()));
+ connect(m_ui->itemsTable, SIGNAL(acceptSelections()), SLOT(accept()));
+ connect(m_ui->itemsTable, SIGNAL(focusInWithoutSelections()), this, SLOT(selectionChanged()));
}
BrowserAccessControlDialog::~BrowserAccessControlDialog()
{
}
-void BrowserAccessControlDialog::setItems(const QList& items, const QString& urlString, bool httpAuth)
+void BrowserAccessControlDialog::setEntries(const QList& entriesToConfirm,
+ const QString& urlString,
+ bool httpAuth)
{
QUrl url(urlString);
m_ui->siteLabel->setText(m_ui->siteLabel->text().arg(
@@ -49,60 +57,114 @@ void BrowserAccessControlDialog::setItems(const QList& items, const QStr
m_ui->rememberDecisionCheckBox->setVisible(!httpAuth);
m_ui->rememberDecisionCheckBox->setChecked(false);
- m_ui->itemsTable->setRowCount(items.count());
+ m_ui->itemsTable->setRowCount(entriesToConfirm.count());
m_ui->itemsTable->setColumnCount(2);
int row = 0;
- for (const auto& entry : items) {
- auto item = new QTableWidgetItem();
- item->setText(entry->title() + " - " + entry->username());
- item->setData(Qt::UserRole, row);
- item->setCheckState(Qt::Checked);
- item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
- m_ui->itemsTable->setItem(row, 0, item);
-
- auto disableButton = new QPushButton(tr("Disable for this site"));
- disableButton->setAutoDefault(false);
- connect(disableButton, &QAbstractButton::pressed, [&, item] {
- emit disableAccess(item);
- m_ui->itemsTable->removeRow(item->row());
- if (m_ui->itemsTable->rowCount() == 0) {
- reject();
- }
- });
- m_ui->itemsTable->setCellWidget(row, 1, disableButton);
+ for (const auto& entry : entriesToConfirm) {
+ addEntryToList(entry, row);
++row;
}
m_ui->itemsTable->resizeColumnsToContents();
m_ui->itemsTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
+ m_ui->itemsTable->selectAll();
m_ui->allowButton->setFocus();
}
+void BrowserAccessControlDialog::addEntryToList(Entry* entry, int row)
+{
+ auto item = new QTableWidgetItem();
+ item->setText(entry->title() + " - " + entry->username());
+ item->setData(Qt::UserRole, row);
+ item->setFlags(item->flags() | Qt::ItemIsSelectable);
+ m_ui->itemsTable->setItem(row, 0, item);
+
+ auto disableButton = new QPushButton();
+ disableButton->setIcon(icons()->icon("entry-delete"));
+ disableButton->setToolTip(tr("Disable for this site"));
+
+ connect(disableButton, &QAbstractButton::pressed, [&, item, disableButton] {
+ auto font = item->font();
+ if (item->flags() == Qt::NoItemFlags) {
+ item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
+ item->setSelected(true);
+
+ font.setStrikeOut(false);
+ item->setFont(font);
+
+ disableButton->setIcon(icons()->icon("entry-delete"));
+ disableButton->setToolTip(tr("Disable for this site"));
+ m_ui->rememberDecisionCheckBox->setEnabled(true);
+ } else {
+ item->setFlags(Qt::NoItemFlags);
+ item->setSelected(false);
+
+ font.setStrikeOut(true);
+ item->setFont(font);
+
+ disableButton->setIcon(icons()->icon("entry-restore"));
+ disableButton->setToolTip(tr("Undo"));
+
+ // Disable Remember checkbox if all items are disabled
+ auto areAllDisabled = BrowserAccessControlDialog::areAllDisabled();
+ m_ui->rememberDecisionCheckBox->setEnabled(!areAllDisabled);
+ }
+ });
+
+ m_ui->itemsTable->setCellWidget(row, 1, disableButton);
+}
+
bool BrowserAccessControlDialog::remember() const
{
return m_ui->rememberDecisionCheckBox->isChecked();
}
-QList BrowserAccessControlDialog::getSelectedEntries() const
+QList BrowserAccessControlDialog::getEntries(SelectionType selectionType) const
{
QList selected;
- for (int i = 0; i < m_ui->itemsTable->rowCount(); ++i) {
- auto item = m_ui->itemsTable->item(i, 0);
- if (item->checkState() == Qt::Checked) {
+ for (auto& item : getAllItems()) {
+ // Add to list depending on selection type and item status
+ if ((selectionType == SelectionType::Selected && item->isSelected())
+ || (selectionType == SelectionType::NonSelected && !item->isSelected())
+ || (selectionType == SelectionType::Disabled && item->flags() == Qt::NoItemFlags)) {
selected.append(item);
}
}
return selected;
}
-QList BrowserAccessControlDialog::getNonSelectedEntries() const
+void BrowserAccessControlDialog::selectionChanged()
{
- QList notSelected;
- for (int i = 0; i < m_ui->itemsTable->rowCount(); ++i) {
- auto item = m_ui->itemsTable->item(i, 0);
- if (item->checkState() != Qt::Checked) {
- notSelected.append(item);
+ auto selectedRows = m_ui->itemsTable->selectionModel()->selectedRows();
+
+ m_ui->allowButton->setEnabled(!selectedRows.isEmpty());
+ m_ui->allowButton->setDefault(!selectedRows.isEmpty());
+ m_ui->allowButton->setAutoDefault(!selectedRows.isEmpty());
+
+ if (selectedRows.isEmpty()) {
+ m_ui->allowButton->clearFocus();
+ m_ui->denyButton->setFocus();
+ }
+}
+
+bool BrowserAccessControlDialog::areAllDisabled() const
+{
+ auto areAllDisabled = true;
+ for (const auto& item : getAllItems()) {
+ if (item->flags() != Qt::NoItemFlags) {
+ areAllDisabled = false;
}
}
- return notSelected;
+
+ return areAllDisabled;
+}
+
+QList BrowserAccessControlDialog::getAllItems() const
+{
+ QList items;
+ for (int i = 0; i < m_ui->itemsTable->rowCount(); ++i) {
+ auto item = m_ui->itemsTable->item(i, 0);
+ items.append(item);
+ }
+ return items;
}
diff --git a/src/browser/BrowserAccessControlDialog.h b/src/browser/BrowserAccessControlDialog.h
index 946db16d9..3ecf5b506 100644
--- a/src/browser/BrowserAccessControlDialog.h
+++ b/src/browser/BrowserAccessControlDialog.h
@@ -1,6 +1,6 @@
/*
+ * Copyright (C) 2023 KeePassXC Team
* Copyright (C) 2013 Francois Ferrand
- * Copyright (C) 2022 KeePassXC Team
*
* 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
@@ -29,6 +29,13 @@ namespace Ui
class BrowserAccessControlDialog;
}
+enum SelectionType
+{
+ Selected,
+ NonSelected,
+ Disabled
+};
+
class BrowserAccessControlDialog : public QDialog
{
Q_OBJECT
@@ -37,20 +44,24 @@ public:
explicit BrowserAccessControlDialog(QWidget* parent = nullptr);
~BrowserAccessControlDialog() override;
- void setItems(const QList& items, const QString& urlString, bool httpAuth);
+ void setEntries(const QList& entriesToConfirm, const QString& urlString, bool httpAuth);
bool remember() const;
-
- QList getSelectedEntries() const;
- QList getNonSelectedEntries() const;
+ QList getEntries(SelectionType selectionType) const;
signals:
void disableAccess(QTableWidgetItem* item);
+private slots:
+ void selectionChanged();
+
+private:
+ void addEntryToList(Entry* entry, int row);
+ bool areAllDisabled() const;
+ QList getAllItems() const;
+
private:
QScopedPointer m_ui;
QList m_entriesToConfirm;
- QList m_allowedEntries;
- bool m_entriesAccepted;
};
#endif // KEEPASSXC_BROWSERACCESSCONTROLDIALOG_H
diff --git a/src/browser/BrowserAccessControlDialog.ui b/src/browser/BrowserAccessControlDialog.ui
index 4224c1633..63f264311 100755
--- a/src/browser/BrowserAccessControlDialog.ui
+++ b/src/browser/BrowserAccessControlDialog.ui
@@ -31,7 +31,7 @@
-
-
+
QAbstractItemView::NoEditTriggers
@@ -39,7 +39,10 @@
false
- QAbstractItemView::NoSelection
+ QAbstractItemView::ExtendedSelection
+
+
+ QAbstractItemView::SelectRows
false
@@ -110,6 +113,19 @@
+
+ rememberDecisionCheckBox
+ allowButton
+ denyButton
+
+
+
+ CustomTableWidget
+ QTableWidget
+ browser/CustomTableWidget.h
+ 1
+
+
diff --git a/src/browser/BrowserService.cpp b/src/browser/BrowserService.cpp
index 60412b5a3..133827dcf 100644
--- a/src/browser/BrowserService.cpp
+++ b/src/browser/BrowserService.cpp
@@ -431,25 +431,48 @@ QList BrowserService::confirmEntries(QList& entriesToConfirm,
denyEntry(entry, siteHost, formUrl, entryParameters.realm);
});
- accessControlDialog.setItems(entriesToConfirm, entryParameters.siteUrl, httpAuth);
+ accessControlDialog.setEntries(entriesToConfirm, entryParameters.siteUrl, httpAuth);
QList allowedEntries;
auto ret = accessControlDialog.exec();
- for (auto item : accessControlDialog.getSelectedEntries()) {
- auto entry = entriesToConfirm[item->row()];
- if (accessControlDialog.remember()) {
- if (ret == QDialog::Accepted) {
+ auto remember = accessControlDialog.remember();
+
+ // All are denied
+ if (ret == QDialog::Rejected && remember) {
+ for (auto& entry : entriesToConfirm) {
+ denyEntry(entry, siteHost, formUrl, entryParameters.realm);
+ }
+ }
+
+ // Some/all are accepted
+ if (ret == QDialog::Accepted) {
+ auto selectedEntries = accessControlDialog.getEntries(SelectionType::Selected);
+ for (auto& item : selectedEntries) {
+ auto entry = entriesToConfirm[item->row()];
+ allowedEntries.append(entry);
+
+ if (remember) {
allowEntry(entry, siteHost, formUrl, entryParameters.realm);
- } else {
- denyEntry(entry, siteHost, formUrl, entryParameters.realm);
}
}
- if (ret == QDialog::Accepted) {
- allowedEntries.append(entry);
+ // Remembered non-selected entries must be denied
+ if (remember) {
+ auto nonSelectedEntries = accessControlDialog.getEntries(SelectionType::NonSelected);
+ for (auto& item : nonSelectedEntries) {
+ auto entry = entriesToConfirm[item->row()];
+ denyEntry(entry, siteHost, formUrl, entryParameters.realm);
+ }
}
}
+ // Handle disabled entries (returned Accept/Reject status does not matter)
+ auto disabledEntries = accessControlDialog.getEntries(SelectionType::Disabled);
+ for (auto& item : disabledEntries) {
+ auto entry = entriesToConfirm[item->row()];
+ denyEntry(entry, siteHost, formUrl, entryParameters.realm);
+ }
+
// Re-hide the application if it wasn't visible before
hideWindow();
m_dialogActive = false;
diff --git a/src/browser/CMakeLists.txt b/src/browser/CMakeLists.txt
index 2c344d31b..3ec5655ff 100755
--- a/src/browser/CMakeLists.txt
+++ b/src/browser/CMakeLists.txt
@@ -28,7 +28,9 @@ if(WITH_XC_BROWSER)
BrowserService.cpp
BrowserSettings.cpp
BrowserShared.cpp
- NativeMessageInstaller.cpp)
+ CustomTableWidget.cpp
+ NativeMessageInstaller.cpp
+ )
if(WITH_XC_BROWSER_PASSKEYS)
list(APPEND keepassxcbrowser_SOURCES
diff --git a/src/browser/CustomTableWidget.cpp b/src/browser/CustomTableWidget.cpp
new file mode 100644
index 000000000..29009f4d5
--- /dev/null
+++ b/src/browser/CustomTableWidget.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 KeePassXC Team
+ *
+ * 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "CustomTableWidget.h"
+
+CustomTableWidget::CustomTableWidget(QWidget* parent)
+ : QTableWidget(parent)
+{
+}
+
+void CustomTableWidget::keyPressEvent(QKeyEvent* event)
+{
+ if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) && !selectedItems().isEmpty()) {
+ emit acceptSelections();
+ } else {
+ QTableView::keyPressEvent(event);
+ }
+}
+
+void CustomTableWidget::focusInEvent(QFocusEvent* event)
+{
+ // For some reason accept button gets selected if table is clicked without any
+ // selections, even if the button is actually disabled. Connecting to this
+ // signal and adjusting the button focuses fixes the issue.
+ if (event->reason() == Qt::MouseFocusReason && selectedItems().isEmpty()) {
+ emit focusInWithoutSelections();
+ }
+}
diff --git a/src/browser/CustomTableWidget.h b/src/browser/CustomTableWidget.h
new file mode 100644
index 000000000..655475e9d
--- /dev/null
+++ b/src/browser/CustomTableWidget.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 KeePassXC Team
+ *
+ * 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef CUSTOMTABLEWIDGET_H
+#define CUSTOMTABLEWIDGET_H
+
+#include
+#include
+#include
+
+class CustomTableWidget : public QTableWidget
+{
+ Q_OBJECT
+
+public:
+ CustomTableWidget(QWidget* parent);
+
+signals:
+ void acceptSelections();
+ void focusInWithoutSelections();
+
+protected:
+ void keyPressEvent(QKeyEvent* event) override;
+ void focusInEvent(QFocusEvent* event) override;
+};
+
+#endif // CUSTOMTABLEWIDGET_H