mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
Add "Restore Entries" feature
This commit is contained in:
parent
e5822974ac
commit
70e62d90db
BIN
share/demo.kdbx
BIN
share/demo.kdbx
Binary file not shown.
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M12,3A9,9 0 0,0 3,12H0L4,16L8,12H5A7,7 0 0,1 12,5A7,7 0 0,1 19,12A7,7 0 0,1 12,19C10.5,19 9.09,18.5 7.94,17.7L6.5,19.14C8.04,20.3 9.94,21 12,21A9,9 0 0,0 21,12A9,9 0 0,0 12,3M14,12A2,2 0 0,0 12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12Z" /></svg>
|
After Width: | Height: | Size: 540 B |
@ -34,6 +34,7 @@
|
|||||||
<file>application/scalable/actions/edit-clear-locationbar-rtl.svg</file>
|
<file>application/scalable/actions/edit-clear-locationbar-rtl.svg</file>
|
||||||
<file>application/scalable/actions/entry-clone.svg</file>
|
<file>application/scalable/actions/entry-clone.svg</file>
|
||||||
<file>application/scalable/actions/entry-delete.svg</file>
|
<file>application/scalable/actions/entry-delete.svg</file>
|
||||||
|
<file>application/scalable/actions/entry-restore.svg</file>
|
||||||
<file>application/scalable/actions/entry-edit.svg</file>
|
<file>application/scalable/actions/entry-edit.svg</file>
|
||||||
<file>application/scalable/actions/entry-new.svg</file>
|
<file>application/scalable/actions/entry-new.svg</file>
|
||||||
<file>application/scalable/actions/favicon-download.svg</file>
|
<file>application/scalable/actions/favicon-download.svg</file>
|
||||||
|
@ -5227,6 +5227,10 @@ We recommend you use the AppImage available on our downloads page.</source>
|
|||||||
<source>Please present or touch your YubiKey to continue…</source>
|
<source>Please present or touch your YubiKey to continue…</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Restore Entry(s)</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>ManageDatabase</name>
|
<name>ManageDatabase</name>
|
||||||
|
@ -1381,6 +1381,14 @@ QString Entry::resolveUrl(const QString& url) const
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Group* Entry::previousParentGroup()
|
||||||
|
{
|
||||||
|
if (!database() || !database()->rootGroup()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return database()->rootGroup()->findGroupByUuid(m_data.previousParentGroupUuid);
|
||||||
|
}
|
||||||
|
|
||||||
const Group* Entry::previousParentGroup() const
|
const Group* Entry::previousParentGroup() const
|
||||||
{
|
{
|
||||||
if (!database() || !database()->rootGroup()) {
|
if (!database() || !database()->rootGroup()) {
|
||||||
|
@ -107,6 +107,7 @@ public:
|
|||||||
QString totp() const;
|
QString totp() const;
|
||||||
QString totpSettingsString() const;
|
QString totpSettingsString() const;
|
||||||
QSharedPointer<Totp::Settings> totpSettings() const;
|
QSharedPointer<Totp::Settings> totpSettings() const;
|
||||||
|
Group* previousParentGroup();
|
||||||
const Group* previousParentGroup() const;
|
const Group* previousParentGroup() const;
|
||||||
QUuid previousParentGroupUuid() const;
|
QUuid previousParentGroupUuid() const;
|
||||||
int size() const;
|
int size() const;
|
||||||
|
@ -475,6 +475,26 @@ void DatabaseWidget::deleteSelectedEntries()
|
|||||||
deleteEntries(std::move(selectedEntries));
|
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<Entry*> 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<Entry*> selectedEntries, bool confirm)
|
void DatabaseWidget::deleteEntries(QList<Entry*> selectedEntries, bool confirm)
|
||||||
{
|
{
|
||||||
if (selectedEntries.isEmpty()) {
|
if (selectedEntries.isEmpty()) {
|
||||||
|
@ -164,6 +164,7 @@ public slots:
|
|||||||
void createEntry();
|
void createEntry();
|
||||||
void cloneEntry();
|
void cloneEntry();
|
||||||
void deleteSelectedEntries();
|
void deleteSelectedEntries();
|
||||||
|
void restoreSelectedEntries();
|
||||||
void deleteEntries(QList<Entry*> entries, bool confirm = true);
|
void deleteEntries(QList<Entry*> entries, bool confirm = true);
|
||||||
void focusOnEntries(bool editIfFocused = false);
|
void focusOnEntries(bool editIfFocused = false);
|
||||||
void focusOnGroups(bool editIfFocused = false);
|
void focusOnGroups(bool editIfFocused = false);
|
||||||
|
@ -129,6 +129,7 @@ MainWindow::MainWindow()
|
|||||||
m_countDefaultAttributes = m_ui->menuEntryCopyAttribute->actions().size();
|
m_countDefaultAttributes = m_ui->menuEntryCopyAttribute->actions().size();
|
||||||
|
|
||||||
m_entryContextMenu = new QMenu(this);
|
m_entryContextMenu = new QMenu(this);
|
||||||
|
m_entryContextMenu->setSeparatorsCollapsible(true);
|
||||||
m_entryContextMenu->addAction(m_ui->actionEntryCopyUsername);
|
m_entryContextMenu->addAction(m_ui->actionEntryCopyUsername);
|
||||||
m_entryContextMenu->addAction(m_ui->actionEntryCopyPassword);
|
m_entryContextMenu->addAction(m_ui->actionEntryCopyPassword);
|
||||||
m_entryContextMenu->addAction(m_ui->menuEntryCopyAttribute->menuAction());
|
m_entryContextMenu->addAction(m_ui->menuEntryCopyAttribute->menuAction());
|
||||||
@ -146,6 +147,8 @@ MainWindow::MainWindow()
|
|||||||
m_entryContextMenu->addSeparator();
|
m_entryContextMenu->addSeparator();
|
||||||
m_entryContextMenu->addAction(m_ui->actionEntryOpenUrl);
|
m_entryContextMenu->addAction(m_ui->actionEntryOpenUrl);
|
||||||
m_entryContextMenu->addAction(m_ui->actionEntryDownloadIcon);
|
m_entryContextMenu->addAction(m_ui->actionEntryDownloadIcon);
|
||||||
|
m_entryContextMenu->addSeparator();
|
||||||
|
m_entryContextMenu->addAction(m_ui->actionEntryRestore);
|
||||||
|
|
||||||
m_entryNewContextMenu = new QMenu(this);
|
m_entryNewContextMenu = new QMenu(this);
|
||||||
m_entryNewContextMenu->addAction(m_ui->actionEntryNew);
|
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->actionEntryAutoTypeSequence->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_V);
|
||||||
m_ui->actionEntryOpenUrl->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_U);
|
m_ui->actionEntryOpenUrl->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_U);
|
||||||
m_ui->actionEntryCopyURL->setShortcut(Qt::CTRL + 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)
|
// Prevent conflicts with global Mac shortcuts (force Control on all platforms)
|
||||||
#ifdef Q_OS_MAC
|
#ifdef Q_OS_MAC
|
||||||
@ -291,6 +295,7 @@ MainWindow::MainWindow()
|
|||||||
m_ui->actionEntryNew->setShortcutVisibleInContextMenu(true);
|
m_ui->actionEntryNew->setShortcutVisibleInContextMenu(true);
|
||||||
m_ui->actionEntryEdit->setShortcutVisibleInContextMenu(true);
|
m_ui->actionEntryEdit->setShortcutVisibleInContextMenu(true);
|
||||||
m_ui->actionEntryDelete->setShortcutVisibleInContextMenu(true);
|
m_ui->actionEntryDelete->setShortcutVisibleInContextMenu(true);
|
||||||
|
m_ui->actionEntryRestore->setShortcutVisibleInContextMenu(true);
|
||||||
m_ui->actionEntryClone->setShortcutVisibleInContextMenu(true);
|
m_ui->actionEntryClone->setShortcutVisibleInContextMenu(true);
|
||||||
m_ui->actionEntryTotp->setShortcutVisibleInContextMenu(true);
|
m_ui->actionEntryTotp->setShortcutVisibleInContextMenu(true);
|
||||||
m_ui->actionEntryDownloadIcon->setShortcutVisibleInContextMenu(true);
|
m_ui->actionEntryDownloadIcon->setShortcutVisibleInContextMenu(true);
|
||||||
@ -374,6 +379,7 @@ MainWindow::MainWindow()
|
|||||||
m_ui->actionEntryClone->setIcon(icons()->icon("entry-clone"));
|
m_ui->actionEntryClone->setIcon(icons()->icon("entry-clone"));
|
||||||
m_ui->actionEntryEdit->setIcon(icons()->icon("entry-edit"));
|
m_ui->actionEntryEdit->setIcon(icons()->icon("entry-edit"));
|
||||||
m_ui->actionEntryDelete->setIcon(icons()->icon("entry-delete"));
|
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->actionEntryAutoType->setIcon(icons()->icon("auto-type"));
|
||||||
m_ui->actionEntryAutoTypeSequence->setIcon(icons()->icon("auto-type"));
|
m_ui->actionEntryAutoTypeSequence->setIcon(icons()->icon("auto-type"));
|
||||||
m_ui->actionEntryAutoTypeUsername->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->actionEntryClone, SIGNAL(triggered()), SLOT(cloneEntry()));
|
||||||
m_actionMultiplexer.connect(m_ui->actionEntryEdit, SIGNAL(triggered()), SLOT(switchToEntryEdit()));
|
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->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->actionEntryTotp, SIGNAL(triggered()), SLOT(showTotp()));
|
||||||
m_actionMultiplexer.connect(m_ui->actionEntrySetupTotp, SIGNAL(triggered()), SLOT(setupTotp()));
|
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->actionEntryClone->setEnabled(singleEntrySelected);
|
||||||
m_ui->actionEntryEdit->setEnabled(singleEntrySelected);
|
m_ui->actionEntryEdit->setEnabled(singleEntrySelected);
|
||||||
m_ui->actionEntryDelete->setEnabled(entriesSelected);
|
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->actionEntryMoveUp->setVisible(!sorted);
|
||||||
m_ui->actionEntryMoveDown->setVisible(!sorted);
|
m_ui->actionEntryMoveDown->setVisible(!sorted);
|
||||||
m_ui->actionEntryMoveUp->setEnabled(singleEntrySelected && !sorted && entryIndex > 0);
|
m_ui->actionEntryMoveUp->setEnabled(singleEntrySelected && !sorted && entryIndex > 0);
|
||||||
|
@ -216,7 +216,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>800</width>
|
<width>800</width>
|
||||||
<height>22</height>
|
<height>21</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="contextMenuPolicy">
|
<property name="contextMenuPolicy">
|
||||||
@ -283,6 +283,9 @@
|
|||||||
<property name="title">
|
<property name="title">
|
||||||
<string>&Entries</string>
|
<string>&Entries</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="separatorsCollapsible">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
<widget class="QMenu" name="menuEntryCopyAttribute">
|
<widget class="QMenu" name="menuEntryCopyAttribute">
|
||||||
<property name="enabled">
|
<property name="enabled">
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
@ -330,6 +333,8 @@
|
|||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionEntryAddToAgent"/>
|
<addaction name="actionEntryAddToAgent"/>
|
||||||
<addaction name="actionEntryRemoveFromAgent"/>
|
<addaction name="actionEntryRemoveFromAgent"/>
|
||||||
|
<addaction name="separator"/>
|
||||||
|
<addaction name="actionEntryRestore"/>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QMenu" name="menuGroups">
|
<widget class="QMenu" name="menuGroups">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
@ -1068,6 +1073,17 @@
|
|||||||
<string>Clone Group...</string>
|
<string>Clone Group...</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionEntryRestore">
|
||||||
|
<property name="text">
|
||||||
|
<string>Restore Entry(s)</string>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Restore Entry(s)</string>
|
||||||
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string notr="true">Ctrl+R</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
|
Loading…
Reference in New Issue
Block a user