Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/keepassxreboot/keepassxc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonathan White <support@dmapps.us>2019-03-20 01:59:14 +0300
committerJonathan White <support@dmapps.us>2019-03-20 01:59:14 +0300
commitc51752df3913329d6fc78563c54ddd6ebd994b0f (patch)
tree54df6109d1459d50d6d7ac2c0378c54a11ef9c07 /src/gui/MainWindow.cpp
parent6fe821c30ec747e7fda0a8abe75d774d61a21054 (diff)
parent348a7e511118f48b9eb0527bf7b30837b01f03e0 (diff)
Release 2.4.02.4.0
- New Database Wizard [#1952] - Advanced Search [#1797] - Automatic update checker [#2648] - KeeShare database synchronization [#2109, #1992, #2738, #2742, #2746, #2739] - Improve favicon fetching; transition to Duck-Duck-Go [#2795, #2011, #2439] - Remove KeePassHttp support [#1752] - CLI: output info to stderr for easier scripting [#2558] - CLI: Add --quiet option [#2507] - CLI: Add create command [#2540] - CLI: Add recursive listing of entries [#2345] - CLI: Fix stdin/stdout encoding on Windows [#2425] - SSH Agent: Support OpenSSH for Windows [#1994] - macOS: TouchID Quick Unlock [#1851] - macOS: Multiple improvements; include CLI in DMG [#2165, #2331, #2583] - Linux: Prevent Klipper from storing secrets in clipboard [#1969] - Linux: Use polling based file watching for NFS [#2171] - Linux: Enable use of browser plugin in Snap build [#2802] - TOTP QR Code Generator [#1167] - High-DPI Scaling for 4k screens [#2404] - Make keyboard shortcuts more consistent [#2431] - Warn user if deleting referenced entries [#1744] - Allow toolbar to be hidden and repositioned [#1819, #2357] - Increase max allowed database timeout to 12 hours [#2173] - Password generator uses existing password length by default [#2318] - Improve alert message box button labels [#2376] - Show message when a database merge makes no changes [#2551] - Browser Integration Enhancements [#1497, #2253, #1904, #2232, #1850, #2218, #2391, #2396, #2542, #2622, #2637, #2790] - Overall Code Improvements [#2316, #2284, #2351, #2402, #2410, #2419, #2422, #2443, #2491, #2506, #2610, #2667, #2709, #2731]
Diffstat (limited to 'src/gui/MainWindow.cpp')
-rw-r--r--src/gui/MainWindow.cpp843
1 files changed, 445 insertions, 398 deletions
diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp
index f6cc2e4bb..6e3c96af0 100644
--- a/src/gui/MainWindow.cpp
+++ b/src/gui/MainWindow.cpp
@@ -20,10 +20,11 @@
#include "ui_MainWindow.h"
#include <QCloseEvent>
+#include <QDesktopServices>
+#include <QFileInfo>
#include <QMimeData>
#include <QShortcut>
#include <QTimer>
-#include <QDesktopServices>
#include "config-keepassx.h"
@@ -32,54 +33,62 @@
#include "core/FilePath.h"
#include "core/InactivityTimer.h"
#include "core/Metadata.h"
-#include "format/KeePass2Writer.h"
+#include "core/Tools.h"
#include "gui/AboutDialog.h"
#include "gui/DatabaseWidget.h"
-#include "gui/DatabaseRepairWidget.h"
-#include "gui/FileDialog.h"
-#include "gui/MessageBox.h"
#include "gui/SearchWidget.h"
+#include "keys/CompositeKey.h"
+#include "keys/FileKey.h"
+#include "keys/PasswordKey.h"
-#ifdef WITH_XC_HTTP
-#include "http/Service.h"
-#include "http/HttpSettings.h"
-#include "http/OptionDialog.h"
+#ifdef WITH_XC_NETWORKING
+#include "gui/MessageBox.h"
+#include "gui/UpdateCheckDialog.h"
+#include "updatecheck/UpdateChecker.h"
#endif
#ifdef WITH_XC_SSHAGENT
#include "sshagent/AgentSettingsPage.h"
#include "sshagent/SSHAgent.h"
#endif
-
+#if defined(WITH_XC_KEESHARE)
+#include "keeshare/KeeShare.h"
+#include "keeshare/SettingsPageKeeShare.h"
+#endif
#ifdef WITH_XC_BROWSER
-#include "browser/NativeMessagingHost.h"
-#include "browser/BrowserSettings.h"
#include "browser/BrowserOptionDialog.h"
+#include "browser/BrowserSettings.h"
+#include "browser/NativeMessagingHost.h"
#endif
-#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) && !defined(QT_NO_DBUS)
+#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) && !defined(QT_NO_DBUS)
+#include "gui/MainWindowAdaptor.h"
#include <QList>
#include <QtDBus/QtDBus>
-#include "gui/MainWindowAdaptor.h"
#endif
-#include "gui/SettingsWidget.h"
+#include "gui/ApplicationSettingsWidget.h"
#include "gui/PasswordGeneratorWidget.h"
-#ifdef WITH_XC_HTTP
-class HttpPlugin: public ISettingsPage
+#include "touchid/TouchID.h"
+
+#ifdef WITH_XC_BROWSER
+class BrowserPlugin : public ISettingsPage
{
public:
- HttpPlugin(DatabaseTabWidget* tabWidget)
+ explicit BrowserPlugin(DatabaseTabWidget* tabWidget)
{
- m_service = new Service(tabWidget);
+ m_nativeMessagingHost =
+ QSharedPointer<NativeMessagingHost>(new NativeMessagingHost(tabWidget, browserSettings()->isEnabled()));
}
- ~HttpPlugin() = default;
+ ~BrowserPlugin()
+ {
+ }
QString name() override
{
- return QObject::tr("Legacy Browser Integration");
+ return QObject::tr("Browser Integration");
}
QIcon icon() override
@@ -87,91 +96,51 @@ public:
return FilePath::instance()->icon("apps", "internet-web-browser");
}
- QWidget * createWidget() override
+ QWidget* createWidget() override
{
- OptionDialog* dlg = new OptionDialog();
- QObject::connect(dlg, SIGNAL(removeSharedEncryptionKeys()), m_service, SLOT(removeSharedEncryptionKeys()));
- QObject::connect(dlg, SIGNAL(removeStoredPermissions()), m_service, SLOT(removeStoredPermissions()));
+ BrowserOptionDialog* dlg = new BrowserOptionDialog();
return dlg;
}
void loadSettings(QWidget* widget) override
{
- qobject_cast<OptionDialog*>(widget)->loadSettings();
+ qobject_cast<BrowserOptionDialog*>(widget)->loadSettings();
}
void saveSettings(QWidget* widget) override
{
- qobject_cast<OptionDialog*>(widget)->saveSettings();
- if (HttpSettings::isEnabled())
- m_service->start();
- else
- m_service->stop();
- }
-private:
- Service* m_service;
-};
-#endif
-
-#ifdef WITH_XC_BROWSER
-class BrowserPlugin: public ISettingsPage
-{
- public:
- BrowserPlugin(DatabaseTabWidget* tabWidget) {
- m_nativeMessagingHost = QSharedPointer<NativeMessagingHost>(new NativeMessagingHost(tabWidget, BrowserSettings::isEnabled()));
- }
-
- ~BrowserPlugin() {
-
- }
-
- QString name() override
- {
- return QObject::tr("Browser Integration");
- }
-
- QIcon icon() override
- {
- return FilePath::instance()->icon("apps", "internet-web-browser");
- }
-
- QWidget* createWidget() override {
- BrowserOptionDialog* dlg = new BrowserOptionDialog();
- QObject::connect(dlg, SIGNAL(removeSharedEncryptionKeys()), m_nativeMessagingHost.data(), SLOT(removeSharedEncryptionKeys()));
- QObject::connect(dlg, SIGNAL(removeStoredPermissions()), m_nativeMessagingHost.data(), SLOT(removeStoredPermissions()));
- return dlg;
- }
-
- void loadSettings(QWidget* widget) override
- {
- qobject_cast<BrowserOptionDialog*>(widget)->loadSettings();
+ qobject_cast<BrowserOptionDialog*>(widget)->saveSettings();
+ if (browserSettings()->isEnabled()) {
+ m_nativeMessagingHost->run();
+ } else {
+ m_nativeMessagingHost->stop();
}
+ }
- void saveSettings(QWidget* widget) override
- {
- qobject_cast<BrowserOptionDialog*>(widget)->saveSettings();
- if (BrowserSettings::isEnabled()) {
- m_nativeMessagingHost->run();
- } else {
- m_nativeMessagingHost->stop();
- }
- }
- private:
- QSharedPointer<NativeMessagingHost> m_nativeMessagingHost;
+private:
+ QSharedPointer<NativeMessagingHost> m_nativeMessagingHost;
};
#endif
const QString MainWindow::BaseWindowTitle = "KeePassXC";
+MainWindow* g_MainWindow = nullptr;
+MainWindow* getMainWindow()
+{
+ return g_MainWindow;
+}
+
MainWindow::MainWindow()
: m_ui(new Ui::MainWindow())
, m_trayIcon(nullptr)
, m_appExitCalled(false)
, m_appExiting(false)
{
+ g_MainWindow = this;
+
m_ui->setupUi(this);
-
-#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) && !defined(QT_NO_DBUS)
+
+#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) && !defined(QT_NO_DBUS)
new MainWindowAdaptor(this);
QDBusConnection dbus = QDBusConnection::sessionBus();
dbus.registerObject("/keepassxc", this);
@@ -180,10 +149,8 @@ MainWindow::MainWindow()
setAcceptDrops(true);
- m_ui->toolBar->setContextMenuPolicy(Qt::PreventContextMenu);
-
// Setup the search widget in the toolbar
- SearchWidget *search = new SearchWidget();
+ auto* search = new SearchWidget();
search->connectSignals(m_actionMultiplexer);
m_searchWidgetAction = m_ui->toolBar->addWidget(search);
m_searchWidgetAction->setEnabled(false);
@@ -191,23 +158,31 @@ MainWindow::MainWindow()
m_countDefaultAttributes = m_ui->menuEntryCopyAttribute->actions().size();
restoreGeometry(config()->get("GUI/MainWindowGeometry").toByteArray());
+ restoreState(config()->get("GUI/MainWindowState").toByteArray());
#ifdef WITH_XC_BROWSER
m_ui->settingsWidget->addSettingsPage(new BrowserPlugin(m_ui->tabWidget));
#endif
-#ifdef WITH_XC_HTTP
- m_ui->settingsWidget->addSettingsPage(new HttpPlugin(m_ui->tabWidget));
-#endif
+
#ifdef WITH_XC_SSHAGENT
SSHAgent::init(this);
connect(SSHAgent::instance(), SIGNAL(error(QString)), this, SLOT(showErrorMessage(QString)));
m_ui->settingsWidget->addSettingsPage(new AgentSettingsPage(m_ui->tabWidget));
#endif
+#if defined(WITH_XC_KEESHARE)
+ KeeShare::init(this);
+ m_ui->settingsWidget->addSettingsPage(new SettingsPageKeeShare(m_ui->tabWidget));
+ connect(KeeShare::instance(),
+ SIGNAL(sharingMessage(QString, MessageWidget::MessageType)),
+ SLOT(displayGlobalMessage(QString, MessageWidget::MessageType)));
+#endif
setWindowIcon(filePath()->applicationIcon());
m_ui->globalMessageWidget->setHidden(true);
- connect(m_ui->globalMessageWidget, &MessageWidget::linkActivated, &MessageWidget::openHttpUrl);
- connect(m_ui->globalMessageWidget, SIGNAL(showAnimationStarted()), m_ui->globalMessageWidgetContainer, SLOT(show()));
- connect(m_ui->globalMessageWidget, SIGNAL(hideAnimationFinished()), m_ui->globalMessageWidgetContainer, SLOT(hide()));
+ // clang-format off
+ connect(m_ui->globalMessageWidget, &MessageWidget::linkActivated, &MessageWidget::openHttpUrl);
+ connect(m_ui->globalMessageWidget, SIGNAL(showAnimationStarted()), m_ui->globalMessageWidgetContainer, SLOT(show()));
+ connect(m_ui->globalMessageWidget, SIGNAL(hideAnimationFinished()), m_ui->globalMessageWidgetContainer, SLOT(hide()));
+ // clang-format on
m_clearHistoryAction = new QAction(tr("Clear history"), m_ui->menuFile);
m_lastDatabasesActions = new QActionGroup(m_ui->menuRecentDatabases);
@@ -216,14 +191,13 @@ MainWindow::MainWindow()
connect(m_ui->menuRecentDatabases, SIGNAL(aboutToShow()), this, SLOT(updateLastDatabasesMenu()));
m_copyAdditionalAttributeActions = new QActionGroup(m_ui->menuEntryCopyAttribute);
- m_actionMultiplexer.connect(m_copyAdditionalAttributeActions, SIGNAL(triggered(QAction*)),
- SLOT(copyAttribute(QAction*)));
- connect(m_ui->menuEntryCopyAttribute, SIGNAL(aboutToShow()),
- this, SLOT(updateCopyAttributesMenu()));
+ m_actionMultiplexer.connect(
+ m_copyAdditionalAttributeActions, SIGNAL(triggered(QAction*)), SLOT(copyAttribute(QAction*)));
+ connect(m_ui->menuEntryCopyAttribute, SIGNAL(aboutToShow()), this, SLOT(updateCopyAttributesMenu()));
Qt::Key globalAutoTypeKey = static_cast<Qt::Key>(config()->get("GlobalAutoTypeKey").toInt());
- Qt::KeyboardModifiers globalAutoTypeModifiers = static_cast<Qt::KeyboardModifiers>(
- config()->get("GlobalAutoTypeModifiers").toInt());
+ Qt::KeyboardModifiers globalAutoTypeModifiers =
+ static_cast<Qt::KeyboardModifiers>(config()->get("GlobalAutoTypeModifiers").toInt());
if (globalAutoTypeKey > 0 && globalAutoTypeModifiers > 0) {
autoType()->registerGlobalShortcut(globalAutoTypeKey, globalAutoTypeModifiers);
}
@@ -231,14 +205,17 @@ MainWindow::MainWindow()
m_ui->actionEntryAutoType->setVisible(autoType()->isAvailable());
m_inactivityTimer = new InactivityTimer(this);
- connect(m_inactivityTimer, SIGNAL(inactivityDetected()),
- this, SLOT(lockDatabasesAfterInactivity()));
+ connect(m_inactivityTimer, SIGNAL(inactivityDetected()), this, SLOT(lockDatabasesAfterInactivity()));
+#ifdef WITH_XC_TOUCHID
+ m_touchIDinactivityTimer = new InactivityTimer(this);
+ connect(m_touchIDinactivityTimer, SIGNAL(inactivityDetected()), this, SLOT(forgetTouchIDAfterInactivity()));
+#endif
applySettingsChanges();
m_ui->actionDatabaseNew->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_N);
setShortcut(m_ui->actionDatabaseOpen, QKeySequence::Open, Qt::CTRL + Qt::Key_O);
setShortcut(m_ui->actionDatabaseSave, QKeySequence::Save, Qt::CTRL + Qt::Key_S);
- setShortcut(m_ui->actionDatabaseSaveAs, QKeySequence::SaveAs);
+ setShortcut(m_ui->actionDatabaseSaveAs, QKeySequence::SaveAs, Qt::CTRL + Qt::SHIFT + Qt::Key_S);
setShortcut(m_ui->actionDatabaseClose, QKeySequence::Close, Qt::CTRL + Qt::Key_W);
m_ui->actionLockDatabases->setShortcut(Qt::CTRL + Qt::Key_L);
setShortcut(m_ui->actionQuit, QKeySequence::Quit, Qt::CTRL + Qt::Key_Q);
@@ -251,11 +228,37 @@ MainWindow::MainWindow()
m_ui->actionEntryCopyTotp->setShortcut(Qt::CTRL + Qt::Key_T);
m_ui->actionEntryCopyUsername->setShortcut(Qt::CTRL + Qt::Key_B);
m_ui->actionEntryCopyPassword->setShortcut(Qt::CTRL + Qt::Key_C);
- setShortcut(m_ui->actionEntryAutoType, QKeySequence::Paste, Qt::CTRL + Qt::Key_V);
- m_ui->actionEntryOpenUrl->setShortcut(Qt::CTRL + Qt::Key_U);
- m_ui->actionEntryCopyURL->setShortcut(Qt::CTRL + Qt::ALT + Qt::Key_U);
+ m_ui->actionEntryAutoType->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);
+
+#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
+ // Qt 5.10 introduced a new "feature" to hide shortcuts in context menus
+ // Unfortunately, Qt::AA_DontShowShortcutsInContextMenus is broken, have to manually enable them
+ m_ui->actionEntryNew->setShortcutVisibleInContextMenu(true);
+ m_ui->actionEntryEdit->setShortcutVisibleInContextMenu(true);
+ m_ui->actionEntryDelete->setShortcutVisibleInContextMenu(true);
+ m_ui->actionEntryClone->setShortcutVisibleInContextMenu(true);
+ m_ui->actionEntryTotp->setShortcutVisibleInContextMenu(true);
+ m_ui->actionEntryCopyTotp->setShortcutVisibleInContextMenu(true);
+ m_ui->actionEntryCopyUsername->setShortcutVisibleInContextMenu(true);
+ m_ui->actionEntryCopyPassword->setShortcutVisibleInContextMenu(true);
+ m_ui->actionEntryAutoType->setShortcutVisibleInContextMenu(true);
+ m_ui->actionEntryOpenUrl->setShortcutVisibleInContextMenu(true);
+ m_ui->actionEntryCopyURL->setShortcutVisibleInContextMenu(true);
+#endif
+ // Control window state
new QShortcut(Qt::CTRL + Qt::Key_M, this, SLOT(showMinimized()));
+ new QShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_M, this, SLOT(hideWindow()));
+ // Control database tabs
+ new QShortcut(Qt::CTRL + Qt::Key_Tab, this, SLOT(selectNextDatabaseTab()));
+ new QShortcut(Qt::CTRL + Qt::Key_PageUp, this, SLOT(selectNextDatabaseTab()));
+ new QShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Tab, this, SLOT(selectPreviousDatabaseTab()));
+ new QShortcut(Qt::CTRL + Qt::Key_PageDown, this, SLOT(selectPreviousDatabaseTab()));
+ // Toggle password and username visibility in entry view
+ new QShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_C, this, SLOT(togglePasswordsHidden()));
+ new QShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_B, this, SLOT(toggleUsernamesHidden()));
m_ui->actionDatabaseNew->setIcon(filePath()->icon("actions", "document-new"));
m_ui->actionDatabaseOpen->setIcon(filePath()->icon("actions", "document-open"));
@@ -263,58 +266,50 @@ MainWindow::MainWindow()
m_ui->actionDatabaseSaveAs->setIcon(filePath()->icon("actions", "document-save-as"));
m_ui->actionDatabaseClose->setIcon(filePath()->icon("actions", "document-close"));
m_ui->actionChangeDatabaseSettings->setIcon(filePath()->icon("actions", "document-edit"));
- m_ui->actionChangeMasterKey->setIcon(filePath()->icon("actions", "database-change-key", false));
- m_ui->actionLockDatabases->setIcon(filePath()->icon("actions", "document-encrypt", false));
+ m_ui->actionChangeMasterKey->setIcon(filePath()->icon("actions", "database-change-key"));
+ m_ui->actionLockDatabases->setIcon(filePath()->icon("actions", "document-encrypt"));
m_ui->actionQuit->setIcon(filePath()->icon("actions", "application-exit"));
- m_ui->actionEntryNew->setIcon(filePath()->icon("actions", "entry-new", false));
- m_ui->actionEntryClone->setIcon(filePath()->icon("actions", "entry-clone", false));
- m_ui->actionEntryEdit->setIcon(filePath()->icon("actions", "entry-edit", false));
- m_ui->actionEntryDelete->setIcon(filePath()->icon("actions", "entry-delete", false));
- m_ui->actionEntryAutoType->setIcon(filePath()->icon("actions", "auto-type", false));
- m_ui->actionEntryCopyUsername->setIcon(filePath()->icon("actions", "username-copy", false));
- m_ui->actionEntryCopyPassword->setIcon(filePath()->icon("actions", "password-copy", false));
- m_ui->actionEntryCopyURL->setIcon(filePath()->icon("actions", "url-copy", false));
+ m_ui->actionEntryNew->setIcon(filePath()->icon("actions", "entry-new"));
+ m_ui->actionEntryClone->setIcon(filePath()->icon("actions", "entry-clone"));
+ m_ui->actionEntryEdit->setIcon(filePath()->icon("actions", "entry-edit"));
+ m_ui->actionEntryDelete->setIcon(filePath()->icon("actions", "entry-delete"));
+ m_ui->actionEntryAutoType->setIcon(filePath()->icon("actions", "auto-type"));
+ m_ui->actionEntryCopyUsername->setIcon(filePath()->icon("actions", "username-copy"));
+ m_ui->actionEntryCopyPassword->setIcon(filePath()->icon("actions", "password-copy"));
+ m_ui->actionEntryCopyURL->setIcon(filePath()->icon("actions", "url-copy"));
- m_ui->actionGroupNew->setIcon(filePath()->icon("actions", "group-new", false));
- m_ui->actionGroupEdit->setIcon(filePath()->icon("actions", "group-edit", false));
- m_ui->actionGroupDelete->setIcon(filePath()->icon("actions", "group-delete", false));
- m_ui->actionGroupEmptyRecycleBin->setIcon(filePath()->icon("actions", "group-empty-trash", false));
+ m_ui->actionGroupNew->setIcon(filePath()->icon("actions", "group-new"));
+ m_ui->actionGroupEdit->setIcon(filePath()->icon("actions", "group-edit"));
+ m_ui->actionGroupDelete->setIcon(filePath()->icon("actions", "group-delete"));
+ m_ui->actionGroupEmptyRecycleBin->setIcon(filePath()->icon("actions", "group-empty-trash"));
m_ui->actionSettings->setIcon(filePath()->icon("actions", "configure"));
- m_ui->actionPasswordGenerator->setIcon(filePath()->icon("actions", "password-generator", false));
+ m_ui->actionPasswordGenerator->setIcon(filePath()->icon("actions", "password-generator"));
m_ui->actionAbout->setIcon(filePath()->icon("actions", "help-about"));
+ m_ui->actionCheckForUpdates->setIcon(filePath()->icon("actions", "system-software-update"));
- m_actionMultiplexer.connect(SIGNAL(currentModeChanged(DatabaseWidget::Mode)),
- this, SLOT(setMenuActionState(DatabaseWidget::Mode)));
- m_actionMultiplexer.connect(SIGNAL(groupChanged()),
- this, SLOT(setMenuActionState()));
- m_actionMultiplexer.connect(SIGNAL(entrySelectionChanged()),
- this, SLOT(setMenuActionState()));
- m_actionMultiplexer.connect(SIGNAL(groupContextMenuRequested(QPoint)),
- this, SLOT(showGroupContextMenu(QPoint)));
- m_actionMultiplexer.connect(SIGNAL(entryContextMenuRequested(QPoint)),
- this, SLOT(showEntryContextMenu(QPoint)));
+ m_actionMultiplexer.connect(
+ SIGNAL(currentModeChanged(DatabaseWidget::Mode)), this, SLOT(setMenuActionState(DatabaseWidget::Mode)));
+ m_actionMultiplexer.connect(SIGNAL(groupChanged()), this, SLOT(setMenuActionState()));
+ m_actionMultiplexer.connect(SIGNAL(entrySelectionChanged()), this, SLOT(setMenuActionState()));
+ m_actionMultiplexer.connect(SIGNAL(groupContextMenuRequested(QPoint)), this, SLOT(showGroupContextMenu(QPoint)));
+ m_actionMultiplexer.connect(SIGNAL(entryContextMenuRequested(QPoint)), this, SLOT(showEntryContextMenu(QPoint)));
// Notify search when the active database changes or gets locked
- connect(m_ui->tabWidget, SIGNAL(activateDatabaseChanged(DatabaseWidget*)),
- search, SLOT(databaseChanged(DatabaseWidget*)));
- connect(m_ui->tabWidget, SIGNAL(databaseLocked(DatabaseWidget*)),
- search, SLOT(databaseChanged()));
-
- connect(m_ui->tabWidget, SIGNAL(tabNameChanged()),
- SLOT(updateWindowTitle()));
- connect(m_ui->tabWidget, SIGNAL(currentChanged(int)),
- SLOT(updateWindowTitle()));
- connect(m_ui->tabWidget, SIGNAL(currentChanged(int)),
- SLOT(databaseTabChanged(int)));
- connect(m_ui->tabWidget, SIGNAL(currentChanged(int)),
- SLOT(setMenuActionState()));
- connect(m_ui->tabWidget, SIGNAL(databaseLocked(DatabaseWidget*)),
- SLOT(databaseStatusChanged(DatabaseWidget*)));
- connect(m_ui->tabWidget, SIGNAL(databaseUnlocked(DatabaseWidget*)),
- SLOT(databaseStatusChanged(DatabaseWidget*)));
+ connect(m_ui->tabWidget,
+ SIGNAL(activateDatabaseChanged(DatabaseWidget*)),
+ search,
+ SLOT(databaseChanged(DatabaseWidget*)));
+ connect(m_ui->tabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), search, SLOT(databaseChanged()));
+
+ connect(m_ui->tabWidget, SIGNAL(tabNameChanged()), SLOT(updateWindowTitle()));
+ connect(m_ui->tabWidget, SIGNAL(currentChanged(int)), SLOT(updateWindowTitle()));
+ connect(m_ui->tabWidget, SIGNAL(currentChanged(int)), SLOT(databaseTabChanged(int)));
+ connect(m_ui->tabWidget, SIGNAL(currentChanged(int)), SLOT(setMenuActionState()));
+ connect(m_ui->tabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), SLOT(databaseStatusChanged(DatabaseWidget*)));
+ connect(m_ui->tabWidget, SIGNAL(databaseUnlocked(DatabaseWidget*)), SLOT(databaseStatusChanged(DatabaseWidget*)));
connect(m_ui->stackedWidget, SIGNAL(currentChanged(int)), SLOT(setMenuActionState()));
connect(m_ui->stackedWidget, SIGNAL(currentChanged(int)), SLOT(updateWindowTitle()));
connect(m_ui->settingsWidget, SIGNAL(accepted()), SLOT(applySettingsChanges()));
@@ -322,73 +317,42 @@ MainWindow::MainWindow()
connect(m_ui->settingsWidget, SIGNAL(accepted()), SLOT(switchToDatabases()));
connect(m_ui->settingsWidget, SIGNAL(rejected()), SLOT(switchToDatabases()));
- connect(m_ui->actionDatabaseNew, SIGNAL(triggered()), m_ui->tabWidget,
- SLOT(newDatabase()));
- 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->actionDatabaseClose, SIGNAL(triggered()), m_ui->tabWidget,
- SLOT(closeDatabase()));
- connect(m_ui->actionDatabaseMerge, SIGNAL(triggered()), m_ui->tabWidget,
- SLOT(mergeDatabase()));
- connect(m_ui->actionChangeMasterKey, SIGNAL(triggered()), m_ui->tabWidget,
- SLOT(changeMasterKey()));
- connect(m_ui->actionChangeDatabaseSettings, SIGNAL(triggered()), m_ui->tabWidget,
- SLOT(changeDatabaseSettings()));
- connect(m_ui->actionImportCsv, SIGNAL(triggered()), m_ui->tabWidget,
- SLOT(importCsv()));
- connect(m_ui->actionImportKeePass1, SIGNAL(triggered()), m_ui->tabWidget,
- SLOT(importKeePass1Database()));
- connect(m_ui->actionRepairDatabase, SIGNAL(triggered()), this,
- SLOT(repairDatabase()));
- connect(m_ui->actionExportCsv, SIGNAL(triggered()), m_ui->tabWidget,
- SLOT(exportToCsv()));
- connect(m_ui->actionLockDatabases, SIGNAL(triggered()), m_ui->tabWidget,
- SLOT(lockDatabases()));
+ connect(m_ui->actionDatabaseNew, SIGNAL(triggered()), m_ui->tabWidget, SLOT(newDatabase()));
+ 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->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()));
+ connect(m_ui->actionChangeDatabaseSettings, SIGNAL(triggered()), m_ui->tabWidget, SLOT(changeDatabaseSettings()));
+ connect(m_ui->actionImportCsv, SIGNAL(triggered()), m_ui->tabWidget, SLOT(importCsv()));
+ connect(m_ui->actionImportKeePass1, SIGNAL(triggered()), m_ui->tabWidget, SLOT(importKeePass1Database()));
+ connect(m_ui->actionExportCsv, SIGNAL(triggered()), m_ui->tabWidget, SLOT(exportToCsv()));
+ connect(m_ui->actionLockDatabases, SIGNAL(triggered()), m_ui->tabWidget, SLOT(lockDatabases()));
connect(m_ui->actionQuit, SIGNAL(triggered()), SLOT(appExit()));
- m_actionMultiplexer.connect(m_ui->actionEntryNew, SIGNAL(triggered()),
- SLOT(createEntry()));
- 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(deleteEntries()));
-
- 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->actionEntryCopyTotp, SIGNAL(triggered()),
- SLOT(copyTotp()));
- m_actionMultiplexer.connect(m_ui->actionEntryCopyTitle, SIGNAL(triggered()),
- SLOT(copyTitle()));
- m_actionMultiplexer.connect(m_ui->actionEntryCopyUsername, SIGNAL(triggered()),
- SLOT(copyUsername()));
- m_actionMultiplexer.connect(m_ui->actionEntryCopyPassword, SIGNAL(triggered()),
- SLOT(copyPassword()));
- m_actionMultiplexer.connect(m_ui->actionEntryCopyURL, SIGNAL(triggered()),
- SLOT(copyURL()));
- m_actionMultiplexer.connect(m_ui->actionEntryCopyNotes, SIGNAL(triggered()),
- SLOT(copyNotes()));
- m_actionMultiplexer.connect(m_ui->actionEntryAutoType, SIGNAL(triggered()),
- SLOT(performAutoType()));
- m_actionMultiplexer.connect(m_ui->actionEntryOpenUrl, SIGNAL(triggered()),
- SLOT(openUrl()));
-
- m_actionMultiplexer.connect(m_ui->actionGroupNew, SIGNAL(triggered()),
- SLOT(createGroup()));
- m_actionMultiplexer.connect(m_ui->actionGroupEdit, SIGNAL(triggered()),
- SLOT(switchToGroupEdit()));
- m_actionMultiplexer.connect(m_ui->actionGroupDelete, SIGNAL(triggered()),
- SLOT(deleteGroup()));
- m_actionMultiplexer.connect(m_ui->actionGroupEmptyRecycleBin, SIGNAL(triggered()),
- SLOT(emptyRecycleBin()));
+ m_actionMultiplexer.connect(m_ui->actionEntryNew, SIGNAL(triggered()), SLOT(createEntry()));
+ 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->actionEntryTotp, SIGNAL(triggered()), SLOT(showTotp()));
+ m_actionMultiplexer.connect(m_ui->actionEntrySetupTotp, SIGNAL(triggered()), SLOT(setupTotp()));
+
+ m_actionMultiplexer.connect(m_ui->actionEntryCopyTotp, SIGNAL(triggered()), SLOT(copyTotp()));
+ m_actionMultiplexer.connect(m_ui->actionEntryTotpQRCode, SIGNAL(triggered()), SLOT(showTotpKeyQrCode()));
+ m_actionMultiplexer.connect(m_ui->actionEntryCopyTitle, SIGNAL(triggered()), SLOT(copyTitle()));
+ m_actionMultiplexer.connect(m_ui->actionEntryCopyUsername, SIGNAL(triggered()), SLOT(copyUsername()));
+ m_actionMultiplexer.connect(m_ui->actionEntryCopyPassword, SIGNAL(triggered()), SLOT(copyPassword()));
+ m_actionMultiplexer.connect(m_ui->actionEntryCopyURL, SIGNAL(triggered()), SLOT(copyURL()));
+ m_actionMultiplexer.connect(m_ui->actionEntryCopyNotes, SIGNAL(triggered()), SLOT(copyNotes()));
+ m_actionMultiplexer.connect(m_ui->actionEntryAutoType, SIGNAL(triggered()), SLOT(performAutoType()));
+ m_actionMultiplexer.connect(m_ui->actionEntryOpenUrl, SIGNAL(triggered()), SLOT(openUrl()));
+
+ m_actionMultiplexer.connect(m_ui->actionGroupNew, SIGNAL(triggered()), SLOT(createGroup()));
+ m_actionMultiplexer.connect(m_ui->actionGroupEdit, SIGNAL(triggered()), SLOT(switchToGroupEdit()));
+ m_actionMultiplexer.connect(m_ui->actionGroupDelete, SIGNAL(triggered()), SLOT(deleteGroup()));
+ m_actionMultiplexer.connect(m_ui->actionGroupEmptyRecycleBin, SIGNAL(triggered()), SLOT(emptyRecycleBin()));
connect(m_ui->actionSettings, SIGNAL(toggled(bool)), SLOT(switchToSettings(bool)));
connect(m_ui->actionPasswordGenerator, SIGNAL(toggled(bool)), SLOT(switchToPasswordGen(bool)));
@@ -398,20 +362,34 @@ MainWindow::MainWindow()
connect(m_ui->welcomeWidget, SIGNAL(openDatabase()), SLOT(switchToOpenDatabase()));
connect(m_ui->welcomeWidget, SIGNAL(openDatabaseFile(QString)), SLOT(switchToDatabaseFile(QString)));
connect(m_ui->welcomeWidget, SIGNAL(importKeePass1Database()), SLOT(switchToKeePass1Database()));
- connect(m_ui->welcomeWidget, SIGNAL(importCsv()), SLOT(switchToImportCsv()));
+ connect(m_ui->welcomeWidget, SIGNAL(importCsv()), SLOT(switchToCsvImport()));
connect(m_ui->actionAbout, SIGNAL(triggered()), SLOT(showAboutDialog()));
connect(m_ui->actionDonate, SIGNAL(triggered()), SLOT(openDonateUrl()));
connect(m_ui->actionBugReport, SIGNAL(triggered()), SLOT(openBugReportUrl()));
-#ifdef Q_OS_MAC
+#ifdef Q_OS_MACOS
setUnifiedTitleAndToolBarOnMac(true);
#endif
- connect(m_ui->tabWidget, SIGNAL(messageGlobal(QString,MessageWidget::MessageType)), this, SLOT(displayGlobalMessage(QString, MessageWidget::MessageType)));
+#ifdef WITH_XC_NETWORKING
+ connect(m_ui->actionCheckForUpdates, SIGNAL(triggered()), SLOT(showUpdateCheckDialog()));
+ connect(UpdateChecker::instance(),
+ SIGNAL(updateCheckFinished(bool, QString, bool)),
+ SLOT(hasUpdateAvailable(bool, QString, bool)));
+ QTimer::singleShot(3000, this, SLOT(showUpdateCheckStartup()));
+#else
+ m_ui->actionCheckForUpdates->setVisible(false);
+#endif
+
+ // clang-format off
+ connect(m_ui->tabWidget,
+ SIGNAL(messageGlobal(QString,MessageWidget::MessageType)),
+ this,
+ SLOT(displayGlobalMessage(QString,MessageWidget::MessageType)));
+ // clang-format on
+
connect(m_ui->tabWidget, SIGNAL(messageDismissGlobal()), this, SLOT(hideGlobalMessage()));
- connect(m_ui->tabWidget, SIGNAL(messageTab(QString,MessageWidget::MessageType)), this, SLOT(displayTabMessage(QString, MessageWidget::MessageType)));
- connect(m_ui->tabWidget, SIGNAL(messageDismissTab()), this, SLOT(hideTabMessage()));
m_screenLockListener = new ScreenLockListener(this);
connect(m_screenLockListener, SIGNAL(screenLocked()), SLOT(handleScreenLock()));
@@ -419,31 +397,32 @@ MainWindow::MainWindow()
updateTrayIcon();
if (config()->hasAccessError()) {
- m_ui->globalMessageWidget->showMessage(
- tr("Access error for config file %1").arg(config()->getFileName()), MessageWidget::Error);
- }
-#ifdef WITH_XC_HTTP
- if (config()->get("Http/Enabled", false).toBool() && config()->get("Http/DeprecationNoticeShown", 0).toInt() < 3) {
- // show message after global widget dismissed all messages
- connect(m_ui->globalMessageWidget, SIGNAL(hideAnimationFinished()), this, SLOT(showKeePassHTTPDeprecationNotice()));
+ m_ui->globalMessageWidget->showMessage(tr("Access error for config file %1").arg(config()->getFileName()),
+ MessageWidget::Error);
}
-#endif
-#if !defined(KEEPASSXC_BUILD_TYPE_RELEASE)
- m_ui->globalMessageWidget->showMessage(tr("WARNING: You are using an unstable build of KeePassXC!\n"
- "There is a high risk of corruption, maintain a backup of your databases.\n"
- "This version is not meant for production use."),
- MessageWidget::Warning, -1);
+#if defined(KEEPASSXC_BUILD_TYPE_SNAPSHOT)
+ m_ui->globalMessageWidget->showMessage(
+ tr("WARNING: You are using an unstable build of KeePassXC!\n"
+ "There is a high risk of corruption, maintain a backup of your databases.\n"
+ "This version is not meant for production use."),
+ MessageWidget::Warning,
+ -1);
+#elif defined(KEEPASSXC_BUILD_TYPE_PRE_RELEASE)
+ m_ui->globalMessageWidget->showMessage(
+ tr("NOTE: You are using a pre-release version of KeePassXC!\n"
+ "Expect some bugs and minor issues, this version is not meant for production use."),
+ MessageWidget::Information,
+ 15000);
#elif (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) && QT_VERSION < QT_VERSION_CHECK(5, 6, 0))
if (!config()->get("QtErrorMessageShown", false).toBool()) {
- m_ui->globalMessageWidget->showMessage(tr("WARNING: Your Qt version may cause KeePassXC to crash with an On-Screen Keyboard!\n"
- "We recommend you use the AppImage available on our downloads page."),
- MessageWidget::Warning, -1);
+ m_ui->globalMessageWidget->showMessage(
+ tr("WARNING: Your Qt version may cause KeePassXC to crash with an On-Screen Keyboard!\n"
+ "We recommend you use the AppImage available on our downloads page."),
+ MessageWidget::Warning,
+ -1);
config()->set("QtErrorMessageShown", true);
}
-#else
- // Show the HTTP deprecation message if enabled above
- emit m_ui->globalMessageWidget->hideAnimationFinished();
#endif
}
@@ -451,20 +430,6 @@ MainWindow::~MainWindow()
{
}
-void MainWindow::showKeePassHTTPDeprecationNotice()
-{
- int warningNum = config()->get("Http/DeprecationNoticeShown", 0).toInt();
- displayGlobalMessage(tr("<p>It looks like you are using KeePassHTTP for browser integration. "
- "This feature has been deprecated and will be removed in the future.<br>"
- "Please switch to KeePassXC-Browser instead! For help with migration, "
- "visit our <a class=\"link\" href=\"https://keepassxc.org/docs/keepassxc-browser-migration\">"
- "migration guide</a> (warning %1 of 3).</p>").arg(warningNum + 1),
- MessageWidget::Warning, true, -1);
-
- config()->set("Http/DeprecationNoticeShown", warningNum + 1);
- disconnect(m_ui->globalMessageWidget, SIGNAL(hideAnimationFinished()), this, SLOT(showKeePassHTTPDeprecationNotice()));
-}
-
void MainWindow::showErrorMessage(const QString& message)
{
m_ui->globalMessageWidget->showMessage(message, MessageWidget::Error);
@@ -529,30 +494,60 @@ void MainWindow::clearLastDatabases()
}
}
-void MainWindow::openDatabase(const QString& fileName, const QString& pw, const QString& keyFile)
+void MainWindow::openDatabase(const QString& filePath, const QString& pw, const QString& keyFile)
{
- m_ui->tabWidget->openDatabase(fileName, pw, keyFile);
+ if (pw.isEmpty() && keyFile.isEmpty()) {
+ m_ui->tabWidget->addDatabaseTab(filePath);
+ return;
+ }
+
+ auto db = QSharedPointer<Database>::create();
+ auto key = QSharedPointer<CompositeKey>::create();
+ if (!pw.isEmpty()) {
+ key->addKey(QSharedPointer<PasswordKey>::create(pw));
+ }
+ if (!keyFile.isEmpty()) {
+ auto fileKey = QSharedPointer<FileKey>::create();
+ fileKey->load(keyFile);
+ key->addKey(fileKey);
+ }
+ if (db->open(filePath, key, nullptr, false)) {
+ auto* dbWidget = new DatabaseWidget(db, this);
+ m_ui->tabWidget->addDatabaseTab(dbWidget);
+ dbWidget->switchToMainView(true);
+ }
}
void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
{
int currentIndex = m_ui->stackedWidget->currentIndex();
+
bool inDatabaseTabWidget = (currentIndex == DatabaseTabScreen);
bool inWelcomeWidget = (currentIndex == WelcomeScreen);
+ bool inDatabaseTabWidgetOrWelcomeWidget = inDatabaseTabWidget || inWelcomeWidget;
+
+ m_ui->actionDatabaseMerge->setEnabled(inDatabaseTabWidget);
+
+ m_ui->actionDatabaseNew->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
+ m_ui->actionDatabaseOpen->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
+ m_ui->menuRecentDatabases->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
+ m_ui->menuImport->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
+
+ m_ui->actionLockDatabases->setEnabled(m_ui->tabWidget->hasLockableDatabases());
if (inDatabaseTabWidget && m_ui->tabWidget->currentIndex() != -1) {
DatabaseWidget* dbWidget = m_ui->tabWidget->currentDatabaseWidget();
Q_ASSERT(dbWidget);
- if (mode == DatabaseWidget::None) {
+ if (mode == DatabaseWidget::Mode::None) {
mode = dbWidget->currentMode();
}
switch (mode) {
- case DatabaseWidget::ViewMode: {
- //bool inSearch = dbWidget->isInSearchMode();
- bool singleEntrySelected = dbWidget->numberOfSelectedEntries() == 1;
- bool entriesSelected = dbWidget->numberOfSelectedEntries() > 0;
+ case DatabaseWidget::Mode::ViewMode: {
+ // bool inSearch = dbWidget->isInSearchMode();
+ bool singleEntrySelected = dbWidget->numberOfSelectedEntries() == 1 && dbWidget->currentEntryHasFocus();
+ bool entriesSelected = dbWidget->numberOfSelectedEntries() > 0 && dbWidget->currentEntryHasFocus();
bool groupSelected = dbWidget->isGroupSelected();
bool recycleBinSelected = dbWidget->isRecycleBinSelected();
@@ -566,12 +561,13 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
m_ui->actionEntryCopyURL->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUrl());
m_ui->actionEntryCopyNotes->setEnabled(singleEntrySelected && dbWidget->currentEntryHasNotes());
m_ui->menuEntryCopyAttribute->setEnabled(singleEntrySelected);
- m_ui->menuEntryTotp->setEnabled(true);
+ m_ui->menuEntryTotp->setEnabled(singleEntrySelected);
m_ui->actionEntryAutoType->setEnabled(singleEntrySelected);
m_ui->actionEntryOpenUrl->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUrl());
m_ui->actionEntryTotp->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp());
m_ui->actionEntryCopyTotp->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp());
m_ui->actionEntrySetupTotp->setEnabled(singleEntrySelected);
+ m_ui->actionEntryTotpQRCode->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp());
m_ui->actionGroupNew->setEnabled(groupSelected);
m_ui->actionGroupEdit->setEnabled(groupSelected);
m_ui->actionGroupDelete->setEnabled(groupSelected && dbWidget->canDeleteCurrentGroup());
@@ -588,9 +584,9 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
break;
}
- case DatabaseWidget::EditMode:
- case DatabaseWidget::ImportMode:
- case DatabaseWidget::LockedMode: {
+ case DatabaseWidget::Mode::EditMode:
+ case DatabaseWidget::Mode::ImportMode:
+ case DatabaseWidget::Mode::LockedMode: {
const QList<QAction*> entryActions = m_ui->menuEntries->actions();
for (QAction* action : entryActions) {
action->setEnabled(false);
@@ -600,13 +596,6 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
for (QAction* action : groupActions) {
action->setEnabled(false);
}
- m_ui->actionEntryCopyTitle->setEnabled(false);
- m_ui->actionEntryCopyUsername->setEnabled(false);
- m_ui->actionEntryCopyPassword->setEnabled(false);
- m_ui->actionEntryCopyURL->setEnabled(false);
- m_ui->actionEntryCopyNotes->setEnabled(false);
- m_ui->menuEntryCopyAttribute->setEnabled(false);
- m_ui->menuEntryTotp->setEnabled(false);
m_ui->actionChangeMasterKey->setEnabled(false);
m_ui->actionChangeDatabaseSettings->setEnabled(false);
@@ -622,8 +611,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
Q_ASSERT(false);
}
m_ui->actionDatabaseClose->setEnabled(true);
- }
- else {
+ } else {
const QList<QAction*> entryActions = m_ui->menuEntries->actions();
for (QAction* action : entryActions) {
action->setEnabled(false);
@@ -633,13 +621,6 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
for (QAction* action : groupActions) {
action->setEnabled(false);
}
- m_ui->actionEntryCopyTitle->setEnabled(false);
- m_ui->actionEntryCopyUsername->setEnabled(false);
- m_ui->actionEntryCopyPassword->setEnabled(false);
- m_ui->actionEntryCopyURL->setEnabled(false);
- m_ui->actionEntryCopyNotes->setEnabled(false);
- m_ui->menuEntryCopyAttribute->setEnabled(false);
- m_ui->menuEntryTotp->setEnabled(false);
m_ui->actionChangeMasterKey->setEnabled(false);
m_ui->actionChangeDatabaseSettings->setEnabled(false);
@@ -652,15 +633,6 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
m_searchWidgetAction->setEnabled(false);
}
- bool inDatabaseTabWidgetOrWelcomeWidget = inDatabaseTabWidget || inWelcomeWidget;
- m_ui->actionDatabaseNew->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
- m_ui->actionDatabaseOpen->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
- m_ui->menuRecentDatabases->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
- m_ui->menuImport->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
- m_ui->actionRepairDatabase->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
-
- m_ui->actionLockDatabases->setEnabled(m_ui->tabWidget->hasLockableDatabases());
-
if ((currentIndex == PasswordGeneratorScreen) != m_ui->actionPasswordGenerator->isChecked()) {
bool blocked = m_ui->actionPasswordGenerator->blockSignals(true);
m_ui->actionPasswordGenerator->toggle();
@@ -680,14 +652,11 @@ void MainWindow::updateWindowTitle()
bool isModified = m_ui->tabWidget->isModified(tabWidgetIndex);
if (stackedWidgetIndex == DatabaseTabScreen && tabWidgetIndex != -1) {
- customWindowTitlePart = m_ui->tabWidget->tabText(tabWidgetIndex);
+ customWindowTitlePart = m_ui->tabWidget->tabName(tabWidgetIndex);
if (isModified) {
// remove asterisk '*' from title
customWindowTitlePart.remove(customWindowTitlePart.size() - 1, 1);
}
- if (m_ui->tabWidget->readOnly(tabWidgetIndex)) {
- customWindowTitlePart.append(QString(" [%1]").arg(tr("read-only")));
- }
m_ui->actionDatabaseSave->setEnabled(m_ui->tabWidget->canSave(tabWidgetIndex));
} else if (stackedWidgetIndex == 1) {
customWindowTitlePart = tr("Settings");
@@ -703,20 +672,66 @@ void MainWindow::updateWindowTitle()
if (customWindowTitlePart.isEmpty() || stackedWidgetIndex == 1) {
setWindowFilePath("");
} else {
- setWindowFilePath(m_ui->tabWidget->databasePath(tabWidgetIndex));
+ setWindowFilePath(m_ui->tabWidget->databaseWidgetFromIndex(tabWidgetIndex)->database()->filePath());
}
- setWindowModified(isModified);
-
setWindowTitle(windowTitle);
+ setWindowModified(isModified);
}
void MainWindow::showAboutDialog()
{
- AboutDialog* aboutDialog = new AboutDialog(this);
+ auto* aboutDialog = new AboutDialog(this);
aboutDialog->open();
}
+void MainWindow::showUpdateCheckStartup()
+{
+#ifdef WITH_XC_NETWORKING
+ if (!config()->get("UpdateCheckMessageShown", false).toBool()) {
+ auto result =
+ MessageBox::question(this,
+ tr("Check for updates on startup?"),
+ tr("Would you like KeePassXC to check for updates on startup?") + "\n\n"
+ + tr("You can always check for updates manually from the application menu."),
+ MessageBox::Yes | MessageBox::No,
+ MessageBox::Yes);
+
+ config()->set("GUI/CheckForUpdates", (result == MessageBox::Yes));
+ config()->set("UpdateCheckMessageShown", true);
+ }
+
+ if (config()->get("GUI/CheckForUpdates", false).toBool()) {
+ updateCheck()->checkForUpdates(false);
+ }
+
+#endif
+}
+
+void MainWindow::hasUpdateAvailable(bool hasUpdate, const QString& version, bool isManuallyRequested)
+{
+#ifdef WITH_XC_NETWORKING
+ if (hasUpdate && !isManuallyRequested) {
+ auto* updateCheckDialog = new UpdateCheckDialog(this);
+ updateCheckDialog->showUpdateCheckResponse(hasUpdate, version);
+ updateCheckDialog->show();
+ }
+#else
+ Q_UNUSED(hasUpdate)
+ Q_UNUSED(version)
+ Q_UNUSED(isManuallyRequested)
+#endif
+}
+
+void MainWindow::showUpdateCheckDialog()
+{
+#ifdef WITH_XC_NETWORKING
+ updateCheck()->checkForUpdates(true);
+ auto* updateCheckDialog = new UpdateCheckDialog(this);
+ updateCheckDialog->show();
+#endif
+}
+
void MainWindow::openDonateUrl()
{
QDesktopServices::openUrl(QUrl("https://keepassxc.org/donate"));
@@ -731,8 +746,7 @@ void MainWindow::switchToDatabases()
{
if (m_ui->tabWidget->currentIndex() == -1) {
m_ui->stackedWidget->setCurrentIndex(WelcomeScreen);
- }
- else {
+ } else {
m_ui->stackedWidget->setCurrentIndex(DatabaseTabScreen);
}
}
@@ -750,13 +764,13 @@ void MainWindow::switchToSettings(bool enabled)
void MainWindow::switchToPasswordGen(bool enabled)
{
if (enabled) {
- m_ui->passwordGeneratorWidget->loadSettings();
- m_ui->passwordGeneratorWidget->regeneratePassword();
- m_ui->passwordGeneratorWidget->setStandaloneMode(true);
- m_ui->stackedWidget->setCurrentIndex(PasswordGeneratorScreen);
+ m_ui->passwordGeneratorWidget->loadSettings();
+ m_ui->passwordGeneratorWidget->regeneratePassword();
+ m_ui->passwordGeneratorWidget->setStandaloneMode(true);
+ m_ui->stackedWidget->setCurrentIndex(PasswordGeneratorScreen);
} else {
- m_ui->passwordGeneratorWidget->saveSettings();
- switchToDatabases();
+ m_ui->passwordGeneratorWidget->saveSettings();
+ switchToDatabases();
}
}
@@ -777,9 +791,9 @@ void MainWindow::switchToOpenDatabase()
switchToDatabases();
}
-void MainWindow::switchToDatabaseFile(QString file)
+void MainWindow::switchToDatabaseFile(const QString& file)
{
- m_ui->tabWidget->openDatabase(file);
+ m_ui->tabWidget->addDatabaseTab(file);
switchToDatabases();
}
@@ -789,29 +803,69 @@ void MainWindow::switchToKeePass1Database()
switchToDatabases();
}
-void MainWindow::switchToImportCsv()
+void MainWindow::switchToCsvImport()
{
m_ui->tabWidget->importCsv();
switchToDatabases();
}
-void MainWindow::databaseStatusChanged(DatabaseWidget *)
+void MainWindow::databaseStatusChanged(DatabaseWidget* dbWidget)
{
+ Q_UNUSED(dbWidget);
updateTrayIcon();
}
+void MainWindow::selectNextDatabaseTab()
+{
+ if (m_ui->stackedWidget->currentIndex() == DatabaseTabScreen) {
+ int index = m_ui->tabWidget->currentIndex() + 1;
+ if (index >= m_ui->tabWidget->count()) {
+ m_ui->tabWidget->setCurrentIndex(0);
+ } else {
+ m_ui->tabWidget->setCurrentIndex(index);
+ }
+ }
+}
+
+void MainWindow::selectPreviousDatabaseTab()
+{
+ if (m_ui->stackedWidget->currentIndex() == DatabaseTabScreen) {
+ int index = m_ui->tabWidget->currentIndex() - 1;
+ if (index < 0) {
+ m_ui->tabWidget->setCurrentIndex(m_ui->tabWidget->count() - 1);
+ } else {
+ m_ui->tabWidget->setCurrentIndex(index);
+ }
+ }
+}
+
void MainWindow::databaseTabChanged(int tabIndex)
{
if (tabIndex != -1 && m_ui->stackedWidget->currentIndex() == WelcomeScreen) {
m_ui->stackedWidget->setCurrentIndex(DatabaseTabScreen);
- }
- else if (tabIndex == -1 && m_ui->stackedWidget->currentIndex() == DatabaseTabScreen) {
+ } else if (tabIndex == -1 && m_ui->stackedWidget->currentIndex() == DatabaseTabScreen) {
m_ui->stackedWidget->setCurrentIndex(WelcomeScreen);
}
m_actionMultiplexer.setCurrentObject(m_ui->tabWidget->currentDatabaseWidget());
}
+void MainWindow::togglePasswordsHidden()
+{
+ auto dbWidget = m_ui->tabWidget->currentDatabaseWidget();
+ if (dbWidget) {
+ dbWidget->setPasswordsHidden(!dbWidget->isPasswordsHidden());
+ }
+}
+
+void MainWindow::toggleUsernamesHidden()
+{
+ auto dbWidget = m_ui->tabWidget->currentDatabaseWidget();
+ if (dbWidget) {
+ dbWidget->setUsernamesHidden(!dbWidget->isUsernamesHidden());
+ }
+}
+
void MainWindow::closeEvent(QCloseEvent* event)
{
// ignore double close events (happens on macOS when closing from the dock)
@@ -820,17 +874,9 @@ void MainWindow::closeEvent(QCloseEvent* event)
return;
}
- bool minimizeOnClose = isTrayIconEnabled() &&
- config()->get("GUI/MinimizeOnClose").toBool();
- if (minimizeOnClose && !m_appExitCalled)
- {
- event->accept();
+ if (config()->get("GUI/MinimizeOnClose").toBool() && !m_appExitCalled) {
+ event->ignore();
hideWindow();
-
- if (config()->get("security/lockdatabaseminimize").toBool()) {
- m_ui->tabWidget->lockDatabases();
- }
-
return;
}
@@ -842,8 +888,7 @@ void MainWindow::closeEvent(QCloseEvent* event)
event->accept();
QApplication::quit();
- }
- else {
+ } else {
event->ignore();
}
}
@@ -852,8 +897,7 @@ void MainWindow::changeEvent(QEvent* event)
{
if ((event->type() == QEvent::WindowStateChange) && isMinimized()) {
if (isTrayIconEnabled() && m_trayIcon && m_trayIcon->isVisible()
- && config()->get("GUI/MinimizeToTray").toBool())
- {
+ && config()->get("GUI/MinimizeToTray").toBool()) {
event->ignore();
QTimer::singleShot(0, this, SLOT(hide()));
}
@@ -861,8 +905,7 @@ void MainWindow::changeEvent(QEvent* event)
if (config()->get("security/lockdatabaseminimize").toBool()) {
m_ui->tabWidget->lockDatabases();
}
- }
- else {
+ } else {
QMainWindow::changeEvent(event);
}
}
@@ -871,6 +914,7 @@ void MainWindow::saveWindowInformation()
{
if (isVisible()) {
config()->set("GUI/MainWindowGeometry", saveGeometry());
+ config()->set("GUI/MainWindowState", saveState());
}
}
@@ -881,20 +925,15 @@ bool MainWindow::saveLastDatabases()
bool openPreviousDatabasesOnStartup = config()->get("OpenPreviousDatabasesOnStartup").toBool();
if (openPreviousDatabasesOnStartup) {
- connect(m_ui->tabWidget, SIGNAL(databaseWithFileClosed(QString)),
- this, SLOT(rememberOpenDatabases(QString)));
+ connect(
+ m_ui->tabWidget, SIGNAL(databaseClosed(const QString&)), this, SLOT(rememberOpenDatabases(const QString&)));
}
- if (!m_ui->tabWidget->closeAllDatabases()) {
- accept = false;
- }
- else {
- accept = true;
- }
+ accept = m_ui->tabWidget->closeAllDatabaseTabs();
if (openPreviousDatabasesOnStartup) {
- disconnect(m_ui->tabWidget, SIGNAL(databaseWithFileClosed(QString)),
- this, SLOT(rememberOpenDatabases(QString)));
+ disconnect(
+ m_ui->tabWidget, SIGNAL(databaseClosed(const QString&)), this, SLOT(rememberOpenDatabases(const QString&)));
config()->set("LastOpenedDatabases", m_openDatabases);
}
@@ -911,7 +950,7 @@ void MainWindow::updateTrayIcon()
QAction* actionToggle = new QAction(tr("Toggle window"), menu);
menu->addAction(actionToggle);
-#ifdef Q_OS_MAC
+#ifdef Q_OS_MACOS
QAction* actionQuit = new QAction(tr("Quit KeePassXC"), menu);
menu->addAction(actionQuit);
@@ -919,24 +958,23 @@ void MainWindow::updateTrayIcon()
#else
menu->addAction(m_ui->actionQuit);
- connect(m_trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
+ connect(m_trayIcon,
+ SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
SLOT(trayIconTriggered(QSystemTrayIcon::ActivationReason)));
#endif
connect(actionToggle, SIGNAL(triggered()), SLOT(toggleWindow()));
m_trayIcon->setContextMenu(menu);
-
+
m_trayIcon->setIcon(filePath()->trayIcon());
m_trayIcon->show();
}
if (m_ui->tabWidget->hasLockableDatabases()) {
m_trayIcon->setIcon(filePath()->trayIconUnlocked());
- }
- else {
+ } else {
m_trayIcon->setIcon(filePath()->trayIconLocked());
}
- }
- else {
+ } else {
if (m_trayIcon) {
m_trayIcon->hide();
delete m_trayIcon;
@@ -959,8 +997,7 @@ void MainWindow::setShortcut(QAction* action, QKeySequence::StandardKey standard
{
if (!QKeySequence::keyBindings(standard).isEmpty()) {
action->setShortcuts(standard);
- }
- else if (fallback != 0) {
+ } else if (fallback != 0) {
action->setShortcut(QKeySequence(fallback));
}
}
@@ -980,11 +1017,34 @@ void MainWindow::applySettingsChanges()
m_inactivityTimer->setInactivityTimeout(timeout);
if (config()->get("security/lockdatabaseidle").toBool()) {
m_inactivityTimer->activate();
- }
- else {
+ } else {
m_inactivityTimer->deactivate();
}
+#ifdef WITH_XC_TOUCHID
+ // forget TouchID (in minutes)
+ timeout = config()->get("security/resettouchidtimeout").toInt() * 60 * 1000;
+ if (timeout <= 0) {
+ timeout = 30 * 60 * 1000;
+ }
+
+ m_touchIDinactivityTimer->setInactivityTimeout(timeout);
+ if (config()->get("security/resettouchid").toBool()) {
+ m_touchIDinactivityTimer->activate();
+ } else {
+ m_touchIDinactivityTimer->deactivate();
+ }
+#endif
+
+ m_ui->toolBar->setHidden(config()->get("GUI/HideToolbar").toBool());
+ m_ui->toolBar->setMovable(config()->get("GUI/MovableToolbar").toBool());
+
+ bool isOk = false;
+ const auto toolButtonStyle = static_cast<Qt::ToolButtonStyle>(config()->get("GUI/ToolButtonStyle").toInt(&isOk));
+ if (isOk) {
+ m_ui->toolBar->setToolButtonStyle(toolButtonStyle);
+ }
+
updateTrayIcon();
}
@@ -998,14 +1058,19 @@ void MainWindow::trayIconTriggered(QSystemTrayIcon::ActivationReason reason)
void MainWindow::hideWindow()
{
saveWindowInformation();
-#if !defined(Q_OS_LINUX) && !defined(Q_OS_MAC)
+#if !defined(Q_OS_LINUX) && !defined(Q_OS_MACOS)
// On some Linux systems, the window should NOT be minimized and hidden (i.e. not shown), at
// the same time (which would happen if both minimize on startup and minimize to tray are set)
// since otherwise it causes problems on restore as seen on issue #1595. Hiding it is enough.
// TODO: Add an explanation for why this is also not done on Mac (or remove the check)
setWindowState(windowState() | Qt::WindowMinimized);
#endif
- QTimer::singleShot(0, this, SLOT(hide()));
+ // Only hide if tray icon is active, otherwise window will be gone forever
+ if (isTrayIconEnabled()) {
+ hide();
+ } else {
+ showMinimized();
+ }
if (config()->get("security/lockdatabaseminimize").toBool()) {
m_ui->tabWidget->lockDatabases();
@@ -1019,17 +1084,19 @@ void MainWindow::toggleWindow()
} else {
bringToFront();
-#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) && !defined(QT_NO_DBUS) && (QT_VERSION < QT_VERSION_CHECK(5, 9, 0))
+#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) && !defined(QT_NO_DBUS) && (QT_VERSION < QT_VERSION_CHECK(5, 9, 0))
// re-register global D-Bus menu (needed on Ubuntu with Unity)
// see https://github.com/keepassxreboot/keepassxc/issues/271
// and https://bugreports.qt.io/browse/QTBUG-58723
// check for !isVisible(), because isNativeMenuBar() does not work with appmenu-qt5
- if (!m_ui->menubar->isVisible()) {
- QDBusMessage msg = QDBusMessage::createMethodCall(
- "com.canonical.AppMenu.Registrar",
- "/com/canonical/AppMenu/Registrar",
- "com.canonical.AppMenu.Registrar",
- "RegisterWindow");
+ const static auto isDesktopSessionUnity = qgetenv("XDG_CURRENT_DESKTOP") == "Unity";
+
+ if (isDesktopSessionUnity && Tools::qtRuntimeVersion() < QT_VERSION_CHECK(5, 9, 0)
+ && !m_ui->menubar->isVisible()) {
+ QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("com.canonical.AppMenu.Registrar"),
+ QStringLiteral("/com/canonical/AppMenu/Registrar"),
+ QStringLiteral("com.canonical.AppMenu.Registrar"),
+ QStringLiteral("RegisterWindow"));
QList<QVariant> args;
args << QVariant::fromValue(static_cast<uint32_t>(winId()))
<< QVariant::fromValue(QDBusObjectPath("/MenuBar/1"));
@@ -1050,51 +1117,30 @@ void MainWindow::lockDatabasesAfterInactivity()
m_ui->tabWidget->lockDatabases();
}
-void MainWindow::repairDatabase()
+void MainWindow::forgetTouchIDAfterInactivity()
{
- QString filter = QString("%1 (*.kdbx);;%2 (*)").arg(tr("KeePass 2 Database"), tr("All files"));
- QString fileName = fileDialog()->getOpenFileName(this, tr("Open database"), QString(),
- filter);
- if (fileName.isEmpty()) {
- return;
- }
-
- QScopedPointer<QDialog> dialog(new QDialog(this));
- DatabaseRepairWidget* dbRepairWidget = new DatabaseRepairWidget(dialog.data());
- connect(dbRepairWidget, SIGNAL(success()), dialog.data(), SLOT(accept()));
- connect(dbRepairWidget, SIGNAL(error()), dialog.data(), SLOT(reject()));
- dbRepairWidget->load(fileName);
- if (dialog->exec() == QDialog::Accepted && dbRepairWidget->database()) {
- QString saveFileName = fileDialog()->getSaveFileName(this, tr("Save repaired database"), QString(),
- tr("KeePass 2 Database").append(" (*.kdbx)"),
- nullptr, 0, "kdbx");
-
- if (!saveFileName.isEmpty()) {
- KeePass2Writer writer;
- writer.writeDatabase(saveFileName, dbRepairWidget->database());
- if (writer.hasError()) {
- displayGlobalMessage(
- tr("Writing the database failed.").append("\n").append(writer.errorString()),
- MessageWidget::Error);
- }
- }
- }
+#ifdef WITH_XC_TOUCHID
+ TouchID::getInstance().reset();
+#endif
}
bool MainWindow::isTrayIconEnabled() const
{
- return config()->get("GUI/ShowTrayIcon").toBool()
- && QSystemTrayIcon::isSystemTrayAvailable();
+ return config()->get("GUI/ShowTrayIcon").toBool() && QSystemTrayIcon::isSystemTrayAvailable();
}
-void MainWindow::displayGlobalMessage(const QString& text, MessageWidget::MessageType type, bool showClosebutton,
+void MainWindow::displayGlobalMessage(const QString& text,
+ MessageWidget::MessageType type,
+ bool showClosebutton,
int autoHideTimeout)
{
m_ui->globalMessageWidget->setCloseButtonVisible(showClosebutton);
m_ui->globalMessageWidget->showMessage(text, type, autoHideTimeout);
}
-void MainWindow::displayTabMessage(const QString& text, MessageWidget::MessageType type, bool showClosebutton,
+void MainWindow::displayTabMessage(const QString& text,
+ MessageWidget::MessageType type,
+ bool showClosebutton,
int autoHideTimeout)
{
m_ui->tabWidget->currentDatabaseWidget()->showMessage(text, type, showClosebutton, autoHideTimeout);
@@ -1105,17 +1151,12 @@ void MainWindow::hideGlobalMessage()
m_ui->globalMessageWidget->hideMessage();
}
-void MainWindow::hideTabMessage()
-{
- if (m_ui->stackedWidget->currentIndex() == DatabaseTabScreen) {
- m_ui->tabWidget->currentDatabaseWidget()->hideMessage();
- }
-}
-
void MainWindow::showYubiKeyPopup()
{
- displayGlobalMessage(tr("Please touch the button on your YubiKey!"), MessageWidget::Information,
- false, MessageWidget::DisableAutoHide);
+ displayGlobalMessage(tr("Please touch the button on your YubiKey!"),
+ MessageWidget::Information,
+ false,
+ MessageWidget::DisableAutoHide);
setEnabled(false);
}
@@ -1128,7 +1169,7 @@ void MainWindow::hideYubiKeyPopup()
void MainWindow::bringToFront()
{
ensurePolished();
- setWindowState(windowState() & ~Qt::WindowMinimized);
+ setWindowState((windowState() & ~Qt::WindowMinimized) | Qt::WindowActive);
show();
raise();
activateWindow();
@@ -1136,15 +1177,21 @@ void MainWindow::bringToFront()
void MainWindow::handleScreenLock()
{
- if (config()->get("security/lockdatabasescreenlock").toBool()){
+ if (config()->get("security/lockdatabasescreenlock").toBool()) {
lockDatabasesAfterInactivity();
}
+
+#ifdef WITH_XC_TOUCHID
+ if (config()->get("security/resettouchidscreenlock").toBool()) {
+ forgetTouchIDAfterInactivity();
+ }
+#endif
}
QStringList MainWindow::kdbxFilesFromUrls(const QList<QUrl>& urls)
{
QStringList kdbxFiles;
- for (const QUrl& url: urls) {
+ for (const QUrl& url : urls) {
const QFileInfo fInfo(url.toLocalFile());
const bool isKdbxFile = fInfo.isFile() && fInfo.suffix().toLower() == "kdbx";
if (isKdbxFile) {
@@ -1174,7 +1221,7 @@ void MainWindow::dropEvent(QDropEvent* event)
if (!kdbxFiles.isEmpty()) {
event->acceptProposedAction();
}
- for (const QString& kdbxFile: kdbxFiles) {
+ for (const QString& kdbxFile : kdbxFiles) {
openDatabase(kdbxFile);
}
}
@@ -1182,7 +1229,7 @@ void MainWindow::dropEvent(QDropEvent* event)
void MainWindow::closeAllDatabases()
{
- m_ui->tabWidget->closeAllDatabases();
+ m_ui->tabWidget->closeAllDatabaseTabs();
}
void MainWindow::lockAllDatabases()