source: trunk/src/gui/util/qcompleter.cpp@ 875

Last change on this file since 875 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

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