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:
Diffstat (limited to 'src/gui/MainWindow.cpp')
-rw-r--r--src/gui/MainWindow.cpp722
1 files changed, 496 insertions, 226 deletions
diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp
index 6452c051b..9751a3e77 100644
--- a/src/gui/MainWindow.cpp
+++ b/src/gui/MainWindow.cpp
@@ -31,23 +31,26 @@
#include "autotype/AutoType.h"
#include "core/Config.h"
-#include "core/FilePath.h"
#include "core/InactivityTimer.h"
#include "core/Metadata.h"
+#include "core/Resources.h"
#include "core/Tools.h"
#include "gui/AboutDialog.h"
#include "gui/DatabaseWidget.h"
+#include "gui/MessageBox.h"
#include "gui/SearchWidget.h"
#include "keys/CompositeKey.h"
#include "keys/FileKey.h"
#include "keys/PasswordKey.h"
#ifdef Q_OS_MACOS
-#include "macutils/MacUtils.h"
+#include "gui/osutils/macutils/MacUtils.h"
+#ifdef WITH_XC_TOUCHID
+#include "touchid/TouchID.h"
+#endif
#endif
#ifdef WITH_XC_UPDATECHECK
-#include "gui/MessageBox.h"
#include "gui/UpdateCheckDialog.h"
#include "updatecheck/UpdateChecker.h"
#endif
@@ -56,7 +59,7 @@
#include "sshagent/AgentSettingsPage.h"
#include "sshagent/SSHAgent.h"
#endif
-#if defined(WITH_XC_KEESHARE)
+#ifdef WITH_XC_KEESHARE
#include "keeshare/KeeShare.h"
#include "keeshare/SettingsPageKeeShare.h"
#endif
@@ -65,10 +68,13 @@
#include "fdosecrets/FdoSecretsPlugin.h"
#endif
+#ifdef WITH_XC_YUBIKEY
+#include "keys/drivers/YubiKey.h"
+#endif
+
#ifdef WITH_XC_BROWSER
-#include "browser/BrowserOptionDialog.h"
-#include "browser/BrowserSettings.h"
-#include "browser/NativeMessagingHost.h"
+#include "browser/BrowserService.h"
+#include "browser/BrowserSettingsPage.h"
#endif
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) && !defined(QT_NO_DBUS)
@@ -77,61 +83,6 @@
#include <QtDBus/QtDBus>
#endif
-#include "gui/ApplicationSettingsWidget.h"
-#include "gui/PasswordGeneratorWidget.h"
-
-#include "touchid/TouchID.h"
-
-#ifdef WITH_XC_BROWSER
-class BrowserPlugin : public ISettingsPage
-{
-public:
- explicit 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();
- return dlg;
- }
-
- void loadSettings(QWidget* widget) override
- {
- qobject_cast<BrowserOptionDialog*>(widget)->loadSettings();
- }
-
- 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;
-};
-#endif
-
const QString MainWindow::BaseWindowTitle = "KeePassXC";
MainWindow* g_MainWindow = nullptr;
@@ -156,6 +107,10 @@ MainWindow::MainWindow()
setAcceptDrops(true);
+ if (config()->get(Config::GUI_CompactMode).toBool()) {
+ m_ui->toolBar->setIconSize({20, 20});
+ }
+
// Setup the search widget in the toolbar
m_searchWidget = new SearchWidget();
m_searchWidget->connectSignals(m_actionMultiplexer);
@@ -175,6 +130,10 @@ MainWindow::MainWindow()
m_entryContextMenu->addAction(m_ui->actionEntryEdit);
m_entryContextMenu->addAction(m_ui->actionEntryClone);
m_entryContextMenu->addAction(m_ui->actionEntryDelete);
+ m_entryContextMenu->addAction(m_ui->actionEntryNew);
+ m_entryContextMenu->addSeparator();
+ m_entryContextMenu->addAction(m_ui->actionEntryMoveUp);
+ m_entryContextMenu->addAction(m_ui->actionEntryMoveDown);
m_entryContextMenu->addSeparator();
m_entryContextMenu->addAction(m_ui->actionEntryOpenUrl);
m_entryContextMenu->addAction(m_ui->actionEntryDownloadIcon);
@@ -182,18 +141,38 @@ MainWindow::MainWindow()
m_entryNewContextMenu = new QMenu(this);
m_entryNewContextMenu->addAction(m_ui->actionEntryNew);
- restoreGeometry(config()->get("GUI/MainWindowGeometry").toByteArray());
- restoreState(config()->get("GUI/MainWindowState").toByteArray());
+ restoreGeometry(config()->get(Config::GUI_MainWindowGeometry).toByteArray());
+ restoreState(config()->get(Config::GUI_MainWindowState).toByteArray());
#ifdef WITH_XC_BROWSER
- m_ui->settingsWidget->addSettingsPage(new BrowserPlugin(m_ui->tabWidget));
+ m_ui->settingsWidget->addSettingsPage(new BrowserSettingsPage());
+ connect(m_ui->tabWidget, &DatabaseTabWidget::databaseLocked, browserService(), &BrowserService::databaseLocked);
+ connect(m_ui->tabWidget, &DatabaseTabWidget::databaseUnlocked, browserService(), &BrowserService::databaseUnlocked);
+ connect(m_ui->tabWidget,
+ &DatabaseTabWidget::activateDatabaseChanged,
+ browserService(),
+ &BrowserService::activeDatabaseChanged);
+ connect(
+ browserService(), &BrowserService::requestUnlock, m_ui->tabWidget, &DatabaseTabWidget::performBrowserUnlock);
#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));
+ connect(sshAgent(), SIGNAL(error(QString)), this, SLOT(showErrorMessage(QString)));
+ connect(sshAgent(), SIGNAL(enabledChanged(bool)), this, SLOT(agentEnabled(bool)));
+ m_ui->settingsWidget->addSettingsPage(new AgentSettingsPage());
+
+ m_entryContextMenu->addSeparator();
+ m_entryContextMenu->addAction(m_ui->actionEntryAddToAgent);
+ m_entryContextMenu->addAction(m_ui->actionEntryRemoveFromAgent);
+
+ m_ui->actionEntryAddToAgent->setIcon(resources()->icon("utilities-terminal"));
+ m_ui->actionEntryRemoveFromAgent->setIcon(resources()->icon("utilities-terminal"));
#endif
+ m_ui->actionEntryAddToAgent->setVisible(false);
+ m_ui->actionEntryRemoveFromAgent->setVisible(false);
+
+ initViewMenu();
+
#if defined(WITH_XC_KEESHARE)
KeeShare::init(this);
m_ui->settingsWidget->addSettingsPage(new SettingsPageKeeShare(m_ui->tabWidget));
@@ -211,13 +190,14 @@ MainWindow::MainWindow()
m_ui->settingsWidget->addSettingsPage(fdoSS);
#endif
- setWindowIcon(filePath()->applicationIcon());
- m_ui->globalMessageWidget->setHidden(true);
- // 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
+#ifdef WITH_XC_YUBIKEY
+ connect(YubiKey::instance(), SIGNAL(userInteractionRequest()), SLOT(showYubiKeyPopup()), Qt::QueuedConnection);
+ connect(YubiKey::instance(), SIGNAL(challengeCompleted()), SLOT(hideYubiKeyPopup()), Qt::QueuedConnection);
+#endif
+
+ setWindowIcon(resources()->applicationIcon());
+ m_ui->globalMessageWidget->hideMessage();
+ connect(m_ui->globalMessageWidget, &MessageWidget::linkActivated, &MessageWidget::openHttpUrl);
m_clearHistoryAction = new QAction(tr("Clear history"), m_ui->menuFile);
m_lastDatabasesActions = new QActionGroup(m_ui->menuRecentDatabases);
@@ -230,13 +210,16 @@ MainWindow::MainWindow()
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::Key globalAutoTypeKey = static_cast<Qt::Key>(config()->get(Config::GlobalAutoTypeKey).toInt());
Qt::KeyboardModifiers globalAutoTypeModifiers =
- static_cast<Qt::KeyboardModifiers>(config()->get("GlobalAutoTypeModifiers").toInt());
+ static_cast<Qt::KeyboardModifiers>(config()->get(Config::GlobalAutoTypeModifiers).toInt());
if (globalAutoTypeKey > 0 && globalAutoTypeModifiers > 0) {
autoType()->registerGlobalShortcut(globalAutoTypeKey, globalAutoTypeModifiers);
}
+ m_ui->toolbarSeparator->setVisible(false);
+ m_showToolbarSeparator = config()->get(Config::GUI_ApplicationTheme).toString() != "classic";
+
m_ui->actionEntryAutoType->setVisible(autoType()->isAvailable());
m_inactivityTimer = new InactivityTimer(this);
@@ -262,11 +245,15 @@ MainWindow::MainWindow()
m_ui->actionEntryTotp->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_T);
m_ui->actionEntryDownloadIcon->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_D);
m_ui->actionEntryCopyTotp->setShortcut(Qt::CTRL + Qt::Key_T);
+ m_ui->actionEntryMoveUp->setShortcut(Qt::CTRL + Qt::ALT + Qt::Key_Up);
+ m_ui->actionEntryMoveDown->setShortcut(Qt::CTRL + Qt::ALT + Qt::Key_Down);
m_ui->actionEntryCopyUsername->setShortcut(Qt::CTRL + Qt::Key_B);
m_ui->actionEntryCopyPassword->setShortcut(Qt::CTRL + Qt::Key_C);
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);
+ m_ui->actionEntryAddToAgent->setShortcut(Qt::CTRL + Qt::Key_H);
+ m_ui->actionEntryRemoveFromAgent->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_H);
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
// Qt 5.10 introduced a new "feature" to hide shortcuts in context menus
@@ -278,11 +265,15 @@ MainWindow::MainWindow()
m_ui->actionEntryTotp->setShortcutVisibleInContextMenu(true);
m_ui->actionEntryDownloadIcon->setShortcutVisibleInContextMenu(true);
m_ui->actionEntryCopyTotp->setShortcutVisibleInContextMenu(true);
+ m_ui->actionEntryMoveUp->setShortcutVisibleInContextMenu(true);
+ m_ui->actionEntryMoveDown->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);
+ m_ui->actionEntryAddToAgent->setShortcutVisibleInContextMenu(true);
+ m_ui->actionEntryRemoveFromAgent->setShortcutVisibleInContextMenu(true);
#endif
connect(m_ui->menuEntries, SIGNAL(aboutToShow()), SLOT(obtainContextFocusLock()));
@@ -295,48 +286,99 @@ MainWindow::MainWindow()
connect(m_ui->menuGroups, SIGNAL(aboutToHide()), SLOT(releaseContextFocusLock()));
// Control window state
- new QShortcut(Qt::CTRL + Qt::Key_M, this, SLOT(showMinimized()));
+ new QShortcut(Qt::CTRL + Qt::Key_M, this, SLOT(minimizeOrHide()));
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_PageDown, this, SLOT(selectNextDatabaseTab()));
new QShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Tab, this, SLOT(selectPreviousDatabaseTab()));
new QShortcut(Qt::CTRL + Qt::Key_PageUp, this, SLOT(selectPreviousDatabaseTab()));
+
+ // Tab selection by number, Windows uses Ctrl, macOS uses Command,
+ // and Linux uses Alt to emulate a browser-like experience
+ auto dbTabModifier = Qt::CTRL;
+#ifdef Q_OS_LINUX
+ dbTabModifier = Qt::ALT;
+#endif
+ auto shortcut = new QShortcut(dbTabModifier + Qt::Key_1, this);
+ connect(shortcut, &QShortcut::activated, [this]() { selectDatabaseTab(0); });
+ shortcut = new QShortcut(dbTabModifier + Qt::Key_2, this);
+ connect(shortcut, &QShortcut::activated, [this]() { selectDatabaseTab(1); });
+ shortcut = new QShortcut(dbTabModifier + Qt::Key_3, this);
+ connect(shortcut, &QShortcut::activated, [this]() { selectDatabaseTab(2); });
+ shortcut = new QShortcut(dbTabModifier + Qt::Key_4, this);
+ connect(shortcut, &QShortcut::activated, [this]() { selectDatabaseTab(3); });
+ shortcut = new QShortcut(dbTabModifier + Qt::Key_5, this);
+ connect(shortcut, &QShortcut::activated, [this]() { selectDatabaseTab(4); });
+ shortcut = new QShortcut(dbTabModifier + Qt::Key_6, this);
+ connect(shortcut, &QShortcut::activated, [this]() { selectDatabaseTab(5); });
+ shortcut = new QShortcut(dbTabModifier + Qt::Key_7, this);
+ connect(shortcut, &QShortcut::activated, [this]() { selectDatabaseTab(6); });
+ shortcut = new QShortcut(dbTabModifier + Qt::Key_8, this);
+ connect(shortcut, &QShortcut::activated, [this]() { selectDatabaseTab(7); });
+ shortcut = new QShortcut(dbTabModifier + Qt::Key_9, this);
+ connect(shortcut, &QShortcut::activated, [this]() { selectDatabaseTab(m_ui->tabWidget->count() - 1); });
+
+ // Allow for direct focus of search, group view, and entry view
+ shortcut = new QShortcut(Qt::Key_F1, this);
+ connect(shortcut, SIGNAL(activated()), m_searchWidget, SLOT(searchFocus()));
+ shortcut = new QShortcut(Qt::Key_F2, this);
+ m_actionMultiplexer.connect(shortcut, SIGNAL(activated()), SLOT(focusOnGroups()));
+ shortcut = new QShortcut(Qt::Key_F3, this);
+ m_actionMultiplexer.connect(shortcut, SIGNAL(activated()), SLOT(focusOnEntries()));
+
// 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"));
- m_ui->actionDatabaseSave->setIcon(filePath()->icon("actions", "document-save"));
- 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"));
- m_ui->actionLockDatabases->setIcon(filePath()->icon("actions", "database-lock"));
- m_ui->actionQuit->setIcon(filePath()->icon("actions", "application-exit"));
-
- 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->actionEntryDownloadIcon->setIcon(filePath()->icon("actions", "favicon-download"));
-
- 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->actionGroupDownloadFavicons->setIcon(filePath()->icon("actions", "favicon-download"));
-
- m_ui->actionSettings->setIcon(filePath()->icon("actions", "configure"));
- 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_ui->actionDatabaseNew->setIcon(resources()->icon("document-new"));
+ m_ui->actionDatabaseOpen->setIcon(resources()->icon("document-open"));
+ m_ui->menuRecentDatabases->setIcon(resources()->icon("document-open-recent"));
+ 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->actionDatabaseSettings->setIcon(resources()->icon("document-edit"));
+ m_ui->actionDatabaseSecurity->setIcon(resources()->icon("database-change-key"));
+ m_ui->actionLockDatabases->setIcon(resources()->icon("database-lock"));
+ m_ui->actionQuit->setIcon(resources()->icon("application-exit"));
+ m_ui->actionDatabaseMerge->setIcon(resources()->icon("database-merge"));
+ m_ui->menuImport->setIcon(resources()->icon("document-import"));
+ m_ui->menuExport->setIcon(resources()->icon("document-export"));
+
+ m_ui->actionEntryNew->setIcon(resources()->icon("entry-new"));
+ m_ui->actionEntryClone->setIcon(resources()->icon("entry-clone"));
+ m_ui->actionEntryEdit->setIcon(resources()->icon("entry-edit"));
+ m_ui->actionEntryDelete->setIcon(resources()->icon("entry-delete"));
+ m_ui->actionEntryAutoType->setIcon(resources()->icon("auto-type"));
+ m_ui->actionEntryMoveUp->setIcon(resources()->icon("move-up"));
+ m_ui->actionEntryMoveDown->setIcon(resources()->icon("move-down"));
+ m_ui->actionEntryCopyUsername->setIcon(resources()->icon("username-copy"));
+ m_ui->actionEntryCopyPassword->setIcon(resources()->icon("password-copy"));
+ m_ui->actionEntryCopyURL->setIcon(resources()->icon("url-copy"));
+ m_ui->actionEntryDownloadIcon->setIcon(resources()->icon("favicon-download"));
+ m_ui->actionGroupSortAsc->setIcon(resources()->icon("sort-alphabetical-ascending"));
+ m_ui->actionGroupSortDesc->setIcon(resources()->icon("sort-alphabetical-descending"));
+
+ m_ui->actionGroupNew->setIcon(resources()->icon("group-new"));
+ m_ui->actionGroupEdit->setIcon(resources()->icon("group-edit"));
+ m_ui->actionGroupDelete->setIcon(resources()->icon("group-delete"));
+ m_ui->actionGroupEmptyRecycleBin->setIcon(resources()->icon("group-empty-trash"));
+ m_ui->actionEntryOpenUrl->setIcon(resources()->icon("web"));
+ m_ui->actionGroupDownloadFavicons->setIcon(resources()->icon("favicon-download"));
+
+ m_ui->actionSettings->setIcon(resources()->icon("configure"));
+ m_ui->actionPasswordGenerator->setIcon(resources()->icon("password-generator"));
+
+ m_ui->actionAbout->setIcon(resources()->icon("help-about"));
+ m_ui->actionDonate->setIcon(resources()->icon("donate"));
+ m_ui->actionBugReport->setIcon(resources()->icon("bugreport"));
+ m_ui->actionGettingStarted->setIcon(resources()->icon("getting-started"));
+ m_ui->actionUserGuide->setIcon(resources()->icon("user-guide"));
+ m_ui->actionOnlineHelp->setIcon(resources()->icon("system-help"));
+ m_ui->actionKeyboardShortcuts->setIcon(resources()->icon("keyboard-shortcuts"));
+ m_ui->actionCheckForUpdates->setIcon(resources()->icon("system-software-update"));
m_actionMultiplexer.connect(
SIGNAL(currentModeChanged(DatabaseWidget::Mode)), this, SLOT(setMenuActionState(DatabaseWidget::Mode)));
@@ -356,10 +398,13 @@ MainWindow::MainWindow()
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(currentChanged(int)), SLOT(updateTrayIcon()));
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(tabVisibilityChanged(bool)), SLOT(updateToolbarSeparatorVisibility()));
connect(m_ui->stackedWidget, SIGNAL(currentChanged(int)), SLOT(setMenuActionState()));
connect(m_ui->stackedWidget, SIGNAL(currentChanged(int)), SLOT(updateWindowTitle()));
+ connect(m_ui->stackedWidget, SIGNAL(currentChanged(int)), SLOT(updateToolbarSeparatorVisibility()));
connect(m_ui->settingsWidget, SIGNAL(accepted()), SLOT(applySettingsChanges()));
connect(m_ui->settingsWidget, SIGNAL(settingsReset()), SLOT(applySettingsChanges()));
connect(m_ui->settingsWidget, SIGNAL(accepted()), SLOT(switchToDatabases()));
@@ -369,10 +414,12 @@ 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()));
- connect(m_ui->actionChangeDatabaseSettings, SIGNAL(triggered()), m_ui->tabWidget, SLOT(changeDatabaseSettings()));
+ connect(m_ui->actionDatabaseSecurity, SIGNAL(triggered()), m_ui->tabWidget, SLOT(showDatabaseSecurity()));
+ connect(m_ui->actionReports, SIGNAL(triggered()), m_ui->tabWidget, SLOT(showDatabaseReports()));
+ connect(m_ui->actionDatabaseSettings, SIGNAL(triggered()), m_ui->tabWidget, SLOT(showDatabaseSettings()));
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->actionImportOpVault, SIGNAL(triggered()), m_ui->tabWidget, SLOT(importOpVaultDatabase()));
@@ -392,6 +439,8 @@ MainWindow::MainWindow()
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->actionEntryMoveUp, SIGNAL(triggered()), SLOT(moveEntryUp()));
+ m_actionMultiplexer.connect(m_ui->actionEntryMoveDown, SIGNAL(triggered()), SLOT(moveEntryDown()));
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()));
@@ -399,6 +448,10 @@ MainWindow::MainWindow()
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->actionEntryDownloadIcon, SIGNAL(triggered()), SLOT(downloadSelectedFavicons()));
+#ifdef WITH_XC_SSHAGENT
+ m_actionMultiplexer.connect(m_ui->actionEntryAddToAgent, SIGNAL(triggered()), SLOT(addToAgent()));
+ m_actionMultiplexer.connect(m_ui->actionEntryRemoveFromAgent, SIGNAL(triggered()), SLOT(removeFromAgent()));
+#endif
m_actionMultiplexer.connect(m_ui->actionGroupNew, SIGNAL(triggered()), SLOT(createGroup()));
m_actionMultiplexer.connect(m_ui->actionGroupEdit, SIGNAL(triggered()), SLOT(switchToGroupEdit()));
@@ -409,8 +462,11 @@ MainWindow::MainWindow()
m_actionMultiplexer.connect(m_ui->actionGroupDownloadFavicons, SIGNAL(triggered()), SLOT(downloadAllFavicons()));
connect(m_ui->actionSettings, SIGNAL(toggled(bool)), SLOT(switchToSettings(bool)));
- connect(m_ui->actionPasswordGenerator, SIGNAL(toggled(bool)), SLOT(switchToPasswordGen(bool)));
- connect(m_ui->passwordGeneratorWidget, SIGNAL(dialogTerminated()), SLOT(closePasswordGen()));
+ connect(m_ui->actionPasswordGenerator, SIGNAL(toggled(bool)), SLOT(togglePasswordGenerator(bool)));
+ connect(m_ui->passwordGeneratorWidget, &PasswordGeneratorWidget::closed, this, [this] {
+ togglePasswordGenerator(false);
+ });
+ m_ui->passwordGeneratorWidget->setStandaloneMode(true);
connect(m_ui->welcomeWidget, SIGNAL(newDatabase()), SLOT(switchToNewDatabase()));
connect(m_ui->welcomeWidget, SIGNAL(openDatabase()), SLOT(switchToOpenDatabase()));
@@ -439,7 +495,11 @@ MainWindow::MainWindow()
connect(UpdateChecker::instance(),
SIGNAL(updateCheckFinished(bool, QString, bool)),
SLOT(hasUpdateAvailable(bool, QString, bool)));
- QTimer::singleShot(500, this, SLOT(showUpdateCheckStartup()));
+ // Setup an update check every hour (checked only occur every 7 days)
+ connect(&m_updateCheckTimer, &QTimer::timeout, this, &MainWindow::performUpdateCheck);
+ m_updateCheckTimer.start(3.6e6);
+ // Perform the startup update check after 500 ms
+ QTimer::singleShot(500, this, SLOT(performUpdateCheck()));
#else
m_ui->actionCheckForUpdates->setVisible(false);
#endif
@@ -450,10 +510,8 @@ MainWindow::MainWindow()
#endif
// clang-format off
- connect(m_ui->tabWidget,
- SIGNAL(messageGlobal(QString,MessageWidget::MessageType)),
- this,
- SLOT(displayGlobalMessage(QString,MessageWidget::MessageType)));
+ connect(m_ui->tabWidget, SIGNAL(messageGlobal(QString,MessageWidget::MessageType)),
+ SLOT(displayGlobalMessage(QString,MessageWidget::MessageType)));
// clang-format on
connect(m_ui->tabWidget, SIGNAL(messageDismissGlobal()), this, SLOT(hideGlobalMessage()));
@@ -469,34 +527,50 @@ MainWindow::MainWindow()
m_trayIconTriggerTimer.setSingleShot(true);
connect(&m_trayIconTriggerTimer, SIGNAL(timeout()), SLOT(processTrayIconTrigger()));
- updateTrayIcon();
-
if (config()->hasAccessError()) {
m_ui->globalMessageWidget->showMessage(tr("Access error for config file %1").arg(config()->getFileName()),
MessageWidget::Error);
}
+#if defined(KEEPASSXC_BUILD_TYPE_SNAPSHOT) || defined(KEEPASSXC_BUILD_TYPE_PRE_RELEASE)
+ auto* hidePreRelWarn = new QAction(tr("Don't show again for this version"), m_ui->globalMessageWidget);
+ m_ui->globalMessageWidget->addAction(hidePreRelWarn);
+ auto hidePreRelWarnConn = QSharedPointer<QMetaObject::Connection>::create();
+ *hidePreRelWarnConn = connect(m_ui->globalMessageWidget, &KMessageWidget::hideAnimationFinished, [=] {
+ m_ui->globalMessageWidget->removeAction(hidePreRelWarn);
+ disconnect(*hidePreRelWarnConn);
+ hidePreRelWarn->deleteLater();
+ });
+ connect(hidePreRelWarn, &QAction::triggered, [=] {
+ m_ui->globalMessageWidget->animatedHide();
+ config()->set(Config::Messages_HidePreReleaseWarning, KEEPASSXC_VERSION);
+ });
+#endif
#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);
+ if (config()->get(Config::Messages_HidePreReleaseWarning) != KEEPASSXC_VERSION) {
+ 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);
+ if (config()->get(Config::Messages_HidePreReleaseWarning) != KEEPASSXC_VERSION) {
+ 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,
+ -1);
+ }
#elif (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) && QT_VERSION < QT_VERSION_CHECK(5, 6, 0))
- if (!config()->get("QtErrorMessageShown", false).toBool()) {
+ if (!config()->get(Config::Messages_Qt55CompatibilityWarning).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);
- config()->set("QtErrorMessageShown", true);
+ config()->set(Config::Messages_Qt55CompatibilityWarning, true);
}
#endif
}
@@ -505,6 +579,15 @@ MainWindow::~MainWindow()
{
}
+QList<DatabaseWidget*> MainWindow::getOpenDatabases()
+{
+ QList<DatabaseWidget*> dbWidgets;
+ for (int i = 0; i < m_ui->tabWidget->count(); ++i) {
+ dbWidgets << m_ui->tabWidget->databaseWidgetFromIndex(i);
+ }
+ return dbWidgets;
+}
+
void MainWindow::showErrorMessage(const QString& message)
{
m_ui->globalMessageWidget->showMessage(message, MessageWidget::Error);
@@ -520,7 +603,7 @@ void MainWindow::updateLastDatabasesMenu()
{
m_ui->menuRecentDatabases->clear();
- const QStringList lastDatabases = config()->get("LastDatabases", QVariant()).toStringList();
+ const QStringList lastDatabases = config()->get(Config::LastDatabases).toStringList();
for (const QString& database : lastDatabases) {
QAction* action = m_ui->menuRecentDatabases->addAction(database);
action->setData(database);
@@ -561,7 +644,7 @@ void MainWindow::openRecentDatabase(QAction* action)
void MainWindow::clearLastDatabases()
{
- config()->set("LastDatabases", QVariant());
+ config()->remove(Config::LastDatabases);
bool inWelcomeWidget = (m_ui->stackedWidget->currentIndex() == 2);
if (inWelcomeWidget) {
@@ -583,12 +666,10 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
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) {
@@ -601,19 +682,25 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
switch (mode) {
case DatabaseWidget::Mode::ViewMode: {
- bool hasFocus = m_contextMenuFocusLock || menuBar()->hasFocus() || m_searchWidget->hasFocus()
- || dbWidget->currentEntryHasFocus();
- bool singleEntrySelected = dbWidget->numberOfSelectedEntries() == 1 && hasFocus;
- bool entriesSelected = dbWidget->numberOfSelectedEntries() > 0 && hasFocus;
+ bool singleEntrySelected = dbWidget->numberOfSelectedEntries() == 1;
+ bool entriesSelected = dbWidget->numberOfSelectedEntries() > 0;
bool groupSelected = dbWidget->isGroupSelected();
bool currentGroupHasChildren = dbWidget->currentGroup()->hasChildren();
bool currentGroupHasEntries = !dbWidget->currentGroup()->entries().isEmpty();
bool recycleBinSelected = dbWidget->isRecycleBinSelected();
+ bool sorted = dbWidget->isSorted();
+ int entryIndex = dbWidget->currentEntryIndex();
+ int numEntries = dbWidget->currentGroup()->entries().size();
m_ui->actionEntryNew->setEnabled(true);
m_ui->actionEntryClone->setEnabled(singleEntrySelected);
m_ui->actionEntryEdit->setEnabled(singleEntrySelected);
m_ui->actionEntryDelete->setEnabled(entriesSelected);
+ m_ui->actionEntryMoveUp->setVisible(!sorted);
+ m_ui->actionEntryMoveDown->setVisible(!sorted);
+ m_ui->actionEntryMoveUp->setEnabled(singleEntrySelected && !sorted && entryIndex > 0);
+ m_ui->actionEntryMoveDown->setEnabled(singleEntrySelected && !sorted && entryIndex >= 0
+ && entryIndex < numEntries - 1);
m_ui->actionEntryCopyTitle->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTitle());
m_ui->actionEntryCopyUsername->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUsername());
// NOTE: Copy password is enabled even if the selected entry's password is blank to prevent Ctrl+C
@@ -641,14 +728,24 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
m_ui->actionGroupDownloadFavicons->setVisible(!recycleBinSelected);
m_ui->actionGroupDownloadFavicons->setEnabled(groupSelected && currentGroupHasEntries
&& !recycleBinSelected);
- m_ui->actionChangeMasterKey->setEnabled(true);
- m_ui->actionChangeDatabaseSettings->setEnabled(true);
+ m_ui->actionDatabaseSecurity->setEnabled(true);
+ m_ui->actionReports->setEnabled(true);
+ m_ui->actionDatabaseSettings->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);
m_ui->actionDatabaseMerge->setEnabled(m_ui->tabWidget->currentIndex() != -1);
+#ifdef WITH_XC_SSHAGENT
+ bool singleEntryHasSshKey =
+ singleEntrySelected && sshAgent()->isEnabled() && dbWidget->currentEntryHasSshKey();
+ m_ui->actionEntryAddToAgent->setVisible(singleEntryHasSshKey);
+ m_ui->actionEntryAddToAgent->setEnabled(singleEntryHasSshKey);
+ m_ui->actionEntryRemoveFromAgent->setVisible(singleEntryHasSshKey);
+ m_ui->actionEntryRemoveFromAgent->setEnabled(singleEntryHasSshKey);
+#endif
m_searchWidgetAction->setEnabled(true);
@@ -687,10 +784,12 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
action->setEnabled(false);
}
- m_ui->actionChangeMasterKey->setEnabled(false);
- m_ui->actionChangeDatabaseSettings->setEnabled(false);
+ m_ui->actionDatabaseSecurity->setEnabled(false);
+ m_ui->actionReports->setEnabled(false);
+ m_ui->actionDatabaseSettings->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);
@@ -714,10 +813,12 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
action->setEnabled(false);
}
- m_ui->actionChangeMasterKey->setEnabled(false);
- m_ui->actionChangeDatabaseSettings->setEnabled(false);
+ m_ui->actionDatabaseSecurity->setEnabled(false);
+ m_ui->actionReports->setEnabled(false);
+ m_ui->actionDatabaseSettings->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);
@@ -738,6 +839,26 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
}
}
+void MainWindow::updateToolbarSeparatorVisibility()
+{
+ if (!m_showToolbarSeparator) {
+ m_ui->toolbarSeparator->setVisible(false);
+ return;
+ }
+
+ switch (m_ui->stackedWidget->currentIndex()) {
+ case DatabaseTabScreen:
+ m_ui->toolbarSeparator->setVisible(!m_ui->tabWidget->tabBar()->isVisible()
+ && m_ui->tabWidget->tabBar()->count() == 1);
+ break;
+ case SettingsScreen:
+ m_ui->toolbarSeparator->setVisible(true);
+ break;
+ default:
+ m_ui->toolbarSeparator->setVisible(false);
+ }
+}
+
void MainWindow::updateWindowTitle()
{
QString customWindowTitlePart;
@@ -779,10 +900,10 @@ void MainWindow::showAboutDialog()
aboutDialog->open();
}
-void MainWindow::showUpdateCheckStartup()
+void MainWindow::performUpdateCheck()
{
#ifdef WITH_XC_UPDATECHECK
- if (!config()->get("UpdateCheckMessageShown", false).toBool()) {
+ if (!config()->get(Config::UpdateCheckMessageShown).toBool()) {
auto result =
MessageBox::question(this,
tr("Check for updates on startup?"),
@@ -791,11 +912,11 @@ void MainWindow::showUpdateCheckStartup()
MessageBox::Yes | MessageBox::No,
MessageBox::Yes);
- config()->set("GUI/CheckForUpdates", (result == MessageBox::Yes));
- config()->set("UpdateCheckMessageShown", true);
+ config()->set(Config::GUI_CheckForUpdates, (result == MessageBox::Yes));
+ config()->set(Config::UpdateCheckMessageShown, true);
}
- if (config()->get("GUI/CheckForUpdates", false).toBool()) {
+ if (config()->get(Config::GUI_CheckForUpdates).toBool()) {
updateCheck()->checkForUpdates(false);
}
@@ -843,12 +964,12 @@ void MainWindow::openBugReportUrl()
void MainWindow::openGettingStartedGuide()
{
- customOpenUrl(QString("file:///%1").arg(filePath()->dataPath("docs/KeePassXC_GettingStarted.pdf")));
+ customOpenUrl(QString("file:///%1").arg(resources()->dataPath("docs/KeePassXC_GettingStarted.html")));
}
void MainWindow::openUserGuide()
{
- customOpenUrl(QString("file:///%1").arg(filePath()->dataPath("docs/KeePassXC_UserGuide.pdf")));
+ customOpenUrl(QString("file:///%1").arg(resources()->dataPath("docs/KeePassXC_UserGuide.html")));
}
void MainWindow::openOnlineHelp()
@@ -858,7 +979,7 @@ void MainWindow::openOnlineHelp()
void MainWindow::openKeyboardShortcuts()
{
- customOpenUrl("https://github.com/keepassxreboot/keepassxc/blob/develop/docs/KEYBINDS.md");
+ customOpenUrl(QString("file:///%1").arg(resources()->dataPath("docs/KeePassXC_KeyboardShortcuts.html")));
}
void MainWindow::switchToDatabases()
@@ -880,24 +1001,18 @@ void MainWindow::switchToSettings(bool enabled)
}
}
-void MainWindow::switchToPasswordGen(bool enabled)
+void MainWindow::togglePasswordGenerator(bool enabled)
{
if (enabled) {
m_ui->passwordGeneratorWidget->loadSettings();
m_ui->passwordGeneratorWidget->regeneratePassword();
m_ui->stackedWidget->setCurrentIndex(PasswordGeneratorScreen);
- m_ui->passwordGeneratorWidget->setStandaloneMode(true);
} else {
m_ui->passwordGeneratorWidget->saveSettings();
switchToDatabases();
}
}
-void MainWindow::closePasswordGen()
-{
- switchToPasswordGen(false);
-}
-
void MainWindow::switchToNewDatabase()
{
m_ui->tabWidget->newDatabase();
@@ -940,28 +1055,38 @@ void MainWindow::databaseStatusChanged(DatabaseWidget* dbWidget)
updateTrayIcon();
}
-void MainWindow::selectNextDatabaseTab()
+/**
+ * Select a database tab by its index. Stays bounded to first/last tab
+ * on overflow unless wrap is true.
+ *
+ * @param tabIndex 0-based tab index selector
+ * @param wrap if true wrap around to first/last tab
+ */
+void MainWindow::selectDatabaseTab(int tabIndex, bool wrap)
{
if (m_ui->stackedWidget->currentIndex() == DatabaseTabScreen) {
- int index = m_ui->tabWidget->currentIndex() + 1;
- if (index >= m_ui->tabWidget->count()) {
- m_ui->tabWidget->setCurrentIndex(0);
+ if (wrap) {
+ if (tabIndex < 0) {
+ tabIndex = m_ui->tabWidget->count() - 1;
+ } else if (tabIndex >= m_ui->tabWidget->count()) {
+ tabIndex = 0;
+ }
} else {
- m_ui->tabWidget->setCurrentIndex(index);
+ tabIndex = qBound(0, tabIndex, m_ui->tabWidget->count() - 1);
}
+
+ m_ui->tabWidget->setCurrentIndex(tabIndex);
}
}
+void MainWindow::selectNextDatabaseTab()
+{
+ selectDatabaseTab(m_ui->tabWidget->currentIndex() + 1, true);
+}
+
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);
- }
- }
+ selectDatabaseTab(m_ui->tabWidget->currentIndex() - 1, true);
}
void MainWindow::databaseTabChanged(int tabIndex)
@@ -1000,7 +1125,8 @@ void MainWindow::closeEvent(QCloseEvent* event)
// Ignore event and hide to tray if this is not an actual close
// request by the system's session manager.
- if (config()->get("GUI/MinimizeOnClose").toBool() && !m_appExitCalled && !isHidden() && !qApp->isSavingSession()) {
+ if (config()->get(Config::GUI_MinimizeOnClose).toBool() && !m_appExitCalled && !isHidden()
+ && !qApp->isSavingSession()) {
event->ignore();
hideWindow();
return;
@@ -1010,11 +1136,12 @@ void MainWindow::closeEvent(QCloseEvent* event)
if (m_appExiting) {
saveWindowInformation();
event->accept();
- QApplication::quit();
+ m_restartRequested ? kpxcApp->restart() : QApplication::quit();
return;
}
m_appExitCalled = false;
+ m_restartRequested = false;
event->ignore();
}
@@ -1022,12 +1149,12 @@ 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(Config::GUI_MinimizeToTray).toBool()) {
event->ignore();
- QTimer::singleShot(0, this, SLOT(hide()));
+ hide();
}
- if (config()->get("security/lockdatabaseminimize").toBool()) {
+ if (config()->get(Config::Security_LockDatabaseMinimize).toBool()) {
m_ui->tabWidget->lockDatabases();
}
} else {
@@ -1035,22 +1162,52 @@ void MainWindow::changeEvent(QEvent* event)
}
}
+bool MainWindow::focusNextPrevChild(bool next)
+{
+ // Only navigate around the main window if the database widget is showing the entry view
+ auto dbWidget = m_ui->tabWidget->currentDatabaseWidget();
+ if (dbWidget && dbWidget->isVisible() && dbWidget->isEntryViewActive()) {
+ // Search Widget <-> Tab Widget <-> DbWidget
+ if (next) {
+ if (m_searchWidget->hasFocus()) {
+ m_ui->tabWidget->setFocus(Qt::TabFocusReason);
+ } else if (m_ui->tabWidget->hasFocus()) {
+ dbWidget->setFocus(Qt::TabFocusReason);
+ } else {
+ m_searchWidget->setFocus(Qt::TabFocusReason);
+ }
+ } else {
+ if (m_searchWidget->hasFocus()) {
+ dbWidget->setFocus(Qt::BacktabFocusReason);
+ } else if (m_ui->tabWidget->hasFocus()) {
+ m_searchWidget->setFocus(Qt::BacktabFocusReason);
+ } else {
+ m_ui->tabWidget->setFocus(Qt::BacktabFocusReason);
+ }
+ }
+ return true;
+ }
+
+ // Defer to Qt to make a decision, this maintains normal behavior
+ return QMainWindow::focusNextPrevChild(next);
+}
+
void MainWindow::saveWindowInformation()
{
if (isVisible()) {
- config()->set("GUI/MainWindowGeometry", saveGeometry());
- config()->set("GUI/MainWindowState", saveState());
+ config()->set(Config::GUI_MainWindowGeometry, saveGeometry());
+ config()->set(Config::GUI_MainWindowState, saveState());
}
}
bool MainWindow::saveLastDatabases()
{
- if (config()->get("OpenPreviousDatabasesOnStartup").toBool()) {
+ if (config()->get(Config::OpenPreviousDatabasesOnStartup).toBool()) {
auto currentDbWidget = m_ui->tabWidget->currentDatabaseWidget();
if (currentDbWidget) {
- config()->set("LastActiveDatabase", currentDbWidget->database()->filePath());
+ config()->set(Config::LastActiveDatabase, currentDbWidget->database()->filePath());
} else {
- config()->set("LastActiveDatabase", {});
+ config()->remove(Config::LastActiveDatabase);
}
QStringList openDatabases;
@@ -1059,10 +1216,10 @@ bool MainWindow::saveLastDatabases()
openDatabases.append(dbWidget->database()->filePath());
}
- config()->set("LastOpenedDatabases", openDatabases);
+ config()->set(Config::LastOpenedDatabases, openDatabases);
} else {
- config()->set("LastActiveDatabase", {});
- config()->set("LastOpenedDatabases", {});
+ config()->remove(Config::LastActiveDatabase);
+ config()->remove(Config::LastOpenedDatabases);
}
return m_ui->tabWidget->closeAllDatabaseTabs();
@@ -1073,11 +1230,11 @@ void MainWindow::updateTrayIcon()
if (isTrayIconEnabled()) {
if (!m_trayIcon) {
m_trayIcon = new QSystemTrayIcon(this);
- QMenu* menu = new QMenu(this);
+ auto* menu = new QMenu(this);
- QAction* actionToggle = new QAction(tr("Toggle window"), menu);
+ auto* actionToggle = new QAction(tr("Toggle window"), menu);
menu->addAction(actionToggle);
- actionToggle->setIcon(filePath()->icon("apps", "keepassxc"));
+ actionToggle->setIcon(resources()->icon("keepassxc-monochrome-dark"));
menu->addAction(m_ui->actionLockDatabases);
@@ -1097,19 +1254,21 @@ void MainWindow::updateTrayIcon()
m_trayIcon->setContextMenu(menu);
- m_trayIcon->setIcon(filePath()->trayIcon());
+ m_trayIcon->setIcon(resources()->trayIcon());
m_trayIcon->show();
}
- if (m_ui->tabWidget->hasLockableDatabases()) {
- m_trayIcon->setIcon(filePath()->trayIconUnlocked());
+
+ if (m_ui->tabWidget->count() == 0) {
+ m_trayIcon->setIcon(resources()->trayIcon());
+ } else if (m_ui->tabWidget->hasLockableDatabases()) {
+ m_trayIcon->setIcon(resources()->trayIconUnlocked());
} else {
- m_trayIcon->setIcon(filePath()->trayIconLocked());
+ m_trayIcon->setIcon(resources()->trayIconLocked());
}
} else {
if (m_trayIcon) {
m_trayIcon->hide();
delete m_trayIcon;
- m_trayIcon = nullptr;
}
}
}
@@ -1124,12 +1283,18 @@ void MainWindow::releaseContextFocusLock()
m_contextMenuFocusLock = false;
}
+void MainWindow::agentEnabled(bool enabled)
+{
+ m_ui->actionEntryAddToAgent->setVisible(enabled);
+ m_ui->actionEntryRemoveFromAgent->setVisible(enabled);
+}
+
void MainWindow::showEntryContextMenu(const QPoint& globalPos)
{
bool entrySelected = false;
auto dbWidget = m_ui->tabWidget->currentDatabaseWidget();
if (dbWidget) {
- entrySelected = dbWidget->currentEntryHasFocus();
+ entrySelected = dbWidget->numberOfSelectedEntries() > 0;
}
if (entrySelected) {
@@ -1155,38 +1320,39 @@ void MainWindow::setShortcut(QAction* action, QKeySequence::StandardKey standard
void MainWindow::applySettingsChanges()
{
- int timeout = config()->get("security/lockdatabaseidlesec").toInt() * 1000;
+ int timeout = config()->get(Config::Security_LockDatabaseIdleSeconds).toInt() * 1000;
if (timeout <= 0) {
timeout = 60;
}
m_inactivityTimer->setInactivityTimeout(timeout);
- if (config()->get("security/lockdatabaseidle").toBool()) {
+ if (config()->get(Config::Security_LockDatabaseIdle).toBool()) {
m_inactivityTimer->activate();
} 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;
- }
+ if (config()->get(Config::Security_ResetTouchId).toBool()) {
+ // Calculate TouchID timeout in milliseconds
+ timeout = config()->get(Config::Security_ResetTouchIdTimeout).toInt() * 60 * 1000;
+ if (timeout <= 0) {
+ timeout = 30 * 60 * 1000;
+ }
- m_touchIDinactivityTimer->setInactivityTimeout(timeout);
- if (config()->get("security/resettouchid").toBool()) {
+ m_touchIDinactivityTimer->setInactivityTimeout(timeout);
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());
+ m_ui->toolBar->setHidden(config()->get(Config::GUI_HideToolbar).toBool());
+ m_ui->toolBar->setMovable(config()->get(Config::GUI_MovableToolbar).toBool());
bool isOk = false;
- const auto toolButtonStyle = static_cast<Qt::ToolButtonStyle>(config()->get("GUI/ToolButtonStyle").toInt(&isOk));
+ const auto toolButtonStyle =
+ static_cast<Qt::ToolButtonStyle>(config()->get(Config::GUI_ToolButtonStyle).toInt(&isOk));
if (isOk) {
m_ui->toolBar->setToolButtonStyle(toolButtonStyle);
}
@@ -1197,7 +1363,7 @@ void MainWindow::applySettingsChanges()
void MainWindow::focusWindowChanged(QWindow* focusWindow)
{
if (focusWindow != windowHandle()) {
- m_lastFocusOutTime = Clock::currentSecondsSinceEpoch();
+ m_lastFocusOutTime = Clock::currentMilliSecondsSinceEpoch();
}
}
@@ -1214,6 +1380,16 @@ void MainWindow::trayIconTriggered(QSystemTrayIcon::ActivationReason reason)
void MainWindow::processTrayIconTrigger()
{
+#ifdef Q_OS_MACOS
+ // Do not toggle the window on macOS and just show the context menu instead.
+ // Right click detection doesn't seem to be working anyway
+ // and anything else will only trigger the context menu AND
+ // toggle the window at the same time, which is confusing at best.
+ // Showing only a context menu for tray icons seems to be best
+ // practice on macOS anyway, so this is probably fine.
+ return;
+#endif
+
if (m_trayIconTriggerReason == QSystemTrayIcon::DoubleClick) {
// Always toggle window on double click
toggleWindow();
@@ -1221,11 +1397,11 @@ void MainWindow::processTrayIconTrigger()
|| m_trayIconTriggerReason == QSystemTrayIcon::MiddleClick) {
// Toggle window if is not in front.
#ifdef Q_OS_WIN
- // If on Windows, check if focus switched within the last second because
+ // If on Windows, check if focus switched within the 500 milliseconds since
// clicking the tray icon removes focus from main window.
- if (isHidden() || (Clock::currentSecondsSinceEpoch() - m_lastFocusOutTime) <= 1) {
+ if (isHidden() || (Clock::currentMilliSecondsSinceEpoch() - m_lastFocusOutTime) <= 500) {
#else
- // If on Linux or macOS, check if the window has focus.
+ // If on Linux, check if the window has focus.
if (hasFocus() || isHidden() || windowHandle()->isActive()) {
#endif
toggleWindow();
@@ -1235,28 +1411,63 @@ void MainWindow::processTrayIconTrigger()
}
}
+void MainWindow::show()
+{
+#ifndef Q_OS_WIN
+ m_lastShowTime = Clock::currentMilliSecondsSinceEpoch();
+#endif
+#ifdef Q_OS_MACOS
+ // Unset minimize state to avoid weird fly-in effects
+ setWindowState(windowState() & ~Qt::WindowMinimized);
+ macUtils()->toggleForegroundApp(true);
+#endif
+ QMainWindow::show();
+}
+
+void MainWindow::hide()
+{
+#ifndef Q_OS_WIN
+ qint64 current_time = Clock::currentMilliSecondsSinceEpoch();
+ if (current_time - m_lastShowTime < 50) {
+ return;
+ }
+#endif
+ QMainWindow::hide();
+#ifdef Q_OS_MACOS
+ macUtils()->toggleForegroundApp(false);
+#endif
+}
+
void MainWindow::hideWindow()
{
saveWindowInformation();
-#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
+
// Only hide if tray icon is active, otherwise window will be gone forever
if (isTrayIconEnabled()) {
+ // On X11, the window should NOT be minimized and hidden at the same time. See issue #1595.
+ // On macOS, we are skipping minimization as well to avoid playing the magic lamp animation.
+ if (QGuiApplication::platformName() != "xcb" && QGuiApplication::platformName() != "cocoa") {
+ setWindowState(windowState() | Qt::WindowMinimized);
+ }
hide();
} else {
showMinimized();
}
- if (config()->get("security/lockdatabaseminimize").toBool()) {
+ if (config()->get(Config::Security_LockDatabaseMinimize).toBool()) {
m_ui->tabWidget->lockDatabases();
}
}
+void MainWindow::minimizeOrHide()
+{
+ if (config()->get(Config::GUI_MinimizeToTray).toBool()) {
+ hideWindow();
+ } else {
+ showMinimized();
+ }
+}
+
void MainWindow::toggleWindow()
{
if (isVisible() && !isMinimized()) {
@@ -1269,7 +1480,7 @@ void MainWindow::toggleWindow()
// 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
- const static auto isDesktopSessionUnity = qgetenv("XDG_CURRENT_DESKTOP") == "Unity";
+ static const auto isDesktopSessionUnity = qgetenv("XDG_CURRENT_DESKTOP") == "Unity";
if (isDesktopSessionUnity && Tools::qtRuntimeVersion() < QT_VERSION_CHECK(5, 9, 0)
&& !m_ui->menubar->isVisible()) {
@@ -1306,7 +1517,7 @@ void MainWindow::forgetTouchIDAfterInactivity()
bool MainWindow::isTrayIconEnabled() const
{
- return config()->get("GUI/ShowTrayIcon").toBool() && QSystemTrayIcon::isSystemTrayAvailable();
+ return config()->get(Config::GUI_ShowTrayIcon).toBool() && QSystemTrayIcon::isSystemTrayAvailable();
}
void MainWindow::displayGlobalMessage(const QString& text,
@@ -1357,12 +1568,12 @@ void MainWindow::bringToFront()
void MainWindow::handleScreenLock()
{
- if (config()->get("security/lockdatabasescreenlock").toBool()) {
+ if (config()->get(Config::Security_LockDatabaseScreenLock).toBool()) {
lockDatabasesAfterInactivity();
}
#ifdef WITH_XC_TOUCHID
- if (config()->get("security/resettouchidscreenlock").toBool()) {
+ if (config()->get(Config::Security_ResetTouchIdScreenlock).toBool()) {
forgetTouchIDAfterInactivity();
}
#endif
@@ -1428,8 +1639,67 @@ void MainWindow::displayDesktopNotification(const QString& msg, QString title, i
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
- m_trayIcon->showMessage(title, msg, filePath()->applicationIcon(), msTimeoutHint);
+ m_trayIcon->showMessage(title, msg, resources()->applicationIcon(), msTimeoutHint);
#else
m_trayIcon->showMessage(title, msg, QSystemTrayIcon::Information, msTimeoutHint);
#endif
}
+
+void MainWindow::restartApp(const QString& message)
+{
+ auto ans = MessageBox::question(
+ this, tr("Restart Application?"), message, MessageBox::Yes | MessageBox::No, MessageBox::Yes);
+ if (ans == MessageBox::Yes) {
+ m_appExitCalled = true;
+ m_restartRequested = true;
+ close();
+ } else {
+ m_restartRequested = false;
+ }
+}
+
+void MainWindow::initViewMenu()
+{
+ m_ui->actionThemeAuto->setData("auto");
+ m_ui->actionThemeLight->setData("light");
+ m_ui->actionThemeDark->setData("dark");
+ m_ui->actionThemeClassic->setData("classic");
+
+ auto themeActions = new QActionGroup(this);
+ themeActions->addAction(m_ui->actionThemeAuto);
+ themeActions->addAction(m_ui->actionThemeLight);
+ themeActions->addAction(m_ui->actionThemeDark);
+ themeActions->addAction(m_ui->actionThemeClassic);
+
+ auto theme = config()->get(Config::GUI_ApplicationTheme).toString();
+ for (auto action : themeActions->actions()) {
+ if (action->data() == theme) {
+ action->setChecked(true);
+ break;
+ }
+ }
+
+ connect(themeActions, &QActionGroup::triggered, this, [this](QAction* action) {
+ if (action->data() != config()->get(Config::GUI_ApplicationTheme)) {
+ config()->set(Config::GUI_ApplicationTheme, action->data());
+ restartApp(tr("You must restart the application to apply this setting. Would you like to restart now?"));
+ }
+ });
+
+ m_ui->actionCompactMode->setChecked(config()->get(Config::GUI_CompactMode).toBool());
+ connect(m_ui->actionCompactMode, &QAction::toggled, this, [this](bool checked) {
+ config()->set(Config::GUI_CompactMode, checked);
+ restartApp(tr("You must restart the application to apply this setting. Would you like to restart now?"));
+ });
+
+ m_ui->actionShowToolbar->setChecked(!config()->get(Config::GUI_HideToolbar).toBool());
+ connect(m_ui->actionShowToolbar, &QAction::toggled, this, [this](bool checked) {
+ config()->set(Config::GUI_HideToolbar, !checked);
+ applySettingsChanges();
+ });
+
+ m_ui->actionShowPreviewPanel->setChecked(!config()->get(Config::GUI_HidePreviewPanel).toBool());
+ connect(m_ui->actionShowPreviewPanel, &QAction::toggled, this, [](bool checked) {
+ config()->set(Config::GUI_HidePreviewPanel, !checked);
+ });
+}