diff --git a/share/demo.kdbx b/share/demo.kdbx
index 7c51608e..7be77579 100644
Binary files a/share/demo.kdbx and b/share/demo.kdbx differ
diff --git a/share/icons/application/scalable/actions/entry-restore.svg b/share/icons/application/scalable/actions/entry-restore.svg
new file mode 100644
index 00000000..da5b69da
--- /dev/null
+++ b/share/icons/application/scalable/actions/entry-restore.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/share/icons/icons.qrc b/share/icons/icons.qrc
index 05e880c0..b35ad77b 100644
--- a/share/icons/icons.qrc
+++ b/share/icons/icons.qrc
@@ -34,6 +34,7 @@
application/scalable/actions/edit-clear-locationbar-rtl.svg
application/scalable/actions/entry-clone.svg
application/scalable/actions/entry-delete.svg
+ application/scalable/actions/entry-restore.svg
application/scalable/actions/entry-edit.svg
application/scalable/actions/entry-new.svg
application/scalable/actions/favicon-download.svg
diff --git a/share/translations/keepassxc_en.ts b/share/translations/keepassxc_en.ts
index e671d762..daef0a8d 100644
--- a/share/translations/keepassxc_en.ts
+++ b/share/translations/keepassxc_en.ts
@@ -5227,6 +5227,10 @@ We recommend you use the AppImage available on our downloads page.
Please present or touch your YubiKey to continue…
+
+ Restore Entry(s)
+
+
ManageDatabase
diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp
index 034a295d..d6e07222 100644
--- a/src/core/Entry.cpp
+++ b/src/core/Entry.cpp
@@ -1381,6 +1381,14 @@ QString Entry::resolveUrl(const QString& url) const
return {};
}
+Group* Entry::previousParentGroup()
+{
+ if (!database() || !database()->rootGroup()) {
+ return nullptr;
+ }
+ return database()->rootGroup()->findGroupByUuid(m_data.previousParentGroupUuid);
+}
+
const Group* Entry::previousParentGroup() const
{
if (!database() || !database()->rootGroup()) {
diff --git a/src/core/Entry.h b/src/core/Entry.h
index 6a19078a..50c3427e 100644
--- a/src/core/Entry.h
+++ b/src/core/Entry.h
@@ -107,6 +107,7 @@ public:
QString totp() const;
QString totpSettingsString() const;
QSharedPointer totpSettings() const;
+ Group* previousParentGroup();
const Group* previousParentGroup() const;
QUuid previousParentGroupUuid() const;
int size() const;
diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp
index 6d88a661..1183810b 100644
--- a/src/gui/DatabaseWidget.cpp
+++ b/src/gui/DatabaseWidget.cpp
@@ -475,6 +475,26 @@ void DatabaseWidget::deleteSelectedEntries()
deleteEntries(std::move(selectedEntries));
}
+void DatabaseWidget::restoreSelectedEntries()
+{
+ const QModelIndexList selected = m_entryView->selectionModel()->selectedRows();
+ if (selected.isEmpty()) {
+ return;
+ }
+
+ // Resolve entries from the selection model
+ QList selectedEntries;
+ for (auto& index : selected) {
+ selectedEntries.append(m_entryView->entryFromIndex(index));
+ }
+
+ for (auto* entry : selectedEntries) {
+ if (entry->previousParentGroup()) {
+ entry->setGroup(entry->previousParentGroup());
+ }
+ }
+}
+
void DatabaseWidget::deleteEntries(QList selectedEntries, bool confirm)
{
if (selectedEntries.isEmpty()) {
diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h
index 3909dfd7..c329b98f 100644
--- a/src/gui/DatabaseWidget.h
+++ b/src/gui/DatabaseWidget.h
@@ -164,6 +164,7 @@ public slots:
void createEntry();
void cloneEntry();
void deleteSelectedEntries();
+ void restoreSelectedEntries();
void deleteEntries(QList entries, bool confirm = true);
void focusOnEntries(bool editIfFocused = false);
void focusOnGroups(bool editIfFocused = false);
diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp
index 1f99448b..5aa39ad5 100644
--- a/src/gui/MainWindow.cpp
+++ b/src/gui/MainWindow.cpp
@@ -129,6 +129,7 @@ MainWindow::MainWindow()
m_countDefaultAttributes = m_ui->menuEntryCopyAttribute->actions().size();
m_entryContextMenu = new QMenu(this);
+ m_entryContextMenu->setSeparatorsCollapsible(true);
m_entryContextMenu->addAction(m_ui->actionEntryCopyUsername);
m_entryContextMenu->addAction(m_ui->actionEntryCopyPassword);
m_entryContextMenu->addAction(m_ui->menuEntryCopyAttribute->menuAction());
@@ -146,6 +147,8 @@ MainWindow::MainWindow()
m_entryContextMenu->addSeparator();
m_entryContextMenu->addAction(m_ui->actionEntryOpenUrl);
m_entryContextMenu->addAction(m_ui->actionEntryDownloadIcon);
+ m_entryContextMenu->addSeparator();
+ m_entryContextMenu->addAction(m_ui->actionEntryRestore);
m_entryNewContextMenu = new QMenu(this);
m_entryNewContextMenu->addAction(m_ui->actionEntryNew);
@@ -275,6 +278,7 @@ MainWindow::MainWindow()
m_ui->actionEntryAutoTypeSequence->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_V);
m_ui->actionEntryOpenUrl->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_U);
m_ui->actionEntryCopyURL->setShortcut(Qt::CTRL + Qt::Key_U);
+ m_ui->actionEntryRestore->setShortcut(Qt::CTRL + Qt::Key_R);
// Prevent conflicts with global Mac shortcuts (force Control on all platforms)
#ifdef Q_OS_MAC
@@ -291,6 +295,7 @@ MainWindow::MainWindow()
m_ui->actionEntryNew->setShortcutVisibleInContextMenu(true);
m_ui->actionEntryEdit->setShortcutVisibleInContextMenu(true);
m_ui->actionEntryDelete->setShortcutVisibleInContextMenu(true);
+ m_ui->actionEntryRestore->setShortcutVisibleInContextMenu(true);
m_ui->actionEntryClone->setShortcutVisibleInContextMenu(true);
m_ui->actionEntryTotp->setShortcutVisibleInContextMenu(true);
m_ui->actionEntryDownloadIcon->setShortcutVisibleInContextMenu(true);
@@ -374,6 +379,7 @@ MainWindow::MainWindow()
m_ui->actionEntryClone->setIcon(icons()->icon("entry-clone"));
m_ui->actionEntryEdit->setIcon(icons()->icon("entry-edit"));
m_ui->actionEntryDelete->setIcon(icons()->icon("entry-delete"));
+ m_ui->actionEntryRestore->setIcon(icons()->icon("entry-restore"));
m_ui->actionEntryAutoType->setIcon(icons()->icon("auto-type"));
m_ui->actionEntryAutoTypeSequence->setIcon(icons()->icon("auto-type"));
m_ui->actionEntryAutoTypeUsername->setIcon(icons()->icon("auto-type"));
@@ -461,6 +467,7 @@ MainWindow::MainWindow()
m_actionMultiplexer.connect(m_ui->actionEntryClone, SIGNAL(triggered()), SLOT(cloneEntry()));
m_actionMultiplexer.connect(m_ui->actionEntryEdit, SIGNAL(triggered()), SLOT(switchToEntryEdit()));
m_actionMultiplexer.connect(m_ui->actionEntryDelete, SIGNAL(triggered()), SLOT(deleteSelectedEntries()));
+ m_actionMultiplexer.connect(m_ui->actionEntryRestore, SIGNAL(triggered()), SLOT(restoreSelectedEntries()));
m_actionMultiplexer.connect(m_ui->actionEntryTotp, SIGNAL(triggered()), SLOT(showTotp()));
m_actionMultiplexer.connect(m_ui->actionEntrySetupTotp, SIGNAL(triggered()), SLOT(setupTotp()));
@@ -800,6 +807,9 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
m_ui->actionEntryClone->setEnabled(singleEntrySelected);
m_ui->actionEntryEdit->setEnabled(singleEntrySelected);
m_ui->actionEntryDelete->setEnabled(entriesSelected);
+ m_ui->actionEntryRestore->setVisible(entriesSelected && recycleBinSelected);
+ m_ui->actionEntryRestore->setEnabled(entriesSelected && recycleBinSelected);
+ m_ui->actionEntryRestore->setText(tr("Restore Entry(s)", "", dbWidget->numberOfSelectedEntries()));
m_ui->actionEntryMoveUp->setVisible(!sorted);
m_ui->actionEntryMoveDown->setVisible(!sorted);
m_ui->actionEntryMoveUp->setEnabled(singleEntrySelected && !sorted && entryIndex > 0);
diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui
index 8b36b97a..f83e8077 100644
--- a/src/gui/MainWindow.ui
+++ b/src/gui/MainWindow.ui
@@ -216,7 +216,7 @@
0
0
800
- 22
+ 21
@@ -283,6 +283,9 @@
&Entries
+
+ true
+