From ee9c71e11e96073c6dc77e543ee51e62f97e07b1 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Fri, 16 Nov 2018 10:00:59 -0500 Subject: [PATCH] Fix multiple issues with entries and keyboard shortcuts (#2431) * Cleanup entry change notification with entryview focus in/out * Change Open URL shortcut to CTRL+SHIFT+U to conform with an "action" including SHIFT * Change Copy URL shortcut to CTRL+U to conform with "copy" without SHIFT * Entry specific toolbar and menu items are disabled unless the entry row has focus (prevents unintended actions) * Reword security setting for password visibility in entry edit view * Add shortcut to hide/unhide usernames (CTRL+SHIFT+B) * Organize entry menu * Fix #1588 - show keyboard shortcuts in context menu * Fix #2403 - Change auto-type shortcut to CTRL + SHIFT + V * Fix #2096 - Add (CTRL+F) to search bar background * Fix #2031 & Fix #2266 - add shortcut to hide/unhide passwords (CTRL+SHIFT+C) * Fix #2166 - Add reveal password button to entry preview --- src/gui/ApplicationSettingsWidgetSecurity.ui | 2 +- src/gui/DatabaseWidget.cpp | 102 +++----- src/gui/DatabaseWidget.h | 4 +- src/gui/EntryPreviewWidget.cpp | 36 ++- src/gui/EntryPreviewWidget.h | 1 + src/gui/EntryPreviewWidget.ui | 259 ++++++++++++------- src/gui/MainWindow.cpp | 65 +++-- src/gui/MainWindow.h | 2 + src/gui/MainWindow.ui | 72 ++++-- src/gui/SearchWidget.cpp | 4 +- src/gui/SearchWidget.ui | 2 +- src/gui/entry/EntryModel.cpp | 22 +- src/gui/entry/EntryModel.h | 12 +- src/gui/entry/EntryView.cpp | 38 ++- src/gui/entry/EntryView.h | 4 +- tests/gui/TestGui.cpp | 27 +- 16 files changed, 396 insertions(+), 256 deletions(-) diff --git a/src/gui/ApplicationSettingsWidgetSecurity.ui b/src/gui/ApplicationSettingsWidgetSecurity.ui index 4052e5d6..344c2b81 100644 --- a/src/gui/ApplicationSettingsWidgetSecurity.ui +++ b/src/gui/ApplicationSettingsWidgetSecurity.ui @@ -175,7 +175,7 @@ - Show passwords in cleartext by default + Don't hide passwords when editing them diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 38891995..3faf43a6 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -114,7 +114,6 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) m_previewView = new EntryPreviewWidget(this); m_previewView->hide(); - connect(this, SIGNAL(pressedEntry(Entry*)), m_previewView, SLOT(setEntry(Entry*))); connect(this, SIGNAL(pressedGroup(Group*)), m_previewView, SLOT(setGroup(Group*))); connect(this, SIGNAL(currentModeChanged(DatabaseWidget::Mode)), m_previewView, SLOT(setDatabaseMode(DatabaseWidget::Mode))); @@ -183,7 +182,7 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) connect(m_entryView, SIGNAL(entryActivated(Entry*,EntryModel::ModelColumn)), SLOT(entryActivationSignalReceived(Entry*,EntryModel::ModelColumn))); - connect(m_entryView, SIGNAL(entrySelectionChanged()), SIGNAL(entrySelectionChanged())); + connect(m_entryView, SIGNAL(entrySelectionChanged()), SLOT(emitEntrySelectionChanged())); connect(m_editEntryWidget, SIGNAL(editFinished(bool)), SLOT(switchToView(bool))); connect(m_editEntryWidget, SIGNAL(historyEntryActivated(Entry*)), SLOT(switchToHistoryView(Entry*))); connect(m_historyEditEntryWidget, SIGNAL(editFinished(bool)), SLOT(switchBackToEntryEdit())); @@ -202,9 +201,7 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) connect(m_groupView, SIGNAL(groupPressed(Group*)), SLOT(emitPressedGroup(Group*))); connect(m_groupView, SIGNAL(groupChanged(Group*)), SLOT(emitPressedGroup(Group*))); - connect(m_entryView, SIGNAL(entryPressed(Entry*)), SLOT(emitPressedEntry(Entry*))); - connect(m_entryView, SIGNAL(entrySelectionChanged()), SLOT(emitPressedEntry())); - connect(m_editEntryWidget, SIGNAL(editFinished(bool)), SLOT(emitPressedEntry())); + connect(m_editEntryWidget, SIGNAL(editFinished(bool)), SLOT(emitEntrySelectionChanged())); m_databaseModified = false; @@ -506,80 +503,60 @@ void DatabaseWidget::setFocus() void DatabaseWidget::copyTitle() { Entry* currentEntry = m_entryView->currentEntry(); - Q_ASSERT(currentEntry); - if (!currentEntry) { - return; + if (currentEntry) { + setClipboardTextAndMinimize(currentEntry->resolveMultiplePlaceholders(currentEntry->title())); } - - setClipboardTextAndMinimize(currentEntry->resolveMultiplePlaceholders(currentEntry->title())); } void DatabaseWidget::copyUsername() { Entry* currentEntry = m_entryView->currentEntry(); - Q_ASSERT(currentEntry); - if (!currentEntry) { - return; + if (currentEntry) { + setClipboardTextAndMinimize(currentEntry->resolveMultiplePlaceholders(currentEntry->username())); } - - setClipboardTextAndMinimize(currentEntry->resolveMultiplePlaceholders(currentEntry->username())); } void DatabaseWidget::copyPassword() { Entry* currentEntry = m_entryView->currentEntry(); - Q_ASSERT(currentEntry); - if (!currentEntry) { - return; + if (currentEntry) { + setClipboardTextAndMinimize(currentEntry->resolveMultiplePlaceholders(currentEntry->password())); } - - setClipboardTextAndMinimize(currentEntry->resolveMultiplePlaceholders(currentEntry->password())); } void DatabaseWidget::copyURL() { Entry* currentEntry = m_entryView->currentEntry(); - Q_ASSERT(currentEntry); - if (!currentEntry) { - return; + if (currentEntry) { + setClipboardTextAndMinimize(currentEntry->resolveMultiplePlaceholders(currentEntry->url())); } - - setClipboardTextAndMinimize(currentEntry->resolveMultiplePlaceholders(currentEntry->url())); } void DatabaseWidget::copyNotes() { Entry* currentEntry = m_entryView->currentEntry(); - Q_ASSERT(currentEntry); - if (!currentEntry) { - return; + if (currentEntry) { + setClipboardTextAndMinimize(currentEntry->resolveMultiplePlaceholders(currentEntry->notes())); } - - setClipboardTextAndMinimize(currentEntry->resolveMultiplePlaceholders(currentEntry->notes())); } void DatabaseWidget::copyAttribute(QAction* action) { Entry* currentEntry = m_entryView->currentEntry(); - Q_ASSERT(currentEntry); - if (!currentEntry) { - return; + if (currentEntry) { + setClipboardTextAndMinimize( + currentEntry->resolveMultiplePlaceholders( + currentEntry->attributes()->value(action->data().toString()))); } - - setClipboardTextAndMinimize( - currentEntry->resolveMultiplePlaceholders(currentEntry->attributes()->value(action->data().toString()))); } void DatabaseWidget::showTotpKeyQrCode() { Entry* currentEntry = m_entryView->currentEntry(); - Q_ASSERT(currentEntry); - if (!currentEntry) { - return; + if (currentEntry) { + auto totpDisplayDialog = new TotpExportSettingsDialog(this, currentEntry); + totpDisplayDialog->open(); } - - auto totpDisplayDialog = new TotpExportSettingsDialog(this, currentEntry); - totpDisplayDialog->open(); } void DatabaseWidget::setClipboardTextAndMinimize(const QString& text) @@ -593,27 +570,22 @@ void DatabaseWidget::setClipboardTextAndMinimize(const QString& text) void DatabaseWidget::performAutoType() { Entry* currentEntry = m_entryView->currentEntry(); - Q_ASSERT(currentEntry); - if (!currentEntry) { - return; + if (currentEntry) { + autoType()->performAutoType(currentEntry, window()); } - - autoType()->performAutoType(currentEntry, window()); } void DatabaseWidget::openUrl() { Entry* currentEntry = m_entryView->currentEntry(); - Q_ASSERT(currentEntry); - if (!currentEntry) { - return; + if (currentEntry) { + openUrlForEntry(currentEntry); } - - openUrlForEntry(currentEntry); } void DatabaseWidget::openUrlForEntry(Entry* entry) { + Q_ASSERT(entry); QString cmdString = entry->resolveMultiplePlaceholders(entry->url()); if (cmdString.startsWith("cmd://")) { // check if decision to execute command was stored @@ -1076,10 +1048,11 @@ void DatabaseWidget::setSearchLimitGroup(bool state) void DatabaseWidget::onGroupChanged(Group* group) { // Intercept group changes if in search mode - if (isInSearchMode()) + if (isInSearchMode()) { search(m_lastSearchText); - else + } else { m_entryView->setGroup(group); + } } QString DatabaseWidget::getCurrentSearch() @@ -1114,20 +1087,14 @@ void DatabaseWidget::emitEntryContextMenuRequested(const QPoint& pos) emit entryContextMenuRequested(m_entryView->viewport()->mapToGlobal(pos)); } -void DatabaseWidget::emitPressedEntry() +void DatabaseWidget::emitEntrySelectionChanged() { Entry* currentEntry = m_entryView->currentEntry(); - emitPressedEntry(currentEntry); -} - -void DatabaseWidget::emitPressedEntry(Entry* currentEntry) -{ - if (!currentEntry) { - // if no entry is pressed, leave in details the last entry - return; + if (currentEntry) { + m_previewView->setEntry(currentEntry); } - emit pressedEntry(currentEntry); + emit entrySelectionChanged(); } void DatabaseWidget::emitPressedGroup(Group* currentGroup) @@ -1354,7 +1321,12 @@ void DatabaseWidget::restoreGroupEntryFocus(const QUuid& groupUuid, const QUuid& bool DatabaseWidget::isGroupSelected() const { - return m_groupView->currentGroup() != nullptr; + return m_groupView->currentGroup(); +} + +bool DatabaseWidget::currentEntryHasFocus() +{ + return m_entryView->numberOfSelectedEntries() > 0 && m_entryView->hasFocus(); } bool DatabaseWidget::currentEntryHasTitle() diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index 828aace5..a5cf538d 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -99,6 +99,7 @@ public: QByteArray entryViewState() const; bool setEntryViewState(const QByteArray& state) const; void clearAllWidgets(); + bool currentEntryHasFocus(); bool currentEntryHasTitle(); bool currentEntryHasUsername(); bool currentEntryHasPassword(); @@ -198,9 +199,8 @@ private slots: void switchToGroupEdit(Group* entry, bool create); void emitGroupContextMenuRequested(const QPoint& pos); void emitEntryContextMenuRequested(const QPoint& pos); - void emitPressedEntry(); - void emitPressedEntry(Entry* currentEntry); void emitPressedGroup(Group* currentGroup); + void emitEntrySelectionChanged(); void openDatabase(bool accepted); void mergeDatabase(bool accepted); void unlockDatabase(bool accepted); diff --git a/src/gui/EntryPreviewWidget.cpp b/src/gui/EntryPreviewWidget.cpp index af6ffac3..b9fa2638 100644 --- a/src/gui/EntryPreviewWidget.cpp +++ b/src/gui/EntryPreviewWidget.cpp @@ -47,12 +47,14 @@ EntryPreviewWidget::EntryPreviewWidget(QWidget* parent) // Entry m_ui->entryTotpButton->setIcon(filePath()->icon("actions", "chronometer")); m_ui->entryCloseButton->setIcon(filePath()->icon("actions", "dialog-close")); + m_ui->togglePasswordButton->setIcon(filePath()->onOffIcon("actions", "password-show")); m_ui->entryAttachmentsWidget->setReadOnly(true); m_ui->entryAttachmentsWidget->setButtonsVisible(false); connect(m_ui->entryTotpButton, SIGNAL(toggled(bool)), m_ui->entryTotpWidget, SLOT(setVisible(bool))); connect(m_ui->entryCloseButton, SIGNAL(clicked()), SLOT(hide())); + connect(m_ui->togglePasswordButton, SIGNAL(clicked(bool)), SLOT(setPasswordVisible(bool))); connect(m_ui->entryTabWidget, SIGNAL(tabBarClicked(int)), SLOT(updateTabIndexes()), Qt::QueuedConnection); connect(&m_totpTimer, SIGNAL(timeout()), this, SLOT(updateTotpLabel())); @@ -152,18 +154,40 @@ void EntryPreviewWidget::updateEntryTotp() } } +void EntryPreviewWidget::setPasswordVisible(bool state) +{ + const QString password = m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->password()); + auto flags = m_ui->entryPasswordLabel->textInteractionFlags(); + if (state) { + m_ui->entryPasswordLabel->setRawText(password); + m_ui->entryPasswordLabel->setToolTip(password); + m_ui->entryPasswordLabel->setTextInteractionFlags(flags | Qt::TextSelectableByMouse); + } else { + m_ui->entryPasswordLabel->setTextInteractionFlags(flags & ~Qt::TextSelectableByMouse); + m_ui->entryPasswordLabel->setToolTip({}); + if (password.isEmpty() && config()->get("security/passwordemptynodots").toBool()) { + m_ui->entryPasswordLabel->setRawText(""); + } else { + m_ui->entryPasswordLabel->setRawText(QString("\u25cf").repeated(6)); + } + } +} + void EntryPreviewWidget::updateEntryGeneralTab() { Q_ASSERT(m_currentEntry); m_ui->entryUsernameLabel->setText(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->username())); - if (!config()->get("security/HidePasswordPreviewPanel").toBool()) { - const QString password = m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->password()); - m_ui->entryPasswordLabel->setRawText(password); - m_ui->entryPasswordLabel->setToolTip(password); + if (config()->get("security/HidePasswordPreviewPanel").toBool()) { + // Hide password + setPasswordVisible(false); + // Show the password toggle button if there are dots in the label + m_ui->togglePasswordButton->setVisible(!m_ui->entryPasswordLabel->rawText().isEmpty()); + m_ui->togglePasswordButton->setChecked(false); } else { - m_ui->entryPasswordLabel->setRawText(QString("\u25cf").repeated(6)); - m_ui->entryPasswordLabel->setToolTip({}); + // Show password + setPasswordVisible(true); + m_ui->togglePasswordButton->setVisible(false); } m_ui->entryUrlLabel->setRawText(m_currentEntry->displayUrl()); diff --git a/src/gui/EntryPreviewWidget.h b/src/gui/EntryPreviewWidget.h index 88c72962..9e687c6d 100644 --- a/src/gui/EntryPreviewWidget.h +++ b/src/gui/EntryPreviewWidget.h @@ -51,6 +51,7 @@ private slots: void updateEntryAttributesTab(); void updateEntryAttachmentsTab(); void updateEntryAutotypeTab(); + void setPasswordVisible(bool state); void updateGroupHeaderLine(); void updateGroupGeneralTab(); diff --git a/src/gui/EntryPreviewWidget.ui b/src/gui/EntryPreviewWidget.ui index 1fde8aa3..7e84d412 100644 --- a/src/gui/EntryPreviewWidget.ui +++ b/src/gui/EntryPreviewWidget.ui @@ -6,8 +6,8 @@ 0 0 - 280 - 267 + 573 + 330 @@ -168,7 +168,7 @@ - + 0 @@ -181,65 +181,21 @@ 0 - - - - - 0 - 0 - + + + + Qt::Horizontal - + + QSizePolicy::Fixed + + - 100 - 0 + 20 + 20 - - - - - - - 0 - 0 - - - - - 100 - 0 - - - - PointingHandCursor - - - - - - Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse - - - - - - - - 0 - 0 - - - - - 100 - 0 - - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse - - + @@ -266,6 +222,50 @@ + + + + + 0 + 0 + + + + + 100 + 0 + + + + username + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Password + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + @@ -288,7 +288,45 @@ - + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + 100 + 0 + + + + PointingHandCursor + + + https://example.com + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse + + + + @@ -296,6 +334,9 @@ 0 + + expired + @@ -320,8 +361,47 @@ - - + + + + 6 + + + 4 + + + + + + + + true + + + + + + + + 0 + 0 + + + + + 100 + 0 + + + + password + + + + + + + Qt::Horizontal @@ -336,37 +416,34 @@ - - - - - 0 - 0 - - - - - 75 - true - - - - Password - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - + + - Qt::Vertical + Qt::Horizontal + + + QSizePolicy::Fixed - 0 - 0 + 20 + 20 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 20 + 20 @@ -688,6 +765,12 @@ Qt::Vertical + + + 0 + 0 + + diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index ac26dd87..d9f9a055 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -183,7 +183,7 @@ MainWindow::MainWindow() m_ui->actionDatabaseNew->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_N); setShortcut(m_ui->actionDatabaseOpen, QKeySequence::Open, Qt::CTRL + Qt::Key_O); setShortcut(m_ui->actionDatabaseSave, QKeySequence::Save, Qt::CTRL + Qt::Key_S); - setShortcut(m_ui->actionDatabaseSaveAs, QKeySequence::SaveAs); + setShortcut(m_ui->actionDatabaseSaveAs, QKeySequence::SaveAs, Qt::CTRL + Qt::SHIFT + Qt::Key_S); setShortcut(m_ui->actionDatabaseClose, QKeySequence::Close, Qt::CTRL + Qt::Key_W); m_ui->actionLockDatabases->setShortcut(Qt::CTRL + Qt::Key_L); setShortcut(m_ui->actionQuit, QKeySequence::Quit, Qt::CTRL + Qt::Key_Q); @@ -196,16 +196,37 @@ MainWindow::MainWindow() m_ui->actionEntryCopyTotp->setShortcut(Qt::CTRL + Qt::Key_T); m_ui->actionEntryCopyUsername->setShortcut(Qt::CTRL + Qt::Key_B); m_ui->actionEntryCopyPassword->setShortcut(Qt::CTRL + Qt::Key_C); - setShortcut(m_ui->actionEntryAutoType, QKeySequence::Paste, Qt::CTRL + Qt::Key_V); - m_ui->actionEntryOpenUrl->setShortcut(Qt::CTRL + Qt::Key_U); - m_ui->actionEntryCopyURL->setShortcut(Qt::CTRL + Qt::ALT + Qt::Key_U); + m_ui->actionEntryAutoType->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_V); + m_ui->actionEntryOpenUrl->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_U); + m_ui->actionEntryCopyURL->setShortcut(Qt::CTRL + Qt::Key_U); +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + // Qt 5.10 introduced a new "feature" to hide shortcuts in context menus + // Unfortunately, Qt::AA_DontShowShortcutsInContextMenus is broken, have to manually enable them + m_ui->actionEntryNew->setShortcutVisibleInContextMenu(true); + m_ui->actionEntryEdit->setShortcutVisibleInContextMenu(true); + m_ui->actionEntryDelete->setShortcutVisibleInContextMenu(true); + m_ui->actionEntryClone->setShortcutVisibleInContextMenu(true); + m_ui->actionEntryTotp->setShortcutVisibleInContextMenu(true); + m_ui->actionEntryCopyTotp->setShortcutVisibleInContextMenu(true); + m_ui->actionEntryCopyUsername->setShortcutVisibleInContextMenu(true); + m_ui->actionEntryCopyPassword->setShortcutVisibleInContextMenu(true); + m_ui->actionEntryAutoType->setShortcutVisibleInContextMenu(true); + m_ui->actionEntryOpenUrl->setShortcutVisibleInContextMenu(true); + m_ui->actionEntryCopyURL->setShortcutVisibleInContextMenu(true); +#endif + + // Control window state new QShortcut(Qt::CTRL + Qt::Key_M, this, SLOT(showMinimized())); new QShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_M, this, SLOT(hideWindow())); + // Control database tabs new QShortcut(Qt::CTRL + Qt::Key_Tab, this, SLOT(selectNextDatabaseTab())); new QShortcut(Qt::CTRL + Qt::Key_PageUp, this, SLOT(selectNextDatabaseTab())); new QShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Tab, this, SLOT(selectPreviousDatabaseTab())); new QShortcut(Qt::CTRL + Qt::Key_PageDown, this, SLOT(selectPreviousDatabaseTab())); + // Toggle password and username visibility in entry view + new QShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_C, this, SLOT(togglePasswordsHidden())); + new QShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_B, this, SLOT(toggleUsernamesHidden())); m_ui->actionDatabaseNew->setIcon(filePath()->icon("actions", "document-new")); m_ui->actionDatabaseOpen->setIcon(filePath()->icon("actions", "document-open")); @@ -457,8 +478,8 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) switch (mode) { case DatabaseWidget::ViewMode: { // bool inSearch = dbWidget->isInSearchMode(); - bool singleEntrySelected = dbWidget->numberOfSelectedEntries() == 1; - bool entriesSelected = dbWidget->numberOfSelectedEntries() > 0; + bool singleEntrySelected = dbWidget->numberOfSelectedEntries() == 1 && dbWidget->currentEntryHasFocus(); + bool entriesSelected = dbWidget->numberOfSelectedEntries() > 0 && dbWidget->currentEntryHasFocus(); bool groupSelected = dbWidget->isGroupSelected(); bool recycleBinSelected = dbWidget->isRecycleBinSelected(); @@ -472,7 +493,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) m_ui->actionEntryCopyURL->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUrl()); m_ui->actionEntryCopyNotes->setEnabled(singleEntrySelected && dbWidget->currentEntryHasNotes()); m_ui->menuEntryCopyAttribute->setEnabled(singleEntrySelected); - m_ui->menuEntryTotp->setEnabled(true); + m_ui->menuEntryTotp->setEnabled(singleEntrySelected); m_ui->actionEntryAutoType->setEnabled(singleEntrySelected); m_ui->actionEntryOpenUrl->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUrl()); m_ui->actionEntryTotp->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp()); @@ -507,13 +528,6 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) for (QAction* action : groupActions) { action->setEnabled(false); } - m_ui->actionEntryCopyTitle->setEnabled(false); - m_ui->actionEntryCopyUsername->setEnabled(false); - m_ui->actionEntryCopyPassword->setEnabled(false); - m_ui->actionEntryCopyURL->setEnabled(false); - m_ui->actionEntryCopyNotes->setEnabled(false); - m_ui->menuEntryCopyAttribute->setEnabled(false); - m_ui->menuEntryTotp->setEnabled(false); m_ui->actionChangeMasterKey->setEnabled(false); m_ui->actionChangeDatabaseSettings->setEnabled(false); @@ -539,13 +553,6 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) for (QAction* action : groupActions) { action->setEnabled(false); } - m_ui->actionEntryCopyTitle->setEnabled(false); - m_ui->actionEntryCopyUsername->setEnabled(false); - m_ui->actionEntryCopyPassword->setEnabled(false); - m_ui->actionEntryCopyURL->setEnabled(false); - m_ui->actionEntryCopyNotes->setEnabled(false); - m_ui->menuEntryCopyAttribute->setEnabled(false); - m_ui->menuEntryTotp->setEnabled(false); m_ui->actionChangeMasterKey->setEnabled(false); m_ui->actionChangeDatabaseSettings->setEnabled(false); @@ -731,6 +738,22 @@ void MainWindow::databaseTabChanged(int tabIndex) m_actionMultiplexer.setCurrentObject(m_ui->tabWidget->currentDatabaseWidget()); } +void MainWindow::togglePasswordsHidden() +{ + auto dbWidget = m_ui->tabWidget->currentDatabaseWidget(); + if (dbWidget) { + dbWidget->setPasswordsHidden(!dbWidget->isPasswordsHidden()); + } +} + +void MainWindow::toggleUsernamesHidden() +{ + auto dbWidget = m_ui->tabWidget->currentDatabaseWidget(); + if (dbWidget) { + dbWidget->setUsernamesHidden(!dbWidget->isUsernamesHidden()); + } +} + void MainWindow::closeEvent(QCloseEvent* event) { // ignore double close events (happens on macOS when closing from the dock) diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index 174e4756..caf2a0c5 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -112,6 +112,8 @@ private slots: void showErrorMessage(const QString& message); void selectNextDatabaseTab(); void selectPreviousDatabaseTab(); + void togglePasswordsHidden(); + void toggleUsernamesHidden(); private: static void setShortcut(QAction* action, QKeySequence::StandardKey standard, int fallback = 0); diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui index 2c74c706..c719cb67 100644 --- a/src/gui/MainWindow.ui +++ b/src/gui/MainWindow.ui @@ -180,7 +180,7 @@ 0 0 800 - 30 + 21 @@ -197,7 +197,7 @@ - Import + &Import @@ -234,8 +234,11 @@ false + + + - Copy att&ribute to clipboard + Copy att&ribute... @@ -247,23 +250,25 @@ false - Time-based one-time password + TOTP... - - - - - - + - + + + + + + + + @@ -366,12 +371,18 @@ - &New database + &New database... + + + Create a new database - Merge from KeePassX database + &Merge from database... + + + Merge from another KDBX database @@ -379,7 +390,10 @@ false - &Add new entry + &New entry + + + Add a new entry @@ -387,7 +401,10 @@ false - &View/Edit entry + &Edit entry + + + View or edit entry @@ -403,7 +420,10 @@ false - &Add new group + &New group + + + Add a new group @@ -435,7 +455,7 @@ false - Change &master key... + Change master &key... @@ -443,7 +463,7 @@ false - &Database settings + &Database settings... Database settings @@ -476,7 +496,7 @@ false - Cop&y password + Copy &password Copy password to clipboard @@ -506,7 +526,7 @@ false - &Perform Auto-Type + Perform &Auto-Type @@ -514,7 +534,7 @@ false - &Open URL + Open &URL @@ -568,12 +588,18 @@ - Import KeePass 1 database... + KeePass 1 database... + + + Import a KeePass 1 database - Import CSV file... + CSV file... + + + Import a CSV file diff --git a/src/gui/SearchWidget.cpp b/src/gui/SearchWidget.cpp index c84ba858..40c63036 100644 --- a/src/gui/SearchWidget.cpp +++ b/src/gui/SearchWidget.cpp @@ -41,9 +41,11 @@ SearchWidget::SearchWidget(QWidget* parent) connect(m_searchTimer, SIGNAL(timeout()), this, SLOT(startSearch())); connect(this, SIGNAL(escapePressed()), m_ui->searchEdit, SLOT(clear())); - new QShortcut(Qt::CTRL + Qt::Key_F, this, SLOT(searchFocus()), nullptr, Qt::ApplicationShortcut); + new QShortcut(QKeySequence::Find, this, SLOT(searchFocus()), nullptr, Qt::ApplicationShortcut); new QShortcut(Qt::Key_Escape, m_ui->searchEdit, SLOT(clear()), nullptr, Qt::ApplicationShortcut); + m_ui->searchEdit->setPlaceholderText(tr("Search (%1)...", "Search placeholder text, %1 is the keyboard shortcut") + .arg(QKeySequence(QKeySequence::Find).toString(QKeySequence::NativeText))); m_ui->searchEdit->installEventFilter(this); QMenu* searchMenu = new QMenu(); diff --git a/src/gui/SearchWidget.ui b/src/gui/SearchWidget.ui index 98395b68..438a242c 100644 --- a/src/gui/SearchWidget.ui +++ b/src/gui/SearchWidget.ui @@ -48,7 +48,7 @@ padding:3px - Search... + false diff --git a/src/gui/entry/EntryModel.cpp b/src/gui/entry/EntryModel.cpp index 8cbf4bfe..2edc49b2 100644 --- a/src/gui/entry/EntryModel.cpp +++ b/src/gui/entry/EntryModel.cpp @@ -468,9 +468,10 @@ bool EntryModel::isUsernamesHidden() const /** * Set state of 'Hide Usernames' setting and signal change */ -void EntryModel::setUsernamesHidden(const bool hide) +void EntryModel::setUsernamesHidden(bool hide) { m_hideUsernames = hide; + emit dataChanged(index(0, 0), index(rowCount()-1, columnCount() - 1)); emit usernamesHiddenChanged(); } @@ -485,28 +486,13 @@ bool EntryModel::isPasswordsHidden() const /** * Set state of 'Hide Passwords' setting and signal change */ -void EntryModel::setPasswordsHidden(const bool hide) +void EntryModel::setPasswordsHidden(bool hide) { m_hidePasswords = hide; + emit dataChanged(index(0, 0), index(rowCount()-1, columnCount() - 1)); emit passwordsHiddenChanged(); } -/** - * Toggle state of 'Hide Usernames' setting - */ -void EntryModel::toggleUsernamesHidden(const bool hide) -{ - setUsernamesHidden(hide); -} - -/** - * Toggle state of 'Hide Passwords' setting - */ -void EntryModel::togglePasswordsHidden(const bool hide) -{ - setPasswordsHidden(hide); -} - void EntryModel::setPaperClipPixmap(const QPixmap& paperclip) { m_paperClipPixmap = paperclip; diff --git a/src/gui/entry/EntryModel.h b/src/gui/entry/EntryModel.h index e8c90f7e..3e9f2824 100644 --- a/src/gui/entry/EntryModel.h +++ b/src/gui/entry/EntryModel.h @@ -61,13 +61,9 @@ public: QMimeData* mimeData(const QModelIndexList& indexes) const override; void setEntryList(const QList& entries); - - bool isUsernamesHidden() const; - void setUsernamesHidden(const bool hide); - bool isPasswordsHidden() const; - void setPasswordsHidden(const bool hide); - void setPaperClipPixmap(const QPixmap& paperclip); + bool isUsernamesHidden() const; + bool isPasswordsHidden() const; signals: void switchedToListMode(); @@ -77,8 +73,8 @@ signals: public slots: void setGroup(Group* group); - void toggleUsernamesHidden(const bool hide); - void togglePasswordsHidden(const bool hide); + void setUsernamesHidden(bool hide); + void setPasswordsHidden(bool hide); private slots: void entryAboutToAdd(Entry* entry); diff --git a/src/gui/entry/EntryView.cpp b/src/gui/entry/EntryView.cpp index d63260c3..ecdccd7b 100644 --- a/src/gui/entry/EntryView.cpp +++ b/src/gui/entry/EntryView.cpp @@ -50,21 +50,21 @@ EntryView::EntryView(QWidget* parent) setDefaultDropAction(Qt::MoveAction); connect(this, SIGNAL(doubleClicked(QModelIndex)), SLOT(emitEntryActivated(QModelIndex))); - connect( - selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SIGNAL(entrySelectionChanged())); + connect(selectionModel(), + SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SIGNAL(entrySelectionChanged())); + connect(m_model, SIGNAL(switchedToListMode()), SLOT(switchToListMode())); connect(m_model, SIGNAL(switchedToSearchMode()), SLOT(switchToSearchMode())); connect(m_model, SIGNAL(usernamesHiddenChanged()), SIGNAL(viewStateChanged())); connect(m_model, SIGNAL(passwordsHiddenChanged()), SIGNAL(viewStateChanged())); - connect(this, SIGNAL(clicked(QModelIndex)), SLOT(emitEntryPressed(QModelIndex))); m_headerMenu = new QMenu(this); m_headerMenu->setTitle(tr("Customize View")); m_headerMenu->addSection(tr("Customize View")); - m_hideUsernamesAction = m_headerMenu->addAction(tr("Hide Usernames"), m_model, SLOT(toggleUsernamesHidden(bool))); + m_hideUsernamesAction = m_headerMenu->addAction(tr("Hide Usernames"), m_model, SLOT(setUsernamesHidden(bool))); m_hideUsernamesAction->setCheckable(true); - m_hidePasswordsAction = m_headerMenu->addAction(tr("Hide Passwords"), m_model, SLOT(togglePasswordsHidden(bool))); + m_hidePasswordsAction = m_headerMenu->addAction(tr("Hide Passwords"), m_model, SLOT(setPasswordsHidden(bool))); m_hidePasswordsAction->setCheckable(true); m_headerMenu->addSeparator(); @@ -146,6 +146,18 @@ void EntryView::keyPressEvent(QKeyEvent* event) QTreeView::keyPressEvent(event); } +void EntryView::focusInEvent(QFocusEvent* event) +{ + emit entrySelectionChanged(); + QTreeView::focusInEvent(event); +} + +void EntryView::focusOutEvent(QFocusEvent* event) +{ + emit entrySelectionChanged(); + QTreeView::focusOutEvent(event); +} + void EntryView::setGroup(Group* group) { m_model->setGroup(group); @@ -176,15 +188,9 @@ bool EntryView::inSearchMode() void EntryView::emitEntryActivated(const QModelIndex& index) { Entry* entry = entryFromIndex(index); - emit entryActivated(entry, static_cast(m_sortModel->mapToSource(index).column())); } -void EntryView::emitEntryPressed(const QModelIndex& index) -{ - emit entryPressed(entryFromIndex(index)); -} - void EntryView::setModel(QAbstractItemModel* model) { Q_UNUSED(model); @@ -268,6 +274,11 @@ bool EntryView::isUsernamesHidden() const */ void EntryView::setUsernamesHidden(const bool hide) { + bool block = m_hideUsernamesAction->signalsBlocked(); + m_hideUsernamesAction->blockSignals(true); + m_hideUsernamesAction->setChecked(hide); + m_hideUsernamesAction->blockSignals(block); + m_model->setUsernamesHidden(hide); } @@ -285,6 +296,11 @@ bool EntryView::isPasswordsHidden() const */ void EntryView::setPasswordsHidden(const bool hide) { + bool block = m_hidePasswordsAction->signalsBlocked(); + m_hidePasswordsAction->blockSignals(true); + m_hidePasswordsAction->setChecked(hide); + m_hidePasswordsAction->blockSignals(block); + m_model->setPasswordsHidden(hide); } diff --git a/src/gui/entry/EntryView.h b/src/gui/entry/EntryView.h index 3d166c48..2030f0ec 100644 --- a/src/gui/entry/EntryView.h +++ b/src/gui/entry/EntryView.h @@ -55,16 +55,16 @@ public slots: signals: void entryActivated(Entry* entry, EntryModel::ModelColumn column); - void entryPressed(Entry* entry); void entrySelectionChanged(); void viewStateChanged(); protected: void keyPressEvent(QKeyEvent* event) override; + void focusInEvent(QFocusEvent* event) override; + void focusOutEvent(QFocusEvent* event) override; private slots: void emitEntryActivated(const QModelIndex& index); - void emitEntryPressed(const QModelIndex& index); void switchToListMode(); void switchToSearchMode(); void showHeaderMenu(const QPoint& position); diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index 8613d184..9b04dd18 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -736,11 +736,9 @@ void TestGui::testTotp() auto* entryView = m_dbWidget->findChild("entryView"); QCOMPARE(entryView->model()->rowCount(), 1); - QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode); QModelIndex item = entryView->model()->index(0, 1); Entry* entry = entryView->entryFromIndex(item); - clickIndex(item, entryView, Qt::LeftButton); triggerAction("actionEntrySetupTotp"); @@ -749,21 +747,28 @@ void TestGui::testTotp() QApplication::processEvents(); + QString exampleSeed = "gezdgnbvgy3tqojqgezdgnbvgy3tqojq"; auto* seedEdit = setupTotpDialog->findChild("seedEdit"); seedEdit->setText(""); - - QString exampleSeed = "gezdgnbvgy3tqojqgezdgnbvgy3tqojq"; QTest::keyClicks(seedEdit, exampleSeed); auto* setupTotpButtonBox = setupTotpDialog->findChild("buttonBox"); QTest::mouseClick(setupTotpButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); + QTRY_VERIFY(!setupTotpDialog->isVisible()); + + // Make sure the entryView is selected and active + entryView->activateWindow(); + QApplication::processEvents(); + QTRY_VERIFY(entryView->hasFocus()); auto* entryEditAction = m_mainWindow->findChild("actionEntryEdit"); QWidget* entryEditWidget = toolBar->widgetForAction(entryEditAction); + QVERIFY(entryEditWidget->isVisible()); + QVERIFY(entryEditWidget->isEnabled()); QTest::mouseClick(entryEditWidget, Qt::LeftButton); QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode); - auto* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); + auto* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); editEntryWidget->setCurrentPage(1); auto* attrTextEdit = editEntryWidget->findChild("attributesEdit"); QTest::mouseClick(editEntryWidget->findChild("revealAttributeButton"), Qt::LeftButton); @@ -836,15 +841,19 @@ void TestGui::testSearch() // Ensure Down focuses on entry view when search text is selected QTest::keyClick(searchTextEdit, Qt::Key_Down); QTRY_VERIFY(entryView->hasFocus()); - // Refocus back to search edit - QTest::mouseClick(searchTextEdit, Qt::LeftButton); - QTRY_VERIFY(searchTextEdit->hasFocus()); - // Test password copy + // Test that password copies (entry has focus) QClipboard* clipboard = QApplication::clipboard(); QTest::keyClick(searchTextEdit, Qt::Key_C, Qt::ControlModifier); QModelIndex searchedItem = entryView->model()->index(0, 1); Entry* searchedEntry = entryView->entryFromIndex(searchedItem); QTRY_COMPARE(searchedEntry->password(), clipboard->text()); + // Refocus back to search edit + QTest::mouseClick(searchTextEdit, Qt::LeftButton); + QTRY_VERIFY(searchTextEdit->hasFocus()); + // Test that password does not copy + searchTextEdit->selectAll(); + QTest::keyClick(searchTextEdit, Qt::Key_C, Qt::ControlModifier); + QTRY_COMPARE(clipboard->text(), QString("someTHING")); // Test case sensitive search searchWidget->setCaseSensitive(true);