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

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

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

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