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

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