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

source code of qtbase/src/widgets/util/qcompleter.cpp