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

Last change on this file since 67 was 2, checked in by Dmitry A. Kuminov, 16 years ago

Initially imported qt-all-opensource-src-4.5.1 from Trolltech.

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