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