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