Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qcompleter.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5/*!
6 \class QCompleter
7 \brief The QCompleter class provides completions based on an item model.
8 \since 4.2
9
10 \inmodule QtWidgets
11
12 You can use QCompleter to provide auto completions in any Qt
13 widget, such as QLineEdit and QComboBox.
14 When the user starts typing a word, QCompleter suggests possible ways of
15 completing the word, based on a word list. The word list is
16 provided as a QAbstractItemModel. (For simple applications, where
17 the word list is static, you can pass a QStringList to
18 QCompleter's constructor.)
19
20 \section1 Basic Usage
21
22 A QCompleter is used typically with a QLineEdit or QComboBox.
23 For example, here's how to provide auto completions from a simple
24 word list in a QLineEdit:
25
26 \snippet code/src_gui_util_qcompleter.cpp 0
27
28 A QFileSystemModel can be used to provide auto completion of file names.
29 For example:
30
31 \snippet code/src_gui_util_qcompleter.cpp 1
32
33 To set the model on which QCompleter should operate, call
34 setModel(). By default, QCompleter will attempt to match the \l
35 {completionPrefix}{completion prefix} (i.e., the word that the
36 user has started typing) against the Qt::EditRole data stored in
37 column 0 in the model case sensitively. This can be changed
38 using setCompletionRole(), setCompletionColumn(), and
39 setCaseSensitivity().
40
41 If the model is sorted on the column and role that are used for completion,
42 you can call setModelSorting() with either
43 QCompleter::CaseSensitivelySortedModel or
44 QCompleter::CaseInsensitivelySortedModel as the argument. On large models,
45 this can lead to significant performance improvements, because QCompleter
46 can then use binary search instead of linear search. The binary search only
47 works when the filterMode is Qt::MatchStartsWith.
48
49 The model can be a \l{QAbstractListModel}{list model},
50 a \l{QAbstractTableModel}{table model}, or a
51 \l{QAbstractItemModel}{tree model}. Completion on tree models
52 is slightly more involved and is covered in the \l{Handling
53 Tree Models} section below.
54
55 The completionMode() determines the mode used to provide completions to
56 the user.
57
58 \section1 Iterating Through Completions
59
60 To retrieve a single candidate string, call setCompletionPrefix()
61 with the text that needs to be completed and call
62 currentCompletion(). You can iterate through the list of
63 completions as below:
64
65 \snippet code/src_gui_util_qcompleter.cpp 2
66
67 completionCount() returns the total number of completions for the
68 current prefix. completionCount() should be avoided when possible,
69 since it requires a scan of the entire model.
70
71 \section1 The Completion Model
72
73 completionModel() return a list model that contains all possible
74 completions for the current completion prefix, in the order in which
75 they appear in the model. This model can be used to display the current
76 completions in a custom view. Calling setCompletionPrefix() automatically
77 refreshes the completion model.
78
79 \section1 Handling Tree Models
80
81 QCompleter can look for completions in tree models, assuming
82 that any item (or sub-item or sub-sub-item) can be unambiguously
83 represented as a string by specifying the path to the item. The
84 completion is then performed one level at a time.
85
86 Let's take the example of a user typing in a file system path.
87 The model is a (hierarchical) QFileSystemModel. The completion
88 occurs for every element in the path. For example, if the current
89 text is \c C:\Wind, QCompleter might suggest \c Windows to
90 complete the current path element. Similarly, if the current text
91 is \c C:\Windows\Sy, QCompleter might suggest \c System.
92
93 For this kind of completion to work, QCompleter needs to be able to
94 split the path into a list of strings that are matched at each level.
95 For \c C:\Windows\Sy, it needs to be split as "C:", "Windows" and "Sy".
96 The default implementation of splitPath(), splits the completionPrefix
97 using QDir::separator() if the model is a QFileSystemModel.
98
99 To provide completions, QCompleter needs to know the path from an index.
100 This is provided by pathFromIndex(). The default implementation of
101 pathFromIndex(), returns the data for the \l{Qt::EditRole}{edit role}
102 for list models and the absolute file path if the mode is a QFileSystemModel.
103
104 \sa QAbstractItemModel, QLineEdit, QComboBox, {Completer Example}
105*/
106
107#include "qcompleter_p.h"
108
109#include "QtWidgets/qscrollbar.h"
110#include "QtCore/qdir.h"
111#if QT_CONFIG(stringlistmodel)
112#include "QtCore/qstringlistmodel.h"
113#endif
114#if QT_CONFIG(filesystemmodel)
115#include "QtGui/qfilesystemmodel.h"
116#endif
117#include "QtWidgets/qheaderview.h"
118#if QT_CONFIG(listview)
119#include "QtWidgets/qlistview.h"
120#endif
121#include "QtWidgets/qapplication.h"
122#include "QtGui/qevent.h"
123#include <private/qapplication_p.h>
124#include <private/qwidget_p.h>
125#if QT_CONFIG(lineedit)
126#include "QtWidgets/qlineedit.h"
127#endif
128#include "QtCore/qdir.h"
129
131
132using namespace Qt::StringLiterals;
133
134QCompletionModel::QCompletionModel(QCompleterPrivate *c, QObject *parent)
135 : QAbstractProxyModel(*new QCompletionModelPrivate, parent),
136 c(c), showAll(false)
137{
139}
140
141int QCompletionModel::columnCount(const QModelIndex &) const
142{
143 Q_D(const QCompletionModel);
144 return d->model->columnCount();
145}
146
147void QCompletionModel::setSourceModel(QAbstractItemModel *source)
148{
149 bool hadModel = (sourceModel() != nullptr);
150
151 if (hadModel)
152 QObject::disconnect(sourceModel(), nullptr, this, nullptr);
153
154 QAbstractProxyModel::setSourceModel(source);
155
156 if (source) {
157 // TODO: Optimize updates in the source model
158 connect(source, SIGNAL(modelReset()), this, SLOT(invalidate()));
159 connect(source, SIGNAL(destroyed()), this, SLOT(modelDestroyed()));
160 connect(source, SIGNAL(layoutChanged()), this, SLOT(invalidate()));
161 connect(source, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(rowsInserted()));
162 connect(source, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(invalidate()));
163 connect(source, SIGNAL(columnsInserted(QModelIndex,int,int)), this, SLOT(invalidate()));
164 connect(source, SIGNAL(columnsRemoved(QModelIndex,int,int)), this, SLOT(invalidate()));
165 connect(source, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(invalidate()));
166 }
167
168 invalidate();
169}
170
172{
173 bool sortedEngine = false;
174 if (c->filterMode == Qt::MatchStartsWith) {
175 switch (c->sorting) {
176 case QCompleter::UnsortedModel:
177 sortedEngine = false;
178 break;
179 case QCompleter::CaseSensitivelySortedModel:
180 sortedEngine = c->cs == Qt::CaseSensitive;
181 break;
182 case QCompleter::CaseInsensitivelySortedModel:
183 sortedEngine = c->cs == Qt::CaseInsensitive;
184 break;
185 }
186 }
187
188 if (sortedEngine)
189 engine.reset(new QSortedModelEngine(c));
190 else
191 engine.reset(new QUnsortedModelEngine(c));
192}
193
194QModelIndex QCompletionModel::mapToSource(const QModelIndex& index) const
195{
196 Q_D(const QCompletionModel);
197 if (!index.isValid())
198 return engine->curParent;
199
200 int row;
201 QModelIndex parent = engine->curParent;
202 if (!showAll) {
203 if (!engine->matchCount())
204 return QModelIndex();
205 Q_ASSERT(index.row() < engine->matchCount());
206 QIndexMapper& rootIndices = engine->historyMatch.indices;
207 if (index.row() < rootIndices.count()) {
208 row = rootIndices[index.row()];
209 parent = QModelIndex();
210 } else {
211 row = engine->curMatch.indices[index.row() - rootIndices.count()];
212 }
213 } else {
214 row = index.row();
215 }
216
217 return d->model->index(row, index.column(), parent);
218}
219
220QModelIndex QCompletionModel::mapFromSource(const QModelIndex& idx) const
221{
222 if (!idx.isValid())
223 return QModelIndex();
224
225 int row = -1;
226 if (!showAll) {
227 if (!engine->matchCount())
228 return QModelIndex();
229
230 QIndexMapper& rootIndices = engine->historyMatch.indices;
231 if (idx.parent().isValid()) {
232 if (idx.parent() != engine->curParent)
233 return QModelIndex();
234 } else {
235 row = rootIndices.indexOf(idx.row());
236 if (row == -1 && engine->curParent.isValid())
237 return QModelIndex(); // source parent and our parent don't match
238 }
239
240 if (row == -1) {
241 QIndexMapper& indices = engine->curMatch.indices;
242 engine->filterOnDemand(idx.row() - indices.last());
243 row = indices.indexOf(idx.row()) + rootIndices.count();
244 }
245
246 if (row == -1)
247 return QModelIndex();
248 } else {
249 if (idx.parent() != engine->curParent)
250 return QModelIndex();
251 row = idx.row();
252 }
253
254 return createIndex(row, idx.column());
255}
256
258{
259 if (row < 0 || !engine->matchCount())
260 return false;
261
262 if (row >= engine->matchCount())
263 engine->filterOnDemand(row + 1 - engine->matchCount());
264
265 if (row >= engine->matchCount()) // invalid row
266 return false;
267
268 engine->curRow = row;
269 return true;
270}
271
273{
274 if (!engine->matchCount())
275 return QModelIndex();
276
277 int row = engine->curRow;
278 if (showAll)
279 row = engine->curMatch.indices[engine->curRow];
280
281 QModelIndex idx = createIndex(row, c->column);
282 if (!sourceIndex)
283 return idx;
284 return mapToSource(idx);
285}
286
287QModelIndex QCompletionModel::index(int row, int column, const QModelIndex& parent) const
288{
289 Q_D(const QCompletionModel);
290 if (row < 0 || column < 0 || column >= columnCount(parent) || parent.isValid())
291 return QModelIndex();
292
293 if (!showAll) {
294 if (!engine->matchCount())
295 return QModelIndex();
296 if (row >= engine->historyMatch.indices.count()) {
297 int want = row + 1 - engine->matchCount();
298 if (want > 0)
299 engine->filterOnDemand(want);
300 if (row >= engine->matchCount())
301 return QModelIndex();
302 }
303 } else {
304 if (row >= d->model->rowCount(engine->curParent))
305 return QModelIndex();
306 }
307
308 return createIndex(row, column);
309}
310
312{
313 if (!engine->matchCount())
314 return 0;
315
316 engine->filterOnDemand(INT_MAX);
317 return engine->matchCount();
318}
319
320int QCompletionModel::rowCount(const QModelIndex &parent) const
321{
322 Q_D(const QCompletionModel);
323 if (parent.isValid())
324 return 0;
325
326 if (showAll) {
327 // Show all items below current parent, even if we have no valid matches
328 if (engine->curParts.size() != 1 && !engine->matchCount()
329 && !engine->curParent.isValid())
330 return 0;
331 return d->model->rowCount(engine->curParent);
332 }
333
334 return completionCount();
335}
336
337void QCompletionModel::setFiltered(bool filtered)
338{
339 if (showAll == !filtered)
340 return;
341 beginResetModel();
342 showAll = !filtered;
343 endResetModel();
344}
345
346bool QCompletionModel::hasChildren(const QModelIndex &parent) const
347{
348 Q_D(const QCompletionModel);
349 if (parent.isValid())
350 return false;
351
352 if (showAll)
353 return d->model->hasChildren(mapToSource(parent));
354
355 if (!engine->matchCount())
356 return false;
357
358 return true;
359}
360
361QVariant QCompletionModel::data(const QModelIndex& index, int role) const
362{
363 Q_D(const QCompletionModel);
364 return d->model->data(mapToSource(index), role);
365}
366
368{
369 QAbstractProxyModel::setSourceModel(nullptr); // switch to static empty model
370 invalidate();
371}
372
374{
375 invalidate();
376 emit rowsAdded();
377}
378
379void QCompletionModel::invalidate()
380{
381 engine->cache.clear();
382 filter(engine->curParts);
383}
384
385void QCompletionModel::filter(const QStringList& parts)
386{
387 Q_D(QCompletionModel);
388 beginResetModel();
389 engine->filter(parts);
390 endResetModel();
391
392 if (d->model->canFetchMore(engine->curParent))
393 d->model->fetchMore(engine->curParent);
394}
395
396//////////////////////////////////////////////////////////////////////////////
397void QCompletionEngine::filter(const QStringList& parts)
398{
399 const QAbstractItemModel *model = c->proxy->sourceModel();
400 curParts = parts;
401 if (curParts.isEmpty())
402 curParts.append(QString());
403
404 curRow = -1;
405 curParent = QModelIndex();
406 curMatch = QMatchData();
407 historyMatch = filterHistory();
408
409 if (!model)
410 return;
411
412 QModelIndex parent;
413 for (int i = 0; i < curParts.size() - 1; i++) {
414 QString part = curParts.at(i);
415 int emi = filter(part, parent, -1).exactMatchIndex;
416 if (emi == -1)
417 return;
418 parent = model->index(emi, c->column, parent);
419 }
420
421 // Note that we set the curParent to a valid parent, even if we have no matches
422 // When filtering is disabled, we show all the items under this parent
423 curParent = parent;
424 if (curParts.constLast().isEmpty())
425 curMatch = QMatchData(QIndexMapper(0, model->rowCount(curParent) - 1), -1, false);
426 else
427 curMatch = filter(curParts.constLast(), curParent, 1); // build at least one
428 curRow = curMatch.isValid() ? 0 : -1;
429}
430
432{
433 QAbstractItemModel *source = c->proxy->sourceModel();
434 if (curParts.size() <= 1 || c->proxy->showAll || !source)
435 return QMatchData();
436
437#if QT_CONFIG(filesystemmodel)
438 const bool isFsModel = (qobject_cast<QFileSystemModel *>(source) != nullptr);
439#else
440 const bool isFsModel = false;
441#endif
442 Q_UNUSED(isFsModel);
443 QList<int> v;
444 QIndexMapper im(v);
445 QMatchData m(im, -1, true);
446
447 for (int i = 0; i < source->rowCount(); i++) {
448 QString str = source->index(i, c->column).data().toString();
449 if (str.startsWith(c->prefix, c->cs)
450#if !defined(Q_OS_WIN)
451 && (!isFsModel || QDir::toNativeSeparators(str) != QDir::separator())
452#endif
453 )
454 m.indices.append(i);
455 }
456 return m;
457}
458
459// Returns a match hint from the cache by chopping the search string
460bool QCompletionEngine::matchHint(const QString &part, const QModelIndex &parent, QMatchData *hint) const
461{
462 if (part.isEmpty())
463 return false; // early out to avoid cache[parent] lookup costs
464
465 const auto cit = cache.find(parent);
466 if (cit == cache.end())
467 return false;
468
469 const CacheItem& map = *cit;
470 const auto mapEnd = map.end();
471
472 QString key = c->cs == Qt::CaseInsensitive ? part.toLower() : part;
473
474 while (!key.isEmpty()) {
475 key.chop(1);
476 const auto it = map.find(key);
477 if (it != mapEnd) {
478 *hint = *it;
479 return true;
480 }
481 }
482
483 return false;
484}
485
486bool QCompletionEngine::lookupCache(const QString &part, const QModelIndex &parent, QMatchData *m) const
487{
488 if (part.isEmpty())
489 return false; // early out to avoid cache[parent] lookup costs
490
491 const auto cit = cache.find(parent);
492 if (cit == cache.end())
493 return false;
494
495 const CacheItem& map = *cit;
496
497 const QString key = c->cs == Qt::CaseInsensitive ? part.toLower() : part;
498
499 const auto it = map.find(key);
500 if (it == map.end())
501 return false;
502
503 *m = it.value();
504 return true;
505}
506
507// When the cache size exceeds 1MB, it clears out about 1/2 of the cache.
508void QCompletionEngine::saveInCache(QString part, const QModelIndex& parent, const QMatchData& m)
509{
510 if (c->filterMode == Qt::MatchEndsWith)
511 return;
512 QMatchData old = cache[parent].take(part);
513 cost = cost + m.indices.cost() - old.indices.cost();
514 if (cost * sizeof(int) > 1024 * 1024) {
515 QMap<QModelIndex, CacheItem>::iterator it1 = cache.begin();
516 while (it1 != cache.end()) {
517 CacheItem& ci = it1.value();
518 int sz = ci.size()/2;
519 QMap<QString, QMatchData>::iterator it2 = ci.begin();
520 int i = 0;
521 while (it2 != ci.end() && i < sz) {
522 cost -= it2.value().indices.cost();
523 it2 = ci.erase(it2);
524 i++;
525 }
526 if (ci.size() == 0) {
527 it1 = cache.erase(it1);
528 } else {
529 ++it1;
530 }
531 }
532 }
533
534 if (c->cs == Qt::CaseInsensitive)
535 part = std::move(part).toLower();
536 cache[parent][part] = m;
537}
538
539///////////////////////////////////////////////////////////////////////////////////
540QIndexMapper QSortedModelEngine::indexHint(QString part, const QModelIndex& parent, Qt::SortOrder order)
541{
542 const QAbstractItemModel *model = c->proxy->sourceModel();
543
544 if (c->cs == Qt::CaseInsensitive)
545 part = std::move(part).toLower();
546
547 const CacheItem& map = cache[parent];
548
549 // Try to find a lower and upper bound for the search from previous results
550 int to = model->rowCount(parent) - 1;
551 int from = 0;
552 const CacheItem::const_iterator it = map.lowerBound(part);
553
554 // look backward for first valid hint
555 for (CacheItem::const_iterator it1 = it; it1 != map.constBegin();) {
556 --it1;
557 const QMatchData& value = it1.value();
558 if (value.isValid()) {
559 if (order == Qt::AscendingOrder) {
560 from = value.indices.last() + 1;
561 } else {
562 to = value.indices.first() - 1;
563 }
564 break;
565 }
566 }
567
568 // look forward for first valid hint
569 for(CacheItem::const_iterator it2 = it; it2 != map.constEnd(); ++it2) {
570 const QMatchData& value = it2.value();
571 if (value.isValid() && !it2.key().startsWith(part)) {
572 if (order == Qt::AscendingOrder) {
573 to = value.indices.first() - 1;
574 } else {
575 from = value.indices.first() + 1;
576 }
577 break;
578 }
579 }
580
581 return QIndexMapper(from, to);
582}
583
584Qt::SortOrder QSortedModelEngine::sortOrder(const QModelIndex &parent) const
585{
586 const QAbstractItemModel *model = c->proxy->sourceModel();
587
588 int rowCount = model->rowCount(parent);
589 if (rowCount < 2)
590 return Qt::AscendingOrder;
591 QString first = model->data(model->index(0, c->column, parent), c->role).toString();
592 QString last = model->data(model->index(rowCount - 1, c->column, parent), c->role).toString();
593 return QString::compare(first, last, c->cs) <= 0 ? Qt::AscendingOrder : Qt::DescendingOrder;
594}
595
596QMatchData QSortedModelEngine::filter(const QString& part, const QModelIndex& parent, int)
597{
598 const QAbstractItemModel *model = c->proxy->sourceModel();
599
600 QMatchData hint;
601 if (lookupCache(part, parent, &hint))
602 return hint;
603
604 QIndexMapper indices;
605 Qt::SortOrder order = sortOrder(parent);
606
607 if (matchHint(part, parent, &hint)) {
608 if (!hint.isValid())
609 return QMatchData();
610 indices = hint.indices;
611 } else {
612 indices = indexHint(part, parent, order);
613 }
614
615 // binary search the model within 'indices' for 'part' under 'parent'
616 int high = indices.to() + 1;
617 int low = indices.from() - 1;
618 int probe;
619 QModelIndex probeIndex;
620 QString probeData;
621
622 while (high - low > 1)
623 {
624 probe = (high + low) / 2;
625 probeIndex = model->index(probe, c->column, parent);
626 probeData = model->data(probeIndex, c->role).toString();
627 const int cmp = QString::compare(probeData, part, c->cs);
628 if ((order == Qt::AscendingOrder && cmp >= 0)
629 || (order == Qt::DescendingOrder && cmp < 0)) {
630 high = probe;
631 } else {
632 low = probe;
633 }
634 }
635
636 if ((order == Qt::AscendingOrder && low == indices.to())
637 || (order == Qt::DescendingOrder && high == indices.from())) { // not found
638 saveInCache(part, parent, QMatchData());
639 return QMatchData();
640 }
641
642 probeIndex = model->index(order == Qt::AscendingOrder ? low+1 : high-1, c->column, parent);
643 probeData = model->data(probeIndex, c->role).toString();
644 if (!probeData.startsWith(part, c->cs)) {
645 saveInCache(part, parent, QMatchData());
646 return QMatchData();
647 }
648
649 const bool exactMatch = QString::compare(probeData, part, c->cs) == 0;
650 int emi = exactMatch ? (order == Qt::AscendingOrder ? low+1 : high-1) : -1;
651
652 int from = 0;
653 int to = 0;
654 if (order == Qt::AscendingOrder) {
655 from = low + 1;
656 high = indices.to() + 1;
657 low = from;
658 } else {
659 to = high - 1;
660 low = indices.from() - 1;
661 high = to;
662 }
663
664 while (high - low > 1)
665 {
666 probe = (high + low) / 2;
667 probeIndex = model->index(probe, c->column, parent);
668 probeData = model->data(probeIndex, c->role).toString();
669 const bool startsWith = probeData.startsWith(part, c->cs);
670 if ((order == Qt::AscendingOrder && startsWith)
671 || (order == Qt::DescendingOrder && !startsWith)) {
672 low = probe;
673 } else {
674 high = probe;
675 }
676 }
677
678 QMatchData m(order == Qt::AscendingOrder ? QIndexMapper(from, high - 1) : QIndexMapper(low+1, to), emi, false);
679 saveInCache(part, parent, m);
680 return m;
681}
682
683////////////////////////////////////////////////////////////////////////////////////////
684int QUnsortedModelEngine::buildIndices(const QString& str, const QModelIndex& parent, int n,
685 const QIndexMapper& indices, QMatchData* m)
686{
687 Q_ASSERT(m->partial);
688 Q_ASSERT(n != -1 || m->exactMatchIndex == -1);
689 const QAbstractItemModel *model = c->proxy->sourceModel();
690 int i, count = 0;
691
692 for (i = 0; i < indices.count() && count != n; ++i) {
693 QModelIndex idx = model->index(indices[i], c->column, parent);
694
695 if (!(model->flags(idx) & Qt::ItemIsSelectable))
696 continue;
697
698 QString data = model->data(idx, c->role).toString();
699
700 switch (c->filterMode) {
701 case Qt::MatchStartsWith:
702 if (!data.startsWith(str, c->cs))
703 continue;
704 break;
705 case Qt::MatchContains:
706 if (!data.contains(str, c->cs))
707 continue;
708 break;
709 case Qt::MatchEndsWith:
710 if (!data.endsWith(str, c->cs))
711 continue;
712 break;
713 case Qt::MatchExactly:
714 case Qt::MatchFixedString:
715 case Qt::MatchCaseSensitive:
716 case Qt::MatchRegularExpression:
717 case Qt::MatchWildcard:
718 case Qt::MatchWrap:
719 case Qt::MatchRecursive:
720 Q_UNREACHABLE();
721 break;
722 }
723 m->indices.append(indices[i]);
724 ++count;
725 if (m->exactMatchIndex == -1 && QString::compare(data, str, c->cs) == 0) {
726 m->exactMatchIndex = indices[i];
727 if (n == -1)
728 return indices[i];
729 }
730 }
731 return indices[i-1];
732}
733
735{
736 Q_ASSERT(matchCount());
737 if (!curMatch.partial)
738 return;
739 Q_ASSERT(n >= -1);
740 const QAbstractItemModel *model = c->proxy->sourceModel();
741 int lastRow = model->rowCount(curParent) - 1;
742 QIndexMapper im(curMatch.indices.last() + 1, lastRow);
743 int lastIndex = buildIndices(curParts.constLast(), curParent, n, im, &curMatch);
744 curMatch.partial = (lastRow != lastIndex);
745 saveInCache(curParts.constLast(), curParent, curMatch);
746}
747
748QMatchData QUnsortedModelEngine::filter(const QString& part, const QModelIndex& parent, int n)
749{
750 QMatchData hint;
751
752 QList<int> v;
753 QIndexMapper im(v);
754 QMatchData m(im, -1, true);
755
756 const QAbstractItemModel *model = c->proxy->sourceModel();
757 bool foundInCache = lookupCache(part, parent, &m);
758
759 if (!foundInCache) {
760 if (matchHint(part, parent, &hint) && !hint.isValid())
761 return QMatchData();
762 }
763
764 if (!foundInCache && !hint.isValid()) {
765 const int lastRow = model->rowCount(parent) - 1;
766 QIndexMapper all(0, lastRow);
767 int lastIndex = buildIndices(part, parent, n, all, &m);
768 m.partial = (lastIndex != lastRow);
769 } else {
770 if (!foundInCache) { // build from hint as much as we can
771 buildIndices(part, parent, INT_MAX, hint.indices, &m);
772 m.partial = hint.partial;
773 }
774 if (m.partial && ((n == -1 && m.exactMatchIndex == -1) || (m.indices.count() < n))) {
775 // need more and have more
776 const int lastRow = model->rowCount(parent) - 1;
777 QIndexMapper rest(hint.indices.last() + 1, lastRow);
778 int want = n == -1 ? -1 : n - m.indices.count();
779 int lastIndex = buildIndices(part, parent, want, rest, &m);
780 m.partial = (lastRow != lastIndex);
781 }
782 }
783
784 saveInCache(part, parent, m);
785 return m;
786}
787
788///////////////////////////////////////////////////////////////////////////////
789QCompleterPrivate::QCompleterPrivate()
790 : widget(nullptr),
791 proxy(nullptr),
792 popup(nullptr),
793 filterMode(Qt::MatchStartsWith),
794 cs(Qt::CaseSensitive),
795 role(Qt::EditRole),
796 column(0),
798 sorting(QCompleter::UnsortedModel),
799 wrap(true),
800 eatFocusOut(true),
802{
803}
804
805void QCompleterPrivate::init(QAbstractItemModel *m)
806{
807 Q_Q(QCompleter);
808 proxy = new QCompletionModel(this, q);
809 QObject::connect(proxy, SIGNAL(rowsAdded()), q, SLOT(_q_autoResizePopup()));
810 q->setModel(m);
811#if !QT_CONFIG(listview)
812 q->setCompletionMode(QCompleter::InlineCompletion);
813#else
814 q->setCompletionMode(QCompleter::PopupCompletion);
815#endif // QT_CONFIG(listview)
816}
817
818void QCompleterPrivate::setCurrentIndex(QModelIndex index, bool select)
819{
820 Q_Q(QCompleter);
821 if (!q->popup())
822 return;
823 if (!select) {
824 popup->selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
825 } else {
826 if (!index.isValid())
827 popup->selectionModel()->clear();
828 else
829 popup->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select
830 | QItemSelectionModel::Rows);
831 }
832 index = popup->selectionModel()->currentIndex();
833 if (!index.isValid())
834 popup->scrollToTop();
835 else
836 popup->scrollTo(index, QAbstractItemView::PositionAtTop);
837}
838
839void QCompleterPrivate::_q_completionSelected(const QItemSelection& selection)
840{
841 QModelIndex index;
842 if (const auto indexes = selection.indexes(); !indexes.isEmpty())
843 index = indexes.first();
844
845 _q_complete(index, true);
846}
847
848void QCompleterPrivate::_q_complete(QModelIndex index, bool highlighted)
849{
850 Q_Q(QCompleter);
851 QString completion;
852
853 if (!index.isValid() || (!proxy->showAll && (index.row() >= proxy->engine->matchCount()))) {
854 completion = prefix;
855 index = QModelIndex();
856 } else {
857 if (!(index.flags() & Qt::ItemIsEnabled))
858 return;
859 QModelIndex si = proxy->mapToSource(index);
860 si = si.sibling(si.row(), column); // for clicked()
861 completion = q->pathFromIndex(si);
862#if QT_CONFIG(filesystemmodel)
863 // add a trailing separator in inline
864 if (mode == QCompleter::InlineCompletion) {
865 if (qobject_cast<QFileSystemModel *>(proxy->sourceModel()) && QFileInfo(completion).isDir())
866 completion += QDir::separator();
867 }
868#endif
869 }
870
871 if (highlighted) {
872 emit q->highlighted(index);
873 emit q->highlighted(completion);
874 } else {
875 emit q->activated(index);
876 emit q->activated(completion);
877 }
878}
879
881{
882 if (!popup || !popup->isVisible())
883 return;
884 showPopup(popupRect);
885}
886
887void QCompleterPrivate::showPopup(const QRect& rect)
888{
889 const QRect screen = widget->screen()->availableGeometry();
890 Qt::LayoutDirection dir = widget->layoutDirection();
891 QPoint pos;
892 int rh, w;
893 int h = (popup->sizeHintForRow(0) * qMin(maxVisibleItems, popup->model()->rowCount()) + 3) + 3;
894 QScrollBar *hsb = popup->horizontalScrollBar();
895 if (hsb && hsb->isVisible())
896 h += popup->horizontalScrollBar()->sizeHint().height();
897
898 if (rect.isValid()) {
899 rh = rect.height();
900 w = rect.width();
901 pos = widget->mapToGlobal(dir == Qt::RightToLeft ? rect.bottomRight() : rect.bottomLeft());
902 } else {
903 rh = widget->height();
904 pos = widget->mapToGlobal(QPoint(0, widget->height() - 2));
905 w = widget->width();
906 }
907
908 if (w > screen.width())
909 w = screen.width();
910 if ((pos.x() + w) > (screen.x() + screen.width()))
911 pos.setX(screen.x() + screen.width() - w);
912 if (pos.x() < screen.x())
913 pos.setX(screen.x());
914
915 int top = pos.y() - rh - screen.top() + 2;
916 int bottom = screen.bottom() - pos.y();
917 h = qMax(h, popup->minimumHeight());
918 if (h > bottom) {
919 h = qMin(qMax(top, bottom), h);
920
921 if (top > bottom)
922 pos.setY(pos.y() - h - rh + 2);
923 }
924
925 popup->setGeometry(pos.x(), pos.y(), w, h);
926
927 if (!popup->isVisible()) {
928 // Make sure popup has a transient parent set, Wayland needs it. QTBUG-130474
929 popup->winId(); // force creation of windowHandle
930 popup->windowHandle()->setTransientParent(widget->window()->windowHandle());
931
932 popup->show();
933 }
934}
935
936#if QT_CONFIG(filesystemmodel)
937static bool isRoot(const QFileSystemModel *model, const QString &path)
938{
939 const auto index = model->index(path);
940 return index.isValid() && model->fileInfo(index).isRoot();
941}
942
943static bool completeOnLoaded(const QFileSystemModel *model,
944 const QString &nativePrefix,
945 const QString &path,
946 Qt::CaseSensitivity caseSensitivity)
947{
948 const auto pathSize = path.size();
949 const auto prefixSize = nativePrefix.size();
950 if (prefixSize < pathSize)
951 return false;
952 const QString prefix = QDir::fromNativeSeparators(nativePrefix);
953 if (prefixSize == pathSize)
954 return path.compare(prefix, caseSensitivity) == 0 && isRoot(model, path);
955 // The user is typing something within that directory and is not in a subdirectory yet.
956 const auto separator = u'/';
957 return prefix.startsWith(path, caseSensitivity) && prefix.at(pathSize) == separator
958 && !QStringView{prefix}.right(prefixSize - pathSize - 1).contains(separator);
959}
960
961void QCompleterPrivate::_q_fileSystemModelDirectoryLoaded(const QString &path)
962{
963 Q_Q(QCompleter);
964 // Slot called when QFileSystemModel has finished loading.
965 // If we hide the popup because there was no match because the model was not loaded yet,
966 // we re-start the completion when we get the results (unless triggered by
967 // something else, see QTBUG-14292).
968 if (hiddenBecauseNoMatch && widget) {
969 if (auto model = qobject_cast<const QFileSystemModel *>(proxy->sourceModel())) {
970 if (completeOnLoaded(model, prefix, path, cs))
971 q->complete();
972 }
973 }
974}
975#else // QT_CONFIG(filesystemmodel)
977#endif
978
979/*!
980 Constructs a completer object with the given \a parent.
981*/
982QCompleter::QCompleter(QObject *parent)
983: QObject(*new QCompleterPrivate(), parent)
984{
985 Q_D(QCompleter);
986 d->init();
987}
988
989/*!
990 Constructs a completer object with the given \a parent that provides completions
991 from the specified \a model.
992*/
993QCompleter::QCompleter(QAbstractItemModel *model, QObject *parent)
994 : QObject(*new QCompleterPrivate(), parent)
995{
996 Q_D(QCompleter);
997 d->init(model);
998}
999
1000#if QT_CONFIG(stringlistmodel)
1001/*!
1002 Constructs a QCompleter object with the given \a parent that uses the specified
1003 \a list as a source of possible completions.
1004*/
1005QCompleter::QCompleter(const QStringList& list, QObject *parent)
1006: QObject(*new QCompleterPrivate(), parent)
1007{
1008 Q_D(QCompleter);
1009 d->init(new QStringListModel(list, this));
1010}
1011#endif // QT_CONFIG(stringlistmodel)
1012
1013/*!
1014 Destroys the completer object.
1015*/
1016QCompleter::~QCompleter()
1017{
1018}
1019
1020/*!
1021 Sets the widget for which completion are provided for to \a widget. This
1022 function is automatically called when a QCompleter is set on a QLineEdit
1023 using QLineEdit::setCompleter() or on a QComboBox using
1024 QComboBox::setCompleter(). The widget needs to be set explicitly when
1025 providing completions for custom widgets.
1026
1027 \sa widget(), setModel(), setPopup()
1028 */
1029void QCompleter::setWidget(QWidget *widget)
1030{
1031 Q_D(QCompleter);
1032 if (widget == d->widget)
1033 return;
1034
1035 if (d->widget)
1036 d->widget->removeEventFilter(this);
1037 d->widget = widget;
1038 if (d->widget)
1039 d->widget->installEventFilter(this);
1040
1041 if (d->popup) {
1042 d->popup->hide();
1043 d->popup->setFocusProxy(d->widget);
1044 }
1045}
1046
1047/*!
1048 Returns the widget for which the completer object is providing completions.
1049
1050 \sa setWidget()
1051 */
1052QWidget *QCompleter::widget() const
1053{
1054 Q_D(const QCompleter);
1055 return d->widget;
1056}
1057
1058/*!
1059 Sets the model which provides completions to \a model. The \a model can
1060 be list model or a tree model. If a model has been already previously set
1061 and it has the QCompleter as its parent, it is deleted.
1062
1063 For convenience, if \a model is a QFileSystemModel, QCompleter switches its
1064 caseSensitivity to Qt::CaseInsensitive on Windows and Qt::CaseSensitive
1065 on other platforms.
1066
1067 \sa completionModel(), modelSorting, {Handling Tree Models}
1068*/
1069void QCompleter::setModel(QAbstractItemModel *model)
1070{
1071 Q_D(QCompleter);
1072 QAbstractItemModel *oldModel = d->proxy->sourceModel();
1073 if (oldModel == model)
1074 return;
1075#if QT_CONFIG(filesystemmodel)
1076 if (qobject_cast<const QFileSystemModel *>(oldModel))
1077 setCompletionRole(Qt::EditRole); // QTBUG-54642, clear FileNameRole set by QFileSystemModel
1078#endif
1079 d->proxy->setSourceModel(model);
1080 if (d->popup)
1081 setPopup(d->popup); // set the model and make new connections
1082 if (oldModel && oldModel->QObject::parent() == this)
1083 delete oldModel;
1084#if QT_CONFIG(filesystemmodel)
1085 QFileSystemModel *fsModel = qobject_cast<QFileSystemModel *>(model);
1086 if (fsModel) {
1087#if defined(Q_OS_WIN)
1088 setCaseSensitivity(Qt::CaseInsensitive);
1089#else
1090 setCaseSensitivity(Qt::CaseSensitive);
1091#endif
1092 setCompletionRole(QFileSystemModel::FileNameRole);
1093 connect(fsModel, SIGNAL(directoryLoaded(QString)), this, SLOT(_q_fileSystemModelDirectoryLoaded(QString)));
1094 }
1095#endif // QT_CONFIG(filesystemmodel)
1096}
1097
1098/*!
1099 Returns the model that provides completion strings.
1100
1101 \sa completionModel()
1102*/
1103QAbstractItemModel *QCompleter::model() const
1104{
1105 Q_D(const QCompleter);
1106 return d->proxy->sourceModel();
1107}
1108
1109/*!
1110 \enum QCompleter::CompletionMode
1111
1112 This enum specifies how completions are provided to the user.
1113
1114 \value PopupCompletion Current completions are displayed in a popup window.
1115 \value InlineCompletion Completions appear inline (as selected text).
1116 \value UnfilteredPopupCompletion All possible completions are displayed in a popup window with the most likely suggestion indicated as current.
1117
1118 \sa setCompletionMode()
1119*/
1120
1121/*!
1122 \property QCompleter::completionMode
1123 \brief how the completions are provided to the user
1124
1125 The default value is QCompleter::PopupCompletion.
1126*/
1127void QCompleter::setCompletionMode(QCompleter::CompletionMode mode)
1128{
1129 Q_D(QCompleter);
1130 d->mode = mode;
1131 d->proxy->setFiltered(mode != QCompleter::UnfilteredPopupCompletion);
1132
1133 if (mode == QCompleter::InlineCompletion) {
1134 if (d->widget)
1135 d->widget->removeEventFilter(this);
1136 if (d->popup) {
1137 d->popup->deleteLater();
1138 d->popup = nullptr;
1139 }
1140 } else {
1141 if (d->widget)
1142 d->widget->installEventFilter(this);
1143 }
1144}
1145
1146QCompleter::CompletionMode QCompleter::completionMode() const
1147{
1148 Q_D(const QCompleter);
1149 return d->mode;
1150}
1151
1152/*!
1153 \property QCompleter::filterMode
1154 \brief This property controls how filtering is performed.
1155 \since 5.2
1156
1157 If filterMode is set to Qt::MatchStartsWith, only those entries that start
1158 with the typed characters will be displayed. Qt::MatchContains will display
1159 the entries that contain the typed characters, and Qt::MatchEndsWith the
1160 ones that end with the typed characters.
1161
1162 Setting filterMode to any other Qt::MatchFlag will issue a warning, and no
1163 action will be performed. Because of this, the \c Qt::MatchCaseSensitive
1164 flag has no effect. Use the \l caseSensitivity property to control case
1165 sensitivity.
1166
1167 The default mode is Qt::MatchStartsWith.
1168
1169 \sa caseSensitivity
1170*/
1171
1172void QCompleter::setFilterMode(Qt::MatchFlags filterMode)
1173{
1174 Q_D(QCompleter);
1175
1176 if (d->filterMode == filterMode)
1177 return;
1178
1179 if (Q_UNLIKELY(filterMode != Qt::MatchStartsWith &&
1180 filterMode != Qt::MatchContains &&
1181 filterMode != Qt::MatchEndsWith)) {
1182 qWarning("Unhandled QCompleter::filterMode flag is used.");
1183 return;
1184 }
1185
1186 d->filterMode = filterMode;
1187 d->proxy->createEngine();
1188 d->proxy->invalidate();
1189}
1190
1191Qt::MatchFlags QCompleter::filterMode() const
1192{
1193 Q_D(const QCompleter);
1194 return d->filterMode;
1195}
1196
1197/*!
1198 Sets the popup used to display completions to \a popup. QCompleter takes
1199 ownership of the view.
1200
1201 A QListView is automatically created when the completionMode() is set to
1202 QCompleter::PopupCompletion or QCompleter::UnfilteredPopupCompletion. The
1203 default popup displays the completionColumn().
1204
1205 Ensure that this function is called before the view settings are modified.
1206 This is required since view's properties may require that a model has been
1207 set on the view (for example, hiding columns in the view requires a model
1208 to be set on the view).
1209
1210 \sa popup()
1211*/
1212void QCompleter::setPopup(QAbstractItemView *popup)
1213{
1214 Q_ASSERT(popup);
1215 Q_D(QCompleter);
1216 if (popup == d->popup)
1217 return;
1218
1219 // Remember existing widget's focus policy, default to NoFocus
1220 const Qt::FocusPolicy origPolicy = d->widget ? d->widget->focusPolicy()
1221 : Qt::NoFocus;
1222
1223 // If popup existed already, disconnect signals and delete object
1224 if (d->popup) {
1225 QObject::disconnect(d->popup->selectionModel(), nullptr, this, nullptr);
1226 QObject::disconnect(d->popup, nullptr, this, nullptr);
1227 delete d->popup;
1228 }
1229
1230 // Assign new object, set model and hide
1231 d->popup = popup;
1232 if (d->popup->model() != d->proxy)
1233 d->popup->setModel(d->proxy);
1234 d->popup->hide();
1235
1236 // Mark the widget window as a popup, so that if the last non-popup window is closed by the
1237 // user, the application should not be prevented from exiting. It needs to be set explicitly via
1238 // setWindowFlag(), because passing the flag via setParent(parent, windowFlags) does not call
1239 // QWidgetPrivate::adjustQuitOnCloseAttribute(), and causes an application not to exit if the
1240 // popup ends up being the last window.
1241 d->popup->setParent(nullptr);
1242 d->popup->setWindowFlag(Qt::Popup);
1243 d->popup->setFocusPolicy(Qt::NoFocus);
1244 if (d->widget)
1245 d->widget->setFocusPolicy(origPolicy);
1246
1247 d->popup->setFocusProxy(d->widget);
1248 d->popup->installEventFilter(this);
1249 d->popup->setItemDelegate(new QCompleterItemDelegate(d->popup));
1250#if QT_CONFIG(listview)
1251 if (QListView *listView = qobject_cast<QListView *>(d->popup)) {
1252 listView->setModelColumn(d->column);
1253 }
1254#endif
1255
1256 QObject::connect(d->popup, SIGNAL(clicked(QModelIndex)),
1257 this, SLOT(_q_complete(QModelIndex)));
1258 QObject::connect(this, SIGNAL(activated(QModelIndex)),
1259 d->popup, SLOT(hide()));
1260
1261 QObject::connect(d->popup->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
1262 this, SLOT(_q_completionSelected(QItemSelection)));
1263}
1264
1265/*!
1266 Returns the popup used to display completions.
1267
1268 \sa setPopup()
1269*/
1270QAbstractItemView *QCompleter::popup() const
1271{
1272 Q_D(const QCompleter);
1273#if QT_CONFIG(listview)
1274 if (!d->popup && completionMode() != QCompleter::InlineCompletion) {
1275 QListView *listView = new QListView;
1276 listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
1277 listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1278 listView->setSelectionBehavior(QAbstractItemView::SelectRows);
1279 listView->setSelectionMode(QAbstractItemView::SingleSelection);
1280 listView->setModelColumn(d->column);
1281 QCompleter *that = const_cast<QCompleter*>(this);
1282 that->setPopup(listView);
1283 }
1284#endif // QT_CONFIG(listview)
1285 return d->popup;
1286}
1287
1288/*!
1289 \reimp
1290*/
1291bool QCompleter::event(QEvent *ev)
1292{
1293 return QObject::event(ev);
1294}
1295
1296/*!
1297 \reimp
1298*/
1299bool QCompleter::eventFilter(QObject *o, QEvent *e)
1300{
1301 Q_D(QCompleter);
1302
1303 if (o == d->widget) {
1304 switch (e->type()) {
1305 case QEvent::FocusOut:
1306 if (d->eatFocusOut) {
1307 d->hiddenBecauseNoMatch = false;
1308 if (d->popup && d->popup->isVisible())
1309 return true;
1310 }
1311 break;
1312 case QEvent::Hide:
1313 if (d->popup)
1314 d->popup->hide();
1315 break;
1316 default:
1317 break;
1318 }
1319 }
1320
1321 if (o != d->popup)
1322 return QObject::eventFilter(o, e);
1323
1324 Q_ASSERT(d->popup);
1325 switch (e->type()) {
1326 case QEvent::KeyPress: {
1327 QKeyEvent *ke = static_cast<QKeyEvent *>(e);
1328
1329 QModelIndex curIndex = d->popup->currentIndex();
1330 QModelIndexList selList = d->popup->selectionModel()->selectedIndexes();
1331
1332 const int key = ke->key();
1333 // In UnFilteredPopup mode, select the current item
1334 if ((key == Qt::Key_Up || key == Qt::Key_Down) && selList.isEmpty() && curIndex.isValid()
1335 && d->mode == QCompleter::UnfilteredPopupCompletion) {
1336 d->setCurrentIndex(curIndex);
1337 return true;
1338 }
1339
1340 // Handle popup navigation keys. These are hardcoded because up/down might make the
1341 // widget do something else (lineedit cursor moves to home/end on mac, for instance)
1342 switch (key) {
1343 case Qt::Key_End:
1344 case Qt::Key_Home:
1345 if (ke->modifiers() & Qt::ControlModifier)
1346 return false;
1347 break;
1348
1349 case Qt::Key_Up:
1350 if (!curIndex.isValid()) {
1351 int rowCount = d->proxy->rowCount();
1352 QModelIndex lastIndex = d->proxy->index(rowCount - 1, d->column);
1353 d->setCurrentIndex(lastIndex);
1354 return true;
1355 } else if (curIndex.row() == 0) {
1356 if (d->wrap)
1357 d->setCurrentIndex(QModelIndex());
1358 return true;
1359 }
1360 return false;
1361
1362 case Qt::Key_Down:
1363 if (!curIndex.isValid()) {
1364 QModelIndex firstIndex = d->proxy->index(0, d->column);
1365 d->setCurrentIndex(firstIndex);
1366 return true;
1367 } else if (curIndex.row() == d->proxy->rowCount() - 1) {
1368 if (d->wrap)
1369 d->setCurrentIndex(QModelIndex());
1370 return true;
1371 }
1372 return false;
1373
1374 case Qt::Key_PageUp:
1375 case Qt::Key_PageDown:
1376 return false;
1377 }
1378
1379 if (d->widget) {
1380 // Send the event to the widget. If the widget accepted the event, do nothing
1381 // If the widget did not accept the event, provide a default implementation
1382 d->eatFocusOut = false;
1383 (static_cast<QObject *>(d->widget))->event(ke);
1384 d->eatFocusOut = true;
1385 }
1386 if (!d->widget || e->isAccepted() || !d->popup->isVisible()) {
1387 // widget lost focus, hide the popup
1388 if (d->widget && (!d->widget->hasFocus()
1389#ifdef QT_KEYPAD_NAVIGATION
1390 || (QApplicationPrivate::keypadNavigationEnabled() && !d->widget->hasEditFocus())
1391#endif
1392 ))
1393 d->popup->hide();
1394 if (e->isAccepted())
1395 return true;
1396 }
1397
1398 // default implementation for keys not handled by the widget when popup is open
1399#if QT_CONFIG(shortcut)
1400 if (ke->matches(QKeySequence::Cancel)) {
1401 d->popup->hide();
1402 return true;
1403 }
1404#endif
1405 switch (key) {
1406#ifdef QT_KEYPAD_NAVIGATION
1407 case Qt::Key_Select:
1408 if (!QApplicationPrivate::keypadNavigationEnabled())
1409 break;
1410#endif
1411 case Qt::Key_Return:
1412 case Qt::Key_Enter:
1413 case Qt::Key_Tab:
1414 d->popup->hide();
1415 if (curIndex.isValid())
1416 d->_q_complete(curIndex);
1417 break;
1418
1419 case Qt::Key_F4:
1420 if (ke->modifiers() & Qt::AltModifier)
1421 d->popup->hide();
1422 break;
1423
1424 case Qt::Key_Backtab:
1425 d->popup->hide();
1426 break;
1427
1428 default:
1429 break;
1430 }
1431
1432 return true;
1433 }
1434
1435#ifdef QT_KEYPAD_NAVIGATION
1436 case QEvent::KeyRelease: {
1437 if (d->widget &&
1438 QApplicationPrivate::keypadNavigationEnabled() && ke->key() == Qt::Key_Back) {
1439 QKeyEvent *ke = static_cast<QKeyEvent *>(e);
1440 // Send the event to the 'widget'. This is what we did for KeyPress, so we need
1441 // to do the same for KeyRelease, in case the widget's KeyPress event set
1442 // up something (such as a timer) that is relying on also receiving the
1443 // key release. I see this as a bug in Qt, and should really set it up for all
1444 // the affected keys. However, it is difficult to tell how this will affect
1445 // existing code, and I can't test for every combination!
1446 d->eatFocusOut = false;
1447 static_cast<QObject *>(d->widget)->event(ke);
1448 d->eatFocusOut = true;
1449 }
1450 break;
1451 }
1452#endif
1453
1454 case QEvent::MouseButtonPress: {
1455#ifdef QT_KEYPAD_NAVIGATION
1456 if (d->widget
1457 && QApplicationPrivate::keypadNavigationEnabled()) {
1458 // if we've clicked in the widget (or its descendant), let it handle the click
1459 QWidget *source = qobject_cast<QWidget *>(o);
1460 if (source) {
1461 QPoint pos = source->mapToGlobal((static_cast<QMouseEvent *>(e))->pos());
1462 QWidget *target = QApplication::widgetAt(pos);
1463 if (target && (d->widget->isAncestorOf(target) ||
1464 target == d->widget)) {
1465 d->eatFocusOut = false;
1466 static_cast<QObject *>(target)->event(e);
1467 d->eatFocusOut = true;
1468 return true;
1469 }
1470 }
1471 }
1472#endif
1473 if (!d->popup->underMouse()) {
1474 if (!QGuiApplicationPrivate::maybeForwardEventToVirtualKeyboard(e))
1475 d->popup->hide();
1476 return true;
1477 }
1478 }
1479 return false;
1480
1481 case QEvent::MouseButtonRelease:
1482 QGuiApplicationPrivate::maybeForwardEventToVirtualKeyboard(e);
1483 return true;
1484 case QEvent::InputMethod:
1485 case QEvent::ShortcutOverride:
1486 if (d->widget)
1487 QCoreApplication::sendEvent(d->widget, e);
1488 break;
1489
1490 default:
1491 return false;
1492 }
1493 return false;
1494}
1495
1496/*!
1497 For QCompleter::PopupCompletion and QCompletion::UnfilteredPopupCompletion
1498 modes, calling this function displays the popup displaying the current
1499 completions. By default, if \a rect is not specified, the popup is displayed
1500 on the bottom of the widget(). If \a rect is specified the popup is
1501 displayed on the left edge of the rectangle.
1502
1503 For QCompleter::InlineCompletion mode, the highlighted() signal is fired
1504 with the current completion.
1505*/
1506void QCompleter::complete(const QRect& rect)
1507{
1508 Q_D(QCompleter);
1509 QModelIndex idx = d->proxy->currentIndex(false);
1510 d->hiddenBecauseNoMatch = false;
1511 if (d->mode == QCompleter::InlineCompletion) {
1512 if (idx.isValid())
1513 d->_q_complete(idx, true);
1514 return;
1515 }
1516
1517 Q_ASSERT(d->widget);
1518 if ((d->mode == QCompleter::PopupCompletion && !idx.isValid())
1519 || (d->mode == QCompleter::UnfilteredPopupCompletion && d->proxy->rowCount() == 0)) {
1520 if (d->popup)
1521 d->popup->hide(); // no suggestion, hide
1522 d->hiddenBecauseNoMatch = true;
1523 return;
1524 }
1525
1526 popup();
1527 if (d->mode == QCompleter::UnfilteredPopupCompletion)
1528 d->setCurrentIndex(idx, false);
1529
1530 d->showPopup(rect);
1531 d->popupRect = rect;
1532}
1533
1534/*!
1535 Sets the current row to the \a row specified. Returns \c true if successful;
1536 otherwise returns \c false.
1537
1538 This function may be used along with currentCompletion() to iterate
1539 through all the possible completions.
1540
1541 \sa currentCompletion(), completionCount()
1542*/
1543bool QCompleter::setCurrentRow(int row)
1544{
1545 Q_D(QCompleter);
1546 return d->proxy->setCurrentRow(row);
1547}
1548
1549/*!
1550 Returns the current row.
1551
1552 \sa setCurrentRow()
1553*/
1554int QCompleter::currentRow() const
1555{
1556 Q_D(const QCompleter);
1557 return d->proxy->currentRow();
1558}
1559
1560/*!
1561 Returns the number of completions for the current prefix. For an unsorted
1562 model with a large number of items this can be expensive. Use setCurrentRow()
1563 and currentCompletion() to iterate through all the completions.
1564*/
1565int QCompleter::completionCount() const
1566{
1567 Q_D(const QCompleter);
1568 return d->proxy->completionCount();
1569}
1570
1571/*!
1572 \enum QCompleter::ModelSorting
1573
1574 This enum specifies how the items in the model are sorted.
1575
1576 \value UnsortedModel The model is unsorted.
1577 \value CaseSensitivelySortedModel The model is sorted case sensitively.
1578 \value CaseInsensitivelySortedModel The model is sorted case insensitively.
1579
1580 \sa setModelSorting()
1581*/
1582
1583/*!
1584 \property QCompleter::modelSorting
1585 \brief the way the model is sorted
1586
1587 By default, no assumptions are made about the order of the items
1588 in the model that provides the completions.
1589
1590 If the model's data for the completionColumn() and completionRole() is sorted in
1591 ascending order, you can set this property to \l CaseSensitivelySortedModel
1592 or \l CaseInsensitivelySortedModel. On large models, this can lead to
1593 significant performance improvements because the completer object can
1594 then use a binary search algorithm instead of linear search algorithm.
1595
1596 The sort order (i.e ascending or descending order) of the model is determined
1597 dynamically by inspecting the contents of the model.
1598
1599 \b{Note:} The performance improvements described above cannot take place
1600 when the completer's \l caseSensitivity is different to the case sensitivity
1601 used by the model's when sorting.
1602
1603 \sa setCaseSensitivity(), QCompleter::ModelSorting
1604*/
1605void QCompleter::setModelSorting(QCompleter::ModelSorting sorting)
1606{
1607 Q_D(QCompleter);
1608 if (d->sorting == sorting)
1609 return;
1610 d->sorting = sorting;
1611 d->proxy->createEngine();
1612 d->proxy->invalidate();
1613}
1614
1615QCompleter::ModelSorting QCompleter::modelSorting() const
1616{
1617 Q_D(const QCompleter);
1618 return d->sorting;
1619}
1620
1621/*!
1622 \property QCompleter::completionColumn
1623 \brief the column in the model in which completions are searched for.
1624
1625 If the popup() is a QListView, it is automatically setup to display
1626 this column.
1627
1628 By default, the match column is 0.
1629
1630 \sa completionRole, caseSensitivity
1631*/
1632void QCompleter::setCompletionColumn(int column)
1633{
1634 Q_D(QCompleter);
1635 if (d->column == column)
1636 return;
1637#if QT_CONFIG(listview)
1638 if (QListView *listView = qobject_cast<QListView *>(d->popup))
1639 listView->setModelColumn(column);
1640#endif
1641 d->column = column;
1642 d->proxy->invalidate();
1643}
1644
1645int QCompleter::completionColumn() const
1646{
1647 Q_D(const QCompleter);
1648 return d->column;
1649}
1650
1651/*!
1652 \property QCompleter::completionRole
1653 \brief the item role to be used to query the contents of items for matching.
1654
1655 The default role is Qt::EditRole.
1656
1657 \sa completionColumn, caseSensitivity
1658*/
1659void QCompleter::setCompletionRole(int role)
1660{
1661 Q_D(QCompleter);
1662 if (d->role == role)
1663 return;
1664 d->role = role;
1665 d->proxy->invalidate();
1666}
1667
1668int QCompleter::completionRole() const
1669{
1670 Q_D(const QCompleter);
1671 return d->role;
1672}
1673
1674/*!
1675 \property QCompleter::wrapAround
1676 \brief the completions wrap around when navigating through items
1677 \since 4.3
1678
1679 The default is true.
1680*/
1681void QCompleter::setWrapAround(bool wrap)
1682{
1683 Q_D(QCompleter);
1684 if (d->wrap == wrap)
1685 return;
1686 d->wrap = wrap;
1687}
1688
1689bool QCompleter::wrapAround() const
1690{
1691 Q_D(const QCompleter);
1692 return d->wrap;
1693}
1694
1695/*!
1696 \property QCompleter::maxVisibleItems
1697 \brief the maximum allowed size on screen of the completer, measured in items
1698 \since 4.6
1699
1700 By default, this property has a value of 7.
1701*/
1702int QCompleter::maxVisibleItems() const
1703{
1704 Q_D(const QCompleter);
1705 return d->maxVisibleItems;
1706}
1707
1708void QCompleter::setMaxVisibleItems(int maxItems)
1709{
1710 Q_D(QCompleter);
1711 if (Q_UNLIKELY(maxItems < 0)) {
1712 qWarning("QCompleter::setMaxVisibleItems: "
1713 "Invalid max visible items (%d) must be >= 0", maxItems);
1714 return;
1715 }
1716 d->maxVisibleItems = maxItems;
1717}
1718
1719/*!
1720 \property QCompleter::caseSensitivity
1721 \brief the case sensitivity of the matching
1722
1723 The default value is \c Qt::CaseSensitive.
1724
1725 \sa completionColumn, completionRole, modelSorting, filterMode
1726*/
1727void QCompleter::setCaseSensitivity(Qt::CaseSensitivity cs)
1728{
1729 Q_D(QCompleter);
1730 if (d->cs == cs)
1731 return;
1732 d->cs = cs;
1733 d->proxy->createEngine();
1734 d->proxy->invalidate();
1735}
1736
1737Qt::CaseSensitivity QCompleter::caseSensitivity() const
1738{
1739 Q_D(const QCompleter);
1740 return d->cs;
1741}
1742
1743/*!
1744 \property QCompleter::completionPrefix
1745 \brief the completion prefix used to provide completions.
1746
1747 The completionModel() is updated to reflect the list of possible
1748 matches for \a prefix.
1749*/
1750void QCompleter::setCompletionPrefix(const QString &prefix)
1751{
1752 Q_D(QCompleter);
1753 d->prefix = prefix;
1754 d->proxy->filter(splitPath(prefix));
1755}
1756
1757QString QCompleter::completionPrefix() const
1758{
1759 Q_D(const QCompleter);
1760 return d->prefix;
1761}
1762
1763/*!
1764 Returns the model index of the current completion in the completionModel().
1765
1766 \sa setCurrentRow(), currentCompletion(), model()
1767*/
1768QModelIndex QCompleter::currentIndex() const
1769{
1770 Q_D(const QCompleter);
1771 return d->proxy->currentIndex(false);
1772}
1773
1774/*!
1775 Returns the current completion string. This includes the \l completionPrefix.
1776 When used alongside setCurrentRow(), it can be used to iterate through
1777 all the matches.
1778
1779 \sa setCurrentRow(), currentIndex()
1780*/
1781QString QCompleter::currentCompletion() const
1782{
1783 Q_D(const QCompleter);
1784 return pathFromIndex(d->proxy->currentIndex(true));
1785}
1786
1787/*!
1788 Returns the completion model. The completion model is a read-only list model
1789 that contains all the possible matches for the current completion prefix.
1790 The completion model is auto-updated to reflect the current completions.
1791
1792 \note The return value of this function is defined to be an QAbstractItemModel
1793 purely for generality. This actual kind of model returned is an instance of an
1794 QAbstractProxyModel subclass.
1795
1796 \sa completionPrefix, model()
1797*/
1798QAbstractItemModel *QCompleter::completionModel() const
1799{
1800 Q_D(const QCompleter);
1801 return d->proxy;
1802}
1803
1804/*!
1805 Returns the path for the given \a index. The completer object uses this to
1806 obtain the completion text from the underlying model.
1807
1808 The default implementation returns the \l{Qt::EditRole}{edit role} of the
1809 item for list models. It returns the absolute file path if the model is a
1810 QFileSystemModel.
1811
1812 \sa splitPath()
1813*/
1814
1815QString QCompleter::pathFromIndex(const QModelIndex& index) const
1816{
1817 Q_D(const QCompleter);
1818 if (!index.isValid())
1819 return QString();
1820
1821 QAbstractItemModel *sourceModel = d->proxy->sourceModel();
1822 if (!sourceModel)
1823 return QString();
1824 bool isFsModel = false;
1825#if QT_CONFIG(filesystemmodel)
1826 isFsModel = qobject_cast<QFileSystemModel *>(d->proxy->sourceModel()) != nullptr;
1827#endif
1828 if (!isFsModel)
1829 return sourceModel->data(index, d->role).toString();
1830
1831 QModelIndex idx = index;
1832 QStringList list;
1833 do {
1834 QString t;
1835#if QT_CONFIG(filesystemmodel)
1836 t = sourceModel->data(idx, QFileSystemModel::FileNameRole).toString();
1837#endif
1838 list.prepend(t);
1839 QModelIndex parent = idx.parent();
1840 idx = parent.sibling(parent.row(), index.column());
1841 } while (idx.isValid());
1842
1843#if !defined(Q_OS_WIN)
1844 if (list.size() == 1) // only the separator or some other text
1845 return list[0];
1846 list[0].clear() ; // the join below will provide the separator
1847#endif
1848
1849 return list.join(QDir::separator());
1850}
1851
1852/*!
1853 Splits the given \a path into strings that are used to match at each level
1854 in the model().
1855
1856 The default implementation of splitPath() splits a file system path based on
1857 QDir::separator() when the sourceModel() is a QFileSystemModel.
1858
1859 When used with list models, the first item in the returned list is used for
1860 matching.
1861
1862 \sa pathFromIndex(), {Handling Tree Models}
1863*/
1864QStringList QCompleter::splitPath(const QString& path) const
1865{
1866 bool isFsModel = false;
1867#if QT_CONFIG(filesystemmodel)
1868 Q_D(const QCompleter);
1869 isFsModel = qobject_cast<QFileSystemModel *>(d->proxy->sourceModel()) != nullptr;
1870#endif
1871
1872 if (!isFsModel || path.isEmpty())
1873 return QStringList(completionPrefix());
1874
1875 QString pathCopy = QDir::toNativeSeparators(path);
1876#if defined(Q_OS_WIN)
1877 if (pathCopy == "\\"_L1 || pathCopy == "\\\\"_L1)
1878 return QStringList(pathCopy);
1879 const bool startsWithDoubleSlash = pathCopy.startsWith("\\\\"_L1);
1880 if (startsWithDoubleSlash)
1881 pathCopy = pathCopy.mid(2);
1882#endif
1883
1884 const QChar sep = QDir::separator();
1885 QStringList parts = pathCopy.split(sep);
1886
1887#if defined(Q_OS_WIN)
1888 if (startsWithDoubleSlash)
1889 parts[0].prepend("\\\\"_L1);
1890#else
1891 if (pathCopy[0] == sep) // readd the "/" at the beginning as the split removed it
1892 parts[0] = u'/';
1893#endif
1894
1895 return parts;
1896}
1897
1898/*!
1899 \fn void QCompleter::activated(const QModelIndex& index)
1900
1901 This signal is sent when an item in the popup() is activated by the user.
1902 (by clicking or pressing return). The item's \a index in the completionModel()
1903 is given.
1904
1905*/
1906
1907/*!
1908 \fn void QCompleter::activated(const QString &text)
1909
1910 This signal is sent when an item in the popup() is activated by the user (by
1911 clicking or pressing return). The item's \a text is given.
1912
1913*/
1914
1915/*!
1916 \fn void QCompleter::highlighted(const QModelIndex& index)
1917
1918 This signal is sent when an item in the popup() is highlighted by
1919 the user. It is also sent if complete() is called with the completionMode()
1920 set to QCompleter::InlineCompletion. The item's \a index in the completionModel()
1921 is given.
1922*/
1923
1924/*!
1925 \fn void QCompleter::highlighted(const QString &text)
1926
1927 This signal is sent when an item in the popup() is highlighted by
1928 the user. It is also sent if complete() is called with the completionMode()
1929 set to QCompleter::InlineCompletion. The item's \a text is given.
1930*/
1931
1932QT_END_NAMESPACE
1933
1934#include "moc_qcompleter.cpp"
1935
1936#include "moc_qcompleter_p.cpp"
void setCurrentIndex(QModelIndex, bool=true)
void _q_fileSystemModelDirectoryLoaded(const QString &path)
void _q_completionSelected(const QItemSelection &)
void init(QAbstractItemModel *model=nullptr)
QAbstractItemView * popup
void _q_complete(QModelIndex, bool=false)
QCompletionModel * proxy
void showPopup(const QRect &)
void saveInCache(QString, const QModelIndex &, const QMatchData &)
void filter(const QStringList &parts)
QMap< QString, QMatchData > CacheItem
int matchCount() const
bool matchHint(const QString &part, const QModelIndex &parent, QMatchData *m) const
bool lookupCache(const QString &part, const QModelIndex &parent, QMatchData *m) const
QCompleterPrivate * c
QMatchData filterHistory()
QModelIndex index(int row, int column, const QModelIndex &=QModelIndex()) const override
Returns the index of the item in the model specified by the given row, column and parent index.
int rowCount(const QModelIndex &index=QModelIndex()) const override
Returns the number of rows under the given parent.
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
\reimp
void setFiltered(bool)
int completionCount() const
int columnCount(const QModelIndex &index=QModelIndex()) const override
Returns the number of columns for the children of the given parent.
QCompleterPrivate * c
QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override
Reimplement this function to return the model index in the proxy model that corresponds to the source...
bool hasChildren(const QModelIndex &parent=QModelIndex()) const override
\reimp
QModelIndex currentIndex(bool) const
void filter(const QStringList &parts)
QModelIndex mapToSource(const QModelIndex &proxyIndex) const override
Reimplement this function to return the model index in the source model that corresponds to the proxy...
void setSourceModel(QAbstractItemModel *sourceModel) override
Sets the given sourceModel to be processed by the proxy model.
bool setCurrentRow(int row)
int from() const
int indexOf(int x) const
int count() const
int operator[](int index) const
QIndexMapper(int f, int t)
int to() const
QMatchData filter(const QString &, const QModelIndex &, int) override
Qt::SortOrder sortOrder(const QModelIndex &) const
QIndexMapper indexHint(QString, const QModelIndex &, Qt::SortOrder)
void filterOnDemand(int) override
QMatchData filter(const QString &, const QModelIndex &, int) override
QMatchData(const QIndexMapper &indices, int em, bool p)
int exactMatchIndex
bool isValid() const