diff options
-rw-r--r-- | COPYING | 93 | ||||
-rw-r--r-- | share/icons/application/scalable/actions/document-save-copy.svg | 1 | ||||
-rw-r--r-- | share/icons/icons.qrc | 1 | ||||
-rw-r--r-- | src/gui/DatabaseTabWidget.cpp | 14 | ||||
-rw-r--r-- | src/gui/DatabaseTabWidget.h | 1 | ||||
-rw-r--r-- | src/gui/DatabaseWidget.cpp | 47 | ||||
-rw-r--r-- | src/gui/DatabaseWidget.h | 1 | ||||
-rw-r--r-- | src/gui/MainWindow.cpp | 5 | ||||
-rw-r--r-- | src/gui/MainWindow.ui | 12 | ||||
-rw-r--r-- | tests/gui/TestGui.cpp | 29 | ||||
-rw-r--r-- | tests/gui/TestGui.h | 1 | ||||
-rw-r--r-- | utils/makeicons.sh | 1 |
12 files changed, 157 insertions, 49 deletions
@@ -124,66 +124,67 @@ Copyright: 2003-2004, David Vignoni <david@icon-king.com> License: LGPL-2.1 Comment: from Nuvola icon theme -Files: share/icons/application/scalable/categories/preferences-other.svg - share/icons/application/scalable/apps/keepassxc-dark.svg - share/icons/application/scalable/apps/preferences-system-network-sharing.svg - share/icons/application/scalable/apps/utilities-terminal.svg - share/icons/application/scalable/apps/keepassxc-locked.svg - share/icons/application/scalable/apps/keepassxc-unlocked.svg - share/icons/application/scalable/apps/keepassxc.svg - share/icons/application/scalable/apps/freedesktop.svg - share/icons/application/scalable/apps/internet-web-browser.svg - share/icons/application/scalable/apps/preferences-desktop-icons.svg - share/icons/application/scalable/status/dialog-information.svg - share/icons/application/scalable/status/dialog-warning.svg - share/icons/application/scalable/status/dialog-error.svg - share/icons/application/scalable/status/security-high.svg - share/icons/application/scalable/mimetypes/application-x-keepassxc.svg - share/icons/application/scalable/actions/document-close.svg - share/icons/application/scalable/actions/application-exit.svg +Files: share/icons/application/scalable/actions/application-exit.svg + share/icons/application/scalable/actions/auto-type.svg + share/icons/application/scalable/actions/chronometer.svg + share/icons/application/scalable/actions/clipboard-text.svg + share/icons/application/scalable/actions/configure.svg share/icons/application/scalable/actions/database-change-key.svg - share/icons/application/scalable/actions/group-new.svg + share/icons/application/scalable/actions/database-lock.svg + share/icons/application/scalable/actions/dialog-close.svg + share/icons/application/scalable/actions/dialog-ok.svg + share/icons/application/scalable/actions/document-close.svg + share/icons/application/scalable/actions/document-edit.svg + share/icons/application/scalable/actions/document-new.svg + share/icons/application/scalable/actions/document-open.svg share/icons/application/scalable/actions/document-properties.svg - share/icons/application/scalable/actions/group-empty-trash.svg - share/icons/application/scalable/actions/statistics.svg + share/icons/application/scalable/actions/document-save.svg + share/icons/application/scalable/actions/document-save-as.svg + share/icons/application/scalable/actions/document-save-copy.svg share/icons/application/scalable/actions/edit-clear-locationbar-ltr.svg - share/icons/application/scalable/actions/entry-delete.svg + share/icons/application/scalable/actions/edit-clear-locationbar-rtl.svg share/icons/application/scalable/actions/entry-clone.svg + share/icons/application/scalable/actions/entry-delete.svg share/icons/application/scalable/actions/entry-edit.svg - share/icons/application/scalable/actions/password-generator.svg - share/icons/application/scalable/actions/dialog-ok.svg - share/icons/application/scalable/actions/chronometer.svg - share/icons/application/scalable/actions/document-new.svg - share/icons/application/scalable/actions/view-history.svg + share/icons/application/scalable/actions/entry-new.svg + share/icons/application/scalable/actions/favicon-download.svg share/icons/application/scalable/actions/group-delete.svg - share/icons/application/scalable/actions/dialog-close.svg share/icons/application/scalable/actions/group-edit.svg - share/icons/application/scalable/actions/document-save.svg - share/icons/application/scalable/actions/password-show-on.svg + share/icons/application/scalable/actions/group-empty-trash.svg + share/icons/application/scalable/actions/group-new.svg + share/icons/application/scalable/actions/help-about.svg + share/icons/application/scalable/actions/key-enter.svg share/icons/application/scalable/actions/message-close.svg - share/icons/application/scalable/actions/entry-new.svg - share/icons/application/scalable/actions/url-copy.svg - share/icons/application/scalable/actions/username-copy.svg - share/icons/application/scalable/actions/auto-type.svg - share/icons/application/scalable/actions/password-show-off.svg share/icons/application/scalable/actions/paperclip.svg - share/icons/application/scalable/actions/configure.svg - share/icons/application/scalable/actions/database-lock.svg share/icons/application/scalable/actions/password-copy.svg - share/icons/application/scalable/actions/system-help.svg - share/icons/application/scalable/actions/help-about.svg - share/icons/application/scalable/actions/system-search.svg - share/icons/application/scalable/actions/key-enter.svg - share/icons/application/scalable/actions/document-edit.svg - share/icons/application/scalable/actions/edit-clear-locationbar-rtl.svg share/icons/application/scalable/actions/password-generate.svg - share/icons/application/scalable/actions/favicon-download.svg - share/icons/application/scalable/actions/document-open.svg - share/icons/application/scalable/actions/document-save-as.svg + share/icons/application/scalable/actions/password-generator.svg + share/icons/application/scalable/actions/password-show-off.svg + share/icons/application/scalable/actions/password-show-on.svg share/icons/application/scalable/actions/refresh.svg - share/icons/application/scalable/actions/clipboard-text.svg share/icons/application/scalable/actions/reports.svg share/icons/application/scalable/actions/reports-exclude.svg + share/icons/application/scalable/actions/statistics.svg + share/icons/application/scalable/actions/system-help.svg + share/icons/application/scalable/actions/system-search.svg + share/icons/application/scalable/actions/url-copy.svg + share/icons/application/scalable/actions/username-copy.svg + share/icons/application/scalable/actions/view-history.svg + share/icons/application/scalable/apps/freedesktop.svg + share/icons/application/scalable/apps/internet-web-browser.svg + share/icons/application/scalable/apps/keepassxc.svg + share/icons/application/scalable/apps/keepassxc-dark.svg + share/icons/application/scalable/apps/keepassxc-locked.svg + share/icons/application/scalable/apps/keepassxc-unlocked.svg + share/icons/application/scalable/apps/preferences-desktop-icons.svg + share/icons/application/scalable/apps/preferences-system-network-sharing.svg + share/icons/application/scalable/apps/utilities-terminal.svg + share/icons/application/scalable/categories/preferences-other.svg + share/icons/application/scalable/mimetypes/application-x-keepassxc.svg + share/icons/application/scalable/status/dialog-error.svg + share/icons/application/scalable/status/dialog-information.svg + share/icons/application/scalable/status/dialog-warning.svg + share/icons/application/scalable/status/security-high.svg Copyright: 2019 Austin Andrews <http://templarian.com/> License: SIL OPEN FONT LICENSE Version 1.1 Comment: Taken from Material Design icon set (https://github.com/templarian/MaterialDesign/) diff --git a/share/icons/application/scalable/actions/document-save-copy.svg b/share/icons/application/scalable/actions/document-save-copy.svg new file mode 100644 index 000000000..863eee856 --- /dev/null +++ b/share/icons/application/scalable/actions/document-save-copy.svg @@ -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" id="mdi-content-save-move-outline" width="24" height="24" viewBox="0 0 24 24"><path d="M13 17H17V14L22 18.5L17 23V20H13V17M14 12.8C13.5 12.31 12.78 12 12 12C10.34 12 9 13.34 9 15C9 16.31 9.84 17.41 11 17.82C11.07 15.67 12.27 13.8 14 12.8M11.09 19H5V5H16.17L19 7.83V12.35C19.75 12.61 20.42 13 21 13.54V7L17 3H5C3.89 3 3 3.9 3 5V19C3 20.1 3.89 21 5 21H11.81C11.46 20.39 11.21 19.72 11.09 19M6 10H15V6H6V10Z" /></svg>
\ No newline at end of file diff --git a/share/icons/icons.qrc b/share/icons/icons.qrc index 0f3ff6aff..f4f551e14 100644 --- a/share/icons/icons.qrc +++ b/share/icons/icons.qrc @@ -23,6 +23,7 @@ <file>application/scalable/actions/document-properties.svg</file> <file>application/scalable/actions/document-save.svg</file> <file>application/scalable/actions/document-save-as.svg</file> + <file>application/scalable/actions/document-save-copy.svg</file> <file>application/scalable/actions/donate.svg</file> <file>application/scalable/actions/edit-clear-locationbar-ltr.svg</file> <file>application/scalable/actions/edit-clear-locationbar-rtl.svg</file> diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp index 8088ae829..f484815af 100644 --- a/src/gui/DatabaseTabWidget.cpp +++ b/src/gui/DatabaseTabWidget.cpp @@ -393,6 +393,20 @@ bool DatabaseTabWidget::saveDatabaseAs(int index) return ok; } +bool DatabaseTabWidget::saveDatabaseBackup(int index) +{ + if (index == -1) { + index = currentIndex(); + } + + auto* dbWidget = databaseWidgetFromIndex(index); + bool ok = dbWidget->saveBackup(); + if (ok) { + updateLastDatabases(dbWidget->database()->filePath()); + } + return ok; +} + void DatabaseTabWidget::closeDatabaseFromSender() { auto* dbWidget = qobject_cast<DatabaseWidget*>(sender()); diff --git a/src/gui/DatabaseTabWidget.h b/src/gui/DatabaseTabWidget.h index b8cbdbb6f..d7c656908 100644 --- a/src/gui/DatabaseTabWidget.h +++ b/src/gui/DatabaseTabWidget.h @@ -68,6 +68,7 @@ public slots: void importOpVaultDatabase(); bool saveDatabase(int index = -1); bool saveDatabaseAs(int index = -1); + bool saveDatabaseBackup(int index = -1); void exportToCsv(); void exportToHtml(); diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index a7531537e..310f141d7 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -1866,6 +1866,53 @@ bool DatabaseWidget::saveAs() } } +/** + * Save copy of database under a new user-selected filename. + * + * @return true on success + */ +bool DatabaseWidget::saveBackup() +{ + while (true) { + QString oldFilePath = m_db->filePath(); + if (!QFileInfo::exists(oldFilePath)) { + oldFilePath = QDir::toNativeSeparators(config()->get(Config::LastDir).toString() + "/" + + tr("Passwords").append(".kdbx")); + } + const QString newFilePath = fileDialog()->getSaveFileName(this, + tr("Save database backup"), + oldFilePath, + tr("KeePass 2 Database").append(" (*.kdbx)"), + nullptr, + nullptr); + + if (!newFilePath.isEmpty()) { + // Ensure we don't recurse back into this function + m_db->setReadOnly(false); + m_db->setFilePath(newFilePath); + m_saveAttempts = 0; + + bool modified = m_db->isModified(); + + if (!save()) { + // Failed to save, try again + m_db->setFilePath(oldFilePath); + continue; + } + + m_db->setFilePath(oldFilePath); + if (modified) { + // Source database is marked as clean when copy is saved, even if source has unsaved changes + m_db->markAsModified(); + } + return true; + } + + // Canceled file selection + return false; + } +} + void DatabaseWidget::showMessage(const QString& text, MessageWidget::MessageType type, bool showClosebutton, diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index 0d0afe8f1..3f25a4361 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -158,6 +158,7 @@ public slots: bool lock(); bool save(); bool saveAs(); + bool saveBackup(); void replaceDatabase(QSharedPointer<Database> db); void createEntry(); diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 0a0118eda..476a675fa 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -324,6 +324,7 @@ MainWindow::MainWindow() m_ui->actionDatabaseOpen->setIcon(resources()->icon("document-open")); m_ui->actionDatabaseSave->setIcon(resources()->icon("document-save")); m_ui->actionDatabaseSaveAs->setIcon(resources()->icon("document-save-as")); + m_ui->actionDatabaseSaveBackup->setIcon(resources()->icon("document-save-copy")); m_ui->actionDatabaseClose->setIcon(resources()->icon("document-close")); m_ui->actionReports->setIcon(resources()->icon("reports")); m_ui->actionChangeDatabaseSettings->setIcon(resources()->icon("document-edit")); @@ -396,6 +397,7 @@ MainWindow::MainWindow() connect(m_ui->actionDatabaseOpen, SIGNAL(triggered()), m_ui->tabWidget, SLOT(openDatabase())); connect(m_ui->actionDatabaseSave, SIGNAL(triggered()), m_ui->tabWidget, SLOT(saveDatabase())); connect(m_ui->actionDatabaseSaveAs, SIGNAL(triggered()), m_ui->tabWidget, SLOT(saveDatabaseAs())); + connect(m_ui->actionDatabaseSaveBackup, SIGNAL(triggered()), m_ui->tabWidget, SLOT(saveDatabaseBackup())); connect(m_ui->actionDatabaseClose, SIGNAL(triggered()), m_ui->tabWidget, SLOT(closeCurrentDatabaseTab())); connect(m_ui->actionDatabaseMerge, SIGNAL(triggered()), m_ui->tabWidget, SLOT(mergeDatabase())); connect(m_ui->actionChangeMasterKey, SIGNAL(triggered()), m_ui->tabWidget, SLOT(changeMasterKey())); @@ -681,6 +683,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) m_ui->actionChangeDatabaseSettings->setEnabled(true); m_ui->actionDatabaseSave->setEnabled(m_ui->tabWidget->canSave()); m_ui->actionDatabaseSaveAs->setEnabled(true); + m_ui->actionDatabaseSaveBackup->setEnabled(true); m_ui->menuExport->setEnabled(true); m_ui->actionExportCsv->setEnabled(true); m_ui->actionExportHtml->setEnabled(true); @@ -736,6 +739,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) m_ui->actionChangeDatabaseSettings->setEnabled(false); m_ui->actionDatabaseSave->setEnabled(false); m_ui->actionDatabaseSaveAs->setEnabled(false); + m_ui->actionDatabaseSaveBackup->setEnabled(false); m_ui->menuExport->setEnabled(false); m_ui->actionExportCsv->setEnabled(false); m_ui->actionExportHtml->setEnabled(false); @@ -764,6 +768,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) m_ui->actionChangeDatabaseSettings->setEnabled(false); m_ui->actionDatabaseSave->setEnabled(false); m_ui->actionDatabaseSaveAs->setEnabled(false); + m_ui->actionDatabaseSaveBackup->setEnabled(false); m_ui->actionDatabaseClose->setEnabled(false); m_ui->menuExport->setEnabled(false); m_ui->actionExportCsv->setEnabled(false); diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui index c40191d6b..bc8aa48de 100644 --- a/src/gui/MainWindow.ui +++ b/src/gui/MainWindow.ui @@ -260,6 +260,7 @@ <addaction name="menuRecentDatabases"/> <addaction name="actionDatabaseSave"/> <addaction name="actionDatabaseSaveAs"/> + <addaction name="actionDatabaseSaveBackup"/> <addaction name="actionDatabaseClose"/> <addaction name="separator"/> <addaction name="actionChangeMasterKey"/> @@ -332,9 +333,6 @@ <addaction name="separator"/> <addaction name="actionEntryOpenUrl"/> <addaction name="actionEntryDownloadIcon"/> - <addaction name="separator"/> - <addaction name="actionEntryAddToAgent"/> - <addaction name="actionEntryRemoveFromAgent"/> </widget> <widget class="QMenu" name="menuGroups"> <property name="title"> @@ -808,6 +806,14 @@ <string notr="true">Ctrl+/</string> </property> </action> + <action name="actionDatabaseSaveBackup"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Save Database Backup...</string> + </property> + </action> <action name="actionEntryAddToAgent"> <property name="text"> <string>Add key to SSH Agent</string> diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index e167b9a30..5ca7ccdbe 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -1212,6 +1212,35 @@ void TestGui::testSaveAs() tmpFile.remove(); } +void TestGui::testSaveBackup() +{ + m_db->metadata()->setName("testSaveBackup"); + + QFileInfo fileInfo(m_dbFilePath); + QDateTime lastModified = fileInfo.lastModified(); + + // open temporary file so it creates a filename + TemporaryFile tmpFile; + QVERIFY(tmpFile.open()); + QString tmpFileName = tmpFile.fileName(); + tmpFile.remove(); + + // wait for modified timer + QTRY_COMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("testSaveBackup*")); + + fileDialog()->setNextFileName(tmpFileName); + + triggerAction("actionDatabaseSaveBackup"); + + QCOMPARE(m_tabWidget->tabName(m_tabWidget->currentIndex()), QString("testSaveBackup*")); + + checkDatabase(tmpFileName); + + fileInfo.refresh(); + QCOMPARE(fileInfo.lastModified(), lastModified); + tmpFile.remove(); +} + void TestGui::testSave() { m_db->metadata()->setName("testSave"); diff --git a/tests/gui/TestGui.h b/tests/gui/TestGui.h index cd329dd06..8d82e021e 100644 --- a/tests/gui/TestGui.h +++ b/tests/gui/TestGui.h @@ -61,6 +61,7 @@ private slots: void testDragAndDropEntry(); void testDragAndDropGroup(); void testSaveAs(); + void testSaveBackup(); void testSave(); void testDatabaseSettings(); void testKeePass1Import(); diff --git a/utils/makeicons.sh b/utils/makeicons.sh index 61ee74e4d..c9753a639 100644 --- a/utils/makeicons.sh +++ b/utils/makeicons.sh @@ -86,6 +86,7 @@ map() { document-properties) echo file-edit-outline ;; document-save) echo content-save-outline ;; document-save-as) echo content-save-all-outline ;; + document-save-copy) echo content-save-move-outline ;; donate) echo gift-outline ;; edit-clear-locationbar-ltr) echo backspace-reverse-outline ;; edit-clear-locationbar-rtl) echo backspace-outline ;; |