src/gui/util/qcompleter.cpp

Go to the documentation of this file.
00001 /****************************************************************************
00002 **
00003 ** Copyright (C) 1992-2006 Trolltech ASA. All rights reserved.
00004 **
00005 ** This file is part of the QtGui module of the Qt Toolkit.
00006 **
00007 ** This file may be used under the terms of the GNU General Public
00008 ** License version 2.0 as published by the Free Software Foundation
00009 ** and appearing in the file LICENSE.GPL included in the packaging of
00010 ** this file.  Please review the following information to ensure GNU
00011 ** General Public Licensing requirements will be met:
00012 ** http://www.trolltech.com/products/qt/opensource.html
00013 **
00014 ** If you are unsure which license is appropriate for your use, please
00015 ** review the following information:
00016 ** http://www.trolltech.com/products/qt/licensing.html or contact the
00017 ** sales department at sales@trolltech.com.
00018 **
00019 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
00020 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
00021 **
00022 ****************************************************************************/
00023 
00141 #include "qcompleter_p.h"
00142 
00143 #ifndef QT_NO_COMPLETER
00144 
00145 #include "QtGui/qscrollbar.h"
00146 #include "QtGui/qstringlistmodel.h"
00147 #include "QtGui/qdirmodel.h"
00148 #include "QtGui/qheaderview.h"
00149 #include "QtGui/qlistview.h"
00150 #include "QtGui/qapplication.h"
00151 #include "QtGui/qevent.h"
00152 #include "QtGui/qheaderview.h"
00153 #include "QtGui/qdesktopwidget.h"
00154 
00155 void QCompletionModel::setSourceModel(QAbstractItemModel *source)
00156 {
00157     if (model)
00158         QObject::disconnect(model, 0, this, 0);
00159 
00160     QAbstractProxyModel::setSourceModel(source);
00161     model = sourceModel();
00162 
00163     // TODO: Optimize updates in the source model
00164     connect(model, SIGNAL(modelReset()), this, SLOT(invalidate()));
00165     connect(model, SIGNAL(destroyed()), this, SLOT(modelDestroyed()));
00166     connect(model, SIGNAL(layoutChanged()), this, SLOT(invalidate()));
00167     connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(invalidate()));
00168     connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(invalidate()));
00169     connect(model, SIGNAL(columnsInserted(QModelIndex,int,int)), this, SLOT(invalidate()));
00170     connect(model, SIGNAL(columnsRemoved(QModelIndex,int,int)), this, SLOT(invalidate()));
00171     connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(invalidate()));
00172 
00173     invalidate();
00174 }
00175 
00176 void QCompletionModel::createEngine()
00177 {
00178     bool sortedEngine = false;
00179     switch (c->sorting) {
00180     case QCompleter::UnsortedModel:
00181         sortedEngine = false;
00182         break;
00183     case QCompleter::CaseSensitivelySortedModel:
00184         sortedEngine = c->cs == Qt::CaseSensitive;
00185         break;
00186     case QCompleter::CaseInsensitivelySortedModel:
00187         sortedEngine = c->cs == Qt::CaseInsensitive;
00188         break;
00189     }
00190 
00191     delete engine;
00192     if (sortedEngine)
00193         engine = new QSortedModelEngine(c);
00194     else
00195         engine = new QUnsortedModelEngine(c);
00196 }
00197 
00198 QModelIndex QCompletionModel::mapToSource(const QModelIndex& index) const
00199 {
00200     if (!index.isValid())
00201         return QModelIndex();
00202 
00203     int row;
00204     QModelIndex parent = engine->curParent;
00205     if (!showAll) {
00206         if (!engine->matchCount())
00207             return QModelIndex();
00208         Q_ASSERT(index.row() < engine->matchCount());
00209         QIndexMapper& rootIndices = engine->historyMatch.indices;
00210         if (index.row() < rootIndices.count()) {
00211             row = rootIndices[index.row()];
00212             parent = QModelIndex();
00213         } else {
00214             row = engine->curMatch.indices[index.row() - rootIndices.count()];
00215         }
00216     } else {
00217         row = index.row();
00218     }
00219 
00220     return model->index(row, index.column(), parent);
00221 }
00222 
00223 QModelIndex QCompletionModel::mapFromSource(const QModelIndex& idx) const
00224 {
00225     if (!idx.isValid())
00226         return QModelIndex();
00227 
00228     int row = -1;
00229     if (!showAll) {
00230         if (!engine->matchCount())
00231             return QModelIndex();
00232 
00233         QIndexMapper& rootIndices = engine->historyMatch.indices;
00234         if (idx.parent().isValid()) {
00235             if (idx.parent() != engine->curParent)
00236                 return QModelIndex();
00237         } else {
00238             row = rootIndices.indexOf(idx.row());
00239             if (row == -1 && engine->curParent.isValid())
00240                 return QModelIndex(); // source parent and our parent don't match
00241         }
00242 
00243         if (row == -1) {
00244             QIndexMapper& indices = engine->curMatch.indices;
00245             engine->filterOnDemand(idx.row() - indices.last());
00246             row = indices.indexOf(idx.row()) + rootIndices.count();
00247         }
00248 
00249         if (row == -1)
00250             return QModelIndex();
00251     } else {
00252         if (idx.parent() != engine->curParent)
00253             return QModelIndex();
00254         row = idx.row();
00255     }
00256 
00257     return createIndex(row, idx.column());
00258 }
00259 
00260 bool QCompletionModel::setCurrentRow(int row)
00261 {
00262     if (row < 0 || !engine->matchCount())
00263         return false;
00264 
00265     if (row >= engine->matchCount())
00266         engine->filterOnDemand(row + 1 - engine->matchCount());
00267 
00268     if (row >= engine->matchCount()) // invalid row
00269         return false;
00270 
00271     engine->curRow = row;
00272     return true;
00273 }
00274 
00275 QModelIndex QCompletionModel::currentIndex(bool sourceIndex) const
00276 {
00277     if (!engine->matchCount())
00278         return QModelIndex();
00279 
00280     int row = engine->curRow;
00281     if (showAll)
00282         row = engine->curMatch.indices[engine->curRow];
00283 
00284     QModelIndex idx = createIndex(row, c->column);
00285     if (!sourceIndex)
00286         return idx;
00287     return mapToSource(idx);
00288 }
00289 
00290 QModelIndex QCompletionModel::index(int row, int column, const QModelIndex& parent) const
00291 {
00292     if (row < 0 || column < 0 || column >= columnCount(parent) || parent.isValid())
00293         return QModelIndex();
00294 
00295     if (!showAll) {
00296         if (!engine->matchCount())
00297             return QModelIndex();
00298         if (row >= engine->historyMatch.indices.count()) {
00299             int want = row + 1 - engine->matchCount();
00300             if (want > 0)
00301                 engine->filterOnDemand(want);
00302             if (row >= engine->matchCount())
00303                 return QModelIndex();
00304         }
00305     } else {
00306         if (row >= model->rowCount(engine->curParent))
00307             return QModelIndex();
00308     }
00309 
00310     return createIndex(row, column);
00311 }
00312 
00313 int QCompletionModel::completionCount() const
00314 {
00315     if (!engine->matchCount())
00316         return 0;
00317 
00318     engine->filterOnDemand(INT_MAX);
00319     return engine->matchCount();
00320 }
00321 
00322 int QCompletionModel::rowCount(const QModelIndex &parent) const
00323 {
00324     if (parent.isValid())
00325         return 0;
00326 
00327     if (showAll) {
00328         // Show all items below current parent, even if we have no valid matches
00329         if (engine->curParts.count() != 1  && !engine->matchCount()
00330             && !engine->curParent.isValid())
00331             return 0;
00332         return model->rowCount(engine->curParent);
00333     }
00334 
00335     return completionCount();
00336 }
00337 
00338 void QCompletionModel::setFiltered(bool filtered)
00339 {
00340     if (showAll == !filtered)
00341         return;
00342     showAll = !filtered;
00343     reset();
00344 }
00345 
00346 bool QCompletionModel::hasChildren(const QModelIndex &parent) const
00347 {
00348     if (parent.isValid())
00349         return false;
00350 
00351     if (showAll)
00352         return model->hasChildren(mapToSource(parent));
00353 
00354     if (!engine->matchCount())
00355         return false;
00356 
00357     return true;
00358 }
00359 
00360 QVariant QCompletionModel::data(const QModelIndex& index, int role) const
00361 {
00362     return model->data(mapToSource(index), role);
00363 }
00364 
00365 void QCompletionModel::modelDestroyed()
00366 {
00367     QAbstractProxyModel::setSourceModel(0); // switch to static empty model
00368     model = sourceModel();
00369     invalidate();
00370 }
00371 
00372 void QCompletionModel::invalidate()
00373 {
00374     engine->cache.clear();
00375     filter(engine->curParts);
00376 }
00377 
00378 void QCompletionModel::filter(const QStringList& parts)
00379 {
00380     engine->filter(parts);
00381     reset();
00382 }
00383 
00385 void QCompletionEngine::filter(const QStringList& parts)
00386 {
00387     const QAbstractItemModel *model = c->proxy->sourceModel();
00388     curParts = parts;
00389     if (curParts.isEmpty())
00390         curParts.append(QString());
00391 
00392     curRow = -1;
00393     curParent = QModelIndex();
00394     curMatch = QMatchData();
00395     historyMatch = filterHistory();
00396 
00397     QModelIndex parent;
00398     for (int i = 0; i < curParts.count() - 1; i++) {
00399         QString part = curParts[i];
00400         int emi = filter(part, parent, -1).exactMatchIndex;
00401         if (emi == -1)
00402             return;
00403         parent = model->index(emi, c->column, parent);
00404     }
00405 
00406     // Note that we set the curParent to a valid parent, even if we have no matches
00407     // When filtering is disabled, we show all the items under this parent
00408     curParent = parent;
00409     if (curParts.last().isEmpty())
00410         curMatch = QMatchData(QIndexMapper(0, model->rowCount(curParent) - 1), -1, false);
00411     else
00412         curMatch = filter(curParts.last(), curParent, 1); // build at least one
00413     curRow = curMatch.isValid() ? 0 : -1;
00414 }
00415 
00416 QMatchData QCompletionEngine::filterHistory()
00417 {
00418     if (curParts.count() <= 1 || c->proxy->showAll)
00419         return QMatchData();
00420     QAbstractItemModel *source = c->proxy->model;
00421     bool dirModel = false;
00422 #ifndef QT_NO_DIRMODEL
00423     dirModel = (qobject_cast<QDirModel *>(source) != 0);
00424 #endif
00425     QVector<int> v;
00426     QIndexMapper im(v);
00427     QMatchData m(im, -1, true);
00428 
00429     for (int i = 0; i < source->rowCount(); i++) {
00430         QString str = source->index(i, c->column).data().toString();
00431         if (str.startsWith(c->prefix, c->cs)
00432 #ifndef Q_OS_WIN
00433             && (!dirModel || str != QDir::separator())
00434 #endif
00435             )
00436             m.indices.append(i);
00437     }
00438     return m;
00439 }
00440 
00441 // Returns a match hint from the cache by chopping the search string
00442 bool QCompletionEngine::matchHint(QString part, const QModelIndex& parent, QMatchData *hint)
00443 {
00444     if (c->cs == Qt::CaseInsensitive)
00445         part = part.toLower();
00446 
00447     const CacheItem& map = cache[parent];
00448 
00449     QString key = part;
00450     while (!key.isEmpty()) {
00451         key.chop(1);
00452         if (map.contains(key)) {
00453             *hint = map[key];
00454             return true;
00455         }
00456     }
00457 
00458     return false;
00459 }
00460 
00461 bool QCompletionEngine::lookupCache(QString part, const QModelIndex& parent, QMatchData *m)
00462 {
00463    if (c->cs == Qt::CaseInsensitive)
00464         part = part.toLower();
00465    const CacheItem& map = cache[parent];
00466    if (!map.contains(part))
00467        return false;
00468    *m = map[part];
00469    return true;
00470 }
00471 
00472 // When the cache size exceeds 1MB, it clears out about 1/2 of the cache.
00473 void QCompletionEngine::saveInCache(QString part, const QModelIndex& parent, const QMatchData& m)
00474 {
00475     QMatchData old = cache[parent].take(part);
00476     cost = cost + m.indices.cost() - old.indices.cost();
00477     if (cost * sizeof(int) > 1024 * 1024) {
00478         QMap<QModelIndex, CacheItem>::iterator it1 ;
00479         for (it1 = cache.begin(); it1 != cache.end(); it1++) {
00480             CacheItem& ci = it1.value();
00481             int sz = ci.count()/2;
00482             QMap<QString, QMatchData>::iterator it2 = ci.begin();
00483             for (int i = 0; it2 != ci.end() && i < sz; i++, ++it2) {
00484                 cost -= it2.value().indices.cost();
00485                 ci.erase(it2);
00486             }
00487             if (ci.count() == 0)
00488                 cache.erase(it1);
00489         }
00490     }
00491 
00492     if (c->cs == Qt::CaseInsensitive)
00493         part = part.toLower();
00494     cache[parent][part] = m;
00495 }
00496 
00498 QIndexMapper QSortedModelEngine::indexHint(QString part, const QModelIndex& parent)
00499 {
00500     const QAbstractItemModel *model = c->proxy->sourceModel();
00501 
00502     if (c->cs == Qt::CaseInsensitive)
00503         part = part.toLower();
00504 
00505     const CacheItem& map = cache[parent];
00506 
00507     // Try to find a lower and upper bound for the search from previous results
00508     int to = model->rowCount(parent) - 1;
00509     int from = 0;
00510     const CacheItem::const_iterator it = map.lowerBound(part);
00511 
00512     // look backward for first valid hint
00513     for(CacheItem::const_iterator it1 = it; it1-- != map.constBegin();) {
00514         const QMatchData& value = it1.value();
00515         if (value.isValid()) {
00516             from = value.indices.last() + 1;
00517             break;
00518         }
00519     }
00520 
00521     // look forward for first valid hint
00522     for(CacheItem::const_iterator it2 = it; it2 != map.constEnd(); ++it2) {
00523         const QMatchData& value = it2.value();
00524         if (value.isValid() && !it2.key().startsWith(part)) {
00525             to = value.indices[0] - 1;
00526             break;
00527         }
00528     }
00529 
00530     return QIndexMapper(from, to);
00531 }
00532 
00533 QMatchData QSortedModelEngine::filter(const QString& part, const QModelIndex& parent, int)
00534 {
00535     const QAbstractItemModel *model = c->proxy->sourceModel();
00536 
00537     QMatchData hint;
00538     if (lookupCache(part, parent, &hint))
00539         return hint;
00540 
00541     QIndexMapper indices;
00542 
00543     if (matchHint(part, parent, &hint)) {
00544         if (!hint.isValid())
00545             return QMatchData();
00546         indices = hint.indices;
00547     } else {
00548         indices = indexHint(part, parent);
00549     }
00550 
00551     // binary search the model within 'indices' for 'part' under 'parent'
00552     int high = indices.to() + 1;
00553     int low = indices.from() - 1;
00554     int probe;
00555     QModelIndex probeIndex;
00556     QString probeData;
00557 
00558     while (high - low > 1)
00559     {
00560         probe = (high + low) / 2;
00561         probeIndex = model->index(probe, c->column, parent);
00562         probeData = model->data(probeIndex, c->role).toString();
00563         if (QString::compare(probeData, part, c->cs) >= 0)
00564             high = probe;
00565         else
00566             low = probe;
00567     }
00568 
00569     if (low == indices.to()) { // not found
00570         saveInCache(part, parent, QMatchData());
00571         return QMatchData();
00572     }
00573 
00574     probeIndex = model->index(low + 1, c->column, parent);
00575     probeData = model->data(probeIndex, c->role).toString();
00576     if (!probeData.startsWith(part, c->cs)) {
00577         saveInCache(part, parent, QMatchData());
00578         return QMatchData();
00579     }
00580 
00581     int emi = QString::compare(probeData, part, c->cs) == 0 ? low+1 : -1;
00582 
00583     int from = low + 1;
00584     high = indices.to() + 1;
00585     low = from;
00586 
00587     while (high - low > 1)
00588     {
00589         probe = (high + low) / 2;
00590         probeIndex = model->index(probe, c->column, parent);
00591         probeData = model->data(probeIndex, c->role).toString();
00592         if (probeData.startsWith(part, c->cs))
00593             low = probe;
00594         else
00595             high = probe;
00596     }
00597 
00598     QMatchData m(QIndexMapper(from, high - 1), emi, false);
00599     saveInCache(part, parent, m);
00600     return m;
00601 }
00602 
00604 int QUnsortedModelEngine::buildIndices(const QString& str, const QModelIndex& parent, int n,
00605                                       const QIndexMapper& indices, QMatchData* m)
00606 {
00607     Q_ASSERT(m->partial);
00608     Q_ASSERT(n != -1 || m->exactMatchIndex == -1);
00609     const QAbstractItemModel *model = c->proxy->sourceModel();
00610     int i, count = 0;
00611 
00612     for (i = 0; i < indices.count() && count != n; ++i) {
00613         QModelIndex idx = model->index(indices[i], c->column, parent);
00614         QString data = model->data(idx, c->role).toString();
00615         if (!data.startsWith(str, c->cs))
00616             continue;
00617         m->indices.append(indices[i]);
00618         ++count;
00619         if (m->exactMatchIndex == -1 && QString::compare(data, str, c->cs) == 0) {
00620             m->exactMatchIndex = indices[i];
00621             if (n == -1)
00622                 return indices[i];
00623         }
00624     }
00625     return indices[i-1];
00626 }
00627 
00628 void QUnsortedModelEngine::filterOnDemand(int n)
00629 {
00630     Q_ASSERT(matchCount());
00631     if (!curMatch.partial)
00632         return;
00633     Q_ASSERT(n >= -1);
00634     const QAbstractItemModel *model = c->proxy->sourceModel();
00635     int lastRow = model->rowCount(curParent) - 1;
00636     QIndexMapper im(curMatch.indices.last() + 1, lastRow);
00637     int lastIndex = buildIndices(curParts.last(), curParent, n, im, &curMatch);
00638     curMatch.partial = (lastRow != lastIndex);
00639     saveInCache(curParts.last(), curParent, curMatch);
00640 }
00641 
00642 QMatchData QUnsortedModelEngine::filter(const QString& part, const QModelIndex& parent, int n)
00643 {
00644     QMatchData hint;
00645 
00646     QVector<int> v;
00647     QIndexMapper im(v);
00648     QMatchData m(im, -1, true);
00649 
00650     const QAbstractItemModel *model = c->proxy->sourceModel();
00651     bool foundInCache = lookupCache(part, parent, &m);
00652 
00653     if (!foundInCache) {
00654         if (matchHint(part, parent, &hint) && !hint.isValid())
00655             return QMatchData();
00656     }
00657 
00658     if (!foundInCache && !hint.isValid()) {
00659         const int lastRow = model->rowCount(parent) - 1;
00660         QIndexMapper all(0, lastRow);
00661         int lastIndex = buildIndices(part, parent, n, all, &m);
00662         m.partial = (lastIndex != lastRow);
00663     } else {
00664         if (!foundInCache) { // build from hint as much as we can
00665             buildIndices(part, parent, INT_MAX, hint.indices, &m);
00666             m.partial = hint.partial;
00667         }
00668         if (m.partial && ((n == -1 && m.exactMatchIndex == -1) || (m.indices.count() < n))) {
00669             // need more and have more
00670             const int lastRow = model->rowCount(parent) - 1;
00671             QIndexMapper rest(hint.indices.last() + 1, lastRow);
00672             int want = n == -1 ? -1 : n - m.indices.count();
00673             int lastIndex = buildIndices(part, parent, want, rest, &m);
00674             m.partial = (lastRow != lastIndex);
00675         }
00676     }
00677 
00678     saveInCache(part, parent, m);
00679     return m;
00680 }
00681 
00683 QCompleterPrivate::QCompleterPrivate()
00684 : widget(0), proxy(0), popup(0), cs(Qt::CaseSensitive), role(Qt::EditRole), column(0),
00685   sorting(QCompleter::UnsortedModel), eatFocusOut(true)
00686 {
00687 }
00688 
00689 void QCompleterPrivate::init(QAbstractItemModel *m)
00690 {
00691     Q_Q(QCompleter);
00692     proxy = new QCompletionModel(this, q);
00693     q->setModel(m);
00694 #ifdef QT_NO_LISTVIEW
00695     q->setCompletionMode(QCompleter::InlineCompletion);
00696 #else
00697     QListView *listView = new QListView;
00698     listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
00699     listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
00700     listView->setSelectionBehavior(QAbstractItemView::SelectRows);
00701     listView->setSelectionMode(QAbstractItemView::SingleSelection);
00702     listView->setModelColumn(column);
00703     q->setPopup(listView);
00704     q->setCompletionMode(QCompleter::PopupCompletion);
00705 #endif // QT_NO_LISTVIEW
00706 }
00707 
00708 void QCompleterPrivate::setCurrentIndex(QModelIndex index, bool select)
00709 {
00710     if (!select) {
00711         popup->selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
00712     } else {
00713         if (!index.isValid())
00714             popup->selectionModel()->clear();
00715         else
00716             popup->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select
00717                                                             | QItemSelectionModel::Rows);
00718     }
00719     index = popup->selectionModel()->currentIndex();
00720     if (!index.isValid())
00721         popup->scrollToTop();
00722     else
00723         popup->scrollTo(index, QAbstractItemView::PositionAtTop);
00724 }
00725 
00726 void QCompleterPrivate::_q_completionSelected(const QItemSelection& selection)
00727 {
00728     QModelIndex index;
00729     if (!selection.indexes().isEmpty())
00730         index = selection.indexes().first();
00731 
00732     _q_complete(index, true);
00733 }
00734 
00735 void QCompleterPrivate::_q_complete(QModelIndex index, bool highlighted)
00736 {
00737     Q_Q(QCompleter);
00738     QString completion;
00739 
00740     if (!index.isValid())
00741         completion = prefix;
00742     else {
00743         QModelIndex si = proxy->mapToSource(index);
00744         si = si.sibling(si.row(), column); // for clicked()
00745         completion = q->pathFromIndex(si);
00746 #ifndef QT_NO_DIRMODEL
00747         // add a trailing separator in inline
00748         if (mode == QCompleter::InlineCompletion) {
00749             if (qobject_cast<QDirModel *>(proxy->sourceModel()) && QFileInfo(completion).isDir())
00750                 completion += QDir::separator();
00751         }
00752 #endif
00753     }
00754 
00755     if (highlighted) {
00756         emit q->highlighted(index);
00757         emit q->highlighted(completion);
00758     } else {
00759         emit q->activated(index);
00760         emit q->activated(completion);
00761     }
00762 }
00763 
00764 void QCompleterPrivate::showPopup(const QRect& rect)
00765 {
00766     const QRect screen = QApplication::desktop()->availableGeometry(widget);
00767     Qt::LayoutDirection dir = widget->layoutDirection();
00768     QPoint pos;
00769     int rw, rh, w;
00770     int h = (popup->sizeHintForRow(0) * qMin(7, popup->model()->rowCount()) + 3) + 3;
00771     QScrollBar *hsb = popup->horizontalScrollBar();
00772     if (hsb && hsb->isVisible())
00773         h += popup->horizontalScrollBar()->sizeHint().height();
00774 
00775     if (rect.isValid()) {
00776         rh = rect.height();
00777         w = rw = rect.width();
00778         pos = widget->mapToGlobal(dir == Qt::RightToLeft ? rect.bottomRight() : rect.bottomLeft());
00779     } else {
00780         rh = widget->height();
00781         rw = widget->width();
00782         pos = widget->mapToGlobal(QPoint(0, widget->height() - 2));
00783         w = widget->width();
00784     }
00785 
00786     if ((pos.x() + rw) > (screen.x() + screen.width()))
00787         pos.setX(screen.x() + screen.width() - w);
00788     if (pos.x() < screen.x())
00789         pos.setX(screen.x());
00790     if (((pos.y() + rh) > (screen.y() + screen.height())) && ((pos.y() - h - rh) >= 0))
00791         pos.setY(pos.y() - qMax(h, popup->minimumHeight()) - rh + 2);
00792 
00793     popup->setGeometry(pos.x(), pos.y(), w, h);
00794 
00795     if (!popup->isVisible())
00796         popup->show();
00797 }
00798 
00802 QCompleter::QCompleter(QObject *parent)
00803 : QObject(*new QCompleterPrivate(), parent)
00804 {
00805     Q_D(QCompleter);
00806     d->init();
00807 }
00808 
00813 QCompleter::QCompleter(QAbstractItemModel *model, QObject *parent)
00814     : QObject(*new QCompleterPrivate(), parent)
00815 {
00816     Q_D(QCompleter);
00817     d->init(model);
00818 }
00819 
00820 #ifndef QT_NO_STRINGLISTMODEL
00821 
00825 QCompleter::QCompleter(const QStringList& list, QObject *parent)
00826 : QObject(*new QCompleterPrivate(), parent)
00827 {
00828     Q_D(QCompleter);
00829     d->init(new QStringListModel(list, this));
00830 }
00831 #endif // QT_NO_STRINGLISTMODEL
00832 
00836 QCompleter::~QCompleter()
00837 {
00838 }
00839 
00849 void QCompleter::setWidget(QWidget *widget)
00850 {
00851     Q_D(QCompleter);
00852     d->widget = widget;
00853     if (d->popup)
00854         d->popup->setFocusProxy(d->widget);
00855     setCompletionMode(d->mode); // will install event filter depending on mode
00856 }
00857 
00863 QWidget *QCompleter::widget() const
00864 {
00865     Q_D(const QCompleter);
00866     return d->widget;
00867 }
00868 
00880 void QCompleter::setModel(QAbstractItemModel *model)
00881 {
00882     Q_D(QCompleter);
00883     QAbstractItemModel *oldModel = d->proxy->model;
00884     d->proxy->setSourceModel(model);
00885     if (d->popup)
00886         setPopup(d->popup); // set the model and make new connections
00887     if (oldModel && oldModel->QObject::parent() == this)
00888         delete oldModel;
00889 #ifndef QT_NO_DIRMODEL
00890     if (qobject_cast<QDirModel *>(model)) {
00891 #ifdef Q_OS_WIN
00892         setCaseSensitivity(Qt::CaseInsensitive);
00893 #else
00894         setCaseSensitivity(Qt::CaseSensitive);
00895 #endif
00896     }
00897 #endif // QT_NO_DIRMODEL
00898 }
00899 
00905 QAbstractItemModel *QCompleter::model() const
00906 {
00907     Q_D(const QCompleter);
00908     return d->proxy->sourceModel();
00909 }
00910 
00929 void QCompleter::setCompletionMode(QCompleter::CompletionMode mode)
00930 {
00931     Q_D(QCompleter);
00932 
00933     d->mode = mode;
00934     d->proxy->setFiltered(mode != QCompleter::UnfilteredPopupCompletion);
00935 
00936     if (mode == QCompleter::InlineCompletion) {
00937         if (d->widget)
00938             d->widget->removeEventFilter(this);
00939         return;
00940     }
00941 
00942     if (d->widget)
00943         d->widget->installEventFilter(this);
00944 }
00945 
00946 QCompleter::CompletionMode QCompleter::completionMode() const
00947 {
00948     Q_D(const QCompleter);
00949     return d->mode;
00950 }
00951 
00967 void QCompleter::setPopup(QAbstractItemView *popup)
00968 {
00969     Q_D(QCompleter);
00970     Q_ASSERT(popup != 0);
00971     if (d->popup) {
00972         QObject::disconnect(d->popup->selectionModel(), 0, this, 0);
00973         QObject::disconnect(d->popup, 0, this, 0);
00974     }
00975     if (d->popup != popup)
00976         delete d->popup;
00977     if (popup->model() != d->proxy)
00978         popup->setModel(d->proxy);
00979     popup->hide();
00980     popup->setParent(0, Qt::Popup);
00981     popup->setFocusPolicy(Qt::NoFocus);
00982     popup->setFocusProxy(d->widget);
00983     popup->installEventFilter(this);
00984     popup->setItemDelegate(new QCompleterItemDelegate(popup));
00985 
00986     QObject::connect(popup, SIGNAL(clicked(QModelIndex)),
00987                      this, SLOT(_q_complete(QModelIndex)));
00988     QObject::connect(popup, SIGNAL(clicked(QModelIndex)), popup, SLOT(hide()));
00989 
00990     QObject::connect(popup->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
00991                      this, SLOT(_q_completionSelected(QItemSelection)));
00992     d->popup = popup;
00993 }
00994 
01000 QAbstractItemView *QCompleter::popup() const
01001 {
01002     Q_D(const QCompleter);
01003     return d->popup;
01004 }
01005 
01009 bool QCompleter::event(QEvent *ev)
01010 {
01011     return QObject::event(ev);
01012 }
01013 
01017 bool QCompleter::eventFilter(QObject *o, QEvent *e)
01018 {
01019     Q_D(QCompleter);
01020 
01021     if (d->eatFocusOut && o == d->widget && e->type() == QEvent::FocusOut) {
01022         if (d->popup && d->popup->isVisible())
01023             return true;
01024     }
01025 
01026     if (o != d->popup)
01027         return QObject::eventFilter(o, e);
01028 
01029     switch (e->type()) {
01030     case QEvent::KeyPress: {
01031         QKeyEvent *ke = static_cast<QKeyEvent *>(e);
01032 
01033         QModelIndex curIndex = d->popup->currentIndex();
01034         QModelIndexList selList = d->popup->selectionModel()->selectedIndexes();
01035 
01036         const int key = ke->key();
01037         // In UnFilteredPopup mode, select the current item
01038         if ((key == Qt::Key_Up || key == Qt::Key_Down) && selList.isEmpty() && curIndex.isValid()
01039             && d->mode == QCompleter::UnfilteredPopupCompletion) {
01040               d->setCurrentIndex(curIndex);
01041               return true;
01042         }
01043 
01044         // Handle popup navigation keys. These are hardcoded because up/down might make the
01045         // widget do something else (lineedit cursor moves to home/end on mac, for instance)
01046         switch (key) {
01047         case Qt::Key_End:
01048         case Qt::Key_Home:
01049             if (ke->modifiers() & Qt::ControlModifier)
01050                 return false;
01051             break;
01052 
01053         case Qt::Key_Up:
01054             if (!curIndex.isValid()) {
01055                 int rowCount = d->proxy->rowCount();
01056                 QModelIndex lastIndex = d->proxy->index(rowCount - 1, 0);
01057                 d->setCurrentIndex(lastIndex);
01058                 return true;
01059             } else if (curIndex.row() == 0) {
01060                 d->setCurrentIndex(QModelIndex());
01061                 return true;
01062             }
01063             return false;
01064 
01065         case Qt::Key_Down:
01066             if (!curIndex.isValid()) {
01067                 QModelIndex firstIndex = d->proxy->index(0, 0);
01068                 d->setCurrentIndex(firstIndex);
01069                 return true;
01070             } else if (curIndex.row() == d->proxy->rowCount() - 1) {
01071                 d->setCurrentIndex(QModelIndex());
01072                 return true;
01073             }
01074             return false;
01075 
01076         case Qt::Key_PageUp:
01077         case Qt::Key_PageDown:
01078             return false;
01079         }
01080 
01081         // Send the event to the widget. If the widget accepted the event, do nothing
01082         // If the widget did not accept the event, provide a default implementation
01083         d->eatFocusOut = false;
01084         (static_cast<QObject *>(d->widget))->event(ke);
01085         d->eatFocusOut = true;
01086         if (!d->widget || e->isAccepted() || !d->popup->isVisible()) {
01087             // widget lost focus, hide the popup
01088             if (d->widget && !d->widget->hasFocus())
01089                 d->popup->hide();
01090             return true;
01091         }
01092 
01093         // default implementation for keys not handled by the widget when popup is open
01094         switch (key) {
01095         case Qt::Key_Return:
01096         case Qt::Key_Enter:
01097         case Qt::Key_Tab:
01098             d->popup->hide();
01099             if (curIndex.isValid())
01100                 d->_q_complete(curIndex);
01101             break;
01102 
01103         case Qt::Key_F4:
01104             if (ke->modifiers() & Qt::AltModifier)
01105                 d->popup->hide();
01106             break;
01107 
01108         case Qt::Key_Backtab:
01109         case Qt::Key_Escape:
01110             d->popup->hide();
01111             break;
01112 
01113         default:
01114             break;
01115         }
01116 
01117         return true;
01118     }
01119 
01120     case QEvent::MouseButtonPress:
01121         if (!d->popup->underMouse()) {
01122             d->popup->hide();
01123             return true;
01124         }
01125         return false;
01126 
01127     default:
01128         return false;
01129     }
01130 }
01131 
01142 void QCompleter::complete(const QRect& rect)
01143 {
01144     Q_D(QCompleter);
01145     QModelIndex idx = d->proxy->currentIndex(false);
01146     if (d->mode == QCompleter::InlineCompletion) {
01147         if (idx.isValid())
01148             d->_q_complete(idx, true);
01149         return;
01150     }
01151 
01152     Q_ASSERT(d->widget != 0);
01153     if ((d->mode == QCompleter::PopupCompletion && !idx.isValid())
01154         || (d->mode == QCompleter::UnfilteredPopupCompletion && d->proxy->rowCount() == 0)) {
01155         d->popup->hide(); // no suggestion, hide
01156         return;
01157     }
01158 
01159     if (d->mode == QCompleter::UnfilteredPopupCompletion)
01160         d->setCurrentIndex(idx, false);
01161 
01162     d->showPopup(rect);
01163 }
01164 
01174 bool QCompleter::setCurrentRow(int row)
01175 {
01176     Q_D(QCompleter);
01177     return d->proxy->setCurrentRow(row);
01178 }
01179 
01185 int QCompleter::currentRow() const
01186 {
01187     Q_D(const QCompleter);
01188     return d->proxy->currentRow();
01189 }
01190 
01196 int QCompleter::completionCount() const
01197 {
01198     Q_D(const QCompleter);
01199     return d->proxy->completionCount();
01200 }
01201 
01233 void QCompleter::setModelSorting(QCompleter::ModelSorting sorting)
01234 {
01235     Q_D(QCompleter);
01236     if (d->sorting == sorting)
01237         return;
01238     d->sorting = sorting;
01239     d->proxy->createEngine();
01240     d->proxy->invalidate();
01241 }
01242 
01243 QCompleter::ModelSorting QCompleter::modelSorting() const
01244 {
01245     Q_D(const QCompleter);
01246     return d->sorting;
01247 }
01248 
01260 void QCompleter::setCompletionColumn(int column)
01261 {
01262     Q_D(QCompleter);
01263     if (d->column == column)
01264         return;
01265 #ifndef QT_NO_LISTVIEW
01266     if (QListView *listView = qobject_cast<QListView *>(d->popup))
01267         listView->setModelColumn(column);
01268 #endif
01269     d->column = column;
01270     d->proxy->invalidate();
01271 }
01272 
01273 int QCompleter::completionColumn() const
01274 {
01275     Q_D(const QCompleter);
01276     return d->column;
01277 }
01278 
01287 void QCompleter::setCompletionRole(int role)
01288 {
01289     Q_D(QCompleter);
01290     if (d->role == role)
01291         return;
01292     d->role = role;
01293     d->proxy->invalidate();
01294 }
01295 
01296 int QCompleter::completionRole() const
01297 {
01298     Q_D(const QCompleter);
01299     return d->role;
01300 }
01301 
01310 void QCompleter::setCaseSensitivity(Qt::CaseSensitivity cs)
01311 {
01312     Q_D(QCompleter);
01313     if (d->cs == cs)
01314         return;
01315     d->cs = cs;
01316     d->proxy->createEngine();
01317     d->proxy->invalidate();
01318 }
01319 
01320 Qt::CaseSensitivity QCompleter::caseSensitivity() const
01321 {
01322     Q_D(const QCompleter);
01323     return d->cs;
01324 }
01325 
01333 void QCompleter::setCompletionPrefix(const QString &prefix)
01334 {
01335     Q_D(QCompleter);
01336     d->prefix = prefix;
01337     d->proxy->filter(splitPath(prefix));
01338 }
01339 
01340 QString QCompleter::completionPrefix() const
01341 {
01342     Q_D(const QCompleter);
01343     return d->prefix;
01344 }
01345 
01351 QModelIndex QCompleter::currentIndex() const
01352 {
01353     Q_D(const QCompleter);
01354     return d->proxy->currentIndex(false);
01355 }
01356 
01364 QString QCompleter::currentCompletion() const
01365 {
01366     Q_D(const QCompleter);
01367     return pathFromIndex(d->proxy->currentIndex(true));
01368 }
01369 
01377 QAbstractItemModel *QCompleter::completionModel() const
01378 {
01379     Q_D(const QCompleter);
01380     return d->proxy;
01381 }
01382 
01393 QString QCompleter::pathFromIndex(const QModelIndex& index) const
01394 {
01395     Q_D(const QCompleter);
01396     if (!index.isValid())
01397         return QString();
01398 
01399     QAbstractItemModel *sourceModel = d->proxy->sourceModel();
01400 #ifndef QT_NO_DIRMODEL
01401     QDirModel *dirModel = qobject_cast<QDirModel *>(sourceModel);
01402     if (!dirModel)
01403 #endif
01404         return sourceModel->data(index, d->role).toString();
01405 
01406     QModelIndex idx = index;
01407     QStringList list;
01408     do {
01409         QString t = sourceModel->data(idx, Qt::EditRole).toString();
01410         list.prepend(t);
01411         QModelIndex parent = idx.parent();
01412         idx = parent.sibling(parent.row(), index.column());
01413     } while (idx.isValid());
01414 
01415 #ifndef Q_OS_WIN
01416     if (list.count() == 1) // only the separator or some other text
01417         return list[0];
01418     list[0].clear() ; // the join below will provide the separator
01419 #endif
01420 
01421     return list.join(QDir::separator());
01422 }
01423 
01436 QStringList QCompleter::splitPath(const QString& path) const
01437 {
01438     Q_D(const QCompleter);
01439     bool isDirModel = false;
01440 #ifndef QT_NO_DIRMODEL
01441     isDirModel = qobject_cast<QDirModel *>(d->proxy->sourceModel()) != 0;
01442 #endif
01443 
01444     if (!isDirModel || path.isEmpty())
01445         return QStringList(completionPrefix());
01446 
01447     QString pathCopy = QDir::toNativeSeparators(path);
01448     QString sep = QDir::separator();
01449 #ifdef Q_OS_WIN
01450     if (pathCopy == QLatin1String("\\") || pathCopy == QLatin1String("\\\\"))
01451         return QStringList(pathCopy);
01452     QString doubleSlash(QLatin1String("\\\\"));
01453     if (pathCopy.startsWith(doubleSlash))
01454         pathCopy = pathCopy.mid(2);
01455     else
01456         doubleSlash.clear();
01457 #endif
01458 
01459     QRegExp re(QLatin1String("[") + QRegExp::escape(sep) + QLatin1String("]"));
01460     QStringList parts = pathCopy.split(re);
01461 
01462 #ifdef Q_OS_WIN
01463     if (!doubleSlash.isEmpty())
01464         parts[0].prepend(doubleSlash);
01465 #else
01466     if (path[0] == sep[0]) // readd the "/" at the beginning as the split removed it
01467         parts[0] = sep[0];
01468 #endif
01469 
01470     return parts;
01471 }
01472 
01507 #include "moc_qcompleter.cpp"
01508 
01509 #endif // QT_NO_COMPLETER

Generated on Thu Mar 15 11:56:37 2007 for Qt 4.2 User's Guide by  doxygen 1.5.1