diff options
author | Lars Knoll <lars.knoll@nokia.com> | 2009-03-23 09:18:55 (GMT) |
---|---|---|
committer | Simon Hausmann <simon.hausmann@nokia.com> | 2009-03-23 09:18:55 (GMT) |
commit | e5fcad302d86d316390c6b0f62759a067313e8a9 (patch) | |
tree | c2afbf6f1066b6ce261f14341cf6d310e5595bc1 /src/gui/util/qcompleter.cpp | |
download | Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.zip Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.tar.gz Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.tar.bz2 |
Long live Qt 4.5!
Diffstat (limited to 'src/gui/util/qcompleter.cpp')
-rw-r--r-- | src/gui/util/qcompleter.cpp | 1712 |
1 files changed, 1712 insertions, 0 deletions
diff --git a/src/gui/util/qcompleter.cpp b/src/gui/util/qcompleter.cpp new file mode 100644 index 0000000..8b07163 --- /dev/null +++ b/src/gui/util/qcompleter.cpp @@ -0,0 +1,1712 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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), engine(0), 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; + } + + delete engine; + if (sortedEngine) + engine = new QSortedModelEngine(c); + else + engine = 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) + && (!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), + 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()) + completion = prefix; + else { + 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(7, 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) + 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); + popup->hide(); + popup->setParent(0, Qt::Popup); + popup->setFocusPolicy(Qt::NoFocus); + 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(popup, SIGNAL(clicked(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, 0); + 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, 0); + 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::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. + + \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) + 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_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(QLatin1String("[") + QRegExp::escape(sep) + QLatin1String("]")); + QStringList parts = pathCopy.split(re); + +#if 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 |