From 8f1cbc976c6a257de0a67b5797225eaba290e7fc Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sun, 30 Jun 2024 23:26:55 -0400 Subject: [PATCH 1/2] Reset splitter sizes on database unlock * Attempt to avoid issue with splitters not being appropriately calculated because the main window isn't sized yet. This can happen if the main window is hidden when the database is loaded and the splitter sizes are not recorded in the config file. --- src/gui/DatabaseWidget.cpp | 2 + src/gui/DatabaseWidget.h | 1 + src/gui/DatabaseWidgetStateSync.cpp | 107 ++++++++++++++-------------- src/gui/DatabaseWidgetStateSync.h | 7 +- 4 files changed, 62 insertions(+), 55 deletions(-) diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index c106362897..8a18adade9 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -1284,6 +1284,7 @@ void DatabaseWidget::loadDatabase(bool accepted) } if (accepted) { + emit databaseAboutToUnlock(); replaceDatabase(openWidget->database()); switchToMainView(); processAutoOpen(); @@ -1440,6 +1441,7 @@ void DatabaseWidget::unlockDatabase(bool accepted) } } + emit databaseAboutToUnlock(); QSharedPointer db; if (senderDialog) { db = senderDialog->database(); diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index 4c13f2a900..3b1b475b5d 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -137,6 +137,7 @@ class DatabaseWidget : public QStackedWidget void databaseModified(); void databaseNonDataChanged(); void databaseSaved(); + void databaseAboutToUnlock(); void databaseUnlocked(); void databaseLockRequested(); void databaseLocked(); diff --git a/src/gui/DatabaseWidgetStateSync.cpp b/src/gui/DatabaseWidgetStateSync.cpp index 44682469e0..52458b5abc 100644 --- a/src/gui/DatabaseWidgetStateSync.cpp +++ b/src/gui/DatabaseWidgetStateSync.cpp @@ -33,7 +33,9 @@ DatabaseWidgetStateSync::DatabaseWidgetStateSync(QObject* parent) m_listViewState = config()->get(Config::GUI_ListViewState).toByteArray(); m_searchViewState = config()->get(Config::GUI_SearchViewState).toByteArray(); - connect(qApp, &QCoreApplication::aboutToQuit, this, &DatabaseWidgetStateSync::sync); + m_syncTimer.setSingleShot(true); + m_syncTimer.setInterval(100); + connect(&m_syncTimer, &QTimer::timeout, this, &DatabaseWidgetStateSync::sync); } DatabaseWidgetStateSync::~DatabaseWidgetStateSync() = default; @@ -43,6 +45,7 @@ DatabaseWidgetStateSync::~DatabaseWidgetStateSync() = default; */ void DatabaseWidgetStateSync::sync() { + m_syncTimer.stop(); config()->set(Config::GUI_SplitterState, intListToVariant(m_splitterSizes.value(Config::GUI_SplitterState))); config()->set(Config::GUI_PreviewSplitterState, intListToVariant(m_splitterSizes.value(Config::GUI_PreviewSplitterState))); @@ -56,79 +59,71 @@ void DatabaseWidgetStateSync::sync() void DatabaseWidgetStateSync::setActive(DatabaseWidget* dbWidget) { if (m_activeDbWidget) { + if (m_activeDbWidget->currentMode() != DatabaseWidget::Mode::LockedMode) { + // Update settings from previously active database if unlocked + updateAll(); + } disconnect(m_activeDbWidget, nullptr, this, nullptr); } m_activeDbWidget = dbWidget; if (m_activeDbWidget) { - // Give the database widget a chance to render itself before restoring the state - QTimer::singleShot(0, this, [this] { - if (!m_activeDbWidget) { - return; - } - - m_blockUpdates = true; - - m_activeDbWidget->setSplitterSizes(m_splitterSizes); - - if (m_activeDbWidget->isSearchActive()) { - restoreSearchView(); - } else { - restoreListView(); - } - - m_blockUpdates = false; - }); + if (m_activeDbWidget->currentMode() != DatabaseWidget::Mode::LockedMode) { + // Immediately apply settings to active database if already unlocked + applySplitterSizes(); + applyViewState(); + } + connect(m_activeDbWidget, SIGNAL(databaseAboutToUnlock()), SLOT(blockUpdates())); + connect(m_activeDbWidget, SIGNAL(databaseUnlocked()), SLOT(applySplitterSizes())); + connect(m_activeDbWidget, SIGNAL(databaseUnlocked()), SLOT(applyViewState())); + connect(m_activeDbWidget, &DatabaseWidget::databaseLocked, this, [this] { updateAll(true); }); connect(m_activeDbWidget, SIGNAL(splitterSizesChanged()), SLOT(updateSplitterSizes())); connect(m_activeDbWidget, SIGNAL(entryViewStateChanged()), SLOT(updateViewState())); - connect(m_activeDbWidget, SIGNAL(listModeActivated()), SLOT(restoreListView())); - connect(m_activeDbWidget, SIGNAL(searchModeActivated()), SLOT(restoreSearchView())); + connect(m_activeDbWidget, SIGNAL(listModeActivated()), SLOT(applyViewState())); + connect(m_activeDbWidget, SIGNAL(searchModeActivated()), SLOT(applyViewState())); connect(m_activeDbWidget, SIGNAL(listModeAboutToActivate()), SLOT(blockUpdates())); connect(m_activeDbWidget, SIGNAL(searchModeAboutToActivate()), SLOT(blockUpdates())); } } -/** - * Restore entry view list view state - * - * NOTE: - * States of entry view 'Hide Usernames'/'Hide Passwords' settings are global, - * i.e. they are the same for both list and search mode - * - * NOTE: - * If m_listViewState is empty, the list view has been activated for the first - * time after starting with a clean (or invalid) config. - */ -void DatabaseWidgetStateSync::restoreListView() +void DatabaseWidgetStateSync::applySplitterSizes() { - if (!m_listViewState.isEmpty()) { - m_activeDbWidget->setEntryViewState(m_listViewState); + if (!m_activeDbWidget) { + return; } + m_blockUpdates = true; + + m_activeDbWidget->setSplitterSizes(m_splitterSizes); + m_blockUpdates = false; } /** - * Restore entry view search view state - * - * NOTE: - * States of entry view 'Hide Usernames'/'Hide Passwords' settings are global, - * i.e. they are the same for both list and search mode + * Restore entry view list view state * * NOTE: - * If m_searchViewState is empty, the search view has been activated for the - * first time after starting with a clean (or invalid) config. Thus, save the - * current state. Without this, m_searchViewState would remain empty until - * there is an actual view state change (e.g. column is resized) + * If m_listViewState is empty, the list view has been activated for the first + * time after starting with a clean (or invalid) config. */ -void DatabaseWidgetStateSync::restoreSearchView() +void DatabaseWidgetStateSync::applyViewState() { - if (!m_searchViewState.isEmpty()) { - m_activeDbWidget->setEntryViewState(m_searchViewState); + if (!m_activeDbWidget) { + return; + } + + m_blockUpdates = true; + + if (m_activeDbWidget->isSearchActive()) { + if (!m_searchViewState.isEmpty()) { + m_activeDbWidget->setEntryViewState(m_searchViewState); + } } else { - m_searchViewState = m_activeDbWidget->entryViewState(); + if (!m_listViewState.isEmpty()) { + m_activeDbWidget->setEntryViewState(m_listViewState); + } } m_blockUpdates = false; @@ -139,19 +134,25 @@ void DatabaseWidgetStateSync::blockUpdates() m_blockUpdates = true; } +void DatabaseWidgetStateSync::updateAll(bool forceSync) +{ + updateSplitterSizes(); + updateViewState(); + if (forceSync) { + sync(); + } +} + void DatabaseWidgetStateSync::updateSplitterSizes() { if (!m_blockUpdates) { m_splitterSizes = m_activeDbWidget->splitterSizes(); + m_syncTimer.start(); } } /** * Update entry view list/search view state - * - * NOTE: - * States of entry view 'Hide Usernames'/'Hide Passwords' settings are global, - * i.e. they are the same for both list and search mode */ void DatabaseWidgetStateSync::updateViewState() { @@ -165,7 +166,7 @@ void DatabaseWidgetStateSync::updateViewState() m_listViewState = m_activeDbWidget->entryViewState(); } - sync(); + m_syncTimer.start(); } QList DatabaseWidgetStateSync::variantToIntList(const QVariant& variant) diff --git a/src/gui/DatabaseWidgetStateSync.h b/src/gui/DatabaseWidgetStateSync.h index 8f8aef6dc8..b9e53fdc6b 100644 --- a/src/gui/DatabaseWidgetStateSync.h +++ b/src/gui/DatabaseWidgetStateSync.h @@ -32,13 +32,14 @@ class DatabaseWidgetStateSync : public QObject public slots: void setActive(DatabaseWidget* dbWidget); - void restoreListView(); - void restoreSearchView(); + void applySplitterSizes(); + void applyViewState(); private slots: void blockUpdates(); void updateSplitterSizes(); void updateViewState(); + void updateAll(bool forceSync = false); void sync(); private: @@ -48,6 +49,8 @@ private slots: QPointer m_activeDbWidget; bool m_blockUpdates; + QTimer m_syncTimer; + QHash> m_splitterSizes; QByteArray m_listViewState; From f948a415f3141fa3a911758fa048ee38a41b717f Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sun, 22 Sep 2024 21:23:54 -0400 Subject: [PATCH 2/2] Improve related splitter UX * Prevent group pane from being hidden just by dragging. Introduce new View menu setting to hide the group pane. * Replace the preview panel "close" icon with a "collapse down" icon making the intention clearer. * Better organize the view menu --- COPYING | 1 + .../scalable/actions/arrow-collapse-down.svg | 1 + share/icons/icons.qrc | 1 + share/translations/keepassxc_en.ts | 8 ++++++++ src/core/Config.cpp | 1 + src/core/Config.h | 1 + src/gui/DatabaseWidget.cpp | 15 +++++++++++++-- src/gui/DatabaseWidget.h | 1 + src/gui/EntryPreviewWidget.cpp | 2 +- src/gui/MainWindow.cpp | 6 ++++++ src/gui/MainWindow.ui | 18 ++++++++++++++++++ 11 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 share/icons/application/scalable/actions/arrow-collapse-down.svg diff --git a/COPYING b/COPYING index 23dd1d168e..9a801fe901 100644 --- a/COPYING +++ b/COPYING @@ -141,6 +141,7 @@ Copyright: 2022 KeePassXC Team License: MIT Files: share/icons/application/scalable/actions/application-exit.svg + share/icons/application/scalable/actions/arrow-collapse-down.svg share/icons/application/scalable/actions/attributes-copy.svg share/icons/application/scalable/actions/auto-type.svg share/icons/application/scalable/actions/bitwarden.svg diff --git a/share/icons/application/scalable/actions/arrow-collapse-down.svg b/share/icons/application/scalable/actions/arrow-collapse-down.svg new file mode 100644 index 0000000000..27cfc42f2c --- /dev/null +++ b/share/icons/application/scalable/actions/arrow-collapse-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/share/icons/icons.qrc b/share/icons/icons.qrc index 4e86186d63..87b8b05b8f 100644 --- a/share/icons/icons.qrc +++ b/share/icons/icons.qrc @@ -6,6 +6,7 @@ application/256x256/apps/keepassxc.png application/scalable/actions/application-exit.svg + application/scalable/actions/arrow-collapse-down.svg application/scalable/actions/attributes-copy.svg application/scalable/actions/auto-type.svg application/scalable/actions/bitwarden.svg diff --git a/share/translations/keepassxc_en.ts b/share/translations/keepassxc_en.ts index 0ee054f6ef..f9384324cc 100644 --- a/share/translations/keepassxc_en.ts +++ b/share/translations/keepassxc_en.ts @@ -6179,6 +6179,14 @@ Expect some bugs and minor issues, this version is meant for testing purposes.Setup Remote Sync… + + Show Group Panel + + + + Toggle Show Group Panel + + ManageDatabase diff --git a/src/core/Config.cpp b/src/core/Config.cpp index 108bbe88c0..84b4a35c2a 100644 --- a/src/core/Config.cpp +++ b/src/core/Config.cpp @@ -96,6 +96,7 @@ static const QHash configStrings = { {Config::GUI_HideMenubar, {QS("GUI/HideMenubar"), Roaming, false}}, {Config::GUI_HideToolbar, {QS("GUI/HideToolbar"), Roaming, false}}, {Config::GUI_MovableToolbar, {QS("GUI/MovableToolbar"), Roaming, false}}, + {Config::GUI_HideGroupPanel, {QS("GUI/HideGroupPanel"), Roaming, false}}, {Config::GUI_HidePreviewPanel, {QS("GUI/HidePreviewPanel"), Roaming, false}}, {Config::GUI_AlwaysOnTop, {QS("GUI/GUI_AlwaysOnTop"), Local, false}}, {Config::GUI_ToolButtonStyle, {QS("GUI/ToolButtonStyle"), Roaming, Qt::ToolButtonIconOnly}}, diff --git a/src/core/Config.h b/src/core/Config.h index 5179b49d7b..8131bc9af8 100644 --- a/src/core/Config.h +++ b/src/core/Config.h @@ -78,6 +78,7 @@ class Config : public QObject GUI_HideMenubar, GUI_HideToolbar, GUI_MovableToolbar, + GUI_HideGroupPanel, GUI_HidePreviewPanel, GUI_AlwaysOnTop, GUI_ToolButtonStyle, diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 8a18adade9..fba27d5a8e 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -128,6 +128,8 @@ DatabaseWidget::DatabaseWidget(QSharedPointer db, QWidget* parent) m_groupSplitter->setStretchFactor(0, 100); m_groupSplitter->setStretchFactor(1, 0); m_groupSplitter->setSizes({1, 1}); + // Initial visibility based on config value + m_groupSplitter->setVisible(!config()->get(Config::GUI_HideGroupPanel).toBool()); auto rightHandSideWidget = new QWidget(m_mainSplitter); auto rightHandSideVBox = new QVBoxLayout(); @@ -140,12 +142,11 @@ DatabaseWidget::DatabaseWidget(QSharedPointer db, QWidget* parent) rightHandSideWidget->setLayout(rightHandSideVBox); m_entryView = new EntryView(rightHandSideWidget); - m_mainSplitter->setChildrenCollapsible(true); + m_mainSplitter->setChildrenCollapsible(false); m_mainSplitter->addWidget(m_groupSplitter); m_mainSplitter->addWidget(rightHandSideWidget); m_mainSplitter->setStretchFactor(0, 0); m_mainSplitter->setStretchFactor(1, 100); - m_mainSplitter->setCollapsible(1, false); m_mainSplitter->setSizes({1, 1}); m_previewSplitter->setOrientation(Qt::Vertical); @@ -217,6 +218,7 @@ DatabaseWidget::DatabaseWidget(QSharedPointer db, QWidget* parent) connect(m_databaseOpenWidget, SIGNAL(dialogFinished(bool)), SLOT(loadDatabase(bool))); connect(this, SIGNAL(currentChanged(int)), SLOT(emitCurrentModeChanged())); connect(this, SIGNAL(requestGlobalAutoType(const QString&)), parent, SLOT(performGlobalAutoType(const QString&))); + connect(config(), &Config::changed, this, &DatabaseWidget::onConfigChanged); // clang-format on connectDatabaseSignals(); @@ -408,6 +410,15 @@ void DatabaseWidget::setSplitterSizes(const QHash> } } +void DatabaseWidget::onConfigChanged(Config::ConfigKey key) +{ + if (key == Config::GUI_HideGroupPanel) { + // Toggle the group splitter visibility and reset the size + m_groupSplitter->setVisible(!config()->get(Config::GUI_HideGroupPanel).toBool()); + setSplitterSizes({{Config::GUI_SplitterState, QList({})}}); + } +} + void DatabaseWidget::setSearchStringForAutoType(const QString& search) { m_searchStringForAutoType = search; diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index 3b1b475b5d..5ef8709259 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -288,6 +288,7 @@ private slots: // Database autoreload slots void reloadDatabaseFile(); void restoreGroupEntryFocus(const QUuid& groupUuid, const QUuid& EntryUuid); + void onConfigChanged(Config::ConfigKey key); private: int addChildWidget(QWidget* w); diff --git a/src/gui/EntryPreviewWidget.cpp b/src/gui/EntryPreviewWidget.cpp index 0e948e4b69..0151c0c1e5 100644 --- a/src/gui/EntryPreviewWidget.cpp +++ b/src/gui/EntryPreviewWidget.cpp @@ -50,7 +50,7 @@ EntryPreviewWidget::EntryPreviewWidget(QWidget* parent) // Entry m_ui->entryTotpButton->setIcon(icons()->icon("totp")); - m_ui->entryCloseButton->setIcon(icons()->icon("dialog-close")); + m_ui->entryCloseButton->setIcon(icons()->icon("arrow-collapse-down")); m_ui->toggleUsernameButton->setIcon(icons()->onOffIcon("password-show", true)); m_ui->togglePasswordButton->setIcon(icons()->onOffIcon("password-show", true)); m_ui->toggleEntryNotesButton->setIcon(icons()->onOffIcon("password-show", true)); diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 06e86b313b..1d9c9ba32f 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -1968,6 +1968,11 @@ void MainWindow::initViewMenu() applySettingsChanges(); }); + m_ui->actionShowGroupPanel->setChecked(!config()->get(Config::GUI_HideGroupPanel).toBool()); + connect(m_ui->actionShowGroupPanel, &QAction::toggled, this, [](bool checked) { + config()->set(Config::GUI_HideGroupPanel, !checked); + }); + 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); @@ -2071,6 +2076,7 @@ void MainWindow::initActionCollection() m_ui->actionShowMenubar, #endif m_ui->actionShowToolbar, + m_ui->actionShowGroupPanel, m_ui->actionShowPreviewPanel, m_ui->actionAllowScreenCapture, m_ui->actionAlwaysOnTop, diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui index de3946c884..1110b64262 100644 --- a/src/gui/MainWindow.ui +++ b/src/gui/MainWindow.ui @@ -387,9 +387,13 @@ + + + + @@ -1285,6 +1289,20 @@ Import… + + + true + + + true + + + Show Group Panel + + + Toggle Show Group Panel + +