diff --git a/COPYING b/COPYING index 3436ec3e47..9293d30e66 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 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/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/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp index b9b26d3775..168b7d9b71 100644 --- a/src/gui/DatabaseTabWidget.cpp +++ b/src/gui/DatabaseTabWidget.cpp @@ -537,25 +537,41 @@ bool DatabaseTabWidget::warnOnExport() return ans == MessageBox::Yes; } -void DatabaseTabWidget::showDatabaseSecurity() +void DatabaseTabWidget::showDatabaseSecurity(bool state) { - currentDatabaseWidget()->switchToDatabaseSecurity(); + if (state) { + currentDatabaseWidget()->switchToDatabaseSecurity(); + } else { + currentDatabaseWidget()->switchToMainView(); + } } -void DatabaseTabWidget::showDatabaseReports() +void DatabaseTabWidget::showDatabaseReports(bool state) { - currentDatabaseWidget()->switchToDatabaseReports(); + if (state) { + currentDatabaseWidget()->switchToDatabaseReports(); + } else { + currentDatabaseWidget()->switchToMainView(); + } } -void DatabaseTabWidget::showDatabaseSettings() +void DatabaseTabWidget::showDatabaseSettings(bool state) { - currentDatabaseWidget()->switchToDatabaseSettings(); + if (state) { + currentDatabaseWidget()->switchToDatabaseSettings(); + } else { + currentDatabaseWidget()->switchToMainView(); + } } #ifdef WITH_XC_BROWSER_PASSKEYS -void DatabaseTabWidget::showPasskeys() +void DatabaseTabWidget::showPasskeys(bool state) { - currentDatabaseWidget()->switchToPasskeys(); + if (state) { + currentDatabaseWidget()->switchToPasskeys(); + } else { + currentDatabaseWidget()->switchToMainView(); + } } void DatabaseTabWidget::importPasskey() diff --git a/src/gui/DatabaseTabWidget.h b/src/gui/DatabaseTabWidget.h index aa8542dd9b..f0d89fadd4 100644 --- a/src/gui/DatabaseTabWidget.h +++ b/src/gui/DatabaseTabWidget.h @@ -82,11 +82,11 @@ public slots: void unlockAnyDatabaseInDialog(DatabaseOpenDialog::Intent intent); void relockPendingDatabase(); - void showDatabaseSecurity(); - void showDatabaseReports(); - void showDatabaseSettings(); + void showDatabaseSecurity(bool state); + void showDatabaseReports(bool state); + void showDatabaseSettings(bool state); #ifdef WITH_XC_BROWSER_PASSKEYS - void showPasskeys(); + void showPasskeys(bool state); void importPasskey(); void importPasskeyToEntry(); void removePasskeyFromEntry(); diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index b497420b65..25d154af7f 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 = m_reportsDialog->onPassKeysPage() ? Mode::PassKeysMode : 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 diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index 6622394e1a..072400073e 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -64,8 +64,12 @@ class DatabaseWidget : public QStackedWidget { None, ViewMode, - EditMode, - LockedMode + EditEntryMode, + EditGroupMode, + LockedMode, + ReportsMode, + PassKeysMode, + DatabaseSettingsMode }; explicit DatabaseWidget(QSharedPointer db, QWidget* parent = nullptr); diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 9e38dcda9a..ee3d72dd6e 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -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")); @@ -458,10 +467,29 @@ MainWindow::MainWindow() 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->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->actionDatabaseSettings, &QAction::toggled, this, [&](bool state) { + if (state) { + // NOTE: Move this into state change + m_ui->actionPasskeys->setChecked(false); + m_ui->actionReports->setChecked(false); + } + m_ui->tabWidget->showDatabaseSettings(state); + }); + connect(m_ui->actionReports, &QAction::toggled, this, [&](bool state) { + if (state) { + m_ui->actionPasskeys->setChecked(false); + m_ui->actionDatabaseSettings->setChecked(false); + } + m_ui->tabWidget->showDatabaseReports(state); + }); #ifdef WITH_XC_BROWSER_PASSKEYS - connect(m_ui->actionPasskeys, SIGNAL(triggered()), m_ui->tabWidget, SLOT(showPasskeys())); + connect(m_ui->actionPasskeys, &QAction::toggled, this, [&](bool state) { + if (state) { + m_ui->actionReports->setChecked(false); + m_ui->actionDatabaseSettings->setChecked(false); + } + m_ui->tabWidget->showPasskeys(state); + }); connect(m_ui->actionImportPasskey, SIGNAL(triggered()), m_ui->tabWidget, SLOT(importPasskey())); connect(m_ui->actionEntryImportPasskey, SIGNAL(triggered()), m_ui->tabWidget, SLOT(importPasskeyToEntry())); connect(m_ui->actionEntryRemovePasskey, SIGNAL(triggered()), m_ui->tabWidget, SLOT(removePasskeyFromEntry())); @@ -846,249 +874,157 @@ void MainWindow::openDatabase(const QString& filePath, const QString& password, void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) { + /* + * MainWindow Content: + * Welcome Screen (No Databases Open) + * Database Widget + * Lock Screen / Import View + * Entry View + * Entry Edit + * Reports + * PassKey Report + * Database Settings + * Application Settings + * Password Generator + * + * Database States: + * None + * Locked + * Unlocked + * + * Entry States: + * Cannot interact with an entry + * Entry is being edited + * One entry selected + * More than one entry selected + * + * Group States: + * Cannot interact with a group + * Group is being edited + * One group is selected + */ + + // 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); + bool databaseOpen = (currentIndex == DatabaseTabScreen && m_ui->tabWidget->count() > 0); + bool hasLockableDatabase = m_ui->tabWidget->hasLockableDatabases(); + + auto dbWidget = (databaseOpen ? m_ui->tabWidget->currentDatabaseWidget() : nullptr); + auto dbMode = (dbWidget ? dbWidget->currentMode() : DatabaseWidget::Mode::None); + + // Database State + bool databaseUnlocked = (dbWidget && !dbWidget->isLocked()); + bool viewingDatabase = (dbMode == DatabaseWidget::Mode::ViewMode); + bool viewingReports = (dbMode == DatabaseWidget::Mode::ReportsMode); + bool editingEntry = (dbMode == DatabaseWidget::Mode::EditEntryMode); + bool editingGroup = (dbMode == DatabaseWidget::Mode::EditGroupMode); + + // Entry State + bool singleEntrySelected = (viewingDatabase && dbWidget->numberOfSelectedEntries() == 1); + bool multiEntrySelected = (viewingDatabase && dbWidget->numberOfSelectedEntries() > 0); + bool entryViewSorted = (viewingDatabase && dbWidget->isSorted()); + bool entryViewAtTop = (viewingDatabase && dbWidget->currentEntryIndex() == 0); + bool entryViewAtBottom = + (viewingDatabase && dbWidget->currentEntryIndex() == dbWidget->currentGroup()->entries().size() - 1); + + // Group State + bool groupSelected = (viewingDatabase && dbWidget->isGroupSelected()); + bool currentGroupHasChildren = (groupSelected && dbWidget->currentGroup()->hasChildren()); + bool currentGroupHasEntries = (groupSelected && !dbWidget->currentGroup()->entries().isEmpty()); + bool recycleBinSelected = (viewingDatabase && dbWidget->isRecycleBinSelected()); + + m_ui->actionEntryNew->setEnabled(viewingDatabase); + m_ui->actionEntryClone->setEnabled(singleEntrySelected); + m_ui->actionEntryEdit->setEnabled(singleEntrySelected); + m_ui->actionEntryDelete->setEnabled(multiEntrySelected); + m_ui->actionEntryRestore->setVisible(multiEntrySelected && recycleBinSelected); + m_ui->actionEntryRestore->setEnabled(multiEntrySelected && recycleBinSelected); + 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(viewingDatabase && !entryViewSorted); + m_ui->actionEntryMoveDown->setVisible(viewingDatabase && !entryViewSorted); + m_ui->actionEntryMoveUp->setEnabled(singleEntrySelected && !entryViewSorted && !entryViewAtTop); + m_ui->actionEntryMoveDown->setEnabled(singleEntrySelected && !entryViewSorted && !entryViewAtBottom); + 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(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(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((multiEntrySelected && !singleEntrySelected) + || (singleEntrySelected && dbWidget->currentEntryHasUrl())); +#ifdef WITH_XC_BROWSER_PASSKEYS + m_ui->actionEntryImportPasskey->setEnabled(singleEntrySelected); +#endif + 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); + 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); + m_ui->actionGroupDownloadFavicons->setEnabled(groupSelected && currentGroupHasEntries && !recycleBinSelected); + + // Database Menu + m_ui->actionDatabaseSecurity->setEnabled(databaseUnlocked); + m_ui->actionReports->setEnabled(databaseUnlocked); + m_ui->actionDatabaseSettings->setEnabled(databaseUnlocked); + m_ui->actionDatabaseSave->setEnabled(m_ui->tabWidget->canSave()); + m_ui->actionDatabaseSaveAs->setEnabled(databaseUnlocked); + m_ui->actionDatabaseSaveBackup->setEnabled(databaseUnlocked); + m_ui->actionDatabaseClose->setEnabled(databaseOpen); + m_ui->actionLockDatabase->setEnabled(databaseUnlocked); + m_ui->actionLockAllDatabases->setEnabled(hasLockableDatabase); + m_ui->actionLockDatabaseToolbar->setEnabled(hasLockableDatabase); + m_ui->menuExport->setEnabled(databaseUnlocked); + m_ui->actionExportCsv->setEnabled(databaseUnlocked); + m_ui->actionExportHtml->setEnabled(databaseUnlocked); + m_ui->actionExportXML->setEnabled(databaseUnlocked); + m_ui->actionDatabaseMerge->setEnabled(databaseUnlocked); #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->actionPasskeys->setEnabled(databaseUnlocked); + m_ui->actionImportPasskey->setEnabled(databaseUnlocked); + m_ui->actionEntryImportPasskey->setEnabled(singleEntrySelected); + m_ui->actionEntryRemovePasskey->setEnabled(singleEntrySelected && dbWidget->currentEntryHasPasskey()); #endif - m_ui->menuRemoteSync->setEnabled(true); + 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 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); - - 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); - -#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); -#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(viewingDatabase); } void MainWindow::updateToolbarSeparatorVisibility() diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui index 7708df2f34..9ffee39c87 100644 --- a/src/gui/MainWindow.ui +++ b/src/gui/MainWindow.ui @@ -436,6 +436,10 @@ + + + + @@ -659,6 +663,9 @@ + + true + false @@ -676,6 +683,9 @@ + + true + false @@ -693,6 +703,9 @@ + + true + false diff --git a/src/gui/reports/ReportsDialog.cpp b/src/gui/reports/ReportsDialog.cpp index bdbeca8a9e..09fdee1b40 100644 --- a/src/gui/reports/ReportsDialog.cpp +++ b/src/gui/reports/ReportsDialog.cpp @@ -135,6 +135,11 @@ void ReportsDialog::activatePasskeysPage() auto index = m_ui->stackedWidget->currentIndex(); m_ui->categoryList->setCurrentCategory(index); } + +bool ReportsDialog::onPassKeysPage() +{ + return m_ui->stackedWidget->currentWidget() == m_passkeysPage->m_passkeysWidget; +} #endif void ReportsDialog::reject() diff --git a/src/gui/reports/ReportsDialog.h b/src/gui/reports/ReportsDialog.h index 6400787b44..5f1ecfc7f9 100644 --- a/src/gui/reports/ReportsDialog.h +++ b/src/gui/reports/ReportsDialog.h @@ -65,6 +65,7 @@ class ReportsDialog : public DialogyWidget void addPage(QSharedPointer page); #ifdef WITH_XC_BROWSER_PASSKEYS void activatePasskeysPage(); + bool onPassKeysPage(); #endif signals: diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index 6799cc64d8..99117a5e5e 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -561,7 +561,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 +576,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 +654,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 +713,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 +739,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 +859,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 +921,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 +1008,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 +1212,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 +1370,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 +1723,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 +1743,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 +1763,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 +1784,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 +2061,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 +2096,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 +2115,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"); 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