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