From e591dbcf109f1f213c5086fd2c3238eaf1c18d64 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sat, 28 Oct 2023 14:13:44 -0400 Subject: [PATCH] Overhaul action states and add icons to toolbar * Fixes #10981 --- COPYING | 7 +- .../scalable/actions/database-settings.svg | 1 + .../scalable/actions/entry-delete.svg | 2 +- .../application/scalable/actions/reports.svg | 2 +- share/icons/icons.qrc | 1 + share/translations/keepassxc_en.ts | 4 + src/fdosecrets/objects/Service.cpp | 2 +- src/gui/DatabaseTabWidget.cpp | 20 +- src/gui/DatabaseTabWidget.h | 4 +- src/gui/DatabaseWidget.cpp | 52 ++- src/gui/DatabaseWidget.h | 9 +- src/gui/EditWidget.cpp | 2 + src/gui/MainWindow.cpp | 402 +++++++----------- src/gui/MainWindow.h | 2 +- src/gui/MainWindow.ui | 135 +----- src/gui/dbsettings/DatabaseSettingsDialog.cpp | 2 + src/gui/reports/ReportsDialog.cpp | 13 +- src/gui/reports/ReportsDialog.h | 3 +- tests/gui/TestGui.cpp | 202 +++++++-- tests/gui/TestGui.h | 1 + tests/gui/TestGuiBrowser.cpp | 4 +- 21 files changed, 430 insertions(+), 440 deletions(-) create mode 100644 share/icons/application/scalable/actions/database-settings.svg diff --git a/COPYING b/COPYING index 3436ec3e47..23dd1d168e 100644 --- a/COPYING +++ b/COPYING @@ -155,6 +155,7 @@ Files: share/icons/application/scalable/actions/application-exit.svg share/icons/application/scalable/actions/database-lock-all.svg share/icons/application/scalable/actions/database-merge.svg share/icons/application/scalable/actions/database-search.svg + share/icons/application/scalable/actions/database-settings.svg share/icons/application/scalable/actions/dialog-close.svg share/icons/application/scalable/actions/dialog-ok.svg share/icons/application/scalable/actions/document-close.svg @@ -243,9 +244,9 @@ Files: share/icons/application/scalable/actions/application-exit.svg share/icons/application/scalable/actions/lock-open-alert.svg share/icons/application/scalable/actions/lock-open.svg share/icons/application/scalable/actions/lock.svg -Copyright: 2019 Austin Andrews -License: SIL OPEN FONT LICENSE Version 1.1 -Comment: Taken from Material Design icon set (https://github.com/templarian/MaterialDesign/) +Copyright: 2023 Pictogrammers +License: Apache-2.0 +Comment: Some icons are modified to fit KeePassXC design (https://pictogrammers.com/library/mdi/) Files: src/streams/qtiocompressor.* src/streams/QtIOCompressor diff --git a/share/icons/application/scalable/actions/database-settings.svg b/share/icons/application/scalable/actions/database-settings.svg new file mode 100644 index 0000000000..7bd0b9cab5 --- /dev/null +++ b/share/icons/application/scalable/actions/database-settings.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/share/icons/application/scalable/actions/entry-delete.svg b/share/icons/application/scalable/actions/entry-delete.svg index 66ae96f1bc..f052113af1 100644 --- a/share/icons/application/scalable/actions/entry-delete.svg +++ b/share/icons/application/scalable/actions/entry-delete.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/share/icons/application/scalable/actions/reports.svg b/share/icons/application/scalable/actions/reports.svg index 3d62971d2c..5453525343 100644 --- a/share/icons/application/scalable/actions/reports.svg +++ b/share/icons/application/scalable/actions/reports.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/share/icons/icons.qrc b/share/icons/icons.qrc index 40e0d5416a..4e86186d63 100644 --- a/share/icons/icons.qrc +++ b/share/icons/icons.qrc @@ -20,6 +20,7 @@ application/scalable/actions/database-lock-all.svg application/scalable/actions/database-merge.svg application/scalable/actions/database-search.svg + application/scalable/actions/database-settings.svg application/scalable/actions/dialog-close.svg application/scalable/actions/dialog-ok.svg application/scalable/actions/document-close.svg diff --git a/share/translations/keepassxc_en.ts b/share/translations/keepassxc_en.ts index 6bb5ae88d3..1bb7afc75f 100644 --- a/share/translations/keepassxc_en.ts +++ b/share/translations/keepassxc_en.ts @@ -1689,6 +1689,10 @@ Are you sure you want to continue with this file?. Remote Sync + + Database Settings: %1 + + DatabaseSettingsWidgetBrowser diff --git a/src/fdosecrets/objects/Service.cpp b/src/fdosecrets/objects/Service.cpp index ae1e9d4b64..e3fcefeb5f 100644 --- a/src/fdosecrets/objects/Service.cpp +++ b/src/fdosecrets/objects/Service.cpp @@ -543,7 +543,7 @@ namespace FdoSecrets } // switch selected to current m_databases->setCurrentWidget(dbWidget); - m_databases->showDatabaseSettings(); + m_databases->showDatabaseSettings(true); // open settings (switch from app settings to m_dbTabs) m_plugin->emitRequestSwitchToDatabases(); diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp index b9b26d3775..6e45742782 100644 --- a/src/gui/DatabaseTabWidget.cpp +++ b/src/gui/DatabaseTabWidget.cpp @@ -537,19 +537,27 @@ bool DatabaseTabWidget::warnOnExport() return ans == MessageBox::Yes; } -void DatabaseTabWidget::showDatabaseSecurity() +void DatabaseTabWidget::showDatabaseReports(bool state) { - currentDatabaseWidget()->switchToDatabaseSecurity(); + if (state) { + currentDatabaseWidget()->switchToDatabaseReports(); + } else { + currentDatabaseWidget()->switchToMainView(); + } } -void DatabaseTabWidget::showDatabaseReports() +void DatabaseTabWidget::showDatabaseSettings(bool state) { - currentDatabaseWidget()->switchToDatabaseReports(); + if (state) { + currentDatabaseWidget()->switchToDatabaseSettings(); + } else { + currentDatabaseWidget()->switchToMainView(); + } } -void DatabaseTabWidget::showDatabaseSettings() +void DatabaseTabWidget::showDatabaseSecurity() { - currentDatabaseWidget()->switchToDatabaseSettings(); + currentDatabaseWidget()->switchToDatabaseSecurity(); } #ifdef WITH_XC_BROWSER_PASSKEYS diff --git a/src/gui/DatabaseTabWidget.h b/src/gui/DatabaseTabWidget.h index aa8542dd9b..7924758611 100644 --- a/src/gui/DatabaseTabWidget.h +++ b/src/gui/DatabaseTabWidget.h @@ -82,9 +82,9 @@ public slots: void unlockAnyDatabaseInDialog(DatabaseOpenDialog::Intent intent); void relockPendingDatabase(); + void showDatabaseReports(bool state); + void showDatabaseSettings(bool state); void showDatabaseSecurity(); - void showDatabaseReports(); - void showDatabaseSettings(); #ifdef WITH_XC_BROWSER_PASSKEYS void showPasskeys(); void importPasskey(); diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index b497420b65..68b0fdcc2b 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -265,15 +265,26 @@ QSharedPointer DatabaseWidget::database() const DatabaseWidget::Mode DatabaseWidget::currentMode() const { - if (currentWidget() == nullptr) { - return Mode::None; - } else if (currentWidget() == m_mainWidget) { - return Mode::ViewMode; - } else if (currentWidget() == m_databaseOpenWidget) { - return Mode::LockedMode; + auto mode = Mode::None; + auto widget = currentWidget(); + if (widget == m_mainWidget) { + mode = Mode::ViewMode; + } else if (widget == m_databaseOpenWidget) { + mode = Mode::LockedMode; + } else if (widget == m_reportsDialog) { + mode = Mode::ReportsMode; + } else if (widget == m_databaseSettingDialog) { + mode = Mode::DatabaseSettingsMode; + } else if (widget == m_editEntryWidget) { + mode = Mode::EditEntryMode; + } else if (widget == m_editGroupWidget) { + mode = Mode::EditGroupMode; } else { - return Mode::EditMode; + // We are missing a condition if we reach here + Q_ASSERT(false); } + + return mode; } bool DatabaseWidget::isLocked() const @@ -1014,7 +1025,7 @@ void DatabaseWidget::openUrlForEntry(Entry* entry) } } -Entry* DatabaseWidget::currentSelectedEntry() +Entry* DatabaseWidget::currentSelectedEntry() const { if (currentWidget() == m_editEntryWidget) { return m_editEntryWidget->currentEntry(); @@ -1516,14 +1527,18 @@ void DatabaseWidget::entryActivationSignalReceived(Entry* entry, EntryModel::Mod void DatabaseWidget::switchToDatabaseReports() { - m_reportsDialog->load(m_db); - setCurrentWidget(m_reportsDialog); + if (currentMode() != Mode::ReportsMode) { + m_reportsDialog->load(m_db); + setCurrentWidget(m_reportsDialog); + } } void DatabaseWidget::switchToDatabaseSettings() { - m_databaseSettingDialog->load(m_db); - setCurrentWidget(m_databaseSettingDialog); + if (currentMode() != Mode::DatabaseSettingsMode) { + m_databaseSettingDialog->load(m_db); + setCurrentWidget(m_databaseSettingDialog); + } } void DatabaseWidget::switchToOpenDatabase() @@ -1865,16 +1880,13 @@ void DatabaseWidget::onEntryChanged(Entry* entry) bool DatabaseWidget::canCloneCurrentGroup() const { - bool isRootGroup = m_db->rootGroup() == m_groupView->currentGroup(); - // bool isRecycleBin = isRecycleBinSelected(); - - return !isRootGroup; + auto currentGroup = m_groupView->currentGroup(); + return currentGroup != m_db->rootGroup() && currentGroup != m_db->metadata()->recycleBin(); } bool DatabaseWidget::canDeleteCurrentGroup() const { - bool isRootGroup = m_db->rootGroup() == m_groupView->currentGroup(); - return !isRootGroup; + return currentGroup() != m_db->rootGroup(); } Group* DatabaseWidget::currentGroup() const @@ -2483,7 +2495,9 @@ void DatabaseWidget::hideMessage() bool DatabaseWidget::isRecycleBinSelected() const { - return m_groupView->currentGroup() && m_groupView->currentGroup() == m_db->metadata()->recycleBin(); + auto group = currentGroup(); + auto entry = currentSelectedEntry(); + return (group && group->isRecycled()) || (entry && entry->isRecycled()); } void DatabaseWidget::emptyRecycleBin() diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index 6622394e1a..b66589d7e8 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -64,8 +64,11 @@ class DatabaseWidget : public QStackedWidget { None, ViewMode, - EditMode, - LockedMode + EditEntryMode, + EditGroupMode, + LockedMode, + ReportsMode, + DatabaseSettingsMode }; explicit DatabaseWidget(QSharedPointer db, QWidget* parent = nullptr); @@ -106,7 +109,7 @@ class DatabaseWidget : public QStackedWidget QStringList customEntryAttributes() const; bool isEditWidgetModified() const; void clearAllWidgets(); - Entry* currentSelectedEntry(); + Entry* currentSelectedEntry() const; bool currentEntryHasTitle(); bool currentEntryHasUsername(); bool currentEntryHasPassword(); diff --git a/src/gui/EditWidget.cpp b/src/gui/EditWidget.cpp index 3a42154c92..291a08b171 100644 --- a/src/gui/EditWidget.cpp +++ b/src/gui/EditWidget.cpp @@ -31,6 +31,7 @@ EditWidget::EditWidget(QWidget* parent) setModified(false); m_ui->messageWidget->setHidden(true); + m_ui->headerLabel->setHidden(true); QFont headerLabelFont = m_ui->headerLabel->font(); headerLabelFont.setBold(true); @@ -118,6 +119,7 @@ void EditWidget::setCurrentPage(int index) void EditWidget::setHeadline(const QString& text) { + m_ui->headerLabel->setHidden(text.isEmpty()); m_ui->headerLabel->setText(text); } diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 9e38dcda9a..5613dab30a 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -127,6 +127,8 @@ MainWindow::MainWindow() m_entryContextMenu = new QMenu(this); m_entryContextMenu->setSeparatorsCollapsible(true); + m_entryContextMenu->addAction(m_ui->actionEntryRestore); + m_entryContextMenu->addSeparator(); m_entryContextMenu->addAction(m_ui->actionEntryCopyUsername); m_entryContextMenu->addAction(m_ui->actionEntryCopyPassword); m_entryContextMenu->addAction(m_ui->actionEntryCopyURL); @@ -154,8 +156,6 @@ MainWindow::MainWindow() m_entryContextMenu->addSeparator(); m_entryContextMenu->addAction(m_ui->actionEntryAddToAgent); m_entryContextMenu->addAction(m_ui->actionEntryRemoveFromAgent); - m_entryContextMenu->addSeparator(); - m_entryContextMenu->addAction(m_ui->actionEntryRestore); m_entryNewContextMenu = new QMenu(this); m_entryNewContextMenu->addAction(m_ui->actionEntryNew); @@ -346,8 +346,10 @@ MainWindow::MainWindow() m_ui->actionDatabaseSaveBackup->setIcon(icons()->icon("document-save-copy")); m_ui->actionDatabaseClose->setIcon(icons()->icon("document-close")); m_ui->actionReports->setIcon(icons()->icon("reports")); - m_ui->actionDatabaseSettings->setIcon(icons()->icon("document-edit")); + m_ui->actionDatabaseSettings->setIcon(icons()->icon("database-settings")); m_ui->actionDatabaseSecurity->setIcon(icons()->icon("database-change-key")); + m_ui->actionPasskeys->setIcon(icons()->icon("passkey")); + m_ui->actionImportPasskey->setIcon(icons()->icon("document-import")); m_ui->actionLockDatabase->setIcon(icons()->icon("database-lock")); m_ui->actionLockDatabaseToolbar->setIcon(icons()->icon("database-lock")); m_ui->actionLockAllDatabases->setIcon(icons()->icon("database-lock-all")); @@ -357,6 +359,12 @@ MainWindow::MainWindow() m_ui->actionImport->setIcon(icons()->icon("document-import")); m_ui->menuExport->setIcon(icons()->icon("document-export")); +#ifndef WITH_XC_BROWSER_PASSKEYS + m_ui->actionPasskeys->setVisible(false); + m_ui->actionImportPasskey->setVisible(false); + m_ui->actionEntryImportPasskey->setVisible(false); +#endif + m_ui->actionEntryNew->setIcon(icons()->icon("entry-new")); m_ui->actionEntryClone->setIcon(icons()->icon("entry-clone")); m_ui->actionEntryEdit->setIcon(icons()->icon("entry-edit")); @@ -381,6 +389,7 @@ MainWindow::MainWindow() m_ui->actionEntryCopyPasswordTotp->setIcon(icons()->icon("totp-copy-password")); m_ui->actionEntryTotpQRCode->setIcon(icons()->icon("qrcode")); m_ui->actionEntrySetupTotp->setIcon(icons()->icon("totp-edit")); + m_ui->actionEntryImportPasskey->setIcon(icons()->icon("document-import")); m_ui->actionEntryAddToAgent->setIcon(icons()->icon("utilities-terminal")); m_ui->actionEntryRemoveFromAgent->setIcon(icons()->icon("utilities-terminal")); m_ui->menuTags->setIcon(icons()->icon("tag-multiple")); @@ -415,11 +424,10 @@ MainWindow::MainWindow() m_ui->actionEntryRemovePasskey->setIcon(icons()->icon("document-close")); #endif - 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(databaseNonDataChanged()), this, SLOT(setMenuActionState())); + m_actionMultiplexer.connect(SIGNAL(currentModeChanged(DatabaseWidget::Mode)), this, SLOT(updateMenuActionState())); + m_actionMultiplexer.connect(SIGNAL(groupChanged()), this, SLOT(updateMenuActionState())); + m_actionMultiplexer.connect(SIGNAL(entrySelectionChanged()), this, SLOT(updateMenuActionState())); + m_actionMultiplexer.connect(SIGNAL(databaseNonDataChanged()), this, SLOT(updateMenuActionState())); m_actionMultiplexer.connect(SIGNAL(groupContextMenuRequested(QPoint)), this, SLOT(showGroupContextMenu(QPoint))); m_actionMultiplexer.connect(SIGNAL(entryContextMenuRequested(QPoint)), this, SLOT(showEntryContextMenu(QPoint))); m_actionMultiplexer.connect(SIGNAL(groupChanged()), this, SLOT(updateEntryCountLabel())); @@ -438,11 +446,11 @@ MainWindow::MainWindow() 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(currentChanged(int)), SLOT(updateMenuActionState())); 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(updateMenuActionState())); 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())); @@ -457,9 +465,9 @@ MainWindow::MainWindow() 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->actionDatabaseSettings, SIGNAL(toggled(bool)), m_ui->tabWidget, SLOT(showDatabaseSettings(bool))); 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->actionReports, SIGNAL(toggled(bool)), m_ui->tabWidget, SLOT(showDatabaseReports(bool))); #ifdef WITH_XC_BROWSER_PASSKEYS connect(m_ui->actionPasskeys, SIGNAL(triggered()), m_ui->tabWidget, SLOT(showPasskeys())); connect(m_ui->actionImportPasskey, SIGNAL(triggered()), m_ui->tabWidget, SLOT(importPasskey())); @@ -668,7 +676,7 @@ MainWindow::MainWindow() statusBar()->addPermanentWidget(m_statusBarLabel); restoreConfigState(); - setMenuActionState(); + updateMenuActionState(); } MainWindow::~MainWindow() @@ -844,251 +852,149 @@ void MainWindow::openDatabase(const QString& filePath, const QString& password, m_ui->tabWidget->addDatabaseTab(filePath, false, password, keyfile); } -void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) +void MainWindow::updateMenuActionState() { + // MainWindow State int currentIndex = m_ui->stackedWidget->currentIndex(); - - bool inDatabaseTabWidget = (currentIndex == DatabaseTabScreen); - bool inWelcomeWidget = (currentIndex == WelcomeScreen); - bool inDatabaseTabWidgetOrWelcomeWidget = inDatabaseTabWidget || inWelcomeWidget; - - m_ui->actionDatabaseClose->setEnabled(true); - m_ui->actionDatabaseMerge->setEnabled(inDatabaseTabWidget); - m_ui->menuRemoteSync->setEnabled(inDatabaseTabWidget); - m_ui->actionDatabaseNew->setEnabled(inDatabaseTabWidgetOrWelcomeWidget); - m_ui->actionDatabaseOpen->setEnabled(inDatabaseTabWidgetOrWelcomeWidget); - m_ui->menuRecentDatabases->setEnabled(inDatabaseTabWidgetOrWelcomeWidget); - m_ui->actionImport->setEnabled(inDatabaseTabWidgetOrWelcomeWidget); - m_ui->actionLockDatabase->setEnabled(m_ui->tabWidget->hasLockableDatabases()); - m_ui->actionLockDatabaseToolbar->setEnabled(m_ui->tabWidget->hasLockableDatabases()); - m_ui->actionLockAllDatabases->setEnabled(m_ui->tabWidget->hasLockableDatabases()); - - if (inDatabaseTabWidget && m_ui->tabWidget->currentIndex() != -1) { - DatabaseWidget* dbWidget = m_ui->tabWidget->currentDatabaseWidget(); - Q_ASSERT(dbWidget); - - if (mode == DatabaseWidget::Mode::None) { - mode = dbWidget->currentMode(); - } - - switch (mode) { - case DatabaseWidget::Mode::ViewMode: { - 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->actionEntryRestore->setVisible(entriesSelected && recycleBinSelected); - m_ui->actionEntryRestore->setEnabled(entriesSelected && recycleBinSelected); - m_ui->actionEntryRestore->setText(tr("Restore Entry(s)", "", dbWidget->numberOfSelectedEntries())); - m_ui->actionEntryRestore->setToolTip(tr("Restore Entry(s)", "", dbWidget->numberOfSelectedEntries())); - 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 - // from copying information from the currently selected cell in the entry view table. - m_ui->actionEntryCopyPassword->setEnabled(singleEntrySelected); - m_ui->actionEntryCopyURL->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUrl()); - m_ui->actionEntryCopyNotes->setEnabled(singleEntrySelected && dbWidget->currentEntryHasNotes()); - m_ui->menuEntryCopyAttribute->setEnabled(singleEntrySelected); - m_ui->menuEntryTotp->setEnabled(singleEntrySelected); - m_ui->menuTags->setEnabled(entriesSelected); - m_ui->actionEntryAutoType->setEnabled(singleEntrySelected && dbWidget->currentEntryHasAutoTypeEnabled()); - m_ui->actionEntryAutoType->menu()->setEnabled(singleEntrySelected - && dbWidget->currentEntryHasAutoTypeEnabled()); - m_ui->actionEntryAutoTypeSequence->setText( - singleEntrySelected ? dbWidget->currentSelectedEntry()->effectiveAutoTypeSequence() - : Group::RootAutoTypeSequence); - m_ui->actionEntryAutoTypeSequence->setEnabled(singleEntrySelected); - m_ui->actionEntryAutoTypeUsername->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUsername()); - m_ui->actionEntryAutoTypeUsernameEnter->setEnabled(singleEntrySelected - && dbWidget->currentEntryHasUsername()); - m_ui->actionEntryAutoTypePassword->setEnabled(singleEntrySelected && dbWidget->currentEntryHasPassword()); - m_ui->actionEntryAutoTypePasswordEnter->setEnabled(singleEntrySelected - && dbWidget->currentEntryHasPassword()); - m_ui->actionEntryAutoTypeTOTP->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp()); - m_ui->actionEntryAutoTypeTOTP->setVisible(singleEntrySelected && dbWidget->currentEntryHasTotp()); - m_ui->actionEntryOpenUrl->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUrl()); - m_ui->actionEntryTotp->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp()); - m_ui->actionEntryCopyTotp->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp()); - m_ui->actionEntryCopyPasswordTotp->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp()); - m_ui->actionEntrySetupTotp->setEnabled(singleEntrySelected); - m_ui->actionEntryTotpQRCode->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp()); - m_ui->actionEntryDownloadIcon->setEnabled((entriesSelected && !singleEntrySelected) - || (singleEntrySelected && dbWidget->currentEntryHasUrl())); - m_ui->actionGroupNew->setEnabled(groupSelected); - m_ui->actionGroupEdit->setEnabled(groupSelected); - m_ui->actionGroupClone->setEnabled(groupSelected && dbWidget->canCloneCurrentGroup()); - m_ui->actionGroupDelete->setEnabled(groupSelected && dbWidget->canDeleteCurrentGroup()); - m_ui->actionGroupSortAsc->setEnabled(groupSelected && currentGroupHasChildren); - m_ui->actionGroupSortDesc->setEnabled(groupSelected && currentGroupHasChildren); - m_ui->actionGroupEmptyRecycleBin->setVisible(recycleBinSelected); - m_ui->actionGroupEmptyRecycleBin->setEnabled(recycleBinSelected); -#ifdef WITH_XC_NETWORKING - m_ui->actionGroupDownloadFavicons->setVisible(!recycleBinSelected); -#endif - m_ui->actionGroupDownloadFavicons->setEnabled(groupSelected && currentGroupHasEntries - && !recycleBinSelected); - 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->actionExportXML->setEnabled(true); - m_ui->actionDatabaseMerge->setEnabled(m_ui->tabWidget->currentIndex() != -1); + bool hasLockableDatabase = m_ui->tabWidget->hasLockableDatabases(); + bool inAppSettings = (currentIndex == SettingsScreen); + bool inPasswordGenerator = (currentIndex == PasswordGeneratorScreen); + + auto dbWidget = (currentIndex == DatabaseTabScreen ? m_ui->tabWidget->currentDatabaseWidget() : nullptr); + auto dbMode = (dbWidget ? dbWidget->currentMode() : DatabaseWidget::Mode::None); + + // Database State + bool databaseUnlocked = (dbWidget && !dbWidget->isLocked()); + bool inDatabase = (dbMode == DatabaseWidget::Mode::ViewMode); + bool inDatabaseSettings = (dbMode == DatabaseWidget::Mode::DatabaseSettingsMode); + bool inReports = (dbMode == DatabaseWidget::Mode::ReportsMode); + bool editingEntry = (dbMode == DatabaseWidget::Mode::EditEntryMode); + + // Synchronize toggle buttons + m_ui->actionDatabaseSettings->blockSignals(true); + m_ui->actionPasswordGenerator->blockSignals(true); + m_ui->actionReports->blockSignals(true); + m_ui->actionSettings->blockSignals(true); + + m_ui->actionDatabaseSettings->setChecked(inDatabaseSettings); + m_ui->actionPasswordGenerator->setChecked(inPasswordGenerator); + m_ui->actionReports->setChecked(inReports); + m_ui->actionSettings->setChecked(inAppSettings); + + m_ui->actionDatabaseSettings->blockSignals(false); + m_ui->actionPasswordGenerator->blockSignals(false); + m_ui->actionReports->blockSignals(false); + m_ui->actionSettings->blockSignals(false); + + // Entry State + bool singleEntrySelected = (inDatabase && dbWidget->numberOfSelectedEntries() == 1); + bool singleEntryOrEditing = (singleEntrySelected || editingEntry); + bool multiEntrySelected = (inDatabase && dbWidget->numberOfSelectedEntries() > 0); + + // Group State + bool groupSelected = (inDatabase && dbWidget->isGroupSelected()); + bool groupHasChildren = (groupSelected && dbWidget->currentGroup()->hasChildren()); + bool groupHasEntries = (groupSelected && !dbWidget->currentGroup()->entries().isEmpty()); + bool inRecycleBin = (inDatabase && dbWidget->isRecycleBinSelected()); + + bool entryViewSorted = (inDatabase && dbWidget->isSorted()); + bool entryViewAtTop = (inDatabase && dbWidget->currentEntryIndex() == 0); + bool entryViewAtBottom = + (groupSelected && dbWidget->currentEntryIndex() == dbWidget->currentGroup()->entries().size() - 1); + + m_ui->actionEntryNew->setEnabled(inDatabase && !inRecycleBin); + m_ui->actionEntryClone->setEnabled(singleEntrySelected && !inRecycleBin); + m_ui->actionEntryEdit->setEnabled(singleEntrySelected); + m_ui->actionEntryDelete->setEnabled(multiEntrySelected); + m_ui->actionEntryRestore->setVisible(multiEntrySelected && inRecycleBin); + m_ui->actionEntryRestore->setEnabled(multiEntrySelected && inRecycleBin); + if (dbWidget) { + m_ui->actionEntryRestore->setText(tr("Restore Entry(s)", "", dbWidget->numberOfSelectedEntries())); + m_ui->actionEntryRestore->setToolTip(tr("Restore Entry(s)", "", dbWidget->numberOfSelectedEntries())); + } + m_ui->actionEntryMoveUp->setVisible(inDatabase && !entryViewSorted); + m_ui->actionEntryMoveDown->setVisible(inDatabase && !entryViewSorted); + m_ui->actionEntryMoveUp->setEnabled(singleEntrySelected && !entryViewSorted && !entryViewAtTop); + m_ui->actionEntryMoveDown->setEnabled(singleEntrySelected && !entryViewSorted && !entryViewAtBottom); + m_ui->actionEntryCopyTitle->setEnabled(singleEntryOrEditing && dbWidget->currentEntryHasTitle()); + m_ui->actionEntryCopyUsername->setEnabled(singleEntryOrEditing && dbWidget->currentEntryHasUsername()); + // NOTE: Copy password is enabled even if the selected entry's password is blank to prevent Ctrl+C + // from copying information from the currently selected cell in the entry view table. + m_ui->actionEntryCopyPassword->setEnabled(singleEntryOrEditing); + m_ui->actionEntryCopyURL->setEnabled(singleEntryOrEditing && dbWidget->currentEntryHasUrl()); + m_ui->actionEntryCopyNotes->setEnabled(singleEntryOrEditing && dbWidget->currentEntryHasNotes()); + m_ui->menuEntryCopyAttribute->setEnabled(singleEntryOrEditing); + m_ui->menuEntryTotp->setEnabled(singleEntrySelected); + m_ui->menuTags->setEnabled(multiEntrySelected); + m_ui->actionEntryAutoType->setEnabled(singleEntrySelected && dbWidget->currentEntryHasAutoTypeEnabled()); + m_ui->actionEntryAutoType->menu()->setEnabled(singleEntrySelected && dbWidget->currentEntryHasAutoTypeEnabled()); + m_ui->actionEntryAutoTypeSequence->setText(singleEntrySelected + ? dbWidget->currentSelectedEntry()->effectiveAutoTypeSequence() + : Group::RootAutoTypeSequence); + m_ui->actionEntryAutoTypeSequence->setEnabled(singleEntrySelected); + m_ui->actionEntryAutoTypeUsername->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUsername()); + m_ui->actionEntryAutoTypeUsernameEnter->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUsername()); + m_ui->actionEntryAutoTypePassword->setEnabled(singleEntrySelected && dbWidget->currentEntryHasPassword()); + m_ui->actionEntryAutoTypePasswordEnter->setEnabled(singleEntrySelected && dbWidget->currentEntryHasPassword()); + m_ui->actionEntryAutoTypeTOTP->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp()); + m_ui->actionEntryAutoTypeTOTP->setVisible(singleEntrySelected && dbWidget->currentEntryHasTotp()); + m_ui->actionEntryOpenUrl->setEnabled(singleEntryOrEditing && dbWidget->currentEntryHasUrl()); + m_ui->actionEntryTotp->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp()); + m_ui->actionEntryCopyTotp->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp()); + m_ui->actionEntryCopyPasswordTotp->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp()); + m_ui->actionEntrySetupTotp->setEnabled(singleEntrySelected); + m_ui->actionEntryTotpQRCode->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp()); + m_ui->actionEntryDownloadIcon->setEnabled((multiEntrySelected && !singleEntrySelected) + || (singleEntrySelected && dbWidget->currentEntryHasUrl())); #ifdef WITH_XC_BROWSER_PASSKEYS - bool singleEntryHasPasskey = singleEntrySelected && dbWidget->currentEntryHasPasskey(); - m_ui->actionPasskeys->setEnabled(true); - m_ui->actionImportPasskey->setEnabled(true); - m_ui->actionEntryImportPasskey->setEnabled(singleEntrySelected); - m_ui->actionEntryRemovePasskey->setEnabled(singleEntryHasPasskey); + m_ui->actionEntryImportPasskey->setVisible(singleEntrySelected); + m_ui->actionEntryImportPasskey->setEnabled(singleEntrySelected); + m_ui->actionEntryRemovePasskey->setVisible(singleEntrySelected && dbWidget->currentEntryHasPasskey()); + m_ui->actionEntryRemovePasskey->setEnabled(singleEntrySelected && dbWidget->currentEntryHasPasskey()); #endif - m_ui->menuRemoteSync->setEnabled(true); #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); + bool hasSSHKey = singleEntrySelected && sshAgent()->isEnabled() && dbWidget->currentEntryHasSshKey(); + m_ui->actionEntryAddToAgent->setVisible(hasSSHKey); + m_ui->actionEntryAddToAgent->setEnabled(hasSSHKey); + m_ui->actionEntryRemoveFromAgent->setVisible(hasSSHKey); + m_ui->actionEntryRemoveFromAgent->setEnabled(hasSSHKey); #endif - m_searchWidgetAction->setEnabled(true); - - break; - } - case DatabaseWidget::Mode::EditMode: - case DatabaseWidget::Mode::LockedMode: { - // Enable select actions when editing an entry - bool editEntryActive = dbWidget->isEntryEditActive(); - const QList editEntryActionsMask{m_ui->actionEntryCopyUsername, - m_ui->actionEntryCopyPassword, - m_ui->actionEntryCopyURL, - m_ui->actionEntryOpenUrl, - m_ui->actionEntryAutoType, - m_ui->actionEntryDownloadIcon, - m_ui->actionEntryCopyNotes, - m_ui->actionEntryCopyTitle, - m_ui->menuEntryCopyAttribute->menuAction(), - m_ui->menuEntryTotp->menuAction(), - m_ui->actionEntrySetupTotp}; - - auto entryActions = m_ui->menuEntries->actions(); - entryActions << m_ui->menuEntryCopyAttribute->actions(); - entryActions << m_ui->menuEntryTotp->actions(); - for (auto action : entryActions) { - bool enabled = editEntryActive && editEntryActionsMask.contains(action); - if (action->menu()) { - action->menu()->setEnabled(enabled); - } - action->setEnabled(enabled); - } - - const auto groupActions = m_ui->menuGroups->actions(); - for (auto action : groupActions) { - action->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); - m_ui->actionDatabaseMerge->setEnabled(false); - m_ui->menuRemoteSync->setEnabled(false); - // Only disable the action in the database menu so that the - // menu remains active in the toolbar, if necessary - m_ui->actionLockDatabase->setEnabled(false); - // Never show in these modes - m_ui->actionEntryMoveUp->setVisible(false); - m_ui->actionEntryMoveDown->setVisible(false); - m_ui->actionEntryRestore->setVisible(false); - m_ui->actionEntryAddToAgent->setVisible(false); - m_ui->actionEntryRemoveFromAgent->setVisible(false); - m_ui->actionGroupEmptyRecycleBin->setVisible(false); - + m_ui->actionGroupNew->setEnabled(groupSelected && !inRecycleBin); + m_ui->actionGroupEdit->setEnabled(groupSelected); + m_ui->actionGroupClone->setEnabled(groupSelected && dbWidget->canCloneCurrentGroup()); + m_ui->actionGroupDelete->setEnabled(groupSelected && dbWidget->canDeleteCurrentGroup()); + m_ui->actionGroupSortAsc->setVisible(groupHasChildren); + m_ui->actionGroupSortAsc->setEnabled(groupHasChildren); + m_ui->actionGroupSortDesc->setVisible(groupHasChildren); + m_ui->actionGroupSortDesc->setEnabled(groupHasChildren); + m_ui->actionGroupEmptyRecycleBin->setVisible(inRecycleBin); + m_ui->actionGroupEmptyRecycleBin->setEnabled(inRecycleBin); +#ifdef WITH_XC_NETWORKING + m_ui->actionGroupDownloadFavicons->setVisible(!inRecycleBin); +#endif + m_ui->actionGroupDownloadFavicons->setEnabled(groupSelected && groupHasEntries && !inRecycleBin); + + // Database Menu + m_ui->actionDatabaseSave->setEnabled(m_ui->tabWidget->canSave()); + m_ui->actionDatabaseSaveAs->setEnabled(databaseUnlocked); + m_ui->actionDatabaseSaveBackup->setEnabled(databaseUnlocked); + m_ui->actionDatabaseClose->setEnabled(dbWidget); + m_ui->actionLockDatabase->setEnabled(databaseUnlocked); + m_ui->actionLockAllDatabases->setEnabled(hasLockableDatabase); + m_ui->actionLockDatabaseToolbar->setEnabled(hasLockableDatabase); + m_ui->actionDatabaseSettings->setEnabled(inDatabase || inDatabaseSettings); + m_ui->actionDatabaseSecurity->setEnabled(inDatabase || inDatabaseSettings); + m_ui->actionReports->setEnabled(inDatabase || inReports); + m_ui->menuRemoteSync->setEnabled(inDatabase || inDatabaseSettings); + m_ui->menuExport->setEnabled(inDatabase); + m_ui->actionDatabaseMerge->setEnabled(inDatabase); #ifdef WITH_XC_BROWSER_PASSKEYS - m_ui->actionPasskeys->setEnabled(false); - m_ui->actionImportPasskey->setEnabled(false); - m_ui->actionEntryImportPasskey->setEnabled(false); - m_ui->actionEntryRemovePasskey->setEnabled(false); -#else - m_ui->actionPasskeys->setVisible(false); - m_ui->actionImportPasskey->setVisible(false); - m_ui->actionEntryImportPasskey->setVisible(false); - m_ui->actionEntryRemovePasskey->setVisible(false); + m_ui->actionPasskeys->setEnabled(inDatabase || inReports); + m_ui->actionImportPasskey->setEnabled(inDatabase); #endif - m_searchWidgetAction->setEnabled(false); - break; - } - default: - Q_ASSERT(false); - } - } else { - const auto entryActions = m_ui->menuEntries->actions(); - for (auto action : entryActions) { - action->setEnabled(false); - } - - const auto groupActions = m_ui->menuGroups->actions(); - for (auto action : groupActions) { - action->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); - m_ui->actionExportHtml->setEnabled(false); - m_ui->actionDatabaseMerge->setEnabled(false); - m_ui->menuRemoteSync->setEnabled(false); - // Hide entry-specific actions - m_ui->actionEntryMoveUp->setVisible(false); - m_ui->actionEntryMoveDown->setVisible(false); - m_ui->actionEntryRestore->setVisible(false); - m_ui->actionEntryAddToAgent->setVisible(false); - m_ui->actionEntryRemoveFromAgent->setVisible(false); - m_ui->actionGroupEmptyRecycleBin->setVisible(false); - - m_searchWidgetAction->setEnabled(false); - } - - if ((currentIndex == PasswordGeneratorScreen) != m_ui->actionPasswordGenerator->isChecked()) { - bool blocked = m_ui->actionPasswordGenerator->blockSignals(true); - m_ui->actionPasswordGenerator->toggle(); - m_ui->actionPasswordGenerator->blockSignals(blocked); - } else if ((currentIndex == SettingsScreen) != m_ui->actionSettings->isChecked()) { - bool blocked = m_ui->actionSettings->blockSignals(true); - m_ui->actionSettings->toggle(); - m_ui->actionSettings->blockSignals(blocked); - } + m_searchWidgetAction->setEnabled(inDatabase); } void MainWindow::updateToolbarSeparatorVisibility() diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index 59ff6042a0..578d6a91f8 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -106,7 +106,7 @@ public slots: bool focusNextPrevChild(bool next) override; private slots: - void setMenuActionState(DatabaseWidget::Mode mode = DatabaseWidget::Mode::None); + void updateMenuActionState(); void updateToolbarSeparatorVisibility(); void updateWindowTitle(); void showAboutDialog(); diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui index 7708df2f34..de3946c884 100644 --- a/src/gui/MainWindow.ui +++ b/src/gui/MainWindow.ui @@ -256,9 +256,9 @@ - + @@ -293,9 +293,6 @@ true - - false - @@ -307,9 +304,6 @@ - - false - TOTP @@ -324,6 +318,7 @@ Tags + @@ -356,16 +351,17 @@ &Groups + + - - - + + @@ -436,6 +432,9 @@ + + + @@ -485,9 +484,6 @@ - - false - &Save Database @@ -496,9 +492,6 @@ - - false - &Close Database @@ -526,9 +519,6 @@ - - false - &New Entry… @@ -540,9 +530,6 @@ - - false - &Edit Entry… @@ -554,9 +541,6 @@ - - false - &Delete Entry… @@ -568,9 +552,6 @@ - - false - &New Group… @@ -579,9 +560,6 @@ - - false - &Edit Group… @@ -590,9 +568,6 @@ - - false - &Delete Group… @@ -601,9 +576,6 @@ - - false - Download All &Favicons… @@ -612,9 +584,6 @@ - - false - Sort &A-Z @@ -623,9 +592,6 @@ - - false - Sort &Z-A @@ -634,9 +600,6 @@ - - false - Sa&ve Database As… @@ -648,9 +611,6 @@ - - false - Database &Security… @@ -659,8 +619,8 @@ - - false + + true Database &Reports… @@ -676,8 +636,8 @@ - - false + + true &Database Settings… @@ -693,9 +653,6 @@ - - false - Passkeys… @@ -707,9 +664,6 @@ - - false - Import Passkey @@ -721,9 +675,6 @@ - - false - &Clone Entry… @@ -735,9 +686,6 @@ - - false - Move u&p @@ -749,9 +697,6 @@ - - false - Move do&wn @@ -763,9 +708,6 @@ - - false - Copy &Username @@ -777,9 +719,6 @@ - - false - Copy &Password @@ -819,25 +758,16 @@ - - false - Perform &Auto-Type - - false - Import Passkey - - false - Remove Passkey From Entry @@ -921,9 +851,6 @@ - - false - Open &URL @@ -932,9 +859,6 @@ - - false - &Lock Database @@ -943,9 +867,6 @@ - - false - Lock &All Databases @@ -954,9 +875,6 @@ - - false - &Title @@ -968,9 +886,6 @@ - - false - Copy &URL @@ -982,9 +897,6 @@ - - false - &Notes @@ -993,9 +905,6 @@ - - false - &CSV File… @@ -1004,9 +913,6 @@ - - false - &HTML File… @@ -1085,9 +991,6 @@ Empty Recycle Bin - - false - @@ -1141,9 +1044,6 @@ - - false - Save Database Backup… @@ -1333,9 +1233,6 @@ - - false - Clone Group... @@ -1352,17 +1249,11 @@ - - false - &Lock Database - - false - &XML File… diff --git a/src/gui/dbsettings/DatabaseSettingsDialog.cpp b/src/gui/dbsettings/DatabaseSettingsDialog.cpp index f309e56bdb..b708357703 100644 --- a/src/gui/dbsettings/DatabaseSettingsDialog.cpp +++ b/src/gui/dbsettings/DatabaseSettingsDialog.cpp @@ -99,6 +99,8 @@ DatabaseSettingsDialog::~DatabaseSettingsDialog() = default; void DatabaseSettingsDialog::load(const QSharedPointer& db) { + setHeadline(tr("Database Settings: %1").arg(db->canonicalFilePath())); + m_generalWidget->loadSettings(db); m_databaseKeyWidget->loadSettings(db); m_encryptionWidget->loadSettings(db); diff --git a/src/gui/reports/ReportsDialog.cpp b/src/gui/reports/ReportsDialog.cpp index bdbeca8a9e..1d32c23222 100644 --- a/src/gui/reports/ReportsDialog.cpp +++ b/src/gui/reports/ReportsDialog.cpp @@ -128,14 +128,23 @@ void ReportsDialog::addPage(QSharedPointer page) m_ui->categoryList->setCurrentCategory(category); } -#ifdef WITH_XC_BROWSER_PASSKEYS void ReportsDialog::activatePasskeysPage() { +#ifdef WITH_XC_BROWSER_PASSKEYS m_ui->stackedWidget->setCurrentWidget(m_passkeysPage->m_passkeysWidget); auto index = m_ui->stackedWidget->currentIndex(); m_ui->categoryList->setCurrentCategory(index); +#endif } + +bool ReportsDialog::onPassKeysPage() +{ +#ifdef WITH_XC_BROWSER_PASSKEYS + return m_ui->stackedWidget->currentWidget() == m_passkeysPage->m_passkeysWidget; +#else + return false; #endif +} void ReportsDialog::reject() { @@ -144,7 +153,7 @@ void ReportsDialog::reject() void ReportsDialog::entryActivationSignalReceived(Entry* entry) { - m_sender = static_cast(sender()); + m_sender = qobject_cast(sender()); m_editEntryWidget->loadEntry(entry, false, false, entry->group()->hierarchy().join(" > "), m_db); m_ui->stackedWidget->setCurrentWidget(m_editEntryWidget); } diff --git a/src/gui/reports/ReportsDialog.h b/src/gui/reports/ReportsDialog.h index 6400787b44..abeaab4814 100644 --- a/src/gui/reports/ReportsDialog.h +++ b/src/gui/reports/ReportsDialog.h @@ -63,9 +63,8 @@ class ReportsDialog : public DialogyWidget void load(const QSharedPointer& db); void addPage(QSharedPointer page); -#ifdef WITH_XC_BROWSER_PASSKEYS void activatePasskeysPage(); -#endif + bool onPassKeysPage(); signals: void editFinished(bool accepted); diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index 6799cc64d8..10199695cc 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -161,14 +161,13 @@ void TestGui::init() // Every test ends with closing the temp database without saving void TestGui::cleanup() { - // DO NOT save the database - m_db->markAsClean(); - MessageBox::setNextAnswer(MessageBox::No); - triggerAction("actionDatabaseClose"); - QApplication::processEvents(); - MessageBox::setNextAnswer(MessageBox::NoButton); - - if (m_dbWidget) { + if (m_tabWidget->isVisible()) { + // DO NOT save the database + m_db->markAsClean(); + MessageBox::setNextAnswer(MessageBox::No); + triggerAction("actionDatabaseClose"); + QApplication::processEvents(); + MessageBox::setNextAnswer(MessageBox::NoButton); delete m_dbWidget; } } @@ -561,7 +560,7 @@ void TestGui::testEditEntry() // Edit the first entry ("Sample Entry") QTest::mouseClick(entryEditWidget, Qt::LeftButton); - QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode); + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditEntryMode); auto* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); auto* titleEdit = editEntryWidget->findChild("titleEdit"); QTest::keyClicks(titleEdit, "_test"); @@ -576,7 +575,7 @@ void TestGui::testEditEntry() // Apply the edit QTRY_VERIFY(applyButton->isEnabled()); QTest::mouseClick(applyButton, Qt::LeftButton); - QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode); + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditEntryMode); QCOMPARE(entry->title(), QString("Sample Entry_test")); QCOMPARE(entry->historyItems().size(), ++editCount); QVERIFY(!applyButton->isEnabled()); @@ -654,7 +653,7 @@ void TestGui::testEditEntry() QTest::mouseClick(entryEditWidget, Qt::LeftButton); okButton = editEntryWidgetButtonBox->button(QDialogButtonBox::Ok); QVERIFY(okButton); - QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode); + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditEntryMode); titleEdit->setText("multiline\ntitle"); editEntryWidget->findChild("usernameComboBox")->lineEdit()->setText("multiline\nusername"); editEntryWidget->findChild("passwordEdit")->setText("multiline\npassword"); @@ -713,7 +712,7 @@ void TestGui::testSearchEditEntry() // Goto "Doggy"'s edit view QTest::keyClick(searchTextEdit, Qt::Key_Return); - QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode); + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditEntryMode); // Check the path in header is "parent-group > entry" QCOMPARE(m_dbWidget->findChild("editEntryWidget")->findChild("headerLabel")->text(), @@ -739,7 +738,7 @@ void TestGui::testAddEntry() // Click the new entry button and check that we enter edit mode QTest::mouseClick(entryNewWidget, Qt::LeftButton); - QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode); + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditEntryMode); // Add entry "test" and confirm added auto* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); @@ -859,7 +858,7 @@ void TestGui::testPasswordEntryEntropy() // Click the new entry button and check that we enter edit mode QTest::mouseClick(entryNewWidget, Qt::LeftButton); - QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode); + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditEntryMode); // Add entry "test" and confirm added auto* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); @@ -921,7 +920,7 @@ void TestGui::testDicewareEntryEntropy() // Click the new entry button and check that we enter edit mode QTest::mouseClick(entryNewWidget, Qt::LeftButton); - QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode); + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditEntryMode); // Add entry "test" and confirm added auto* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); @@ -1008,7 +1007,7 @@ void TestGui::testTotp() QVERIFY(entryEditWidget->isVisible()); QVERIFY(entryEditWidget->isEnabled()); QTest::mouseClick(entryEditWidget, Qt::LeftButton); - QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode); + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditEntryMode); auto* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); editEntryWidget->setCurrentPage(1); @@ -1212,7 +1211,7 @@ void TestGui::testSearch() QModelIndex item = entryView->model()->index(0, 1); Entry* entry = entryView->entryFromIndex(item); QTest::keyClick(searchTextEdit, Qt::Key_Return); - QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode); + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditEntryMode); // Perform the edit and save it EditEntryWidget* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); @@ -1370,7 +1369,7 @@ void TestGui::testEntryPlaceholders() // Click the new entry button and check that we enter edit mode QTest::mouseClick(entryNewWidget, Qt::LeftButton); - QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode); + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditEntryMode); // Add entry "test" and confirm added auto* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); @@ -1723,7 +1722,7 @@ void TestGui::testDatabaseSettings() QWidget* entryNewWidget = toolBar->widgetForAction(entryNewAction); QTest::mouseClick(entryNewWidget, Qt::LeftButton); - QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode); + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditEntryMode); auto* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); QVERIFY(editEntryWidget); @@ -1743,7 +1742,7 @@ void TestGui::testDatabaseSettings() // 2.d) Create second entry to test delay timer reset QTest::mouseClick(entryNewWidget, Qt::LeftButton); - QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode); + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditEntryMode); QTest::keyClicks(titleEdit, "Test autosaveDelay 2"); // 2.e) Save changes @@ -1763,7 +1762,7 @@ void TestGui::testDatabaseSettings() // 4 Test no delay when disabled autosave or autosaveDelay // 4.a) create new entry QTest::mouseClick(entryNewWidget, Qt::LeftButton); - QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode); + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditEntryMode); QTest::keyClicks(titleEdit, "Test autosaveDelay 3"); // 4.b) Save changes @@ -1784,7 +1783,7 @@ void TestGui::testDatabaseSettings() // 4.f) Repeat for autosaveDelay config()->set(Config::AutoSaveAfterEveryChange, true); QTest::mouseClick(entryNewWidget, Qt::LeftButton); - QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode); + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditEntryMode); QTest::keyClicks(titleEdit, "Test autosaveDelay 4"); editEntryWidget->setCurrentPage(0); editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); @@ -2061,7 +2060,7 @@ void TestGui::testAutoType() QVERIFY(entryNewWidget->isEnabled()); QTest::mouseClick(entryNewWidget, Qt::LeftButton); - QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode); + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditEntryMode); auto* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); QVERIFY(editEntryWidget); @@ -2096,7 +2095,7 @@ void TestGui::testAutoType() // 2.a) Click the new entry button and set the title QTest::mouseClick(entryNewWidget, Qt::LeftButton); - QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode); + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditEntryMode); QTest::keyClicks(titleEdit, "2. Entry With Default Auto-Type Sequence"); QTest::mouseClick(usernameComboBox, Qt::LeftButton); QTest::keyClicks(usernameComboBox, "AutocompletionUsername"); @@ -2115,7 +2114,7 @@ void TestGui::testAutoType() // 3.a) Click the new entry button and set the title QTest::mouseClick(entryNewWidget, Qt::LeftButton); - QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode); + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditEntryMode); QTest::keyClicks(titleEdit, "3. Entry With Custom Auto-Type Sequence"); QTest::mouseClick(usernameComboBox, Qt::LeftButton); QTest::keyClicks(usernameComboBox, "AutocompletionUsername"); @@ -2192,6 +2191,155 @@ void TestGui::testAutoType() entryView->selectionModel()->clearSelection(); } +void TestGui::testMenuActionStates() +{ + auto isActionEnabled = [this](const QString& actionName) -> bool { + auto action = m_mainWindow->findChild(actionName); + if (!action) { + QTest::qFail(qPrintable(QString("Invalid action specified: %1").arg(actionName)), __FILE__, __LINE__); + return false; + } + return action->isEnabled(); + }; + + // Start with database open and unlocked + qInfo("Actions Test: Database open and unlocked"); + + QVERIFY(isActionEnabled("actionEntryNew")); + QVERIFY(isActionEnabled("actionGroupNew")); + QVERIFY(isActionEnabled("actionDatabaseSaveAs")); + QVERIFY(isActionEnabled("actionDatabaseClose")); + QVERIFY(isActionEnabled("actionDatabaseMerge")); + QVERIFY(isActionEnabled("actionDatabaseSettings")); + QVERIFY(isActionEnabled("actionReports")); + QVERIFY(isActionEnabled("actionLockDatabase")); + QVERIFY(isActionEnabled("actionLockAllDatabases")); + QVERIFY(isActionEnabled("actionImport")); + QVERIFY(isActionEnabled("actionExportCsv")); + QVERIFY(isActionEnabled("actionSettings")); + QVERIFY(isActionEnabled("actionPasswordGenerator")); + + // Edit entry actions + qInfo("Actions Test: Editing an entry"); + + triggerAction("actionEntryEdit"); + + QVERIFY(!isActionEnabled("actionEntryNew")); + QVERIFY(isActionEnabled("actionEntryCopyUsername")); + QVERIFY(!isActionEnabled("actionEntrySetupTotp")); + QVERIFY(!isActionEnabled("actionGroupNew")); + QVERIFY(isActionEnabled("actionDatabaseSaveAs")); + QVERIFY(isActionEnabled("actionDatabaseClose")); + QVERIFY(!isActionEnabled("actionDatabaseMerge")); + QVERIFY(!isActionEnabled("actionDatabaseSettings")); + QVERIFY(!isActionEnabled("actionReports")); + QVERIFY(isActionEnabled("actionLockDatabase")); + QVERIFY(isActionEnabled("actionLockAllDatabases")); + QVERIFY(isActionEnabled("actionSettings")); + QVERIFY(isActionEnabled("actionPasswordGenerator")); + + // Special Case - Recycle Bin + qInfo("Actions Test: Special case - Recycle Bin"); + + m_dbWidget->switchToMainView(); + QApplication::processEvents(); + + QVERIFY(m_db->metadata()->recycleBinEnabled()); + triggerAction("actionEntryDelete"); + m_dbWidget->groupView()->setCurrentGroup(m_db->metadata()->recycleBin()); + QVERIFY(m_dbWidget->isRecycleBinSelected()); + QVERIFY(isActionEnabled("actionEntryRestore")); + QVERIFY(isActionEnabled("actionGroupEmptyRecycleBin")); + QVERIFY(!isActionEnabled("actionEntryNew")); + QVERIFY(!isActionEnabled("actionEntryClone")); + QVERIFY(!isActionEnabled("actionGroupNew")); + QVERIFY(!isActionEnabled("actionGroupClone")); + + // Database Settings + qInfo("Actions Test: Database settings"); + triggerAction("actionDatabaseSettings"); + + QVERIFY(!isActionEnabled("actionEntryNew")); + QVERIFY(!isActionEnabled("actionEntrySetupTotp")); + QVERIFY(!isActionEnabled("actionGroupNew")); + QVERIFY(isActionEnabled("actionDatabaseSaveAs")); + QVERIFY(isActionEnabled("actionDatabaseClose")); + QVERIFY(!isActionEnabled("actionDatabaseMerge")); + QVERIFY(isActionEnabled("actionDatabaseSettings")); + QVERIFY(isActionEnabled("actionDatabaseSecurity")); + QVERIFY(!isActionEnabled("actionReports")); + QVERIFY(isActionEnabled("actionLockDatabase")); + QVERIFY(isActionEnabled("actionSettings")); + QVERIFY(isActionEnabled("actionPasswordGenerator")); + + // Database Reports + qInfo("Actions Test: Database reports"); + + triggerAction("actionDatabaseSettings"); + triggerAction("actionReports"); + + QVERIFY(!isActionEnabled("actionEntryNew")); + QVERIFY(!isActionEnabled("actionEntrySetupTotp")); + QVERIFY(!isActionEnabled("actionGroupNew")); + QVERIFY(isActionEnabled("actionDatabaseSaveAs")); + QVERIFY(isActionEnabled("actionDatabaseClose")); + QVERIFY(!isActionEnabled("actionDatabaseMerge")); + QVERIFY(!isActionEnabled("actionDatabaseSettings")); + QVERIFY(!isActionEnabled("actionDatabaseSecurity")); + QVERIFY(isActionEnabled("actionReports")); + QVERIFY(isActionEnabled("actionLockDatabase")); + QVERIFY(isActionEnabled("actionSettings")); + QVERIFY(isActionEnabled("actionPasswordGenerator")); + + // Application Settings + qInfo("Actions Test: Application settings"); + + triggerAction("actionSettings"); + + QVERIFY(!isActionEnabled("actionDatabaseSettings")); + QVERIFY(!isActionEnabled("actionDatabaseSecurity")); + QVERIFY(!isActionEnabled("actionReports")); + QVERIFY(isActionEnabled("actionSettings")); + QVERIFY(isActionEnabled("actionPasswordGenerator")); + + // Locked Database + qInfo("Actions Test: Database locked"); + + triggerAction("actionSettings"); + MessageBox::setNextAnswer(MessageBox::Discard); + triggerAction("actionLockDatabase"); + + QVERIFY(!isActionEnabled("actionEntryNew")); + QVERIFY(!isActionEnabled("actionGroupNew")); + QVERIFY(!isActionEnabled("actionDatabaseSaveAs")); + QVERIFY(isActionEnabled("actionDatabaseClose")); + QVERIFY(!isActionEnabled("actionDatabaseMerge")); + QVERIFY(!isActionEnabled("actionDatabaseSettings")); + QVERIFY(!isActionEnabled("actionReports")); + QVERIFY(!isActionEnabled("actionLockDatabase")); + QVERIFY(!isActionEnabled("actionLockAllDatabases")); + QVERIFY(isActionEnabled("actionSettings")); + QVERIFY(isActionEnabled("actionPasswordGenerator")); + + // Welcome Screen + qInfo("Actions Test: Welcome screen"); + + triggerAction("actionDatabaseClose"); + + QVERIFY(!isActionEnabled("actionEntryNew")); + QVERIFY(!isActionEnabled("actionGroupNew")); + QVERIFY(!isActionEnabled("actionDatabaseSaveAs")); + QVERIFY(!isActionEnabled("actionDatabaseClose")); + QVERIFY(!isActionEnabled("actionDatabaseMerge")); + QVERIFY(!isActionEnabled("actionDatabaseSettings")); + QVERIFY(!isActionEnabled("actionReports")); + QVERIFY(!isActionEnabled("actionLockDatabase")); + QVERIFY(!isActionEnabled("actionLockAllDatabases")); + QVERIFY(isActionEnabled("actionImport")); + QVERIFY(isActionEnabled("actionSettings")); + QVERIFY(isActionEnabled("actionPasswordGenerator")); +} + void TestGui::addCannedEntries() { // Find buttons @@ -2266,8 +2414,8 @@ void TestGui::checkStatusBarText(const QString& textFragment) void TestGui::triggerAction(const QString& name) { auto* action = m_mainWindow->findChild(name); - QVERIFY(action); - QVERIFY(action->isEnabled()); + QVERIFY2(action, qPrintable(QString("Action doesn't exist: %1").arg(name))); + QVERIFY2(action->isEnabled(), qPrintable(QString("Action is disabled: %1").arg(name))); action->trigger(); QApplication::processEvents(); } diff --git a/tests/gui/TestGui.h b/tests/gui/TestGui.h index 70ff5dfc5b..320ce3d648 100644 --- a/tests/gui/TestGui.h +++ b/tests/gui/TestGui.h @@ -69,6 +69,7 @@ private slots: void testAutoType(); void testTrayRestoreHide(); void testShortcutConfig(); + void testMenuActionStates(); private: void addCannedEntries(); diff --git a/tests/gui/TestGuiBrowser.cpp b/tests/gui/TestGuiBrowser.cpp index ba45c1f1a9..51bd01f522 100644 --- a/tests/gui/TestGuiBrowser.cpp +++ b/tests/gui/TestGuiBrowser.cpp @@ -142,7 +142,7 @@ void TestGuiBrowser::testEntrySettings() auto* entryEditAction = m_mainWindow->findChild("actionEntryEdit"); QWidget* entryEditWidget = toolBar->widgetForAction(entryEditAction); QTest::mouseClick(entryEditWidget, Qt::LeftButton); - QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode); + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditEntryMode); auto* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); // Switch to Properties page and select all rows from the custom data table @@ -186,7 +186,7 @@ void TestGuiBrowser::testAdditionalURLs() auto* entryEditAction = m_mainWindow->findChild("actionEntryEdit"); QWidget* entryEditWidget = toolBar->widgetForAction(entryEditAction); QTest::mouseClick(entryEditWidget, Qt::LeftButton); - QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode); + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditEntryMode); auto* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); // Switch to Browser Integration page and add three URL's