src/gui/util/qcompleter.cpp
changeset 0 1918ee327afb
child 4 3b1da2848fc7
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gui/util/qcompleter.cpp	Mon Jan 11 14:00:40 2010 +0000
@@ -0,0 +1,1758 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $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 QDirModel 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) QDirModel. 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 QDirModel.
+
+    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 QDirModel.
+
+    \sa QAbstractItemModel, QLineEdit, QComboBox, {Completer Example}
+*/
+
+#include "qcompleter_p.h"
+
+#ifndef QT_NO_COMPLETER
+
+#include "QtGui/qscrollbar.h"
+#include "QtGui/qstringlistmodel.h"
+#include "QtGui/qdirmodel.h"
+#include "QtGui/qheaderview.h"
+#include "QtGui/qlistview.h"
+#include "QtGui/qapplication.h"
+#include "QtGui/qevent.h"
+#include "QtGui/qheaderview.h"
+#include "QtGui/qdesktopwidget.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 QModelIndex();
+
+    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 dirModel = false;
+#ifndef QT_NO_DIRMODEL
+    dirModel = (qobject_cast<QDirModel *>(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)
+            && (!dirModel || 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 ;
+        for (it1 = cache.begin(); it1 != cache.end(); ++it1) {
+            CacheItem& ci = it1.value();
+            int sz = ci.count()/2;
+            QMap<QString, QMatchData>::iterator it2 = ci.begin();
+            for (int i = 0; it2 != ci.end() && i < sz; i++, ++it2) {
+                cost -= it2.value().indices.cost();
+                ci.erase(it2);
+            }
+            if (ci.count() == 0)
+                cache.erase(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)
+{
+}
+
+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
+    }
+
+    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 rw, 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 = rw = rect.width();
+        pos = widget->mapToGlobal(dir == Qt::RightToLeft ? rect.bottomRight() : rect.bottomLeft());
+    } else {
+        rh = widget->height();
+        rw = widget->width();
+        pos = widget->mapToGlobal(QPoint(0, widget->height() - 2));
+        w = widget->width();
+    }
+
+    if ((pos.x() + rw) > (screen.x() + screen.width()))
+        pos.setX(screen.x() + screen.width() - w);
+    if (pos.x() < screen.x())
+        pos.setX(screen.x());
+    if (((pos.y() + rh) > (screen.y() + screen.height())) && ((pos.y() - h - rh) >= 0))
+        pos.setY(pos.y() - qMax(h, popup->minimumHeight()) - rh + 2);
+
+    popup->setGeometry(pos.x(), pos.y(), w, h);
+
+    if (!popup->isVisible())
+        popup->show();
+}
+
+/*!
+    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 QDirModel, 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
+}
+
+/*!
+    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);
+#ifdef Q_OS_MAC
+     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) {
+        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);
+    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
+        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
+    QDirModel.
+
+    \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();
+#ifndef QT_NO_DIRMODEL
+    QDirModel *dirModel = qobject_cast<QDirModel *>(sourceModel);
+    if (!dirModel)
+#endif
+        return sourceModel->data(index, d->role).toString();
+
+    QModelIndex idx = index;
+    QStringList list;
+    do {
+        QString t = sourceModel->data(idx, Qt::EditRole).toString();
+        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 QDirModel.
+
+    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;
+#ifndef QT_NO_DIRMODEL
+    Q_D(const QCompleter);
+    isDirModel = qobject_cast<QDirModel *>(d->proxy->sourceModel()) != 0;
+#endif
+
+    if (!isDirModel || 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