summaryrefslogtreecommitdiffstats
path: root/src/widgets/util/qcompleter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/widgets/util/qcompleter.cpp')
-rw-r--r--src/widgets/util/qcompleter.cpp1833
1 files changed, 1833 insertions, 0 deletions
diff --git a/src/widgets/util/qcompleter.cpp b/src/widgets/util/qcompleter.cpp
new file mode 100644
index 0000000000..d1ec03f1e4
--- /dev/null
+++ b/src/widgets/util/qcompleter.cpp
@@ -0,0 +1,1833 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+ \class QCompleter
+ \brief The QCompleter class provides completions based on an item model.
+ \since 4.2
+
+ You can use QCompleter to provide auto completions in any Qt
+ widget, such as QLineEdit and QComboBox.
+ When the user starts typing a word, QCompleter suggests possible ways of
+ completing the word, based on a word list. The word list is
+ provided as a QAbstractItemModel. (For simple applications, where
+ the word list is static, you can pass a QStringList to
+ QCompleter's constructor.)
+
+ \tableofcontents
+
+ \section1 Basic Usage
+
+ A QCompleter is used typically with a QLineEdit or QComboBox.
+ For example, here's how to provide auto completions from a simple
+ word list in a QLineEdit:
+
+ \snippet doc/src/snippets/code/src_gui_util_qcompleter.cpp 0
+
+ A QFileSystemModel can be used to provide auto completion of file names.
+ For example:
+
+ \snippet doc/src/snippets/code/src_gui_util_qcompleter.cpp 1
+
+ To set the model on which QCompleter should operate, call
+ setModel(). By default, QCompleter will attempt to match the \l
+ {completionPrefix}{completion prefix} (i.e., the word that the
+ user has started typing) against the Qt::EditRole data stored in
+ column 0 in the model case sensitively. This can be changed
+ using setCompletionRole(), setCompletionColumn(), and
+ setCaseSensitivity().
+
+ If the model is sorted on the column and role that are used for completion,
+ you can call setModelSorting() with either
+ QCompleter::CaseSensitivelySortedModel or
+ QCompleter::CaseInsensitivelySortedModel as the argument. On large models,
+ this can lead to significant performance improvements, because QCompleter
+ can then use binary search instead of linear search.
+
+ The model can be a \l{QAbstractListModel}{list model},
+ a \l{QAbstractTableModel}{table model}, or a
+ \l{QAbstractItemModel}{tree model}. Completion on tree models
+ is slightly more involved and is covered in the \l{Handling
+ Tree Models} section below.
+
+ The completionMode() determines the mode used to provide completions to
+ the user.
+
+ \section1 Iterating Through Completions
+
+ To retrieve a single candidate string, call setCompletionPrefix()
+ with the text that needs to be completed and call
+ currentCompletion(). You can iterate through the list of
+ completions as below:
+
+ \snippet doc/src/snippets/code/src_gui_util_qcompleter.cpp 2
+
+ completionCount() returns the total number of completions for the
+ current prefix. completionCount() should be avoided when possible,
+ since it requires a scan of the entire model.
+
+ \section1 The Completion Model
+
+ completionModel() return a list model that contains all possible
+ completions for the current completion prefix, in the order in which
+ they appear in the model. This model can be used to display the current
+ completions in a custom view. Calling setCompletionPrefix() automatically
+ refreshes the completion model.
+
+ \section1 Handling Tree Models
+
+ QCompleter can look for completions in tree models, assuming
+ that any item (or sub-item or sub-sub-item) can be unambiguously
+ represented as a string by specifying the path to the item. The
+ completion is then performed one level at a time.
+
+ Let's take the example of a user typing in a file system path.
+ The model is a (hierarchical) QFileSystemModel. The completion
+ occurs for every element in the path. For example, if the current
+ text is \c C:\Wind, QCompleter might suggest \c Windows to
+ complete the current path element. Similarly, if the current text
+ is \c C:\Windows\Sy, QCompleter might suggest \c System.
+
+ For this kind of completion to work, QCompleter needs to be able to
+ split the path into a list of strings that are matched at each level.
+ For \c C:\Windows\Sy, it needs to be split as "C:", "Windows" and "Sy".
+ The default implementation of splitPath(), splits the completionPrefix
+ using QDir::separator() if the model is a QFileSystemModel.
+
+ To provide completions, QCompleter needs to know the path from an index.
+ This is provided by pathFromIndex(). The default implementation of
+ pathFromIndex(), returns the data for the \l{Qt::EditRole}{edit role}
+ for list models and the absolute file path if the mode is a QFileSystemModel.
+
+ \sa QAbstractItemModel, QLineEdit, QComboBox, {Completer Example}
+*/
+
+#include "qcompleter_p.h"
+
+#ifndef QT_NO_COMPLETER
+
+#include "QtWidgets/qscrollbar.h"
+#include "QtWidgets/qstringlistmodel.h"
+#include "QtWidgets/qdirmodel.h"
+#include "QtWidgets/qfilesystemmodel.h"
+#include "QtWidgets/qheaderview.h"
+#include "QtWidgets/qlistview.h"
+#include "QtWidgets/qapplication.h"
+#include "QtGui/qevent.h"
+#include "QtWidgets/qheaderview.h"
+#include "QtWidgets/qdesktopwidget.h"
+#include "QtWidgets/qlineedit.h"
+
+QT_BEGIN_NAMESPACE
+
+QCompletionModel::QCompletionModel(QCompleterPrivate *c, QObject *parent)
+ : QAbstractProxyModel(*new QCompletionModelPrivate, parent),
+ c(c), showAll(false)
+{
+ createEngine();
+}
+
+int QCompletionModel::columnCount(const QModelIndex &) const
+{
+ Q_D(const QCompletionModel);
+ return d->model->columnCount();
+}
+
+void QCompletionModel::setSourceModel(QAbstractItemModel *source)
+{
+ bool hadModel = (sourceModel() != 0);
+
+ if (hadModel)
+ QObject::disconnect(sourceModel(), 0, this, 0);
+
+ QAbstractProxyModel::setSourceModel(source);
+
+ if (source) {
+ // TODO: Optimize updates in the source model
+ connect(source, SIGNAL(modelReset()), this, SLOT(invalidate()));
+ connect(source, SIGNAL(destroyed()), this, SLOT(modelDestroyed()));
+ connect(source, SIGNAL(layoutChanged()), this, SLOT(invalidate()));
+ connect(source, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(rowsInserted()));
+ connect(source, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(invalidate()));
+ connect(source, SIGNAL(columnsInserted(QModelIndex,int,int)), this, SLOT(invalidate()));
+ connect(source, SIGNAL(columnsRemoved(QModelIndex,int,int)), this, SLOT(invalidate()));
+ connect(source, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(invalidate()));
+ }
+
+ invalidate();
+}
+
+void QCompletionModel::createEngine()
+{
+ bool sortedEngine = false;
+ switch (c->sorting) {
+ case QCompleter::UnsortedModel:
+ sortedEngine = false;
+ break;
+ case QCompleter::CaseSensitivelySortedModel:
+ sortedEngine = c->cs == Qt::CaseSensitive;
+ break;
+ case QCompleter::CaseInsensitivelySortedModel:
+ sortedEngine = c->cs == Qt::CaseInsensitive;
+ break;
+ }
+
+ if (sortedEngine)
+ engine.reset(new QSortedModelEngine(c));
+ else
+ engine.reset(new QUnsortedModelEngine(c));
+}
+
+QModelIndex QCompletionModel::mapToSource(const QModelIndex& index) const
+{
+ Q_D(const QCompletionModel);
+ if (!index.isValid())
+ return engine->curParent;
+
+ int row;
+ QModelIndex parent = engine->curParent;
+ if (!showAll) {
+ if (!engine->matchCount())
+ return QModelIndex();
+ Q_ASSERT(index.row() < engine->matchCount());
+ QIndexMapper& rootIndices = engine->historyMatch.indices;
+ if (index.row() < rootIndices.count()) {
+ row = rootIndices[index.row()];
+ parent = QModelIndex();
+ } else {
+ row = engine->curMatch.indices[index.row() - rootIndices.count()];
+ }
+ } else {
+ row = index.row();
+ }
+
+ return d->model->index(row, index.column(), parent);
+}
+
+QModelIndex QCompletionModel::mapFromSource(const QModelIndex& idx) const
+{
+ if (!idx.isValid())
+ return QModelIndex();
+
+ int row = -1;
+ if (!showAll) {
+ if (!engine->matchCount())
+ return QModelIndex();
+
+ QIndexMapper& rootIndices = engine->historyMatch.indices;
+ if (idx.parent().isValid()) {
+ if (idx.parent() != engine->curParent)
+ return QModelIndex();
+ } else {
+ row = rootIndices.indexOf(idx.row());
+ if (row == -1 && engine->curParent.isValid())
+ return QModelIndex(); // source parent and our parent don't match
+ }
+
+ if (row == -1) {
+ QIndexMapper& indices = engine->curMatch.indices;
+ engine->filterOnDemand(idx.row() - indices.last());
+ row = indices.indexOf(idx.row()) + rootIndices.count();
+ }
+
+ if (row == -1)
+ return QModelIndex();
+ } else {
+ if (idx.parent() != engine->curParent)
+ return QModelIndex();
+ row = idx.row();
+ }
+
+ return createIndex(row, idx.column());
+}
+
+bool QCompletionModel::setCurrentRow(int row)
+{
+ if (row < 0 || !engine->matchCount())
+ return false;
+
+ if (row >= engine->matchCount())
+ engine->filterOnDemand(row + 1 - engine->matchCount());
+
+ if (row >= engine->matchCount()) // invalid row
+ return false;
+
+ engine->curRow = row;
+ return true;
+}
+
+QModelIndex QCompletionModel::currentIndex(bool sourceIndex) const
+{
+ if (!engine->matchCount())
+ return QModelIndex();
+
+ int row = engine->curRow;
+ if (showAll)
+ row = engine->curMatch.indices[engine->curRow];
+
+ QModelIndex idx = createIndex(row, c->column);
+ if (!sourceIndex)
+ return idx;
+ return mapToSource(idx);
+}
+
+QModelIndex QCompletionModel::index(int row, int column, const QModelIndex& parent) const
+{
+ Q_D(const QCompletionModel);
+ if (row < 0 || column < 0 || column >= columnCount(parent) || parent.isValid())
+ return QModelIndex();
+
+ if (!showAll) {
+ if (!engine->matchCount())
+ return QModelIndex();
+ if (row >= engine->historyMatch.indices.count()) {
+ int want = row + 1 - engine->matchCount();
+ if (want > 0)
+ engine->filterOnDemand(want);
+ if (row >= engine->matchCount())
+ return QModelIndex();
+ }
+ } else {
+ if (row >= d->model->rowCount(engine->curParent))
+ return QModelIndex();
+ }
+
+ return createIndex(row, column);
+}
+
+int QCompletionModel::completionCount() const
+{
+ if (!engine->matchCount())
+ return 0;
+
+ engine->filterOnDemand(INT_MAX);
+ return engine->matchCount();
+}
+
+int QCompletionModel::rowCount(const QModelIndex &parent) const
+{
+ Q_D(const QCompletionModel);
+ if (parent.isValid())
+ return 0;
+
+ if (showAll) {
+ // Show all items below current parent, even if we have no valid matches
+ if (engine->curParts.count() != 1 && !engine->matchCount()
+ && !engine->curParent.isValid())
+ return 0;
+ return d->model->rowCount(engine->curParent);
+ }
+
+ return completionCount();
+}
+
+void QCompletionModel::setFiltered(bool filtered)
+{
+ if (showAll == !filtered)
+ return;
+ showAll = !filtered;
+ resetModel();
+}
+
+bool QCompletionModel::hasChildren(const QModelIndex &parent) const
+{
+ Q_D(const QCompletionModel);
+ if (parent.isValid())
+ return false;
+
+ if (showAll)
+ return d->model->hasChildren(mapToSource(parent));
+
+ if (!engine->matchCount())
+ return false;
+
+ return true;
+}
+
+QVariant QCompletionModel::data(const QModelIndex& index, int role) const
+{
+ Q_D(const QCompletionModel);
+ return d->model->data(mapToSource(index), role);
+}
+
+void QCompletionModel::modelDestroyed()
+{
+ QAbstractProxyModel::setSourceModel(0); // switch to static empty model
+ invalidate();
+}
+
+void QCompletionModel::rowsInserted()
+{
+ invalidate();
+ emit rowsAdded();
+}
+
+void QCompletionModel::invalidate()
+{
+ engine->cache.clear();
+ filter(engine->curParts);
+}
+
+void QCompletionModel::filter(const QStringList& parts)
+{
+ Q_D(QCompletionModel);
+ engine->filter(parts);
+ resetModel();
+
+ if (d->model->canFetchMore(engine->curParent))
+ d->model->fetchMore(engine->curParent);
+}
+
+void QCompletionModel::resetModel()
+{
+ if (rowCount() == 0) {
+ reset();
+ return;
+ }
+
+ emit layoutAboutToBeChanged();
+ QModelIndexList piList = persistentIndexList();
+ QModelIndexList empty;
+ for (int i = 0; i < piList.size(); i++)
+ empty.append(QModelIndex());
+ changePersistentIndexList(piList, empty);
+ emit layoutChanged();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+void QCompletionEngine::filter(const QStringList& parts)
+{
+ const QAbstractItemModel *model = c->proxy->sourceModel();
+ curParts = parts;
+ if (curParts.isEmpty())
+ curParts.append(QString());
+
+ curRow = -1;
+ curParent = QModelIndex();
+ curMatch = QMatchData();
+ historyMatch = filterHistory();
+
+ if (!model)
+ return;
+
+ QModelIndex parent;
+ for (int i = 0; i < curParts.count() - 1; i++) {
+ QString part = curParts[i];
+ int emi = filter(part, parent, -1).exactMatchIndex;
+ if (emi == -1)
+ return;
+ parent = model->index(emi, c->column, parent);
+ }
+
+ // Note that we set the curParent to a valid parent, even if we have no matches
+ // When filtering is disabled, we show all the items under this parent
+ curParent = parent;
+ if (curParts.last().isEmpty())
+ curMatch = QMatchData(QIndexMapper(0, model->rowCount(curParent) - 1), -1, false);
+ else
+ curMatch = filter(curParts.last(), curParent, 1); // build at least one
+ curRow = curMatch.isValid() ? 0 : -1;
+}
+
+QMatchData QCompletionEngine::filterHistory()
+{
+ QAbstractItemModel *source = c->proxy->sourceModel();
+ if (curParts.count() <= 1 || c->proxy->showAll || !source)
+ return QMatchData();
+ bool isDirModel = false;
+ bool isFsModel = false;
+#ifndef QT_NO_DIRMODEL
+ isDirModel = (qobject_cast<QDirModel *>(source) != 0);
+#endif
+#ifndef QT_NO_FILESYSTEMMODEL
+ isFsModel = (qobject_cast<QFileSystemModel *>(source) != 0);
+#endif
+ QVector<int> v;
+ QIndexMapper im(v);
+ QMatchData m(im, -1, true);
+
+ for (int i = 0; i < source->rowCount(); i++) {
+ QString str = source->index(i, c->column).data().toString();
+ if (str.startsWith(c->prefix, c->cs)
+#if (!defined(Q_OS_WIN) || defined(Q_OS_WINCE)) && !defined(Q_OS_SYMBIAN)
+ && ((!isFsModel && !isDirModel) || QDir::toNativeSeparators(str) != QDir::separator())
+#endif
+ )
+ m.indices.append(i);
+ }
+ return m;
+}
+
+// Returns a match hint from the cache by chopping the search string
+bool QCompletionEngine::matchHint(QString part, const QModelIndex& parent, QMatchData *hint)
+{
+ if (c->cs == Qt::CaseInsensitive)
+ part = part.toLower();
+
+ const CacheItem& map = cache[parent];
+
+ QString key = part;
+ while (!key.isEmpty()) {
+ key.chop(1);
+ if (map.contains(key)) {
+ *hint = map[key];
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool QCompletionEngine::lookupCache(QString part, const QModelIndex& parent, QMatchData *m)
+{
+ if (c->cs == Qt::CaseInsensitive)
+ part = part.toLower();
+ const CacheItem& map = cache[parent];
+ if (!map.contains(part))
+ return false;
+ *m = map[part];
+ return true;
+}
+
+// When the cache size exceeds 1MB, it clears out about 1/2 of the cache.
+void QCompletionEngine::saveInCache(QString part, const QModelIndex& parent, const QMatchData& m)
+{
+ QMatchData old = cache[parent].take(part);
+ cost = cost + m.indices.cost() - old.indices.cost();
+ if (cost * sizeof(int) > 1024 * 1024) {
+ QMap<QModelIndex, CacheItem>::iterator it1 = cache.begin();
+ while (it1 != cache.end()) {
+ CacheItem& ci = it1.value();
+ int sz = ci.count()/2;
+ QMap<QString, QMatchData>::iterator it2 = ci.begin();
+ int i = 0;
+ while (it2 != ci.end() && i < sz) {
+ cost -= it2.value().indices.cost();
+ it2 = ci.erase(it2);
+ i++;
+ }
+ if (ci.count() == 0) {
+ it1 = cache.erase(it1);
+ } else {
+ ++it1;
+ }
+ }
+ }
+
+ if (c->cs == Qt::CaseInsensitive)
+ part = part.toLower();
+ cache[parent][part] = m;
+}
+
+///////////////////////////////////////////////////////////////////////////////////
+QIndexMapper QSortedModelEngine::indexHint(QString part, const QModelIndex& parent, Qt::SortOrder order)
+{
+ const QAbstractItemModel *model = c->proxy->sourceModel();
+
+ if (c->cs == Qt::CaseInsensitive)
+ part = part.toLower();
+
+ const CacheItem& map = cache[parent];
+
+ // Try to find a lower and upper bound for the search from previous results
+ int to = model->rowCount(parent) - 1;
+ int from = 0;
+ const CacheItem::const_iterator it = map.lowerBound(part);
+
+ // look backward for first valid hint
+ for(CacheItem::const_iterator it1 = it; it1-- != map.constBegin();) {
+ const QMatchData& value = it1.value();
+ if (value.isValid()) {
+ if (order == Qt::AscendingOrder) {
+ from = value.indices.last() + 1;
+ } else {
+ to = value.indices.first() - 1;
+ }
+ break;
+ }
+ }
+
+ // look forward for first valid hint
+ for(CacheItem::const_iterator it2 = it; it2 != map.constEnd(); ++it2) {
+ const QMatchData& value = it2.value();
+ if (value.isValid() && !it2.key().startsWith(part)) {
+ if (order == Qt::AscendingOrder) {
+ to = value.indices.first() - 1;
+ } else {
+ from = value.indices.first() + 1;
+ }
+ break;
+ }
+ }
+
+ return QIndexMapper(from, to);
+}
+
+Qt::SortOrder QSortedModelEngine::sortOrder(const QModelIndex &parent) const
+{
+ const QAbstractItemModel *model = c->proxy->sourceModel();
+
+ int rowCount = model->rowCount(parent);
+ if (rowCount < 2)
+ return Qt::AscendingOrder;
+ QString first = model->data(model->index(0, c->column, parent), c->role).toString();
+ QString last = model->data(model->index(rowCount - 1, c->column, parent), c->role).toString();
+ return QString::compare(first, last, c->cs) <= 0 ? Qt::AscendingOrder : Qt::DescendingOrder;
+}
+
+QMatchData QSortedModelEngine::filter(const QString& part, const QModelIndex& parent, int)
+{
+ const QAbstractItemModel *model = c->proxy->sourceModel();
+
+ QMatchData hint;
+ if (lookupCache(part, parent, &hint))
+ return hint;
+
+ QIndexMapper indices;
+ Qt::SortOrder order = sortOrder(parent);
+
+ if (matchHint(part, parent, &hint)) {
+ if (!hint.isValid())
+ return QMatchData();
+ indices = hint.indices;
+ } else {
+ indices = indexHint(part, parent, order);
+ }
+
+ // binary search the model within 'indices' for 'part' under 'parent'
+ int high = indices.to() + 1;
+ int low = indices.from() - 1;
+ int probe;
+ QModelIndex probeIndex;
+ QString probeData;
+
+ while (high - low > 1)
+ {
+ probe = (high + low) / 2;
+ probeIndex = model->index(probe, c->column, parent);
+ probeData = model->data(probeIndex, c->role).toString();
+ const int cmp = QString::compare(probeData, part, c->cs);
+ if ((order == Qt::AscendingOrder && cmp >= 0)
+ || (order == Qt::DescendingOrder && cmp < 0)) {
+ high = probe;
+ } else {
+ low = probe;
+ }
+ }
+
+ if ((order == Qt::AscendingOrder && low == indices.to())
+ || (order == Qt::DescendingOrder && high == indices.from())) { // not found
+ saveInCache(part, parent, QMatchData());
+ return QMatchData();
+ }
+
+ probeIndex = model->index(order == Qt::AscendingOrder ? low+1 : high-1, c->column, parent);
+ probeData = model->data(probeIndex, c->role).toString();
+ if (!probeData.startsWith(part, c->cs)) {
+ saveInCache(part, parent, QMatchData());
+ return QMatchData();
+ }
+
+ const bool exactMatch = QString::compare(probeData, part, c->cs) == 0;
+ int emi = exactMatch ? (order == Qt::AscendingOrder ? low+1 : high-1) : -1;
+
+ int from = 0;
+ int to = 0;
+ if (order == Qt::AscendingOrder) {
+ from = low + 1;
+ high = indices.to() + 1;
+ low = from;
+ } else {
+ to = high - 1;
+ low = indices.from() - 1;
+ high = to;
+ }
+
+ while (high - low > 1)
+ {
+ probe = (high + low) / 2;
+ probeIndex = model->index(probe, c->column, parent);
+ probeData = model->data(probeIndex, c->role).toString();
+ const bool startsWith = probeData.startsWith(part, c->cs);
+ if ((order == Qt::AscendingOrder && startsWith)
+ || (order == Qt::DescendingOrder && !startsWith)) {
+ low = probe;
+ } else {
+ high = probe;
+ }
+ }
+
+ QMatchData m(order == Qt::AscendingOrder ? QIndexMapper(from, high - 1) : QIndexMapper(low+1, to), emi, false);
+ saveInCache(part, parent, m);
+ return m;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+int QUnsortedModelEngine::buildIndices(const QString& str, const QModelIndex& parent, int n,
+ const QIndexMapper& indices, QMatchData* m)
+{
+ Q_ASSERT(m->partial);
+ Q_ASSERT(n != -1 || m->exactMatchIndex == -1);
+ const QAbstractItemModel *model = c->proxy->sourceModel();
+ int i, count = 0;
+
+ for (i = 0; i < indices.count() && count != n; ++i) {
+ QModelIndex idx = model->index(indices[i], c->column, parent);
+ QString data = model->data(idx, c->role).toString();
+ if (!data.startsWith(str, c->cs) || !(model->flags(idx) & Qt::ItemIsSelectable))
+ continue;
+ m->indices.append(indices[i]);
+ ++count;
+ if (m->exactMatchIndex == -1 && QString::compare(data, str, c->cs) == 0) {
+ m->exactMatchIndex = indices[i];
+ if (n == -1)
+ return indices[i];
+ }
+ }
+ return indices[i-1];
+}
+
+void QUnsortedModelEngine::filterOnDemand(int n)
+{
+ Q_ASSERT(matchCount());
+ if (!curMatch.partial)
+ return;
+ Q_ASSERT(n >= -1);
+ const QAbstractItemModel *model = c->proxy->sourceModel();
+ int lastRow = model->rowCount(curParent) - 1;
+ QIndexMapper im(curMatch.indices.last() + 1, lastRow);
+ int lastIndex = buildIndices(curParts.last(), curParent, n, im, &curMatch);
+ curMatch.partial = (lastRow != lastIndex);
+ saveInCache(curParts.last(), curParent, curMatch);
+}
+
+QMatchData QUnsortedModelEngine::filter(const QString& part, const QModelIndex& parent, int n)
+{
+ QMatchData hint;
+
+ QVector<int> v;
+ QIndexMapper im(v);
+ QMatchData m(im, -1, true);
+
+ const QAbstractItemModel *model = c->proxy->sourceModel();
+ bool foundInCache = lookupCache(part, parent, &m);
+
+ if (!foundInCache) {
+ if (matchHint(part, parent, &hint) && !hint.isValid())
+ return QMatchData();
+ }
+
+ if (!foundInCache && !hint.isValid()) {
+ const int lastRow = model->rowCount(parent) - 1;
+ QIndexMapper all(0, lastRow);
+ int lastIndex = buildIndices(part, parent, n, all, &m);
+ m.partial = (lastIndex != lastRow);
+ } else {
+ if (!foundInCache) { // build from hint as much as we can
+ buildIndices(part, parent, INT_MAX, hint.indices, &m);
+ m.partial = hint.partial;
+ }
+ if (m.partial && ((n == -1 && m.exactMatchIndex == -1) || (m.indices.count() < n))) {
+ // need more and have more
+ const int lastRow = model->rowCount(parent) - 1;
+ QIndexMapper rest(hint.indices.last() + 1, lastRow);
+ int want = n == -1 ? -1 : n - m.indices.count();
+ int lastIndex = buildIndices(part, parent, want, rest, &m);
+ m.partial = (lastRow != lastIndex);
+ }
+ }
+
+ saveInCache(part, parent, m);
+ return m;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+QCompleterPrivate::QCompleterPrivate()
+: widget(0), proxy(0), popup(0), cs(Qt::CaseSensitive), role(Qt::EditRole), column(0),
+ maxVisibleItems(7), sorting(QCompleter::UnsortedModel), wrap(true), eatFocusOut(true),
+ hiddenBecauseNoMatch(false)
+{
+}
+
+void QCompleterPrivate::init(QAbstractItemModel *m)
+{
+ Q_Q(QCompleter);
+ proxy = new QCompletionModel(this, q);
+ QObject::connect(proxy, SIGNAL(rowsAdded()), q, SLOT(_q_autoResizePopup()));
+ q->setModel(m);
+#ifdef QT_NO_LISTVIEW
+ q->setCompletionMode(QCompleter::InlineCompletion);
+#else
+ q->setCompletionMode(QCompleter::PopupCompletion);
+#endif // QT_NO_LISTVIEW
+}
+
+void QCompleterPrivate::setCurrentIndex(QModelIndex index, bool select)
+{
+ Q_Q(QCompleter);
+ if (!q->popup())
+ return;
+ if (!select) {
+ popup->selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
+ } else {
+ if (!index.isValid())
+ popup->selectionModel()->clear();
+ else
+ popup->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select
+ | QItemSelectionModel::Rows);
+ }
+ index = popup->selectionModel()->currentIndex();
+ if (!index.isValid())
+ popup->scrollToTop();
+ else
+ popup->scrollTo(index, QAbstractItemView::PositionAtTop);
+}
+
+void QCompleterPrivate::_q_completionSelected(const QItemSelection& selection)
+{
+ QModelIndex index;
+ if (!selection.indexes().isEmpty())
+ index = selection.indexes().first();
+
+ _q_complete(index, true);
+}
+
+void QCompleterPrivate::_q_complete(QModelIndex index, bool highlighted)
+{
+ Q_Q(QCompleter);
+ QString completion;
+
+ if (!index.isValid() || (!proxy->showAll && (index.row() >= proxy->engine->matchCount()))) {
+ completion = prefix;
+ } else {
+ if (!(index.flags() & Qt::ItemIsEnabled))
+ return;
+ QModelIndex si = proxy->mapToSource(index);
+ si = si.sibling(si.row(), column); // for clicked()
+ completion = q->pathFromIndex(si);
+#ifndef QT_NO_DIRMODEL
+ // add a trailing separator in inline
+ if (mode == QCompleter::InlineCompletion) {
+ if (qobject_cast<QDirModel *>(proxy->sourceModel()) && QFileInfo(completion).isDir())
+ completion += QDir::separator();
+ }
+#endif
+#ifndef QT_NO_FILESYSTEMMODEL
+ // add a trailing separator in inline
+ if (mode == QCompleter::InlineCompletion) {
+ if (qobject_cast<QFileSystemModel *>(proxy->sourceModel()) && QFileInfo(completion).isDir())
+ completion += QDir::separator();
+ }
+#endif
+ }
+
+ if (highlighted) {
+ emit q->highlighted(index);
+ emit q->highlighted(completion);
+ } else {
+ emit q->activated(index);
+ emit q->activated(completion);
+ }
+}
+
+void QCompleterPrivate::_q_autoResizePopup()
+{
+ if (!popup || !popup->isVisible())
+ return;
+ showPopup(popupRect);
+}
+
+void QCompleterPrivate::showPopup(const QRect& rect)
+{
+ const QRect screen = QApplication::desktop()->availableGeometry(widget);
+ Qt::LayoutDirection dir = widget->layoutDirection();
+ QPoint pos;
+ int rh, w;
+ int h = (popup->sizeHintForRow(0) * qMin(maxVisibleItems, popup->model()->rowCount()) + 3) + 3;
+ QScrollBar *hsb = popup->horizontalScrollBar();
+ if (hsb && hsb->isVisible())
+ h += popup->horizontalScrollBar()->sizeHint().height();
+
+ if (rect.isValid()) {
+ rh = rect.height();
+ w = rect.width();
+ pos = widget->mapToGlobal(dir == Qt::RightToLeft ? rect.bottomRight() : rect.bottomLeft());
+ } else {
+ rh = widget->height();
+ pos = widget->mapToGlobal(QPoint(0, widget->height() - 2));
+ w = widget->width();
+ }
+
+ if (w > screen.width())
+ w = screen.width();
+ if ((pos.x() + w) > (screen.x() + screen.width()))
+ pos.setX(screen.x() + screen.width() - w);
+ if (pos.x() < screen.x())
+ pos.setX(screen.x());
+
+ int top = pos.y() - rh - screen.top() + 2;
+ int bottom = screen.bottom() - pos.y();
+ h = qMax(h, popup->minimumHeight());
+ if (h > bottom) {
+ h = qMin(qMax(top, bottom), h);
+
+ if (top > bottom)
+ pos.setY(pos.y() - h - rh + 2);
+ }
+
+ popup->setGeometry(pos.x(), pos.y(), w, h);
+
+ if (!popup->isVisible())
+ popup->show();
+}
+
+void QCompleterPrivate::_q_fileSystemModelDirectoryLoaded(const QString &path)
+{
+ Q_Q(QCompleter);
+ // Slot called when QFileSystemModel has finished loading.
+ // If we hide the popup because there was no match because the model was not loaded yet,
+ // we re-start the completion when we get the results
+ if (hiddenBecauseNoMatch
+ && prefix.startsWith(path) && prefix != (path + QLatin1Char('/'))
+ && widget) {
+ q->complete();
+ }
+}
+
+/*!
+ Constructs a completer object with the given \a parent.
+*/
+QCompleter::QCompleter(QObject *parent)
+: QObject(*new QCompleterPrivate(), parent)
+{
+ Q_D(QCompleter);
+ d->init();
+}
+
+/*!
+ Constructs a completer object with the given \a parent that provides completions
+ from the specified \a model.
+*/
+QCompleter::QCompleter(QAbstractItemModel *model, QObject *parent)
+ : QObject(*new QCompleterPrivate(), parent)
+{
+ Q_D(QCompleter);
+ d->init(model);
+}
+
+#ifndef QT_NO_STRINGLISTMODEL
+/*!
+ Constructs a QCompleter object with the given \a parent that uses the specified
+ \a list as a source of possible completions.
+*/
+QCompleter::QCompleter(const QStringList& list, QObject *parent)
+: QObject(*new QCompleterPrivate(), parent)
+{
+ Q_D(QCompleter);
+ d->init(new QStringListModel(list, this));
+}
+#endif // QT_NO_STRINGLISTMODEL
+
+/*!
+ Destroys the completer object.
+*/
+QCompleter::~QCompleter()
+{
+}
+
+/*!
+ Sets the widget for which completion are provided for to \a widget. This
+ function is automatically called when a QCompleter is set on a QLineEdit
+ using QLineEdit::setCompleter() or on a QComboBox using
+ QComboBox::setCompleter(). The widget needs to be set explicitly when
+ providing completions for custom widgets.
+
+ \sa widget(), setModel(), setPopup()
+ */
+void QCompleter::setWidget(QWidget *widget)
+{
+ Q_D(QCompleter);
+ if (d->widget)
+ d->widget->removeEventFilter(this);
+ d->widget = widget;
+ if (d->widget)
+ d->widget->installEventFilter(this);
+ if (d->popup) {
+ d->popup->hide();
+ d->popup->setFocusProxy(d->widget);
+ }
+}
+
+/*!
+ Returns the widget for which the completer object is providing completions.
+
+ \sa setWidget()
+ */
+QWidget *QCompleter::widget() const
+{
+ Q_D(const QCompleter);
+ return d->widget;
+}
+
+/*!
+ Sets the model which provides completions to \a model. The \a model can
+ be list model or a tree model. If a model has been already previously set
+ and it has the QCompleter as its parent, it is deleted.
+
+ For convenience, if \a model is a QFileSystemModel, QCompleter switches its
+ caseSensitivity to Qt::CaseInsensitive on Windows and Qt::CaseSensitive
+ on other platforms.
+
+ \sa completionModel(), modelSorting, {Handling Tree Models}
+*/
+void QCompleter::setModel(QAbstractItemModel *model)
+{
+ Q_D(QCompleter);
+ QAbstractItemModel *oldModel = d->proxy->sourceModel();
+ d->proxy->setSourceModel(model);
+ if (d->popup)
+ setPopup(d->popup); // set the model and make new connections
+ if (oldModel && oldModel->QObject::parent() == this)
+ delete oldModel;
+#ifndef QT_NO_DIRMODEL
+ if (qobject_cast<QDirModel *>(model)) {
+#if (defined(Q_OS_WIN) && !defined(Q_OS_WINCE)) || defined(Q_OS_SYMBIAN)
+ setCaseSensitivity(Qt::CaseInsensitive);
+#else
+ setCaseSensitivity(Qt::CaseSensitive);
+#endif
+ }
+#endif // QT_NO_DIRMODEL
+#ifndef QT_NO_FILESYSTEMMODEL
+ QFileSystemModel *fsModel = qobject_cast<QFileSystemModel *>(model);
+ if (fsModel) {
+#if (defined(Q_OS_WIN) && !defined(Q_OS_WINCE)) || defined(Q_OS_SYMBIAN)
+ setCaseSensitivity(Qt::CaseInsensitive);
+#else
+ setCaseSensitivity(Qt::CaseSensitive);
+#endif
+ setCompletionRole(QFileSystemModel::FileNameRole);
+ connect(fsModel, SIGNAL(directoryLoaded(QString)), this, SLOT(_q_fileSystemModelDirectoryLoaded(QString)));
+ }
+#endif // QT_NO_FILESYSTEMMODEL
+}
+
+/*!
+ Returns the model that provides completion strings.
+
+ \sa completionModel()
+*/
+QAbstractItemModel *QCompleter::model() const
+{
+ Q_D(const QCompleter);
+ return d->proxy->sourceModel();
+}
+
+/*!
+ \enum QCompleter::CompletionMode
+
+ This enum specifies how completions are provided to the user.
+
+ \value PopupCompletion Current completions are displayed in a popup window.
+ \value InlineCompletion Completions appear inline (as selected text).
+ \value UnfilteredPopupCompletion All possible completions are displayed in a popup window with the most likely suggestion indicated as current.
+
+ \sa setCompletionMode()
+*/
+
+/*!
+ \property QCompleter::completionMode
+ \brief how the completions are provided to the user
+
+ The default value is QCompleter::PopupCompletion.
+*/
+void QCompleter::setCompletionMode(QCompleter::CompletionMode mode)
+{
+ Q_D(QCompleter);
+ d->mode = mode;
+ d->proxy->setFiltered(mode != QCompleter::UnfilteredPopupCompletion);
+
+ if (mode == QCompleter::InlineCompletion) {
+ if (d->widget)
+ d->widget->removeEventFilter(this);
+ if (d->popup) {
+ d->popup->deleteLater();
+ d->popup = 0;
+ }
+ } else {
+ if (d->widget)
+ d->widget->installEventFilter(this);
+ }
+}
+
+QCompleter::CompletionMode QCompleter::completionMode() const
+{
+ Q_D(const QCompleter);
+ return d->mode;
+}
+
+/*!
+ Sets the popup used to display completions to \a popup. QCompleter takes
+ ownership of the view.
+
+ A QListView is automatically created when the completionMode() is set to
+ QCompleter::PopupCompletion or QCompleter::UnfilteredPopupCompletion. The
+ default popup displays the completionColumn().
+
+ Ensure that this function is called before the view settings are modified.
+ This is required since view's properties may require that a model has been
+ set on the view (for example, hiding columns in the view requires a model
+ to be set on the view).
+
+ \sa popup()
+*/
+void QCompleter::setPopup(QAbstractItemView *popup)
+{
+ Q_D(QCompleter);
+ Q_ASSERT(popup != 0);
+ if (d->popup) {
+ QObject::disconnect(d->popup->selectionModel(), 0, this, 0);
+ QObject::disconnect(d->popup, 0, this, 0);
+ }
+ if (d->popup != popup)
+ delete d->popup;
+ if (popup->model() != d->proxy)
+ popup->setModel(d->proxy);
+#if defined(Q_OS_MAC) && !defined(QT_MAC_USE_COCOA)
+ popup->show();
+#else
+ popup->hide();
+#endif
+
+ Qt::FocusPolicy origPolicy = Qt::NoFocus;
+ if (d->widget)
+ origPolicy = d->widget->focusPolicy();
+ popup->setParent(0, Qt::Popup);
+ popup->setFocusPolicy(Qt::NoFocus);
+ if (d->widget)
+ d->widget->setFocusPolicy(origPolicy);
+
+ popup->setFocusProxy(d->widget);
+ popup->installEventFilter(this);
+ popup->setItemDelegate(new QCompleterItemDelegate(popup));
+#ifndef QT_NO_LISTVIEW
+ if (QListView *listView = qobject_cast<QListView *>(popup)) {
+ listView->setModelColumn(d->column);
+ }
+#endif
+
+ QObject::connect(popup, SIGNAL(clicked(QModelIndex)),
+ this, SLOT(_q_complete(QModelIndex)));
+ QObject::connect(this, SIGNAL(activated(QModelIndex)),
+ popup, SLOT(hide()));
+
+ QObject::connect(popup->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
+ this, SLOT(_q_completionSelected(QItemSelection)));
+ d->popup = popup;
+}
+
+/*!
+ Returns the popup used to display completions.
+
+ \sa setPopup()
+*/
+QAbstractItemView *QCompleter::popup() const
+{
+ Q_D(const QCompleter);
+#ifndef QT_NO_LISTVIEW
+ if (!d->popup && completionMode() != QCompleter::InlineCompletion) {
+ QListView *listView = new QListView;
+ listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
+ listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ listView->setSelectionBehavior(QAbstractItemView::SelectRows);
+ listView->setSelectionMode(QAbstractItemView::SingleSelection);
+ listView->setModelColumn(d->column);
+ QCompleter *that = const_cast<QCompleter*>(this);
+ that->setPopup(listView);
+ }
+#endif // QT_NO_LISTVIEW
+ return d->popup;
+}
+
+/*!
+ \reimp
+*/
+bool QCompleter::event(QEvent *ev)
+{
+ return QObject::event(ev);
+}
+
+/*!
+ \reimp
+*/
+bool QCompleter::eventFilter(QObject *o, QEvent *e)
+{
+ Q_D(QCompleter);
+
+ if (d->eatFocusOut && o == d->widget && e->type() == QEvent::FocusOut) {
+ d->hiddenBecauseNoMatch = false;
+ if (d->popup && d->popup->isVisible())
+ return true;
+ }
+
+ if (o != d->popup)
+ return QObject::eventFilter(o, e);
+
+ switch (e->type()) {
+ case QEvent::KeyPress: {
+ QKeyEvent *ke = static_cast<QKeyEvent *>(e);
+
+ QModelIndex curIndex = d->popup->currentIndex();
+ QModelIndexList selList = d->popup->selectionModel()->selectedIndexes();
+
+ const int key = ke->key();
+ // In UnFilteredPopup mode, select the current item
+ if ((key == Qt::Key_Up || key == Qt::Key_Down) && selList.isEmpty() && curIndex.isValid()
+ && d->mode == QCompleter::UnfilteredPopupCompletion) {
+ d->setCurrentIndex(curIndex);
+ return true;
+ }
+
+ // Handle popup navigation keys. These are hardcoded because up/down might make the
+ // widget do something else (lineedit cursor moves to home/end on mac, for instance)
+ switch (key) {
+ case Qt::Key_End:
+ case Qt::Key_Home:
+ if (ke->modifiers() & Qt::ControlModifier)
+ return false;
+ break;
+
+ case Qt::Key_Up:
+ if (!curIndex.isValid()) {
+ int rowCount = d->proxy->rowCount();
+ QModelIndex lastIndex = d->proxy->index(rowCount - 1, d->column);
+ d->setCurrentIndex(lastIndex);
+ return true;
+ } else if (curIndex.row() == 0) {
+ if (d->wrap)
+ d->setCurrentIndex(QModelIndex());
+ return true;
+ }
+ return false;
+
+ case Qt::Key_Down:
+ if (!curIndex.isValid()) {
+ QModelIndex firstIndex = d->proxy->index(0, d->column);
+ d->setCurrentIndex(firstIndex);
+ return true;
+ } else if (curIndex.row() == d->proxy->rowCount() - 1) {
+ if (d->wrap)
+ d->setCurrentIndex(QModelIndex());
+ return true;
+ }
+ return false;
+
+ case Qt::Key_PageUp:
+ case Qt::Key_PageDown:
+ return false;
+ }
+
+ // Send the event to the widget. If the widget accepted the event, do nothing
+ // If the widget did not accept the event, provide a default implementation
+ d->eatFocusOut = false;
+ (static_cast<QObject *>(d->widget))->event(ke);
+ d->eatFocusOut = true;
+ if (!d->widget || e->isAccepted() || !d->popup->isVisible()) {
+ // widget lost focus, hide the popup
+ if (d->widget && (!d->widget->hasFocus()
+#ifdef QT_KEYPAD_NAVIGATION
+ || (QApplication::keypadNavigationEnabled() && !d->widget->hasEditFocus())
+#endif
+ ))
+ d->popup->hide();
+ if (e->isAccepted())
+ return true;
+ }
+
+ // default implementation for keys not handled by the widget when popup is open
+ switch (key) {
+#ifdef QT_KEYPAD_NAVIGATION
+ case Qt::Key_Select:
+ if (!QApplication::keypadNavigationEnabled())
+ break;
+#endif
+ case Qt::Key_Return:
+ case Qt::Key_Enter:
+ case Qt::Key_Tab:
+ d->popup->hide();
+ if (curIndex.isValid())
+ d->_q_complete(curIndex);
+ break;
+
+ case Qt::Key_F4:
+ if (ke->modifiers() & Qt::AltModifier)
+ d->popup->hide();
+ break;
+
+ case Qt::Key_Backtab:
+ case Qt::Key_Escape:
+ d->popup->hide();
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+ }
+
+#ifdef QT_KEYPAD_NAVIGATION
+ case QEvent::KeyRelease: {
+ QKeyEvent *ke = static_cast<QKeyEvent *>(e);
+ if (QApplication::keypadNavigationEnabled() && ke->key() == Qt::Key_Back) {
+ // Send the event to the 'widget'. This is what we did for KeyPress, so we need
+ // to do the same for KeyRelease, in case the widget's KeyPress event set
+ // up something (such as a timer) that is relying on also receiving the
+ // key release. I see this as a bug in Qt, and should really set it up for all
+ // the affected keys. However, it is difficult to tell how this will affect
+ // existing code, and I can't test for every combination!
+ d->eatFocusOut = false;
+ static_cast<QObject *>(d->widget)->event(ke);
+ d->eatFocusOut = true;
+ }
+ break;
+ }
+#endif
+
+ case QEvent::MouseButtonPress: {
+#ifdef QT_KEYPAD_NAVIGATION
+ if (QApplication::keypadNavigationEnabled()) {
+ // if we've clicked in the widget (or its descendant), let it handle the click
+ QWidget *source = qobject_cast<QWidget *>(o);
+ if (source) {
+ QPoint pos = source->mapToGlobal((static_cast<QMouseEvent *>(e))->pos());
+ QWidget *target = QApplication::widgetAt(pos);
+ if (target && (d->widget->isAncestorOf(target) ||
+ target == d->widget)) {
+ d->eatFocusOut = false;
+ static_cast<QObject *>(target)->event(e);
+ d->eatFocusOut = true;
+ return true;
+ }
+ }
+ }
+#endif
+ if (!d->popup->underMouse()) {
+ d->popup->hide();
+ return true;
+ }
+ }
+ return false;
+
+ case QEvent::InputMethod:
+ case QEvent::ShortcutOverride:
+ QApplication::sendEvent(d->widget, e);
+ break;
+
+ default:
+ return false;
+ }
+ return false;
+}
+
+/*!
+ For QCompleter::PopupCompletion and QCompletion::UnfilteredPopupCompletion
+ modes, calling this function displays the popup displaying the current
+ completions. By default, if \a rect is not specified, the popup is displayed
+ on the bottom of the widget(). If \a rect is specified the popup is
+ displayed on the left edge of the rectangle.
+
+ For QCompleter::InlineCompletion mode, the highlighted() signal is fired
+ with the current completion.
+*/
+void QCompleter::complete(const QRect& rect)
+{
+ Q_D(QCompleter);
+ QModelIndex idx = d->proxy->currentIndex(false);
+ d->hiddenBecauseNoMatch = false;
+ if (d->mode == QCompleter::InlineCompletion) {
+ if (idx.isValid())
+ d->_q_complete(idx, true);
+ return;
+ }
+
+ Q_ASSERT(d->widget != 0);
+ if ((d->mode == QCompleter::PopupCompletion && !idx.isValid())
+ || (d->mode == QCompleter::UnfilteredPopupCompletion && d->proxy->rowCount() == 0)) {
+ if (d->popup)
+ d->popup->hide(); // no suggestion, hide
+ d->hiddenBecauseNoMatch = true;
+ return;
+ }
+
+ popup();
+ if (d->mode == QCompleter::UnfilteredPopupCompletion)
+ d->setCurrentIndex(idx, false);
+
+ d->showPopup(rect);
+ d->popupRect = rect;
+}
+
+/*!
+ Sets the current row to the \a row specified. Returns true if successful;
+ otherwise returns false.
+
+ This function may be used along with currentCompletion() to iterate
+ through all the possible completions.
+
+ \sa currentCompletion(), completionCount()
+*/
+bool QCompleter::setCurrentRow(int row)
+{
+ Q_D(QCompleter);
+ return d->proxy->setCurrentRow(row);
+}
+
+/*!
+ Returns the current row.
+
+ \sa setCurrentRow()
+*/
+int QCompleter::currentRow() const
+{
+ Q_D(const QCompleter);
+ return d->proxy->currentRow();
+}
+
+/*!
+ Returns the number of completions for the current prefix. For an unsorted
+ model with a large number of items this can be expensive. Use setCurrentRow()
+ and currentCompletion() to iterate through all the completions.
+*/
+int QCompleter::completionCount() const
+{
+ Q_D(const QCompleter);
+ return d->proxy->completionCount();
+}
+
+/*!
+ \enum QCompleter::ModelSorting
+
+ This enum specifies how the items in the model are sorted.
+
+ \value UnsortedModel The model is unsorted.
+ \value CaseSensitivelySortedModel The model is sorted case sensitively.
+ \value CaseInsensitivelySortedModel The model is sorted case insensitively.
+
+ \sa setModelSorting()
+*/
+
+/*!
+ \property QCompleter::modelSorting
+ \brief the way the model is sorted
+
+ By default, no assumptions are made about the order of the items
+ in the model that provides the completions.
+
+ If the model's data for the completionColumn() and completionRole() is sorted in
+ ascending order, you can set this property to \l CaseSensitivelySortedModel
+ or \l CaseInsensitivelySortedModel. On large models, this can lead to
+ significant performance improvements because the completer object can
+ then use a binary search algorithm instead of linear search algorithm.
+
+ The sort order (i.e ascending or descending order) of the model is determined
+ dynamically by inspecting the contents of the model.
+
+ \bold{Note:} The performance improvements described above cannot take place
+ when the completer's \l caseSensitivity is different to the case sensitivity
+ used by the model's when sorting.
+
+ \sa setCaseSensitivity(), QCompleter::ModelSorting
+*/
+void QCompleter::setModelSorting(QCompleter::ModelSorting sorting)
+{
+ Q_D(QCompleter);
+ if (d->sorting == sorting)
+ return;
+ d->sorting = sorting;
+ d->proxy->createEngine();
+ d->proxy->invalidate();
+}
+
+QCompleter::ModelSorting QCompleter::modelSorting() const
+{
+ Q_D(const QCompleter);
+ return d->sorting;
+}
+
+/*!
+ \property QCompleter::completionColumn
+ \brief the column in the model in which completions are searched for.
+
+ If the popup() is a QListView, it is automatically setup to display
+ this column.
+
+ By default, the match column is 0.
+
+ \sa completionRole, caseSensitivity
+*/
+void QCompleter::setCompletionColumn(int column)
+{
+ Q_D(QCompleter);
+ if (d->column == column)
+ return;
+#ifndef QT_NO_LISTVIEW
+ if (QListView *listView = qobject_cast<QListView *>(d->popup))
+ listView->setModelColumn(column);
+#endif
+ d->column = column;
+ d->proxy->invalidate();
+}
+
+int QCompleter::completionColumn() const
+{
+ Q_D(const QCompleter);
+ return d->column;
+}
+
+/*!
+ \property QCompleter::completionRole
+ \brief the item role to be used to query the contents of items for matching.
+
+ The default role is Qt::EditRole.
+
+ \sa completionColumn, caseSensitivity
+*/
+void QCompleter::setCompletionRole(int role)
+{
+ Q_D(QCompleter);
+ if (d->role == role)
+ return;
+ d->role = role;
+ d->proxy->invalidate();
+}
+
+int QCompleter::completionRole() const
+{
+ Q_D(const QCompleter);
+ return d->role;
+}
+
+/*!
+ \property QCompleter::wrapAround
+ \brief the completions wrap around when navigating through items
+ \since 4.3
+
+ The default is true.
+*/
+void QCompleter::setWrapAround(bool wrap)
+{
+ Q_D(QCompleter);
+ if (d->wrap == wrap)
+ return;
+ d->wrap = wrap;
+}
+
+bool QCompleter::wrapAround() const
+{
+ Q_D(const QCompleter);
+ return d->wrap;
+}
+
+/*!
+ \property QCompleter::maxVisibleItems
+ \brief the maximum allowed size on screen of the completer, measured in items
+ \since 4.6
+
+ By default, this property has a value of 7.
+*/
+int QCompleter::maxVisibleItems() const
+{
+ Q_D(const QCompleter);
+ return d->maxVisibleItems;
+}
+
+void QCompleter::setMaxVisibleItems(int maxItems)
+{
+ Q_D(QCompleter);
+ if (maxItems < 0) {
+ qWarning("QCompleter::setMaxVisibleItems: "
+ "Invalid max visible items (%d) must be >= 0", maxItems);
+ return;
+ }
+ d->maxVisibleItems = maxItems;
+}
+
+/*!
+ \property QCompleter::caseSensitivity
+ \brief the case sensitivity of the matching
+
+ The default is Qt::CaseSensitive.
+
+ \sa completionColumn, completionRole, modelSorting
+*/
+void QCompleter::setCaseSensitivity(Qt::CaseSensitivity cs)
+{
+ Q_D(QCompleter);
+ if (d->cs == cs)
+ return;
+ d->cs = cs;
+ d->proxy->createEngine();
+ d->proxy->invalidate();
+}
+
+Qt::CaseSensitivity QCompleter::caseSensitivity() const
+{
+ Q_D(const QCompleter);
+ return d->cs;
+}
+
+/*!
+ \property QCompleter::completionPrefix
+ \brief the completion prefix used to provide completions.
+
+ The completionModel() is updated to reflect the list of possible
+ matches for \a prefix.
+*/
+void QCompleter::setCompletionPrefix(const QString &prefix)
+{
+ Q_D(QCompleter);
+ d->prefix = prefix;
+ d->proxy->filter(splitPath(prefix));
+}
+
+QString QCompleter::completionPrefix() const
+{
+ Q_D(const QCompleter);
+ return d->prefix;
+}
+
+/*!
+ Returns the model index of the current completion in the completionModel().
+
+ \sa setCurrentRow(), currentCompletion(), model()
+*/
+QModelIndex QCompleter::currentIndex() const
+{
+ Q_D(const QCompleter);
+ return d->proxy->currentIndex(false);
+}
+
+/*!
+ Returns the current completion string. This includes the \l completionPrefix.
+ When used alongside setCurrentRow(), it can be used to iterate through
+ all the matches.
+
+ \sa setCurrentRow(), currentIndex()
+*/
+QString QCompleter::currentCompletion() const
+{
+ Q_D(const QCompleter);
+ return pathFromIndex(d->proxy->currentIndex(true));
+}
+
+/*!
+ Returns the completion model. The completion model is a read-only list model
+ that contains all the possible matches for the current completion prefix.
+ The completion model is auto-updated to reflect the current completions.
+
+ \note The return value of this function is defined to be an QAbstractItemModel
+ purely for generality. This actual kind of model returned is an instance of an
+ QAbstractProxyModel subclass.
+
+ \sa completionPrefix, model()
+*/
+QAbstractItemModel *QCompleter::completionModel() const
+{
+ Q_D(const QCompleter);
+ return d->proxy;
+}
+
+/*!
+ Returns the path for the given \a index. The completer object uses this to
+ obtain the completion text from the underlying model.
+
+ The default implementation returns the \l{Qt::EditRole}{edit role} of the
+ item for list models. It returns the absolute file path if the model is a
+ QFileSystemModel.
+
+ \sa splitPath()
+*/
+
+QString QCompleter::pathFromIndex(const QModelIndex& index) const
+{
+ Q_D(const QCompleter);
+ if (!index.isValid())
+ return QString();
+
+ QAbstractItemModel *sourceModel = d->proxy->sourceModel();
+ if (!sourceModel)
+ return QString();
+ bool isDirModel = false;
+ bool isFsModel = false;
+#ifndef QT_NO_DIRMODEL
+ isDirModel = qobject_cast<QDirModel *>(d->proxy->sourceModel()) != 0;
+#endif
+#ifndef QT_NO_FILESYSTEMMODEL
+ isFsModel = qobject_cast<QFileSystemModel *>(d->proxy->sourceModel()) != 0;
+#endif
+ if (!isDirModel && !isFsModel)
+ return sourceModel->data(index, d->role).toString();
+
+ QModelIndex idx = index;
+ QStringList list;
+ do {
+ QString t;
+ if (isDirModel)
+ t = sourceModel->data(idx, Qt::EditRole).toString();
+#ifndef QT_NO_FILESYSTEMMODEL
+ else
+ t = sourceModel->data(idx, QFileSystemModel::FileNameRole).toString();
+#endif
+ list.prepend(t);
+ QModelIndex parent = idx.parent();
+ idx = parent.sibling(parent.row(), index.column());
+ } while (idx.isValid());
+
+#if (!defined(Q_OS_WIN) || defined(Q_OS_WINCE)) && !defined(Q_OS_SYMBIAN)
+ if (list.count() == 1) // only the separator or some other text
+ return list[0];
+ list[0].clear() ; // the join below will provide the separator
+#endif
+
+ return list.join(QDir::separator());
+}
+
+/*!
+ Splits the given \a path into strings that are used to match at each level
+ in the model().
+
+ The default implementation of splitPath() splits a file system path based on
+ QDir::separator() when the sourceModel() is a QFileSystemModel.
+
+ When used with list models, the first item in the returned list is used for
+ matching.
+
+ \sa pathFromIndex(), {Handling Tree Models}
+*/
+QStringList QCompleter::splitPath(const QString& path) const
+{
+ bool isDirModel = false;
+ bool isFsModel = false;
+#ifndef QT_NO_DIRMODEL
+ Q_D(const QCompleter);
+ isDirModel = qobject_cast<QDirModel *>(d->proxy->sourceModel()) != 0;
+#endif
+#ifndef QT_NO_FILESYSTEMMODEL
+#ifdef QT_NO_DIRMODEL
+ Q_D(const QCompleter);
+#endif
+ isFsModel = qobject_cast<QFileSystemModel *>(d->proxy->sourceModel()) != 0;
+#endif
+
+ if ((!isDirModel && !isFsModel) || path.isEmpty())
+ return QStringList(completionPrefix());
+
+ QString pathCopy = QDir::toNativeSeparators(path);
+ QString sep = QDir::separator();
+#if defined(Q_OS_SYMBIAN)
+ if (pathCopy == QLatin1String("\\"))
+ return QStringList(pathCopy);
+#elif defined(Q_OS_WIN) && !defined(Q_OS_WINCE)
+ if (pathCopy == QLatin1String("\\") || pathCopy == QLatin1String("\\\\"))
+ return QStringList(pathCopy);
+ QString doubleSlash(QLatin1String("\\\\"));
+ if (pathCopy.startsWith(doubleSlash))
+ pathCopy = pathCopy.mid(2);
+ else
+ doubleSlash.clear();
+#endif
+
+ QRegExp re(QLatin1Char('[') + QRegExp::escape(sep) + QLatin1Char(']'));
+ QStringList parts = pathCopy.split(re);
+
+#if defined(Q_OS_SYMBIAN)
+ // Do nothing
+#elif defined(Q_OS_WIN) && !defined(Q_OS_WINCE)
+ if (!doubleSlash.isEmpty())
+ parts[0].prepend(doubleSlash);
+#else
+ if (pathCopy[0] == sep[0]) // readd the "/" at the beginning as the split removed it
+ parts[0] = QDir::fromNativeSeparators(QString(sep[0]));
+#endif
+
+ return parts;
+}
+
+/*!
+ \fn void QCompleter::activated(const QModelIndex& index)
+
+ This signal is sent when an item in the popup() is activated by the user.
+ (by clicking or pressing return). The item's \a index in the completionModel()
+ is given.
+
+*/
+
+/*!
+ \fn void QCompleter::activated(const QString &text)
+
+ This signal is sent when an item in the popup() is activated by the user (by
+ clicking or pressing return). The item's \a text is given.
+
+*/
+
+/*!
+ \fn void QCompleter::highlighted(const QModelIndex& index)
+
+ This signal is sent when an item in the popup() is highlighted by
+ the user. It is also sent if complete() is called with the completionMode()
+ set to QCompleter::InlineCompletion. The item's \a index in the completionModel()
+ is given.
+*/
+
+/*!
+ \fn void QCompleter::highlighted(const QString &text)
+
+ This signal is sent when an item in the popup() is highlighted by
+ the user. It is also sent if complete() is called with the completionMode()
+ set to QCompleter::InlineCompletion. The item's \a text is given.
+*/
+
+QT_END_NAMESPACE
+
+#include "moc_qcompleter.cpp"
+
+#endif // QT_NO_COMPLETER