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

Last change on this file since 651 was 651, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.6.2 sources.

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