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