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 Qt::LayoutDirection dir = widget->layoutDirection();
893 QPoint pos;
894 int rh, w;
895 int h = (popup->sizeHintForRow(0) * qMin(maxVisibleItems, popup->model()->rowCount()) + 3) + 3;
896 QScrollBar *hsb = popup->horizontalScrollBar();
897 if (hsb && hsb->isVisible())
898 h += popup->horizontalScrollBar()->sizeHint().height();
899
900 if (rect.isValid()) {
901 rh = rect.height();
902 w = rect.width();
903 pos = widget->mapToGlobal(dir == Qt::RightToLeft ? rect.bottomRight() : rect.bottomLeft());
904 } else {
905 rh = widget->height();
906 pos = widget->mapToGlobal(QPoint(0, widget->height() - 2));
907 w = widget->width();
908 }
909
910 if (w > screen.width())
911 w = screen.width();
912 if ((pos.x() + w) > (screen.x() + screen.width()))
913 pos.setX(screen.x() + screen.width() - w);
914 if (pos.x() < screen.x())
915 pos.setX(screen.x());
916
917 int top = pos.y() - rh - screen.top() + 2;
918 int bottom = screen.bottom() - pos.y();
919 h = qMax(h, popup->minimumHeight());
920 if (h > bottom) {
921 h = qMin(qMax(top, bottom), h);
922
923 if (top > bottom)
924 pos.setY(pos.y() - h - rh + 2);
925 }
926
927 popup->setGeometry(pos.x(), pos.y(), w, h);
928
929 if (!popup->isVisible()) {
930#if QT_CONFIG(wayland)
931 popup->createWinId();
932 if (auto waylandWindow = dynamic_cast<QNativeInterface::Private::QWaylandWindow*>(popup->windowHandle()->handle())) {
933 popup->windowHandle()->setTransientParent(widget->window()->windowHandle());
934 if (!rect.isValid()) { // Automatically positioned popup
935 const QRect controlGeometry = QRect(
936 widget->mapTo(widget->topLevelWidget(), QPoint(0, 0)), widget->size());
937 waylandWindow->setParentControlGeometry(controlGeometry);
938 waylandWindow->setExtendedWindowType(
939 QNativeInterface::Private::QWaylandWindow::ComboBox);
940 }
941 }
942#endif
943 popup->show();
944 }
945}
946
947#if QT_CONFIG(filesystemmodel)
948static bool isRoot(const QFileSystemModel *model, const QString &path)
949{
950 const auto index = model->index(path);
951 return index.isValid() && model->fileInfo(index).isRoot();
952}
953
954static bool completeOnLoaded(const QFileSystemModel *model,
955 const QString &nativePrefix,
956 const QString &path,
957 Qt::CaseSensitivity caseSensitivity)
958{
959 const auto pathSize = path.size();
960 const auto prefixSize = nativePrefix.size();
961 if (prefixSize < pathSize)
962 return false;
963 const QString prefix = QDir::fromNativeSeparators(nativePrefix);
964 if (prefixSize == pathSize)
965 return path.compare(prefix, caseSensitivity) == 0 && isRoot(model, path);
966 // The user is typing something within that directory and is not in a subdirectory yet.
967 const auto separator = u'/';
968 return prefix.startsWith(path, caseSensitivity) && prefix.at(pathSize) == separator
969 && !QStringView{prefix}.right(prefixSize - pathSize - 1).contains(separator);
970}
971
972void QCompleterPrivate::_q_fileSystemModelDirectoryLoaded(const QString &path)
973{
974 Q_Q(QCompleter);
975 // Slot called when QFileSystemModel has finished loading.
976 // If we hide the popup because there was no match because the model was not loaded yet,
977 // we re-start the completion when we get the results (unless triggered by
978 // something else, see QTBUG-14292).
979 if (hiddenBecauseNoMatch && widget) {
980 if (auto model = qobject_cast<const QFileSystemModel *>(proxy->sourceModel())) {
981 if (completeOnLoaded(model, prefix, path, cs))
982 q->complete();
983 }
984 }
985}
986#else // QT_CONFIG(filesystemmodel)
988#endif
989
990/*!
991 Constructs a completer object with the given \a parent.
992*/
993QCompleter::QCompleter(QObject *parent)
994: QObject(*new QCompleterPrivate(), parent)
995{
996 Q_D(QCompleter);
997 d->init();
998}
999
1000/*!
1001 Constructs a completer object with the given \a parent that provides completions
1002 from the specified \a model.
1003*/
1004QCompleter::QCompleter(QAbstractItemModel *model, QObject *parent)
1005 : QObject(*new QCompleterPrivate(), parent)
1006{
1007 Q_D(QCompleter);
1008 d->init(model);
1009}
1010
1011#if QT_CONFIG(stringlistmodel)
1012/*!
1013 Constructs a QCompleter object with the given \a parent that uses the specified
1014 \a list as a source of possible completions.
1015*/
1016QCompleter::QCompleter(const QStringList& list, QObject *parent)
1017: QObject(*new QCompleterPrivate(), parent)
1018{
1019 Q_D(QCompleter);
1020 d->init(new QStringListModel(list, this));
1021}
1022#endif // QT_CONFIG(stringlistmodel)
1023
1024/*!
1025 Destroys the completer object.
1026*/
1027QCompleter::~QCompleter()
1028{
1029}
1030
1031/*!
1032 Sets the widget for which completion are provided for to \a widget. This
1033 function is automatically called when a QCompleter is set on a QLineEdit
1034 using QLineEdit::setCompleter() or on a QComboBox using
1035 QComboBox::setCompleter(). The widget needs to be set explicitly when
1036 providing completions for custom widgets.
1037
1038 \sa widget(), setModel(), setPopup()
1039 */
1040void QCompleter::setWidget(QWidget *widget)
1041{
1042 Q_D(QCompleter);
1043 if (widget == d->widget)
1044 return;
1045
1046 if (d->widget)
1047 d->widget->removeEventFilter(this);
1048 d->widget = widget;
1049 if (d->widget)
1050 d->widget->installEventFilter(this);
1051
1052 if (d->popup) {
1053 d->popup->hide();
1054 d->popup->setFocusProxy(d->widget);
1055 }
1056}
1057
1058/*!
1059 Returns the widget for which the completer object is providing completions.
1060
1061 \sa setWidget()
1062 */
1063QWidget *QCompleter::widget() const
1064{
1065 Q_D(const QCompleter);
1066 return d->widget;
1067}
1068
1069/*!
1070 Sets the model which provides completions to \a model. The \a model can
1071 be list model or a tree model. If a model has been already previously set
1072 and it has the QCompleter as its parent, it is deleted.
1073
1074 For convenience, if \a model is a QFileSystemModel, QCompleter switches its
1075 caseSensitivity to Qt::CaseInsensitive on Windows and Qt::CaseSensitive
1076 on other platforms.
1077
1078 \sa completionModel(), modelSorting, {Handling Tree Models}
1079*/
1080void QCompleter::setModel(QAbstractItemModel *model)
1081{
1082 Q_D(QCompleter);
1083 QAbstractItemModel *oldModel = d->proxy->sourceModel();
1084 if (oldModel == model)
1085 return;
1086#if QT_CONFIG(filesystemmodel)
1087 if (qobject_cast<const QFileSystemModel *>(oldModel))
1088 setCompletionRole(Qt::EditRole); // QTBUG-54642, clear FileNameRole set by QFileSystemModel
1089#endif
1090 d->proxy->setSourceModel(model);
1091 if (d->popup)
1092 setPopup(d->popup); // set the model and make new connections
1093 if (oldModel && oldModel->QObject::parent() == this)
1094 delete oldModel;
1095#if QT_CONFIG(filesystemmodel)
1096 QFileSystemModel *fsModel = qobject_cast<QFileSystemModel *>(model);
1097 if (fsModel) {
1098#if defined(Q_OS_WIN)
1099 setCaseSensitivity(Qt::CaseInsensitive);
1100#else
1101 setCaseSensitivity(Qt::CaseSensitive);
1102#endif
1103 setCompletionRole(QFileSystemModel::FileNameRole);
1104 connect(fsModel, SIGNAL(directoryLoaded(QString)), this, SLOT(_q_fileSystemModelDirectoryLoaded(QString)));
1105 }
1106#endif // QT_CONFIG(filesystemmodel)
1107}
1108
1109/*!
1110 Returns the model that provides completion strings.
1111
1112 \sa completionModel()
1113*/
1114QAbstractItemModel *QCompleter::model() const
1115{
1116 Q_D(const QCompleter);
1117 return d->proxy->sourceModel();
1118}
1119
1120/*!
1121 \enum QCompleter::CompletionMode
1122
1123 This enum specifies how completions are provided to the user.
1124
1125 \value PopupCompletion Current completions are displayed in a popup window.
1126 \value InlineCompletion Completions appear inline (as selected text).
1127 \value UnfilteredPopupCompletion All possible completions are displayed in a popup window with the most likely suggestion indicated as current.
1128
1129 \sa setCompletionMode()
1130*/
1131
1132/*!
1133 \property QCompleter::completionMode
1134 \brief how the completions are provided to the user
1135
1136 The default value is QCompleter::PopupCompletion.
1137*/
1138void QCompleter::setCompletionMode(QCompleter::CompletionMode mode)
1139{
1140 Q_D(QCompleter);
1141 d->mode = mode;
1142 d->proxy->setFiltered(mode != QCompleter::UnfilteredPopupCompletion);
1143
1144 if (mode == QCompleter::InlineCompletion) {
1145 if (d->widget)
1146 d->widget->removeEventFilter(this);
1147 if (d->popup) {
1148 d->popup->deleteLater();
1149 d->popup = nullptr;
1150 }
1151 } else {
1152 if (d->widget)
1153 d->widget->installEventFilter(this);
1154 }
1155}
1156
1157QCompleter::CompletionMode QCompleter::completionMode() const
1158{
1159 Q_D(const QCompleter);
1160 return d->mode;
1161}
1162
1163/*!
1164 \property QCompleter::filterMode
1165 \brief This property controls how filtering is performed.
1166 \since 5.2
1167
1168 If filterMode is set to Qt::MatchStartsWith, only those entries that start
1169 with the typed characters will be displayed. Qt::MatchContains will display
1170 the entries that contain the typed characters, and Qt::MatchEndsWith the
1171 ones that end with the typed characters.
1172
1173 Setting filterMode to any other Qt::MatchFlag will issue a warning, and no
1174 action will be performed. Because of this, the \c Qt::MatchCaseSensitive
1175 flag has no effect. Use the \l caseSensitivity property to control case
1176 sensitivity.
1177
1178 The default mode is Qt::MatchStartsWith.
1179
1180 \sa caseSensitivity
1181*/
1182
1183void QCompleter::setFilterMode(Qt::MatchFlags filterMode)
1184{
1185 Q_D(QCompleter);
1186
1187 if (d->filterMode == filterMode)
1188 return;
1189
1190 if (Q_UNLIKELY(filterMode != Qt::MatchStartsWith &&
1191 filterMode != Qt::MatchContains &&
1192 filterMode != Qt::MatchEndsWith)) {
1193 qWarning("Unhandled QCompleter::filterMode flag is used.");
1194 return;
1195 }
1196
1197 d->filterMode = filterMode;
1198 d->proxy->createEngine();
1199 d->proxy->invalidate();
1200}
1201
1202Qt::MatchFlags QCompleter::filterMode() const
1203{
1204 Q_D(const QCompleter);
1205 return d->filterMode;
1206}
1207
1208/*!
1209 Sets the popup used to display completions to \a popup. QCompleter takes
1210 ownership of the view.
1211
1212 A QListView is automatically created when the completionMode() is set to
1213 QCompleter::PopupCompletion or QCompleter::UnfilteredPopupCompletion. The
1214 default popup displays the completionColumn().
1215
1216 Ensure that this function is called before the view settings are modified.
1217 This is required since view's properties may require that a model has been
1218 set on the view (for example, hiding columns in the view requires a model
1219 to be set on the view).
1220
1221 \sa popup()
1222*/
1223void QCompleter::setPopup(QAbstractItemView *popup)
1224{
1225 Q_ASSERT(popup);
1226 Q_D(QCompleter);
1227 if (popup == d->popup)
1228 return;
1229
1230 // Remember existing widget's focus policy, default to NoFocus
1231 const Qt::FocusPolicy origPolicy = d->widget ? d->widget->focusPolicy()
1232 : Qt::NoFocus;
1233
1234 // If popup existed already, disconnect signals and delete object
1235 if (d->popup) {
1236 QObject::disconnect(d->popup->selectionModel(), nullptr, this, nullptr);
1237 QObject::disconnect(d->popup, nullptr, this, nullptr);
1238 delete d->popup;
1239 }
1240
1241 // Assign new object, set model and hide
1242 d->popup = popup;
1243 if (d->popup->model() != d->proxy)
1244 d->popup->setModel(d->proxy);
1245 d->popup->hide();
1246
1247 // Mark the widget window as a popup, so that if the last non-popup window is closed by the
1248 // user, the application should not be prevented from exiting. It needs to be set explicitly via
1249 // setWindowFlag(), because passing the flag via setParent(parent, windowFlags) does not call
1250 // QWidgetPrivate::adjustQuitOnCloseAttribute(), and causes an application not to exit if the
1251 // popup ends up being the last window.
1252 d->popup->setParent(nullptr);
1253 d->popup->setWindowFlag(Qt::Popup);
1254 d->popup->setFocusPolicy(Qt::NoFocus);
1255 if (d->widget)
1256 d->widget->setFocusPolicy(origPolicy);
1257
1258 d->popup->setFocusProxy(d->widget);
1259 d->popup->installEventFilter(this);
1260 d->popup->setItemDelegate(new QCompleterItemDelegate(d->popup));
1261#if QT_CONFIG(listview)
1262 if (QListView *listView = qobject_cast<QListView *>(d->popup)) {
1263 listView->setModelColumn(d->column);
1264 }
1265#endif
1266
1267 QObject::connect(d->popup, SIGNAL(clicked(QModelIndex)),
1268 this, SLOT(_q_complete(QModelIndex)));
1269 QObject::connect(this, SIGNAL(activated(QModelIndex)),
1270 d->popup, SLOT(hide()));
1271
1272 QObject::connect(d->popup->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
1273 this, SLOT(_q_completionSelected(QItemSelection)));
1274}
1275
1276/*!
1277 Returns the popup used to display completions.
1278
1279 \sa setPopup()
1280*/
1281QAbstractItemView *QCompleter::popup() const
1282{
1283 Q_D(const QCompleter);
1284#if QT_CONFIG(listview)
1285 if (!d->popup && completionMode() != QCompleter::InlineCompletion) {
1286 QListView *listView = new QListView;
1287 listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
1288 listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1289 listView->setSelectionBehavior(QAbstractItemView::SelectRows);
1290 listView->setSelectionMode(QAbstractItemView::SingleSelection);
1291 listView->setModelColumn(d->column);
1292 QCompleter *that = const_cast<QCompleter*>(this);
1293 that->setPopup(listView);
1294 }
1295#endif // QT_CONFIG(listview)
1296 return d->popup;
1297}
1298
1299/*!
1300 \reimp
1301*/
1302bool QCompleter::event(QEvent *ev)
1303{
1304 return QObject::event(ev);
1305}
1306
1307/*!
1308 \reimp
1309*/
1310bool QCompleter::eventFilter(QObject *o, QEvent *e)
1311{
1312 Q_D(QCompleter);
1313
1314 if (o == d->widget) {
1315 switch (e->type()) {
1316 case QEvent::FocusOut:
1317 if (d->eatFocusOut) {
1318 d->hiddenBecauseNoMatch = false;
1319 if (d->popup && d->popup->isVisible())
1320 return true;
1321 }
1322 break;
1323 case QEvent::Hide:
1324 if (d->popup)
1325 d->popup->hide();
1326 break;
1327 default:
1328 break;
1329 }
1330 }
1331
1332 if (o != d->popup)
1333 return QObject::eventFilter(o, e);
1334
1335 Q_ASSERT(d->popup);
1336 switch (e->type()) {
1337 case QEvent::KeyPress: {
1338 QKeyEvent *ke = static_cast<QKeyEvent *>(e);
1339
1340 QModelIndex curIndex = d->popup->currentIndex();
1341 QModelIndexList selList = d->popup->selectionModel()->selectedIndexes();
1342
1343 const int key = ke->key();
1344 // In UnFilteredPopup mode, select the current item
1345 if ((key == Qt::Key_Up || key == Qt::Key_Down) && selList.isEmpty() && curIndex.isValid()
1346 && d->mode == QCompleter::UnfilteredPopupCompletion) {
1347 d->setCurrentIndex(curIndex);
1348 return true;
1349 }
1350
1351 // Handle popup navigation keys. These are hardcoded because up/down might make the
1352 // widget do something else (lineedit cursor moves to home/end on mac, for instance)
1353 switch (key) {
1354 case Qt::Key_End:
1355 case Qt::Key_Home:
1356 if (ke->modifiers() & Qt::ControlModifier)
1357 return false;
1358 break;
1359
1360 case Qt::Key_Up:
1361 if (!curIndex.isValid()) {
1362 int rowCount = d->proxy->rowCount();
1363 QModelIndex lastIndex = d->proxy->index(rowCount - 1, d->column);
1364 d->setCurrentIndex(lastIndex);
1365 return true;
1366 } else if (curIndex.row() == 0) {
1367 if (d->wrap)
1368 d->setCurrentIndex(QModelIndex());
1369 return true;
1370 }
1371 return false;
1372
1373 case Qt::Key_Down:
1374 if (!curIndex.isValid()) {
1375 QModelIndex firstIndex = d->proxy->index(0, d->column);
1376 d->setCurrentIndex(firstIndex);
1377 return true;
1378 } else if (curIndex.row() == d->proxy->rowCount() - 1) {
1379 if (d->wrap)
1380 d->setCurrentIndex(QModelIndex());
1381 return true;
1382 }
1383 return false;
1384
1385 case Qt::Key_PageUp:
1386 case Qt::Key_PageDown:
1387 return false;
1388 }
1389
1390 if (d->widget) {
1391 // Send the event to the widget. If the widget accepted the event, do nothing
1392 // If the widget did not accept the event, provide a default implementation
1393 d->eatFocusOut = false;
1394 (static_cast<QObject *>(d->widget))->event(ke);
1395 d->eatFocusOut = true;
1396 }
1397 if (!d->widget || e->isAccepted() || !d->popup->isVisible()) {
1398 // widget lost focus, hide the popup
1399 if (d->widget && (!d->widget->hasFocus()
1400#ifdef QT_KEYPAD_NAVIGATION
1401 || (QApplicationPrivate::keypadNavigationEnabled() && !d->widget->hasEditFocus())
1402#endif
1403 ))
1404 d->popup->hide();
1405 if (e->isAccepted())
1406 return true;
1407 }
1408
1409 // default implementation for keys not handled by the widget when popup is open
1410#if QT_CONFIG(shortcut)
1411 if (ke->matches(QKeySequence::Cancel)) {
1412 d->popup->hide();
1413 return true;
1414 }
1415#endif
1416 switch (key) {
1417#ifdef QT_KEYPAD_NAVIGATION
1418 case Qt::Key_Select:
1419 if (!QApplicationPrivate::keypadNavigationEnabled())
1420 break;
1421#endif
1422 case Qt::Key_Return:
1423 case Qt::Key_Enter:
1424 case Qt::Key_Tab:
1425 d->popup->hide();
1426 if (curIndex.isValid())
1427 d->_q_complete(curIndex);
1428 break;
1429
1430 case Qt::Key_F4:
1431 if (ke->modifiers() & Qt::AltModifier)
1432 d->popup->hide();
1433 break;
1434
1435 case Qt::Key_Backtab:
1436 d->popup->hide();
1437 break;
1438
1439 default:
1440 break;
1441 }
1442
1443 return true;
1444 }
1445
1446#ifdef QT_KEYPAD_NAVIGATION
1447 case QEvent::KeyRelease: {
1448 if (d->widget &&
1449 QApplicationPrivate::keypadNavigationEnabled() && ke->key() == Qt::Key_Back) {
1450 QKeyEvent *ke = static_cast<QKeyEvent *>(e);
1451 // Send the event to the 'widget'. This is what we did for KeyPress, so we need
1452 // to do the same for KeyRelease, in case the widget's KeyPress event set
1453 // up something (such as a timer) that is relying on also receiving the
1454 // key release. I see this as a bug in Qt, and should really set it up for all
1455 // the affected keys. However, it is difficult to tell how this will affect
1456 // existing code, and I can't test for every combination!
1457 d->eatFocusOut = false;
1458 static_cast<QObject *>(d->widget)->event(ke);
1459 d->eatFocusOut = true;
1460 }
1461 break;
1462 }
1463#endif
1464
1465 case QEvent::MouseButtonPress: {
1466#ifdef QT_KEYPAD_NAVIGATION
1467 if (d->widget
1468 && QApplicationPrivate::keypadNavigationEnabled()) {
1469 // if we've clicked in the widget (or its descendant), let it handle the click
1470 QWidget *source = qobject_cast<QWidget *>(o);
1471 if (source) {
1472 QPoint pos = source->mapToGlobal((static_cast<QMouseEvent *>(e))->pos());
1473 QWidget *target = QApplication::widgetAt(pos);
1474 if (target && (d->widget->isAncestorOf(target) ||
1475 target == d->widget)) {
1476 d->eatFocusOut = false;
1477 static_cast<QObject *>(target)->event(e);
1478 d->eatFocusOut = true;
1479 return true;
1480 }
1481 }
1482 }
1483#endif
1484 if (!d->popup->underMouse()) {
1485 if (!QGuiApplicationPrivate::maybeForwardEventToVirtualKeyboard(e))
1486 d->popup->hide();
1487 return true;
1488 }
1489 }
1490 return false;
1491
1492 case QEvent::MouseButtonRelease:
1493 QGuiApplicationPrivate::maybeForwardEventToVirtualKeyboard(e);
1494 return true;
1495 case QEvent::InputMethod:
1496 case QEvent::ShortcutOverride:
1497 if (d->widget)
1498 QCoreApplication::sendEvent(d->widget, e);
1499 break;
1500
1501 default:
1502 return false;
1503 }
1504 return false;
1505}
1506
1507/*!
1508 For QCompleter::PopupCompletion and QCompletion::UnfilteredPopupCompletion
1509 modes, calling this function displays the popup displaying the current
1510 completions. By default, if \a rect is not specified, the popup is displayed
1511 on the bottom of the widget(). If \a rect is specified the popup is
1512 displayed on the left edge of the rectangle.
1513
1514 For QCompleter::InlineCompletion mode, the highlighted() signal is fired
1515 with the current completion.
1516*/
1517void QCompleter::complete(const QRect& rect)
1518{
1519 Q_D(QCompleter);
1520 QModelIndex idx = d->proxy->currentIndex(false);
1521 d->hiddenBecauseNoMatch = false;
1522 if (d->mode == QCompleter::InlineCompletion) {
1523 if (idx.isValid())
1524 d->_q_complete(idx, true);
1525 return;
1526 }
1527
1528 Q_ASSERT(d->widget);
1529 if ((d->mode == QCompleter::PopupCompletion && !idx.isValid())
1530 || (d->mode == QCompleter::UnfilteredPopupCompletion && d->proxy->rowCount() == 0)) {
1531 if (d->popup)
1532 d->popup->hide(); // no suggestion, hide
1533 d->hiddenBecauseNoMatch = true;
1534 return;
1535 }
1536
1537 popup();
1538 if (d->mode == QCompleter::UnfilteredPopupCompletion)
1539 d->setCurrentIndex(idx, false);
1540
1541 d->showPopup(rect);
1542 d->popupRect = rect;
1543}
1544
1545/*!
1546 Sets the current row to the \a row specified. Returns \c true if successful;
1547 otherwise returns \c false.
1548
1549 This function may be used along with currentCompletion() to iterate
1550 through all the possible completions.
1551
1552 \sa currentCompletion(), completionCount()
1553*/
1554bool QCompleter::setCurrentRow(int row)
1555{
1556 Q_D(QCompleter);
1557 return d->proxy->setCurrentRow(row);
1558}
1559
1560/*!
1561 Returns the current row.
1562
1563 \sa setCurrentRow()
1564*/
1565int QCompleter::currentRow() const
1566{
1567 Q_D(const QCompleter);
1568 return d->proxy->currentRow();
1569}
1570
1571/*!
1572 Returns the number of completions for the current prefix. For an unsorted
1573 model with a large number of items this can be expensive. Use setCurrentRow()
1574 and currentCompletion() to iterate through all the completions.
1575*/
1576int QCompleter::completionCount() const
1577{
1578 Q_D(const QCompleter);
1579 return d->proxy->completionCount();
1580}
1581
1582/*!
1583 \enum QCompleter::ModelSorting
1584
1585 This enum specifies how the items in the model are sorted.
1586
1587 \value UnsortedModel The model is unsorted.
1588 \value CaseSensitivelySortedModel The model is sorted case sensitively.
1589 \value CaseInsensitivelySortedModel The model is sorted case insensitively.
1590
1591 \sa setModelSorting()
1592*/
1593
1594/*!
1595 \property QCompleter::modelSorting
1596 \brief the way the model is sorted
1597
1598 By default, no assumptions are made about the order of the items
1599 in the model that provides the completions.
1600
1601 If the model's data for the completionColumn() and completionRole() is sorted in
1602 ascending order, you can set this property to \l CaseSensitivelySortedModel
1603 or \l CaseInsensitivelySortedModel. On large models, this can lead to
1604 significant performance improvements because the completer object can
1605 then use a binary search algorithm instead of linear search algorithm.
1606
1607 The sort order (i.e ascending or descending order) of the model is determined
1608 dynamically by inspecting the contents of the model.
1609
1610 \b{Note:} The performance improvements described above cannot take place
1611 when the completer's \l caseSensitivity is different to the case sensitivity
1612 used by the model's when sorting.
1613
1614 \sa setCaseSensitivity(), QCompleter::ModelSorting
1615*/
1616void QCompleter::setModelSorting(QCompleter::ModelSorting sorting)
1617{
1618 Q_D(QCompleter);
1619 if (d->sorting == sorting)
1620 return;
1621 d->sorting = sorting;
1622 d->proxy->createEngine();
1623 d->proxy->invalidate();
1624}
1625
1626QCompleter::ModelSorting QCompleter::modelSorting() const
1627{
1628 Q_D(const QCompleter);
1629 return d->sorting;
1630}
1631
1632/*!
1633 \property QCompleter::completionColumn
1634 \brief the column in the model in which completions are searched for.
1635
1636 If the popup() is a QListView, it is automatically setup to display
1637 this column.
1638
1639 By default, the match column is 0.
1640
1641 \sa completionRole, caseSensitivity
1642*/
1643void QCompleter::setCompletionColumn(int column)
1644{
1645 Q_D(QCompleter);
1646 if (d->column == column)
1647 return;
1648#if QT_CONFIG(listview)
1649 if (QListView *listView = qobject_cast<QListView *>(d->popup))
1650 listView->setModelColumn(column);
1651#endif
1652 d->column = column;
1653 d->proxy->invalidate();
1654}
1655
1656int QCompleter::completionColumn() const
1657{
1658 Q_D(const QCompleter);
1659 return d->column;
1660}
1661
1662/*!
1663 \property QCompleter::completionRole
1664 \brief the item role to be used to query the contents of items for matching.
1665
1666 The default role is Qt::EditRole.
1667
1668 \sa completionColumn, caseSensitivity
1669*/
1670void QCompleter::setCompletionRole(int role)
1671{
1672 Q_D(QCompleter);
1673 if (d->role == role)
1674 return;
1675 d->role = role;
1676 d->proxy->invalidate();
1677}
1678
1679int QCompleter::completionRole() const
1680{
1681 Q_D(const QCompleter);
1682 return d->role;
1683}
1684
1685/*!
1686 \property QCompleter::wrapAround
1687 \brief the completions wrap around when navigating through items
1688 \since 4.3
1689
1690 The default is true.
1691*/
1692void QCompleter::setWrapAround(bool wrap)
1693{
1694 Q_D(QCompleter);
1695 if (d->wrap == wrap)
1696 return;
1697 d->wrap = wrap;
1698}
1699
1700bool QCompleter::wrapAround() const
1701{
1702 Q_D(const QCompleter);
1703 return d->wrap;
1704}
1705
1706/*!
1707 \property QCompleter::maxVisibleItems
1708 \brief the maximum allowed size on screen of the completer, measured in items
1709 \since 4.6
1710
1711 By default, this property has a value of 7.
1712*/
1713int QCompleter::maxVisibleItems() const
1714{
1715 Q_D(const QCompleter);
1716 return d->maxVisibleItems;
1717}
1718
1719void QCompleter::setMaxVisibleItems(int maxItems)
1720{
1721 Q_D(QCompleter);
1722 if (Q_UNLIKELY(maxItems < 0)) {
1723 qWarning("QCompleter::setMaxVisibleItems: "
1724 "Invalid max visible items (%d) must be >= 0", maxItems);
1725 return;
1726 }
1727 d->maxVisibleItems = maxItems;
1728}
1729
1730/*!
1731 \property QCompleter::caseSensitivity
1732 \brief the case sensitivity of the matching
1733
1734 The default value is \c Qt::CaseSensitive.
1735
1736 \sa completionColumn, completionRole, modelSorting, filterMode
1737*/
1738void QCompleter::setCaseSensitivity(Qt::CaseSensitivity cs)
1739{
1740 Q_D(QCompleter);
1741 if (d->cs == cs)
1742 return;
1743 d->cs = cs;
1744 d->proxy->createEngine();
1745 d->proxy->invalidate();
1746}
1747
1748Qt::CaseSensitivity QCompleter::caseSensitivity() const
1749{
1750 Q_D(const QCompleter);
1751 return d->cs;
1752}
1753
1754/*!
1755 \property QCompleter::completionPrefix
1756 \brief the completion prefix used to provide completions.
1757
1758 The completionModel() is updated to reflect the list of possible
1759 matches for \a prefix.
1760*/
1761void QCompleter::setCompletionPrefix(const QString &prefix)
1762{
1763 Q_D(QCompleter);
1764 d->prefix = prefix;
1765 d->proxy->filter(splitPath(prefix));
1766}
1767
1768QString QCompleter::completionPrefix() const
1769{
1770 Q_D(const QCompleter);
1771 return d->prefix;
1772}
1773
1774/*!
1775 Returns the model index of the current completion in the completionModel().
1776
1777 \sa setCurrentRow(), currentCompletion(), model()
1778*/
1779QModelIndex QCompleter::currentIndex() const
1780{
1781 Q_D(const QCompleter);
1782 return d->proxy->currentIndex(false);
1783}
1784
1785/*!
1786 Returns the current completion string. This includes the \l completionPrefix.
1787 When used alongside setCurrentRow(), it can be used to iterate through
1788 all the matches.
1789
1790 \sa setCurrentRow(), currentIndex()
1791*/
1792QString QCompleter::currentCompletion() const
1793{
1794 Q_D(const QCompleter);
1795 return pathFromIndex(d->proxy->currentIndex(true));
1796}
1797
1798/*!
1799 Returns the completion model. The completion model is a read-only list model
1800 that contains all the possible matches for the current completion prefix.
1801 The completion model is auto-updated to reflect the current completions.
1802
1803 \note The return value of this function is defined to be an QAbstractItemModel
1804 purely for generality. This actual kind of model returned is an instance of an
1805 QAbstractProxyModel subclass.
1806
1807 \sa completionPrefix, model()
1808*/
1809QAbstractItemModel *QCompleter::completionModel() const
1810{
1811 Q_D(const QCompleter);
1812 return d->proxy;
1813}
1814
1815/*!
1816 Returns the path for the given \a index. The completer object uses this to
1817 obtain the completion text from the underlying model.
1818
1819 The default implementation returns the \l{Qt::EditRole}{edit role} of the
1820 item for list models. It returns the absolute file path if the model is a
1821 QFileSystemModel.
1822
1823 \sa splitPath()
1824*/
1825
1826QString QCompleter::pathFromIndex(const QModelIndex& index) const
1827{
1828 Q_D(const QCompleter);
1829 if (!index.isValid())
1830 return QString();
1831
1832 QAbstractItemModel *sourceModel = d->proxy->sourceModel();
1833 if (!sourceModel)
1834 return QString();
1835 bool isFsModel = false;
1836#if QT_CONFIG(filesystemmodel)
1837 isFsModel = qobject_cast<QFileSystemModel *>(d->proxy->sourceModel()) != nullptr;
1838#endif
1839 if (!isFsModel)
1840 return sourceModel->data(index, d->role).toString();
1841
1842 QModelIndex idx = index;
1843 QStringList list;
1844 do {
1845 QString t;
1846#if QT_CONFIG(filesystemmodel)
1847 t = sourceModel->data(idx, QFileSystemModel::FileNameRole).toString();
1848#endif
1849 list.prepend(t);
1850 QModelIndex parent = idx.parent();
1851 idx = parent.sibling(parent.row(), index.column());
1852 } while (idx.isValid());
1853
1854#if !defined(Q_OS_WIN)
1855 if (list.size() == 1) // only the separator or some other text
1856 return list[0];
1857 list[0].clear() ; // the join below will provide the separator
1858#endif
1859
1860 return list.join(QDir::separator());
1861}
1862
1863/*!
1864 Splits the given \a path into strings that are used to match at each level
1865 in the model().
1866
1867 The default implementation of splitPath() splits a file system path based on
1868 QDir::separator() when the sourceModel() is a QFileSystemModel.
1869
1870 When used with list models, the first item in the returned list is used for
1871 matching.
1872
1873 \sa pathFromIndex(), {Handling Tree Models}
1874*/
1875QStringList QCompleter::splitPath(const QString& path) const
1876{
1877 bool isFsModel = false;
1878#if QT_CONFIG(filesystemmodel)
1879 Q_D(const QCompleter);
1880 isFsModel = qobject_cast<QFileSystemModel *>(d->proxy->sourceModel()) != nullptr;
1881#endif
1882
1883 if (!isFsModel || path.isEmpty())
1884 return QStringList(completionPrefix());
1885
1886 QString pathCopy = QDir::toNativeSeparators(path);
1887#if defined(Q_OS_WIN)
1888 if (pathCopy == "\\"_L1 || pathCopy == "\\\\"_L1)
1889 return QStringList(pathCopy);
1890 const bool startsWithDoubleSlash = pathCopy.startsWith("\\\\"_L1);
1891 if (startsWithDoubleSlash)
1892 pathCopy = pathCopy.mid(2);
1893#endif
1894
1895 const QChar sep = QDir::separator();
1896 QStringList parts = pathCopy.split(sep);
1897
1898#if defined(Q_OS_WIN)
1899 if (startsWithDoubleSlash)
1900 parts[0].prepend("\\\\"_L1);
1901#else
1902 if (pathCopy[0] == sep) // readd the "/" at the beginning as the split removed it
1903 parts[0] = u'/';
1904#endif
1905
1906 return parts;
1907}
1908
1909/*!
1910 \fn void QCompleter::activated(const QModelIndex& index)
1911
1912 This signal is sent when an item in the popup() is activated by the user.
1913 (by clicking or pressing return). The item's \a index in the completionModel()
1914 is given.
1915
1916*/
1917
1918/*!
1919 \fn void QCompleter::activated(const QString &text)
1920
1921 This signal is sent when an item in the popup() is activated by the user (by
1922 clicking or pressing return). The item's \a text is given.
1923
1924*/
1925
1926/*!
1927 \fn void QCompleter::highlighted(const QModelIndex& index)
1928
1929 This signal is sent when an item in the popup() is highlighted by
1930 the user. It is also sent if complete() is called with the completionMode()
1931 set to QCompleter::InlineCompletion. The item's \a index in the completionModel()
1932 is given.
1933*/
1934
1935/*!
1936 \fn void QCompleter::highlighted(const QString &text)
1937
1938 This signal is sent when an item in the popup() is highlighted by
1939 the user. It is also sent if complete() is called with the completionMode()
1940 set to QCompleter::InlineCompletion. The item's \a text is given.
1941*/
1942
1943QT_END_NAMESPACE
1944
1945#include "moc_qcompleter.cpp"
1946
1947#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