From 7ec0f1f5a8b43ae955b1502fef616c39d3db85e2 Mon Sep 17 00:00:00 2001 From: Jessy LANGE <89694096+jessy2027@users.noreply.github.com> Date: Sun, 6 Jul 2025 20:16:25 +0200 Subject: [PATCH] Add "press enter to search" option (#12263) * Also increase auto-search timeout to 500 ms to improve user experience, especially with large databases. The previous value of 100ms guaranteed a search was performed after every character entered, even when typing relatively fast. --------- Co-authored-by: Jonathan White --- share/translations/keepassxc_en.ts | 4 +++ src/core/Config.h | 1 + src/gui/SearchWidget.cpp | 41 +++++++++++++++++---------- src/gui/SearchWidget.h | 2 ++ tests/gui/TestGui.cpp | 45 ++++++++++++++++++++++++++++++ 5 files changed, 79 insertions(+), 14 deletions(-) diff --git a/share/translations/keepassxc_en.ts b/share/translations/keepassxc_en.ts index 413c9fb1c..16cd879b9 100644 --- a/share/translations/keepassxc_en.ts +++ b/share/translations/keepassxc_en.ts @@ -9955,6 +9955,10 @@ This option is deprecated, use --set-key-file instead. Limit search to selected group + + Press Enter to search + + SettingsClientModel diff --git a/src/core/Config.h b/src/core/Config.h index b4f3bdc04..5678bbc6e 100644 --- a/src/core/Config.h +++ b/src/core/Config.h @@ -98,6 +98,7 @@ public: GUI_CompactMode, GUI_CheckForUpdates, GUI_CheckForUpdatesIncludeBetas, + SearchWaitForEnter, GUI_ShowExpiredEntriesOnDatabaseUnlock, GUI_ShowExpiredEntriesOnDatabaseUnlockOffsetDays, GUI_FontSizeOffset, diff --git a/src/gui/SearchWidget.cpp b/src/gui/SearchWidget.cpp index a504518bc..5fce8a5aa 100644 --- a/src/gui/SearchWidget.cpp +++ b/src/gui/SearchWidget.cpp @@ -54,6 +54,7 @@ SearchWidget::SearchWidget(QWidget* parent) connect(m_searchTimer, SIGNAL(timeout()), SLOT(startSearch())); connect(m_clearSearchTimer, SIGNAL(timeout()), SLOT(clearSearch())); connect(this, SIGNAL(escapePressed()), SLOT(clearSearch())); + connect(m_ui->searchEdit, &QLineEdit::returnPressed, this, &SearchWidget::onReturnPressed); m_ui->searchEdit->setPlaceholderText(tr("Search (%1)…", "Search placeholder text, %1 is the keyboard shortcut") .arg(QKeySequence(QKeySequence::Find).toString(QKeySequence::NativeText))); @@ -69,6 +70,12 @@ SearchWidget::SearchWidget(QWidget* parent) m_actionLimitGroup->setCheckable(true); m_actionLimitGroup->setChecked(config()->get(Config::SearchLimitGroup).toBool()); + m_actionWaitForEnter = m_searchMenu->addAction( + tr("Press Enter to search"), this, [](bool state) { config()->set(Config::SearchWaitForEnter, state); }); + m_actionWaitForEnter->setObjectName("actionSearchWaitForEnter"); + m_actionWaitForEnter->setCheckable(true); + m_actionWaitForEnter->setChecked(config()->get(Config::SearchWaitForEnter).toBool()); + m_ui->searchIcon->setIcon(icons()->icon("system-search")); m_ui->searchEdit->addAction(m_ui->searchIcon, QLineEdit::LeadingPosition); @@ -153,12 +160,12 @@ void SearchWidget::connectSignals(SignalMultiplexer& mx) mx.connect(SIGNAL(entrySelectionChanged()), this, SLOT(resetSearchClearTimer())); mx.connect(SIGNAL(currentModeChanged(DatabaseWidget::Mode)), this, SLOT(resetSearchClearTimer())); mx.connect(SIGNAL(databaseUnlocked()), this, SLOT(focusSearch())); - mx.connect(m_ui->searchEdit, SIGNAL(returnPressed()), SLOT(switchToEntryEdit())); + mx.connect(this, SIGNAL(enterPressed()), SLOT(switchToEntryEdit())); } void SearchWidget::databaseChanged(DatabaseWidget* dbWidget) { - if (dbWidget != nullptr) { + if (dbWidget) { // Set current search text from this database m_ui->searchEdit->setText(dbWidget->getCurrentSearch()); // Enforce search policy @@ -171,18 +178,15 @@ void SearchWidget::databaseChanged(DatabaseWidget* dbWidget) void SearchWidget::startSearchTimer() { - if (!m_searchTimer->isActive()) { + if (m_actionWaitForEnter->isChecked()) { m_searchTimer->stop(); + } else { + m_searchTimer->start(500); } - m_searchTimer->start(100); } void SearchWidget::startSearch() { - if (!m_searchTimer->isActive()) { - m_searchTimer->stop(); - } - m_ui->saveIcon->setVisible(true); search(m_ui->searchEdit->text()); } @@ -200,18 +204,18 @@ void SearchWidget::updateCaseSensitive() emit caseSensitiveChanged(m_actionCaseSensitive->isChecked()); } -void SearchWidget::setCaseSensitive(bool state) -{ - m_actionCaseSensitive->setChecked(state); - updateCaseSensitive(); -} - void SearchWidget::updateLimitGroup() { config()->set(Config::SearchLimitGroup, m_actionLimitGroup->isChecked()); emit limitGroupChanged(m_actionLimitGroup->isChecked()); } +void SearchWidget::setCaseSensitive(bool state) +{ + m_actionCaseSensitive->setChecked(state); + updateCaseSensitive(); +} + void SearchWidget::setLimitGroup(bool state) { m_actionLimitGroup->setChecked(state); @@ -244,3 +248,12 @@ void SearchWidget::showSearchMenu() { m_searchMenu->exec(m_ui->searchEdit->mapToGlobal(m_ui->searchEdit->rect().bottomLeft())); } + +void SearchWidget::onReturnPressed() +{ + if (m_actionWaitForEnter->isChecked()) { + emit search(m_ui->searchEdit->text()); + } else { + emit enterPressed(); + } +} diff --git a/src/gui/SearchWidget.h b/src/gui/SearchWidget.h index 8d2c63394..1d048d12b 100644 --- a/src/gui/SearchWidget.h +++ b/src/gui/SearchWidget.h @@ -68,6 +68,7 @@ public slots: void clearSearch(); private slots: + void onReturnPressed(); void startSearchTimer(); void startSearch(); void updateCaseSensitive(); @@ -83,6 +84,7 @@ private: QTimer* m_clearSearchTimer; QAction* m_actionCaseSensitive; QAction* m_actionLimitGroup; + QAction* m_actionWaitForEnter; QMenu* m_searchMenu; }; diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index f711817d5..9011d445c 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -1126,6 +1126,7 @@ void TestGui::testSearch() auto* searchWidget = toolBar->findChild("SearchWidget"); QVERIFY(searchWidget->isEnabled()); auto* searchTextEdit = searchWidget->findChild("searchEdit"); + auto* waitForEnterAction = searchWidget->findChild("actionSearchWaitForEnter"); auto* entryView = m_dbWidget->findChild("entryView"); QVERIFY(entryView->isVisible()); @@ -1137,6 +1138,50 @@ void TestGui::testSearch() QVERIFY(helpButton->isVisible()); QVERIFY(!helpPanel->isVisible()); + // Test "wait for enter" toggle + QVERIFY(waitForEnterAction->isVisible()); + QVERIFY(waitForEnterAction->isCheckable()); + + // Test search with "wait for enter" disabled (default) + searchTextEdit->clear(); + QTest::keyClicks(searchTextEdit, "ZZZ"); + QTRY_COMPARE(searchTextEdit->text(), QString("ZZZ")); + QTRY_VERIFY(m_dbWidget->isSearchActive()); + QTRY_COMPARE(entryView->model()->rowCount(), 0); + + // Clear search + searchTextEdit->clear(); + QTRY_VERIFY(!m_dbWidget->isSearchActive()); + + // Enable "wait for enter" mode + waitForEnterAction->trigger(); + QVERIFY(waitForEnterAction->isChecked()); + + // Test search with "wait for enter" enabled + QTest::keyClicks(searchTextEdit, "ZZZ"); + QTRY_VERIFY(!m_dbWidget->isSearchActive()); + + // Press Enter to execute search + QTest::keyClick(searchTextEdit, Qt::Key_Return); + QTRY_VERIFY(m_dbWidget->isSearchActive()); + QTRY_COMPARE(entryView->model()->rowCount(), 0); + // Check that search remains active even after clearing + searchTextEdit->clear(); + QTRY_VERIFY(m_dbWidget->isSearchActive()); + + // Disable "wait for enter" mode + waitForEnterAction->trigger(); + QVERIFY(!waitForEnterAction->isChecked()); + + // Test search with "wait for enter" disabled again + QTest::keyClicks(searchTextEdit, "ZZZ"); + QTRY_VERIFY(m_dbWidget->isSearchActive()); + QTRY_COMPARE(entryView->model()->rowCount(), 0); + + // Clear search + searchTextEdit->clear(); + QTRY_VERIFY(!m_dbWidget->isSearchActive()); + // Enter search QTest::mouseClick(searchTextEdit, Qt::LeftButton); QTRY_VERIFY(searchTextEdit->hasFocus());