┌   ┐
54
└   ┘

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/kitemviews/kitemlistcontroller.cpp6
-rw-r--r--src/kitemviews/kitemlistcontroller.h2
-rw-r--r--src/kitemviews/private/kitemlistkeyboardsearchmanager.cpp34
-rw-r--r--src/kitemviews/private/kitemlistkeyboardsearchmanager.h3
-rw-r--r--src/tests/kitemlistkeyboardsearchmanagertest.cpp143
5 files changed, 139 insertions, 49 deletions
diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp
index fdde48abc..7971de5b7 100644
--- a/src/kitemviews/kitemlistcontroller.cpp
+++ b/src/kitemviews/kitemlistcontroller.cpp
@@ -58,7 +58,7 @@ KItemListController::KItemListController(KItemModelBase *model, KItemListView *v
, m_keyboardAnchorIndex(-1)
, m_keyboardAnchorPos(0)
{
- connect(m_keyboardManager, &KItemListKeyboardSearchManager::changeCurrentItem, this, &KItemListController::slotChangeCurrentItem);
+ connect(m_keyboardManager, &KItemListKeyboardSearchManager::changeCurrentItem, this, &KItemListController::slotChangeCurrentItem, Qt::DirectConnection);
connect(m_selectionManager, &KItemListSelectionManager::currentChanged, m_keyboardManager, &KItemListKeyboardSearchManager::slotCurrentChanged);
connect(m_selectionManager, &KItemListSelectionManager::selectionChanged, m_keyboardManager, &KItemListKeyboardSearchManager::slotSelectionChanged);
@@ -515,8 +515,9 @@ bool KItemListController::keyPressEvent(QKeyEvent *event)
return true;
}
-void KItemListController::slotChangeCurrentItem(const QString &text, bool searchFromNextItem)
+void KItemListController::slotChangeCurrentItem(const QString &text, bool searchFromNextItem, bool *found)
{
+ *found = false;
if (!m_model || m_model->count() == 0) {
return;
}
@@ -542,6 +543,7 @@ void KItemListController::slotChangeCurrentItem(const QString &text, bool search
}
m_view->scrollToItem(index, KItemListView::ViewItemPosition::Beginning);
+ *found = true;
}
}
diff --git a/src/kitemviews/kitemlistcontroller.h b/src/kitemviews/kitemlistcontroller.h
index 48da07206..48a518610 100644
--- a/src/kitemviews/kitemlistcontroller.h
+++ b/src/kitemviews/kitemlistcontroller.h
@@ -233,7 +233,7 @@ private Q_SLOTS:
*/
void slotRubberBandChanged();
- void slotChangeCurrentItem(const QString &text, bool searchFromNextItem);
+ void slotChangeCurrentItem(const QString &text, bool searchFromNextItem, bool *found);
void slotAutoActivationTimeout();
diff --git a/src/kitemviews/private/kitemlistkeyboardsearchmanager.cpp b/src/kitemviews/private/kitemlistkeyboardsearchmanager.cpp
index afc3bb071..f2f834c4c 100644
--- a/src/kitemviews/private/kitemlistkeyboardsearchmanager.cpp
+++ b/src/kitemviews/private/kitemlistkeyboardsearchmanager.cpp
@@ -35,6 +35,7 @@ void KItemListKeyboardSearchManager::addKeys(const QString &keys)
{
if (shouldClearSearchIfInputTimeReached()) {
m_searchedString.clear();
+ m_lastSuccessfulSearch.clear();
}
const bool newSearch = m_searchedString.isEmpty();
@@ -48,17 +49,32 @@ void KItemListKeyboardSearchManager::addKeys(const QString &keys)
if (!keys.isEmpty()) {
m_searchedString.append(keys);
- // Special case:
- // If the same key is pressed repeatedly, the next item matching that key should be highlighted
- const QChar firstKey = m_searchedString.length() > 0 ? m_searchedString.at(0) : QChar();
- const bool sameKey = m_searchedString.length() > 1 && m_searchedString.count(firstKey) == m_searchedString.length();
+ const QChar firstChar = m_searchedString.at(0);
+ const bool allSameChars = m_searchedString.length() > 1 &&
+ m_searchedString.count(firstChar) == m_searchedString.length();
- // Searching for a matching item should start from the next item if either
- // 1. a new search is started, or
- // 2. a 'repeated key' search is done.
- const bool searchFromNextItem = newSearch || sameKey;
+ // Overall strategy:
+ // 1. First attempt full string matching for exact file names
+ // 2. If full match fails and user is typing repeating characters,
+ // fall back to rapid navigation using either:
+ // - Last successful search string (for extended searches like "444" -> "4444")
+ // - First character only (original rapid navigation behavior)
+ bool found = false;
+ Q_EMIT changeCurrentItem(m_searchedString, newSearch, &found);
- Q_EMIT changeCurrentItem(sameKey ? firstKey : m_searchedString, searchFromNextItem);
+ if (found) {
+ m_lastSuccessfulSearch = m_searchedString;
+ } else if (allSameChars && !newSearch) {
+ QString rapidSearchString;
+
+ if (!m_lastSuccessfulSearch.isEmpty() && m_searchedString.startsWith(m_lastSuccessfulSearch)) {
+ rapidSearchString = m_lastSuccessfulSearch;
+ } else {
+ rapidSearchString = QString(firstChar);
+ }
+
+ Q_EMIT changeCurrentItem(rapidSearchString, true, &found);
+ }
}
m_keyboardInputTime.start();
}
diff --git a/src/kitemviews/private/kitemlistkeyboardsearchmanager.h b/src/kitemviews/private/kitemlistkeyboardsearchmanager.h
index 5cb8effbf..4437f97b4 100644
--- a/src/kitemviews/private/kitemlistkeyboardsearchmanager.h
+++ b/src/kitemviews/private/kitemlistkeyboardsearchmanager.h
@@ -66,7 +66,7 @@ Q_SIGNALS:
*/
// TODO: Think about getting rid of the bool parameter
// (see https://doc.qt.io/archives/qq/qq13-apis.html#thebooleanparametertrap)
- void changeCurrentItem(const QString &string, bool searchFromNextItem);
+ void changeCurrentItem(const QString &string, bool searchFromNextItem, bool *found);
private:
bool shouldClearSearchIfInputTimeReached();
@@ -76,6 +76,7 @@ private:
QElapsedTimer m_keyboardInputTime;
/** Time in milliseconds in which a key press is considered as a continuation of the previous search input. */
qint64 m_timeout;
+ QString m_lastSuccessfulSearch;
};
#endif
diff --git a/src/tests/kitemlistkeyboardsearchmanagertest.cpp b/src/tests/kitemlistkeyboardsearchmanagertest.cpp
index bbdb9c397..feba6acba 100644
--- a/src/tests/kitemlistkeyboardsearchmanagertest.cpp
+++ b/src/tests/kitemlistkeyboardsearchmanagertest.cpp
@@ -10,6 +10,8 @@
#include <QStandardPaths>
#include <QTest>
+#include <source_location>
+
class KItemListKeyboardSearchManagerTest : public QObject
{
Q_OBJECT
@@ -25,6 +27,68 @@ private Q_SLOTS:
private:
KItemListKeyboardSearchManager m_keyboardSearchManager;
+
+ // Helper function to verify signal parameters
+ void verifySignal(QSignalSpy &spy,
+ const QString &expectedString,
+ bool expectedSearchFromNextItem,
+ const std::source_location &location = std::source_location::current())
+ {
+ if (spy.count() != 1) {
+ QTest::qFail(QString("Compared values are not the same\n"
+ " Actual (spy.count()): %1\n"
+ " Expected (1) : 1")
+ .arg(spy.count())
+ .toUtf8()
+ .constData(),
+ location.file_name(),
+ location.line());
+ return;
+ }
+
+ QList<QVariant> arguments = spy.takeFirst();
+
+ if (arguments.size() != 3) {
+ QTest::qFail(QString("Compared values are not the same\n"
+ " Actual (arguments.size()): %1\n"
+ " Expected (3) : 3")
+ .arg(arguments.size())
+ .toUtf8()
+ .constData(),
+ location.file_name(),
+ location.line());
+ return;
+ }
+
+ QString actualText = arguments.at(0).toString();
+ if (actualText != expectedString) {
+ QTest::qFail(QString("Compared values are not the same\n"
+ " Actual (arguments.at(0).toString()): \"%1\"\n"
+ " Expected (QString(\"%2\")) : \"%2\"")
+ .arg(actualText)
+ .arg(expectedString)
+ .toUtf8()
+ .constData(),
+ location.file_name(),
+ location.line());
+ return;
+ }
+
+ bool actualBool = arguments.at(1).toBool();
+ if (actualBool != expectedSearchFromNextItem) {
+ QTest::qFail(QString("Compared values are not the same\n"
+ " Actual (arguments.at(1).toBool()): %1\n"
+ " Expected (%2) : %2")
+ .arg(actualBool ? "true" : "false")
+ .arg(expectedSearchFromNextItem ? "true" : "false")
+ .toUtf8()
+ .constData(),
+ location.file_name(),
+ location.line());
+ return;
+ }
+ // Ignore the third parameter (bool* found)
+ }
};
void KItemListKeyboardSearchManagerTest::initTestCase()
@@ -44,20 +108,16 @@ void KItemListKeyboardSearchManagerTest::testBasicKeyboardSearch()
QVERIFY(spy.isValid());
m_keyboardSearchManager.addKeys("f");
- QCOMPARE(spy.count(), 1);
- QCOMPARE(spy.takeFirst(), QList<QVariant>() << "f" << true);
+ verifySignal(spy, "f", true);
m_keyboardSearchManager.addKeys("i");
- QCOMPARE(spy.count(), 1);
- QCOMPARE(spy.takeFirst(), QList<QVariant>() << "fi" << false);
+ verifySignal(spy, "fi", false);
m_keyboardSearchManager.addKeys("l");
- QCOMPARE(spy.count(), 1);
- QCOMPARE(spy.takeFirst(), QList<QVariant>() << "fil" << false);
+ verifySignal(spy, "fil", false);
m_keyboardSearchManager.addKeys("e");
- QCOMPARE(spy.count(), 1);
- QCOMPARE(spy.takeFirst(), QList<QVariant>() << "file" << false);
+ verifySignal(spy, "file", false);
}
void KItemListKeyboardSearchManagerTest::testAbortedKeyboardSearch()
@@ -70,60 +130,74 @@ void KItemListKeyboardSearchManagerTest::testAbortedKeyboardSearch()
QVERIFY(spy.isValid());
m_keyboardSearchManager.addKeys("f");
- QCOMPARE(spy.count(), 1);
- QCOMPARE(spy.takeFirst(), QList<QVariant>() << "f" << true);
+ verifySignal(spy, "f", true);
m_keyboardSearchManager.addKeys("i");
- QCOMPARE(spy.count(), 1);
- QCOMPARE(spy.takeFirst(), QList<QVariant>() << "fi" << false);
+ verifySignal(spy, "fi", false);
// If the delay between two key presses is larger than the chosen timeout,
// a new search is started. We add a small safety margin to avoid race conditions.
QTest::qWait(m_keyboardSearchManager.timeout() + 10);
m_keyboardSearchManager.addKeys("l");
- QCOMPARE(spy.count(), 1);
- QCOMPARE(spy.takeFirst(), QList<QVariant>() << "l" << true);
+ verifySignal(spy, "l", true);
m_keyboardSearchManager.addKeys("e");
- QCOMPARE(spy.count(), 1);
- QCOMPARE(spy.takeFirst(), QList<QVariant>() << "le" << false);
+ verifySignal(spy, "le", false);
// the selection was deselected, for instance with Esc or a click outside the selection
m_keyboardSearchManager.slotSelectionChanged(KItemSet(), KItemSet() << 1);
m_keyboardSearchManager.addKeys("a");
- QCOMPARE(spy.count(), 1);
- QCOMPARE(spy.takeFirst(), QList<QVariant>() << "a" << true);
+ verifySignal(spy, "a", true);
}
void KItemListKeyboardSearchManagerTest::testRepeatedKeyPress()
{
- // If the same key is pressed repeatedly, the next matching item should be highlighted after
- // each key press. To achieve, that, the manager emits the changeCurrentItem(QString,bool)
- // signal, where
- // 1. the string contains the repeated key only once, and
- // 2. the bool searchFromNextItem is true.
+ // With the new implementation, pressing the same key repeatedly will:
+ // 1. First attempt a full match with the complete string
+ // 2. If full match fails, fall back to rapid navigation with the first character
QSignalSpy spy(&m_keyboardSearchManager, &KItemListKeyboardSearchManager::changeCurrentItem);
QVERIFY(spy.isValid());
+ // First key press - new search, only one signal
m_keyboardSearchManager.addKeys("p");
QCOMPARE(spy.count(), 1);
- QCOMPARE(spy.takeFirst(), QList<QVariant>() << "p" << true);
+ verifySignal(spy, "p", true);
+ // Second key press - repeating character, two signals
m_keyboardSearchManager.addKeys("p");
- QCOMPARE(spy.count(), 1);
- QCOMPARE(spy.takeFirst(), QList<QVariant>() << "p" << true);
+ QCOMPARE(spy.count(), 2);
+ // First signal: full string match attempt
+ QList<QVariant> arguments = spy.takeFirst();
+ QCOMPARE(arguments.size(), 3);
+ QCOMPARE(arguments.at(0).toString(), QString("pp"));
+ QCOMPARE(arguments.at(1).toBool(), false);
+ // Second signal: rapid navigation fallback
+ arguments = spy.takeFirst();
+ QCOMPARE(arguments.size(), 3);
+ QCOMPARE(arguments.at(0).toString(), QString("p"));
+ QCOMPARE(arguments.at(1).toBool(), true);
+ // Third key press - repeating character, two signals
m_keyboardSearchManager.addKeys("p");
- QCOMPARE(spy.count(), 1);
- QCOMPARE(spy.takeFirst(), QList<QVariant>() << "p" << true);
+ QCOMPARE(spy.count(), 2);
+ // First signal: full string match attempt
+ arguments = spy.takeFirst();
+ QCOMPARE(arguments.size(), 3);
+ QCOMPARE(arguments.at(0).toString(), QString("ppp"));
+ QCOMPARE(arguments.at(1).toBool(), false);
+ // Second signal: rapid navigation fallback
+ arguments = spy.takeFirst();
+ QCOMPARE(arguments.size(), 3);
+ QCOMPARE(arguments.at(0).toString(), QString("p"));
+ QCOMPARE(arguments.at(1).toBool(), true);
- // Now press another key -> the search string contains all pressed keys
+ // Now press another key -> only one signal for non-repeating character
m_keyboardSearchManager.addKeys("q");
QCOMPARE(spy.count(), 1);
- QCOMPARE(spy.takeFirst(), QList<QVariant>() << "pppq" << false);
+ verifySignal(spy, "pppq", false);
}
void KItemListKeyboardSearchManagerTest::testPressShift()
@@ -138,19 +212,16 @@ void KItemListKeyboardSearchManagerTest::testPressShift()
// Simulate that the user enters "a_b".
m_keyboardSearchManager.addKeys("a");
- QCOMPARE(spy.count(), 1);
- QCOMPARE(spy.takeFirst(), QList<QVariant>() << "a" << true);
+ verifySignal(spy, "a", true);
m_keyboardSearchManager.addKeys("");
QCOMPARE(spy.count(), 0);
m_keyboardSearchManager.addKeys("_");
- QCOMPARE(spy.count(), 1);
- QCOMPARE(spy.takeFirst(), QList<QVariant>() << "a_" << false);
+ verifySignal(spy, "a_", false);
m_keyboardSearchManager.addKeys("b");
- QCOMPARE(spy.count(), 1);
- QCOMPARE(spy.takeFirst(), QList<QVariant>() << "a_b" << false);
+ verifySignal(spy, "a_b", false);
}
QTEST_GUILESS_MAIN(KItemListKeyboardSearchManagerTest)