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