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 <support@dmapps.us>
This commit is contained in:
Jessy LANGE 2025-07-06 20:16:25 +02:00 committed by GitHub
parent 76b2f377df
commit 7ec0f1f5a8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 79 additions and 14 deletions

View file

@ -9955,6 +9955,10 @@ This option is deprecated, use --set-key-file instead.</source>
<source>Limit search to selected group</source> <source>Limit search to selected group</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Press Enter to search</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>SettingsClientModel</name> <name>SettingsClientModel</name>

View file

@ -98,6 +98,7 @@ public:
GUI_CompactMode, GUI_CompactMode,
GUI_CheckForUpdates, GUI_CheckForUpdates,
GUI_CheckForUpdatesIncludeBetas, GUI_CheckForUpdatesIncludeBetas,
SearchWaitForEnter,
GUI_ShowExpiredEntriesOnDatabaseUnlock, GUI_ShowExpiredEntriesOnDatabaseUnlock,
GUI_ShowExpiredEntriesOnDatabaseUnlockOffsetDays, GUI_ShowExpiredEntriesOnDatabaseUnlockOffsetDays,
GUI_FontSizeOffset, GUI_FontSizeOffset,

View file

@ -54,6 +54,7 @@ SearchWidget::SearchWidget(QWidget* parent)
connect(m_searchTimer, SIGNAL(timeout()), SLOT(startSearch())); connect(m_searchTimer, SIGNAL(timeout()), SLOT(startSearch()));
connect(m_clearSearchTimer, SIGNAL(timeout()), SLOT(clearSearch())); connect(m_clearSearchTimer, SIGNAL(timeout()), SLOT(clearSearch()));
connect(this, SIGNAL(escapePressed()), 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") m_ui->searchEdit->setPlaceholderText(tr("Search (%1)…", "Search placeholder text, %1 is the keyboard shortcut")
.arg(QKeySequence(QKeySequence::Find).toString(QKeySequence::NativeText))); .arg(QKeySequence(QKeySequence::Find).toString(QKeySequence::NativeText)));
@ -69,6 +70,12 @@ SearchWidget::SearchWidget(QWidget* parent)
m_actionLimitGroup->setCheckable(true); m_actionLimitGroup->setCheckable(true);
m_actionLimitGroup->setChecked(config()->get(Config::SearchLimitGroup).toBool()); 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->searchIcon->setIcon(icons()->icon("system-search"));
m_ui->searchEdit->addAction(m_ui->searchIcon, QLineEdit::LeadingPosition); 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(entrySelectionChanged()), this, SLOT(resetSearchClearTimer()));
mx.connect(SIGNAL(currentModeChanged(DatabaseWidget::Mode)), this, SLOT(resetSearchClearTimer())); mx.connect(SIGNAL(currentModeChanged(DatabaseWidget::Mode)), this, SLOT(resetSearchClearTimer()));
mx.connect(SIGNAL(databaseUnlocked()), this, SLOT(focusSearch())); 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) void SearchWidget::databaseChanged(DatabaseWidget* dbWidget)
{ {
if (dbWidget != nullptr) { if (dbWidget) {
// Set current search text from this database // Set current search text from this database
m_ui->searchEdit->setText(dbWidget->getCurrentSearch()); m_ui->searchEdit->setText(dbWidget->getCurrentSearch());
// Enforce search policy // Enforce search policy
@ -171,18 +178,15 @@ void SearchWidget::databaseChanged(DatabaseWidget* dbWidget)
void SearchWidget::startSearchTimer() void SearchWidget::startSearchTimer()
{ {
if (!m_searchTimer->isActive()) { if (m_actionWaitForEnter->isChecked()) {
m_searchTimer->stop(); m_searchTimer->stop();
} else {
m_searchTimer->start(500);
} }
m_searchTimer->start(100);
} }
void SearchWidget::startSearch() void SearchWidget::startSearch()
{ {
if (!m_searchTimer->isActive()) {
m_searchTimer->stop();
}
m_ui->saveIcon->setVisible(true); m_ui->saveIcon->setVisible(true);
search(m_ui->searchEdit->text()); search(m_ui->searchEdit->text());
} }
@ -200,18 +204,18 @@ void SearchWidget::updateCaseSensitive()
emit caseSensitiveChanged(m_actionCaseSensitive->isChecked()); emit caseSensitiveChanged(m_actionCaseSensitive->isChecked());
} }
void SearchWidget::setCaseSensitive(bool state)
{
m_actionCaseSensitive->setChecked(state);
updateCaseSensitive();
}
void SearchWidget::updateLimitGroup() void SearchWidget::updateLimitGroup()
{ {
config()->set(Config::SearchLimitGroup, m_actionLimitGroup->isChecked()); config()->set(Config::SearchLimitGroup, m_actionLimitGroup->isChecked());
emit limitGroupChanged(m_actionLimitGroup->isChecked()); emit limitGroupChanged(m_actionLimitGroup->isChecked());
} }
void SearchWidget::setCaseSensitive(bool state)
{
m_actionCaseSensitive->setChecked(state);
updateCaseSensitive();
}
void SearchWidget::setLimitGroup(bool state) void SearchWidget::setLimitGroup(bool state)
{ {
m_actionLimitGroup->setChecked(state); m_actionLimitGroup->setChecked(state);
@ -244,3 +248,12 @@ void SearchWidget::showSearchMenu()
{ {
m_searchMenu->exec(m_ui->searchEdit->mapToGlobal(m_ui->searchEdit->rect().bottomLeft())); 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();
}
}

View file

@ -68,6 +68,7 @@ public slots:
void clearSearch(); void clearSearch();
private slots: private slots:
void onReturnPressed();
void startSearchTimer(); void startSearchTimer();
void startSearch(); void startSearch();
void updateCaseSensitive(); void updateCaseSensitive();
@ -83,6 +84,7 @@ private:
QTimer* m_clearSearchTimer; QTimer* m_clearSearchTimer;
QAction* m_actionCaseSensitive; QAction* m_actionCaseSensitive;
QAction* m_actionLimitGroup; QAction* m_actionLimitGroup;
QAction* m_actionWaitForEnter;
QMenu* m_searchMenu; QMenu* m_searchMenu;
}; };

View file

@ -1126,6 +1126,7 @@ void TestGui::testSearch()
auto* searchWidget = toolBar->findChild<SearchWidget*>("SearchWidget"); auto* searchWidget = toolBar->findChild<SearchWidget*>("SearchWidget");
QVERIFY(searchWidget->isEnabled()); QVERIFY(searchWidget->isEnabled());
auto* searchTextEdit = searchWidget->findChild<QLineEdit*>("searchEdit"); auto* searchTextEdit = searchWidget->findChild<QLineEdit*>("searchEdit");
auto* waitForEnterAction = searchWidget->findChild<QAction*>("actionSearchWaitForEnter");
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView"); auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
QVERIFY(entryView->isVisible()); QVERIFY(entryView->isVisible());
@ -1137,6 +1138,50 @@ void TestGui::testSearch()
QVERIFY(helpButton->isVisible()); QVERIFY(helpButton->isVisible());
QVERIFY(!helpPanel->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 // Enter search
QTest::mouseClick(searchTextEdit, Qt::LeftButton); QTest::mouseClick(searchTextEdit, Qt::LeftButton);
QTRY_VERIFY(searchTextEdit->hasFocus()); QTRY_VERIFY(searchTextEdit->hasFocus());