QCompleter::setFilterMode() add property filterMode.

QCompleter::setFilterMode(Qt::MatchContains) will enable filtering
out entries that contain typed characters in any place, instead of the
default behavior when only those entries that start with typed characters
are displayed. Qt::MatchEndsWith is also possible.
QCompleter::setFilterMode(Qt::MatchStartsWith) will bring the default
behavior back.
Task-number: QTBUG-3414

Change-Id: I3845704c59eb8fc401e9a650c54a9c934ed28c2e
Reviewed-by: Giuseppe D'Angelo <giuseppe.dangelo@kdab.com>
This commit is contained in:
Oto Magaldadze 2012-12-31 14:11:25 +04:00 committed by The Qt Project
parent ef5455429f
commit 6b95130faa
4 changed files with 223 additions and 16 deletions

View File

@ -82,7 +82,8 @@
QCompleter::CaseSensitivelySortedModel or
QCompleter::CaseInsensitivelySortedModel as the argument. On large models,
this can lead to significant performance improvements, because QCompleter
can then use binary search instead of linear search.
can then use binary search instead of linear search. The binary search only
works when the filterMode is Qt::MatchStartsWith.
The model can be a \l{QAbstractListModel}{list model},
a \l{QAbstractTableModel}{table model}, or a
@ -199,16 +200,18 @@ void QCompletionModel::setSourceModel(QAbstractItemModel *source)
void QCompletionModel::createEngine()
{
bool sortedEngine = false;
switch (c->sorting) {
case QCompleter::UnsortedModel:
sortedEngine = false;
break;
case QCompleter::CaseSensitivelySortedModel:
sortedEngine = c->cs == Qt::CaseSensitive;
break;
case QCompleter::CaseInsensitivelySortedModel:
sortedEngine = c->cs == Qt::CaseInsensitive;
break;
if (c->filterMode == Qt::MatchStartsWith) {
switch (c->sorting) {
case QCompleter::UnsortedModel:
sortedEngine = false;
break;
case QCompleter::CaseSensitivelySortedModel:
sortedEngine = c->cs == Qt::CaseSensitive;
break;
case QCompleter::CaseInsensitivelySortedModel:
sortedEngine = c->cs == Qt::CaseInsensitive;
break;
}
}
if (sortedEngine)
@ -522,6 +525,8 @@ bool QCompletionEngine::lookupCache(QString part, const QModelIndex& parent, QMa
// When the cache size exceeds 1MB, it clears out about 1/2 of the cache.
void QCompletionEngine::saveInCache(QString part, const QModelIndex& parent, const QMatchData& m)
{
if (c->filterMode == Qt::MatchEndsWith)
return;
QMatchData old = cache[parent].take(part);
cost = cost + m.indices.cost() - old.indices.cost();
if (cost * sizeof(int) > 1024 * 1024) {
@ -703,9 +708,35 @@ int QUnsortedModelEngine::buildIndices(const QString& str, const QModelIndex& pa
for (i = 0; i < indices.count() && count != n; ++i) {
QModelIndex idx = model->index(indices[i], c->column, parent);
QString data = model->data(idx, c->role).toString();
if (!data.startsWith(str, c->cs) || !(model->flags(idx) & Qt::ItemIsSelectable))
if (!(model->flags(idx) & Qt::ItemIsSelectable))
continue;
QString data = model->data(idx, c->role).toString();
switch (c->filterMode) {
case Qt::MatchStartsWith:
if (!data.startsWith(str, c->cs))
continue;
break;
case Qt::MatchContains:
if (!data.contains(str, c->cs))
continue;
break;
case Qt::MatchEndsWith:
if (!data.endsWith(str, c->cs))
continue;
break;
case Qt::MatchExactly:
case Qt::MatchFixedString:
case Qt::MatchCaseSensitive:
case Qt::MatchRegExp:
case Qt::MatchWildcard:
case Qt::MatchWrap:
case Qt::MatchRecursive:
Q_UNREACHABLE();
break;
}
m->indices.append(indices[i]);
++count;
if (m->exactMatchIndex == -1 && QString::compare(data, str, c->cs) == 0) {
@ -773,9 +804,9 @@ QMatchData QUnsortedModelEngine::filter(const QString& part, const QModelIndex&
///////////////////////////////////////////////////////////////////////////////
QCompleterPrivate::QCompleterPrivate()
: widget(0), proxy(0), popup(0), cs(Qt::CaseSensitive), role(Qt::EditRole), column(0),
maxVisibleItems(7), sorting(QCompleter::UnsortedModel), wrap(true), eatFocusOut(true),
hiddenBecauseNoMatch(false)
: widget(0), proxy(0), popup(0), filterMode(Qt::MatchStartsWith), cs(Qt::CaseSensitive),
role(Qt::EditRole), column(0), maxVisibleItems(7), sorting(QCompleter::UnsortedModel),
wrap(true), eatFocusOut(true), hiddenBecauseNoMatch(false)
{
}
@ -1097,6 +1128,48 @@ QCompleter::CompletionMode QCompleter::completionMode() const
return d->mode;
}
/*!
\property QCompleter::filterMode
\brief how the filtering is performed
\since 5.2
If filterMode is set to Qt::MatchStartsWith, only those entries that start
with the typed characters will be displayed. Qt::MatchContains will display
the entries that contain the typed characters, and Qt::MatchEndsWith the
ones that end with the typed characters.
Currently, only these three modes are implemented. Setting filterMode to
any other Qt::MatchFlag will issue a warning, and no action will be
performed.
The default mode is Qt::MatchStartsWith.
*/
void QCompleter::setFilterMode(Qt::MatchFlags filterMode)
{
Q_D(QCompleter);
if (d->filterMode == filterMode)
return;
if (filterMode != Qt::MatchStartsWith
&& filterMode != Qt::MatchContains
&& filterMode != Qt::MatchEndsWith) {
qWarning("Unhandled QCompleter::filterMode flag is used.");
return;
}
d->filterMode = filterMode;
d->proxy->createEngine();
d->proxy->invalidate();
}
Qt::MatchFlags QCompleter::filterMode() const
{
Q_D(const QCompleter);
return d->filterMode;
}
/*!
Sets the popup used to display completions to \a popup. QCompleter takes
ownership of the view.

View File

@ -63,6 +63,7 @@ class Q_WIDGETS_EXPORT QCompleter : public QObject
Q_OBJECT
Q_PROPERTY(QString completionPrefix READ completionPrefix WRITE setCompletionPrefix)
Q_PROPERTY(ModelSorting modelSorting READ modelSorting WRITE setModelSorting)
Q_PROPERTY(Qt::MatchFlags filterMode READ filterMode WRITE setFilterMode)
Q_PROPERTY(CompletionMode completionMode READ completionMode WRITE setCompletionMode)
Q_PROPERTY(int completionColumn READ completionColumn WRITE setCompletionColumn)
Q_PROPERTY(int completionRole READ completionRole WRITE setCompletionRole)
@ -99,6 +100,9 @@ public:
void setCompletionMode(CompletionMode mode);
CompletionMode completionMode() const;
void setFilterMode(Qt::MatchFlags filterMode);
Qt::MatchFlags filterMode() const;
QAbstractItemView *popup() const;
void setPopup(QAbstractItemView *popup);

View File

@ -82,6 +82,7 @@ public:
QCompletionModel *proxy;
QAbstractItemView *popup;
QCompleter::CompletionMode mode;
Qt::MatchFlags filterMode;
QString prefix;
Qt::CaseSensitivity cs;

View File

@ -1418,6 +1418,135 @@ void tst_QCompleter::task253125_lineEditCompletion()
QCOMPARE(edit.text(), QString("iota"));
edit.clear();
completer->setCompletionMode(QCompleter::PopupCompletion);
completer->setFilterMode(Qt::MatchContains);
QTest::keyClick(&edit, 't');
QCOMPARE(edit.completer()->currentCompletion(), QString("beta"));
QTest::keyClick(edit.completer()->popup(), Qt::Key_Down);
QTest::keyClick(edit.completer()->popup(), Qt::Key_Enter);
QCOMPARE(edit.text(), QString("beta"));
edit.clear();
QTest::keyClick(&edit, 'p');
QTest::keyClick(&edit, 'p');
QCOMPARE(edit.completer()->currentCompletion(), QString("kappa"));
QTest::keyClick(edit.completer()->popup(), Qt::Key_Down);
QTest::keyClick(edit.completer()->popup(), Qt::Key_Enter);
QCOMPARE(edit.text(), QString("kappa"));
edit.clear();
completer->setFilterMode(Qt::MatchStartsWith);
QTest::keyClick(&edit, 't');
QCOMPARE(edit.completer()->currentCompletion(), QString("theta"));
QTest::keyClick(edit.completer()->popup(), Qt::Key_Down);
QTest::keyClick(edit.completer()->popup(), Qt::Key_Enter);
QCOMPARE(edit.text(), QString("theta"));
edit.clear();
QTest::keyClick(&edit, 'p');
QTest::keyClick(&edit, 'p');
QCOMPARE(edit.completer()->currentCompletion(), QString());
QTest::keyClick(edit.completer()->popup(), Qt::Key_Down);
QTest::keyClick(edit.completer()->popup(), Qt::Key_Enter);
QCOMPARE(edit.text(), QString("pp"));
edit.clear();
QTest::keyClick(&edit, 'u');
QTest::keyClick(&edit, 'p');
QTest::keyClick(&edit, 's');
QCOMPARE(edit.completer()->currentCompletion(), QString("upsilon"));
QTest::keyClick(edit.completer()->popup(), Qt::Key_Down);
QTest::keyClick(edit.completer()->popup(), Qt::Key_Enter);
QCOMPARE(edit.text(), QString("upsilon"));
edit.clear();
completer->setFilterMode(Qt::MatchEndsWith);
QTest::keyClick(&edit, 'm');
QTest::keyClick(&edit, 'm');
QTest::keyClick(&edit, 'a');
QCOMPARE(edit.completer()->currentCompletion(), QString("gamma"));
QTest::keyClick(edit.completer()->popup(), Qt::Key_Down);
QTest::keyClick(edit.completer()->popup(), Qt::Key_Enter);
QCOMPARE(edit.text(), QString("gamma"));
edit.clear();
QTest::keyClick(&edit, 'g');
QTest::keyClick(&edit, 'm');
QTest::keyClick(&edit, 'a');
QCOMPARE(edit.completer()->currentCompletion(), QString("sigma"));
QTest::keyClick(edit.completer()->popup(), Qt::Key_Down);
QTest::keyClick(edit.completer()->popup(), Qt::Key_Enter);
QCOMPARE(edit.text(), QString("sigma"));
edit.clear();
QTest::keyClick(&edit, 'm');
QTest::keyClick(&edit, 'm');
QCOMPARE(edit.completer()->currentCompletion(), QString());
QTest::keyClick(edit.completer()->popup(), Qt::Key_Down);
QTest::keyClick(edit.completer()->popup(), Qt::Key_Enter);
QCOMPARE(edit.text(), QString("mm"));
edit.clear();
completer->setFilterMode(Qt::MatchStartsWith);
QTest::keyClick(&edit, 'z');
QTest::keyClick(&edit, 'e');
QCOMPARE(edit.completer()->currentCompletion(), QString("zeta"));
QTest::keyClick(edit.completer()->popup(), Qt::Key_Down);
QTest::keyClick(edit.completer()->popup(), Qt::Key_Enter);
QCOMPARE(edit.text(), QString("zeta"));
edit.clear();
completer->setFilterMode(Qt::MatchEndsWith);
QTest::keyClick(&edit, 'e');
QTest::keyClick(&edit, 'g');
QTest::keyClick(&edit, 'a');
QCOMPARE(edit.completer()->currentCompletion(), QString("omega"));
QTest::keyClick(edit.completer()->popup(), Qt::Key_Down);
QTest::keyClick(edit.completer()->popup(), Qt::Key_Enter);
QCOMPARE(edit.text(), QString("omega"));
edit.clear();
completer->setFilterMode(Qt::MatchContains);
QTest::keyClick(&edit, 'c');
QTest::keyClick(&edit, 'r');
QCOMPARE(edit.completer()->currentCompletion(), QString("omicron"));
QTest::keyClick(edit.completer()->popup(), Qt::Key_Down);
QTest::keyClick(edit.completer()->popup(), Qt::Key_Enter);
QCOMPARE(edit.text(), QString("omicron"));
edit.clear();
QTest::keyClick(&edit, 'z');
QTest::keyClick(&edit, 'z');
QCOMPARE(edit.completer()->currentCompletion(), QString());
QTest::keyClick(edit.completer()->popup(), Qt::Key_Down);
QTest::keyClick(edit.completer()->popup(), Qt::Key_Enter);
QCOMPARE(edit.text(), QString("zz"));
delete completer;
delete model;
}