~ubuntu-branches/debian/sid/kexi/sid

« back to all changes in this revision

Viewing changes to src/kexiutils/completer/KexiCompleter.cpp

  • Committer: Package Import Robot
  • Author(s): Pino Toscano
  • Date: 2017-06-24 20:10:10 UTC
  • Revision ID: package-import@ubuntu.com-20170624201010-5lrzd5r2vwthwifp
Tags: upstream-3.0.1.1
ImportĀ upstreamĀ versionĀ 3.0.1.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/****************************************************************************
 
2
**
 
3
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
 
4
** All rights reserved.
 
5
** Contact: Nokia Corporation (qt-info@nokia.com)
 
6
**
 
7
** This file is part of the QtGui module of the Qt Toolkit.
 
8
**
 
9
** $QT_BEGIN_LICENSE:LGPL$
 
10
** GNU Lesser General Public License Usage
 
11
** This file may be used under the terms of the GNU Lesser General Public
 
12
** License version 2.1 as published by the Free Software Foundation and
 
13
** appearing in the file LICENSE.LGPL included in the packaging of this
 
14
** file. Please review the following information to ensure the GNU Lesser
 
15
** General Public License version 2.1 requirements will be met:
 
16
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
 
17
**
 
18
** In addition, as a special exception, Nokia gives you certain additional
 
19
** rights. These rights are described in the Nokia Qt LGPL Exception
 
20
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
 
21
**
 
22
** GNU General Public License Usage
 
23
** Alternatively, this file may be used under the terms of the GNU General
 
24
** Public License version 3.0 as published by the Free Software Foundation
 
25
** and appearing in the file LICENSE.GPL included in the packaging of this
 
26
** file. Please review the following information to ensure the GNU General
 
27
** Public License version 3.0 requirements will be met:
 
28
** http://www.gnu.org/copyleft/gpl.html.
 
29
**
 
30
** Other Usage
 
31
** Alternatively, this file may be used in accordance with the terms and
 
32
** conditions contained in a signed written agreement between you and Nokia.
 
33
**
 
34
**
 
35
**
 
36
**
 
37
**
 
38
** $QT_END_LICENSE$
 
39
**
 
40
****************************************************************************/
 
41
 
 
42
/*!
 
43
    \class KexiCompleter
 
44
    \brief The KexiCompleter class provides completions based on an item model.
 
45
    \since 4.2
 
46
 
 
47
    You can use KexiCompleter to provide auto completions in any Qt
 
48
    widget, such as QLineEdit and QComboBox.
 
49
    When the user starts typing a word, KexiCompleter suggests possible ways of
 
50
    completing the word, based on a word list. The word list is
 
51
    provided as a QAbstractItemModel. (For simple applications, where
 
52
    the word list is static, you can pass a QStringList to
 
53
    KexiCompleter's constructor.)
 
54
 
 
55
    \tableofcontents
 
56
 
 
57
    \section1 Basic Usage
 
58
 
 
59
    A KexiCompleter is used typically with a QLineEdit or QComboBox.
 
60
    For example, here's how to provide auto completions from a simple
 
61
    word list in a QLineEdit:
 
62
 
 
63
    \snippet doc/src/snippets/code/src_gui_util_qcompleter.cpp 0
 
64
 
 
65
    A QFileSystemModel can be used to provide auto completion of file names.
 
66
    For example:
 
67
 
 
68
    \snippet doc/src/snippets/code/src_gui_util_qcompleter.cpp 1
 
69
 
 
70
    To set the model on which KexiCompleter should operate, call
 
71
    setModel(). By default, KexiCompleter will attempt to match the \l
 
72
    {completionPrefix}{completion prefix} (i.e., the word that the
 
73
    user has started typing) against the Qt::EditRole data stored in
 
74
    column 0 in the  model case sensitively. This can be changed
 
75
    using setCompletionRole(), setCompletionColumn(), and
 
76
    setCaseSensitivity().
 
77
 
 
78
    If the model is sorted on the column and role that are used for completion,
 
79
    you can call setModelSorting() with either
 
80
    KexiCompleter::CaseSensitivelySortedModel or
 
81
    KexiCompleter::CaseInsensitivelySortedModel as the argument. On large models,
 
82
    this can lead to significant performance improvements, because KexiCompleter
 
83
    can then use binary search instead of linear search.
 
84
 
 
85
    The model can be a \l{QAbstractListModel}{list model},
 
86
    a \l{QAbstractTableModel}{table model}, or a
 
87
    \l{QAbstractItemModel}{tree model}. Completion on tree models
 
88
    is slightly more involved and is covered in the \l{Handling
 
89
    Tree Models} section below.
 
90
 
 
91
    The completionMode() determines the mode used to provide completions to
 
92
    the user.
 
93
 
 
94
    \section1 Iterating Through Completions
 
95
 
 
96
    To retrieve a single candidate string, call setCompletionPrefix()
 
97
    with the text that needs to be completed and call
 
98
    currentCompletion(). You can iterate through the list of
 
99
    completions as below:
 
100
 
 
101
    \snippet doc/src/snippets/code/src_gui_util_qcompleter.cpp 2
 
102
 
 
103
    completionCount() returns the total number of completions for the
 
104
    current prefix. completionCount() should be avoided when possible,
 
105
    since it requires a scan of the entire model.
 
106
 
 
107
    \section1 The Completion Model
 
108
 
 
109
    completionModel() return a list model that contains all possible
 
110
    completions for the current completion prefix, in the order in which
 
111
    they appear in the model. This model can be used to display the current
 
112
    completions in a custom view. Calling setCompletionPrefix() automatically
 
113
    refreshes the completion model.
 
114
 
 
115
    \section1 Handling Tree Models
 
116
 
 
117
    KexiCompleter can look for completions in tree models, assuming
 
118
    that any item (or sub-item or sub-sub-item) can be unambiguously
 
119
    represented as a string by specifying the path to the item. The
 
120
    completion is then performed one level at a time.
 
121
 
 
122
    Let's take the example of a user typing in a file system path.
 
123
    The model is a (hierarchical) QFileSystemModel. The completion
 
124
    occurs for every element in the path. For example, if the current
 
125
    text is \c C:\Wind, KexiCompleter might suggest \c Windows to
 
126
    complete the current path element. Similarly, if the current text
 
127
    is \c C:\Windows\Sy, KexiCompleter might suggest \c System.
 
128
 
 
129
    For this kind of completion to work, KexiCompleter needs to be able to
 
130
    split the path into a list of strings that are matched at each level.
 
131
    For \c C:\Windows\Sy, it needs to be split as "C:", "Windows" and "Sy".
 
132
    The default implementation of splitPath(), splits the completionPrefix
 
133
    using QDir::separator() if the model is a QFileSystemModel.
 
134
 
 
135
    To provide completions, KexiCompleter needs to know the path from an index.
 
136
    This is provided by pathFromIndex(). The default implementation of
 
137
    pathFromIndex(), returns the data for the \l{Qt::EditRole}{edit role}
 
138
    for list models and the absolute file path if the mode is a QFileSystemModel.
 
139
 
 
140
    \sa QAbstractItemModel, QLineEdit, QComboBox, {Completer Example}
 
141
*/
 
142
 
 
143
#include "KexiCompleter_p.h"
 
144
 
 
145
#ifndef QT_NO_COMPLETER
 
146
 
 
147
#include <QScrollBar>
 
148
#include <QStringListModel>
 
149
#include <QDirModel>
 
150
#include <QFileSystemModel>
 
151
#include <QListView>
 
152
#include <QApplication>
 
153
#include <QEvent>
 
154
#include <QDesktopWidget>
 
155
#include <QLineEdit>
 
156
#include <QKeyEvent>
 
157
 
 
158
#include <limits.h>
 
159
 
 
160
class KexiEmptyItemModel : public QAbstractItemModel
 
161
{
 
162
    Q_OBJECT
 
163
public:
 
164
    explicit KexiEmptyItemModel(QObject *parent = 0) : QAbstractItemModel(parent) {}
 
165
    QModelIndex index(int, int, const QModelIndex &) const { return QModelIndex(); }
 
166
    QModelIndex parent(const QModelIndex &) const { return QModelIndex(); }
 
167
    int rowCount(const QModelIndex &) const { return 0; }
 
168
    int columnCount(const QModelIndex &) const { return 0; }
 
169
    bool hasChildren(const QModelIndex &) const { return false; }
 
170
    QVariant data(const QModelIndex &, int) const { return QVariant(); }
 
171
};
 
172
 
 
173
Q_GLOBAL_STATIC(KexiEmptyItemModel, kexiEmptyModel)
 
174
 
 
175
QAbstractItemModel *KexiAbstractItemModelPrivate::staticEmptyModel()
 
176
{
 
177
    return kexiEmptyModel();
 
178
}
 
179
 
 
180
namespace {
 
181
    struct DefaultRoleNames : public QHash<int, QByteArray>
 
182
    {
 
183
        DefaultRoleNames() {
 
184
            (*this)[Qt::DisplayRole] = "display";
 
185
            (*this)[Qt::DecorationRole] = "decoration";
 
186
            (*this)[Qt::EditRole] = "edit";
 
187
            (*this)[Qt::ToolTipRole] = "toolTip";
 
188
            (*this)[Qt::StatusTipRole] = "statusTip";
 
189
            (*this)[Qt::WhatsThisRole] = "whatsThis";
 
190
        }
 
191
    };
 
192
}
 
193
 
 
194
Q_GLOBAL_STATIC(DefaultRoleNames, qDefaultRoleNames)
 
195
 
 
196
const QHash<int,QByteArray> &KexiAbstractItemModelPrivate::defaultRoleNames()
 
197
{
 
198
    return *qDefaultRoleNames();
 
199
}
 
200
 
 
201
 
 
202
KexiCompletionModel::KexiCompletionModel(KexiCompleterPrivate *c, QObject *parent)
 
203
    : QAbstractProxyModel(parent),
 
204
      c(c), showAll(false), d(new KexiCompletionModelPrivate(this))
 
205
{
 
206
    QAbstractProxyModel::setSourceModel(KexiAbstractItemModelPrivate::staticEmptyModel());
 
207
    createEngine();
 
208
}
 
209
 
 
210
KexiCompletionModel::~KexiCompletionModel()
 
211
{
 
212
    delete d;
 
213
}
 
214
 
 
215
int KexiCompletionModel::columnCount(const QModelIndex &) const
 
216
{
 
217
    return sourceModel()->columnCount();
 
218
}
 
219
 
 
220
void KexiCompletionModel::setSourceModel(QAbstractItemModel *source)
 
221
{
 
222
    bool hadModel = (sourceModel() != 0);
 
223
 
 
224
    if (hadModel)
 
225
        QObject::disconnect(sourceModel(), 0, this, 0);
 
226
 
 
227
    QAbstractProxyModel::setSourceModel(source ? source : KexiAbstractItemModelPrivate::staticEmptyModel());
 
228
 
 
229
    if (source) {
 
230
        // TODO: Optimize updates in the source model
 
231
        connect(source, SIGNAL(modelReset()), this, SLOT(invalidate()));
 
232
        connect(source, SIGNAL(destroyed()), this, SLOT(modelDestroyed()));
 
233
        connect(source, SIGNAL(layoutChanged()), this, SLOT(invalidate()));
 
234
        connect(source, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(rowsInserted()));
 
235
        connect(source, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(invalidate()));
 
236
        connect(source, SIGNAL(columnsInserted(QModelIndex,int,int)), this, SLOT(invalidate()));
 
237
        connect(source, SIGNAL(columnsRemoved(QModelIndex,int,int)), this, SLOT(invalidate()));
 
238
        connect(source, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(invalidate()));
 
239
    }
 
240
 
 
241
    invalidate();
 
242
}
 
243
 
 
244
void KexiCompletionModel::createEngine()
 
245
{
 
246
    bool sortedEngine = false;
 
247
    switch (c->sorting) {
 
248
    case KexiCompleter::UnsortedModel:
 
249
        sortedEngine = false;
 
250
        break;
 
251
    case KexiCompleter::CaseSensitivelySortedModel:
 
252
        sortedEngine = c->cs == Qt::CaseSensitive;
 
253
        break;
 
254
    case KexiCompleter::CaseInsensitivelySortedModel:
 
255
        sortedEngine = c->cs == Qt::CaseInsensitive;
 
256
        break;
 
257
    }
 
258
 
 
259
    if (sortedEngine)
 
260
        engine.reset(new QSortedModelEngine(c));
 
261
    else
 
262
        engine.reset(new QUnsortedModelEngine(c));
 
263
}
 
264
 
 
265
QModelIndex KexiCompletionModel::mapToSource(const QModelIndex& index) const
 
266
{
 
267
    if (!index.isValid())
 
268
        return engine->curParent;
 
269
 
 
270
    int row;
 
271
    QModelIndex parent = engine->curParent;
 
272
    if (!showAll) {
 
273
        if (!engine->matchCount())
 
274
            return QModelIndex();
 
275
        Q_ASSERT(index.row() < engine->matchCount());
 
276
        KexiIndexMapper& rootIndices = engine->historyMatch.indices;
 
277
        if (index.row() < rootIndices.count()) {
 
278
            row = rootIndices[index.row()];
 
279
            parent = QModelIndex();
 
280
        } else {
 
281
            row = engine->curMatch.indices[index.row() - rootIndices.count()];
 
282
        }
 
283
    } else {
 
284
        row = index.row();
 
285
    }
 
286
 
 
287
    return sourceModel()->index(row, index.column(), parent);
 
288
}
 
289
 
 
290
QModelIndex KexiCompletionModel::mapFromSource(const QModelIndex& idx) const
 
291
{
 
292
    if (!idx.isValid())
 
293
        return QModelIndex();
 
294
 
 
295
    int row = -1;
 
296
    if (!showAll) {
 
297
        if (!engine->matchCount())
 
298
            return QModelIndex();
 
299
 
 
300
        KexiIndexMapper& rootIndices = engine->historyMatch.indices;
 
301
        if (idx.parent().isValid()) {
 
302
            if (idx.parent() != engine->curParent)
 
303
                return QModelIndex();
 
304
        } else {
 
305
            row = rootIndices.indexOf(idx.row());
 
306
            if (row == -1 && engine->curParent.isValid())
 
307
                return QModelIndex(); // source parent and our parent don't match
 
308
        }
 
309
 
 
310
        if (row == -1) {
 
311
            KexiIndexMapper& indices = engine->curMatch.indices;
 
312
            engine->filterOnDemand(idx.row() - indices.last());
 
313
            row = indices.indexOf(idx.row()) + rootIndices.count();
 
314
        }
 
315
 
 
316
        if (row == -1)
 
317
            return QModelIndex();
 
318
    } else {
 
319
        if (idx.parent() != engine->curParent)
 
320
            return QModelIndex();
 
321
        row = idx.row();
 
322
    }
 
323
 
 
324
    return createIndex(row, idx.column());
 
325
}
 
326
 
 
327
bool KexiCompletionModel::setCurrentRow(int row)
 
328
{
 
329
    if (row < 0 || !engine->matchCount())
 
330
        return false;
 
331
 
 
332
    if (row >= engine->matchCount())
 
333
        engine->filterOnDemand(row + 1 - engine->matchCount());
 
334
 
 
335
    if (row >= engine->matchCount()) // invalid row
 
336
        return false;
 
337
 
 
338
    engine->curRow = row;
 
339
    return true;
 
340
}
 
341
 
 
342
QModelIndex KexiCompletionModel::currentIndex(bool sourceIndex) const
 
343
{
 
344
    if (!engine->matchCount())
 
345
        return QModelIndex();
 
346
 
 
347
    int row = engine->curRow;
 
348
    if (showAll)
 
349
        row = engine->curMatch.indices[engine->curRow];
 
350
 
 
351
    QModelIndex idx = createIndex(row, c->column);
 
352
    if (!sourceIndex)
 
353
        return idx;
 
354
    return mapToSource(idx);
 
355
}
 
356
 
 
357
QModelIndex KexiCompletionModel::index(int row, int column, const QModelIndex& parent) const
 
358
{
 
359
    if (row < 0 || column < 0 || column >= columnCount(parent) || parent.isValid())
 
360
        return QModelIndex();
 
361
 
 
362
    if (!showAll) {
 
363
        if (!engine->matchCount())
 
364
            return QModelIndex();
 
365
        if (row >= engine->historyMatch.indices.count()) {
 
366
            int want = row + 1 - engine->matchCount();
 
367
            if (want > 0)
 
368
                engine->filterOnDemand(want);
 
369
            if (row >= engine->matchCount())
 
370
                return QModelIndex();
 
371
        }
 
372
    } else {
 
373
        if (row >= sourceModel()->rowCount(engine->curParent))
 
374
            return QModelIndex();
 
375
    }
 
376
 
 
377
    return createIndex(row, column);
 
378
}
 
379
 
 
380
int KexiCompletionModel::completionCount() const
 
381
{
 
382
    if (!engine->matchCount())
 
383
        return 0;
 
384
 
 
385
    engine->filterOnDemand(INT_MAX);
 
386
    return engine->matchCount();
 
387
}
 
388
 
 
389
int KexiCompletionModel::rowCount(const QModelIndex &parent) const
 
390
{
 
391
    if (parent.isValid())
 
392
        return 0;
 
393
 
 
394
    if (showAll) {
 
395
        // Show all items below current parent, even if we have no valid matches
 
396
        if (engine->curParts.count() != 1  && !engine->matchCount()
 
397
            && !engine->curParent.isValid())
 
398
            return 0;
 
399
        return sourceModel()->rowCount(engine->curParent);
 
400
    }
 
401
 
 
402
    return completionCount();
 
403
}
 
404
 
 
405
void KexiCompletionModel::setFiltered(bool filtered)
 
406
{
 
407
    if (showAll == !filtered)
 
408
        return;
 
409
    showAll = !filtered;
 
410
    resetModel();
 
411
}
 
412
 
 
413
bool KexiCompletionModel::hasChildren(const QModelIndex &parent) const
 
414
{
 
415
    if (parent.isValid())
 
416
        return false;
 
417
 
 
418
    if (showAll)
 
419
        return sourceModel()->hasChildren(mapToSource(parent));
 
420
 
 
421
    if (!engine->matchCount())
 
422
        return false;
 
423
 
 
424
    return true;
 
425
}
 
426
 
 
427
QVariant KexiCompletionModel::data(const QModelIndex& index, int role) const
 
428
{
 
429
    return sourceModel()->data(mapToSource(index), role);
 
430
}
 
431
 
 
432
void KexiCompletionModel::modelDestroyed()
 
433
{
 
434
    QAbstractProxyModel::setSourceModel(0); // switch to static empty model
 
435
    invalidate();
 
436
}
 
437
 
 
438
void KexiCompletionModel::rowsInserted()
 
439
{
 
440
    invalidate();
 
441
    emit rowsAdded();
 
442
}
 
443
 
 
444
void KexiCompletionModel::invalidate()
 
445
{
 
446
    engine->cache.clear();
 
447
    filter(engine->curParts);
 
448
}
 
449
 
 
450
void KexiCompletionModel::filter(const QStringList& parts)
 
451
{
 
452
    engine->filter(parts);
 
453
    resetModel();
 
454
 
 
455
    if (sourceModel()->canFetchMore(engine->curParent))
 
456
        sourceModel()->fetchMore(engine->curParent);
 
457
}
 
458
 
 
459
void KexiCompletionModel::resetModel()
 
460
{
 
461
    if (rowCount() == 0) {
 
462
        beginResetModel();
 
463
        endResetModel();
 
464
        return;
 
465
    }
 
466
 
 
467
    emit layoutAboutToBeChanged();
 
468
    QModelIndexList piList = persistentIndexList();
 
469
    QModelIndexList empty;
 
470
    for (int i = 0; i < piList.size(); i++)
 
471
        empty.append(QModelIndex());
 
472
    changePersistentIndexList(piList, empty);
 
473
    emit layoutChanged();
 
474
}
 
475
 
 
476
//////////////////////////////////////////////////////////////////////////////
 
477
void KexiCompletionEngine::filter(const QStringList& parts)
 
478
{
 
479
    const QAbstractItemModel *model = c->proxy->sourceModel();
 
480
    curParts = parts;
 
481
    if (curParts.isEmpty())
 
482
        curParts.append(QString());
 
483
 
 
484
    curRow = -1;
 
485
    curParent = QModelIndex();
 
486
    curMatch = KexiMatchData();
 
487
    historyMatch = filterHistory();
 
488
 
 
489
    if (!model)
 
490
        return;
 
491
 
 
492
    QModelIndex parent;
 
493
    for (int i = 0; i < curParts.count() - 1; i++) {
 
494
        QString part = curParts[i];
 
495
        int emi = filter(part, parent, -1).exactMatchIndex;
 
496
        if (emi == -1)
 
497
            return;
 
498
        parent = model->index(emi, c->column, parent);
 
499
    }
 
500
 
 
501
    // Note that we set the curParent to a valid parent, even if we have no matches
 
502
    // When filtering is disabled, we show all the items under this parent
 
503
    curParent = parent;
 
504
    if (curParts.last().isEmpty())
 
505
        curMatch = KexiMatchData(KexiIndexMapper(0, model->rowCount(curParent) - 1), -1, false);
 
506
    else
 
507
        curMatch = filter(curParts.last(), curParent, 1); // build at least one
 
508
    curRow = curMatch.isValid() ? 0 : -1;
 
509
}
 
510
 
 
511
inline bool matchPrefix(const QString& s1, const QString& s2, Qt::CaseSensitivity cs)
 
512
{
 
513
    return s1.startsWith(s2, cs);
 
514
}
 
515
 
 
516
inline bool matchSubstring(const QString& s1, const QString& s2, Qt::CaseSensitivity cs)
 
517
{
 
518
    return s1.contains(s2, cs);
 
519
}
 
520
 
 
521
typedef bool (*MatchFunction)(const QString&, const QString&, Qt::CaseSensitivity);
 
522
 
 
523
KexiMatchData KexiCompletionEngine::filterHistory()
 
524
{
 
525
    QAbstractItemModel *source = c->proxy->sourceModel();
 
526
    if (curParts.count() <= 1 || c->proxy->showAll || !source)
 
527
        return KexiMatchData();
 
528
#ifdef QT_NO_DIRMODEL
 
529
    bool isDirModel = false;
 
530
#else
 
531
    bool isDirModel = qobject_cast<QDirModel *>(source) != 0;
 
532
#endif
 
533
#ifdef QT_NO_FILESYSTEMMODEL
 
534
    bool isFsModel = false;
 
535
#else
 
536
    bool isFsModel = qobject_cast<QFileSystemModel *>(source) != 0;
 
537
#endif
 
538
    QVector<int> v;
 
539
    KexiIndexMapper im(v);
 
540
    KexiMatchData m(im, -1, true);
 
541
 
 
542
    MatchFunction matchFunction = c->substringCompletion ? &matchSubstring : &matchPrefix;
 
543
    for (int i = 0; i < source->rowCount(); i++) {
 
544
        QString str = source->index(i, c->column).data().toString();
 
545
        if (matchFunction(str, c->prefix, c->cs)
 
546
#if (!defined(Q_OS_WIN) || defined(Q_OS_WINCE)) && !defined(Q_OS_SYMBIAN)
 
547
            && ((!isFsModel && !isDirModel) || QDir::toNativeSeparators(str) != QDir::separator())
 
548
#endif
 
549
            )
 
550
            m.indices.append(i);
 
551
    }
 
552
    return m;
 
553
}
 
554
 
 
555
// Returns a match hint from the cache by chopping the search string
 
556
bool KexiCompletionEngine::matchHint(QString part, const QModelIndex& parent, KexiMatchData *hint)
 
557
{
 
558
    if (c->cs == Qt::CaseInsensitive)
 
559
        part = part.toLower();
 
560
 
 
561
    const CacheItem& map = cache[parent];
 
562
 
 
563
    QString key = part;
 
564
    while (!key.isEmpty()) {
 
565
        key.chop(1);
 
566
        if (map.contains(key)) {
 
567
            *hint = map[key];
 
568
            return true;
 
569
        }
 
570
    }
 
571
 
 
572
    return false;
 
573
}
 
574
 
 
575
bool KexiCompletionEngine::lookupCache(QString part, const QModelIndex& parent, KexiMatchData *m)
 
576
{
 
577
   if (c->cs == Qt::CaseInsensitive)
 
578
        part = part.toLower();
 
579
   const CacheItem& map = cache[parent];
 
580
   if (!map.contains(part))
 
581
       return false;
 
582
   *m = map[part];
 
583
   return true;
 
584
}
 
585
 
 
586
// When the cache size exceeds 1MB, it clears out about 1/2 of the cache.
 
587
void KexiCompletionEngine::saveInCache(QString part, const QModelIndex& parent, const KexiMatchData& m)
 
588
{
 
589
    KexiMatchData old = cache[parent].take(part);
 
590
    cost = cost + m.indices.cost() - old.indices.cost();
 
591
    if (cost * sizeof(int) > 1024 * 1024) {
 
592
        QMap<QModelIndex, CacheItem>::iterator it1 = cache.begin();
 
593
        while (it1 != cache.end()) {
 
594
            CacheItem& ci = it1.value();
 
595
            int sz = ci.count()/2;
 
596
            QMap<QString, KexiMatchData>::iterator it2 = ci.begin();
 
597
            int i = 0;
 
598
            while (it2 != ci.end() && i < sz) {
 
599
                cost -= it2.value().indices.cost();
 
600
                it2 = ci.erase(it2);
 
601
                i++;
 
602
            }
 
603
            if (ci.count() == 0) {
 
604
              it1 = cache.erase(it1);
 
605
            } else {
 
606
              ++it1;
 
607
            }
 
608
        }
 
609
    }
 
610
 
 
611
    if (c->cs == Qt::CaseInsensitive)
 
612
        part = part.toLower();
 
613
    cache[parent][part] = m;
 
614
}
 
615
 
 
616
///////////////////////////////////////////////////////////////////////////////////
 
617
KexiIndexMapper QSortedModelEngine::indexHint(QString part, const QModelIndex& parent, Qt::SortOrder order)
 
618
{
 
619
    const QAbstractItemModel *model = c->proxy->sourceModel();
 
620
 
 
621
    if (c->cs == Qt::CaseInsensitive)
 
622
        part = part.toLower();
 
623
 
 
624
    const CacheItem& map = cache[parent];
 
625
 
 
626
    // Try to find a lower and upper bound for the search from previous results
 
627
    int to = model->rowCount(parent) - 1;
 
628
    int from = 0;
 
629
    const CacheItem::const_iterator it = map.lowerBound(part);
 
630
 
 
631
    // look backward for first valid hint
 
632
    for(CacheItem::const_iterator it1 = it; it1-- != map.constBegin();) {
 
633
        const KexiMatchData& value = it1.value();
 
634
        if (value.isValid()) {
 
635
            if (order == Qt::AscendingOrder) {
 
636
                from = value.indices.last() + 1;
 
637
            } else {
 
638
                to = value.indices.first() - 1;
 
639
            }
 
640
            break;
 
641
        }
 
642
    }
 
643
 
 
644
    // look forward for first valid hint
 
645
    for(CacheItem::const_iterator it2 = it; it2 != map.constEnd(); ++it2) {
 
646
        const KexiMatchData& value = it2.value();
 
647
        if (value.isValid() && !it2.key().startsWith(part)) {
 
648
            if (order == Qt::AscendingOrder) {
 
649
                to = value.indices.first() - 1;
 
650
            } else {
 
651
                from = value.indices.first() + 1;
 
652
            }
 
653
            break;
 
654
        }
 
655
    }
 
656
 
 
657
    return KexiIndexMapper(from, to);
 
658
}
 
659
 
 
660
Qt::SortOrder QSortedModelEngine::sortOrder(const QModelIndex &parent) const
 
661
{
 
662
    const QAbstractItemModel *model = c->proxy->sourceModel();
 
663
 
 
664
    int rowCount = model->rowCount(parent);
 
665
    if (rowCount < 2)
 
666
        return Qt::AscendingOrder;
 
667
    QString first = model->data(model->index(0, c->column, parent), c->role).toString();
 
668
    QString last = model->data(model->index(rowCount - 1, c->column, parent), c->role).toString();
 
669
    return QString::compare(first, last, c->cs) <= 0 ? Qt::AscendingOrder : Qt::DescendingOrder;
 
670
}
 
671
 
 
672
KexiMatchData QSortedModelEngine::filter(const QString& part, const QModelIndex& parent, int)
 
673
{
 
674
    const QAbstractItemModel *model = c->proxy->sourceModel();
 
675
 
 
676
    KexiMatchData hint;
 
677
    if (lookupCache(part, parent, &hint))
 
678
        return hint;
 
679
 
 
680
    KexiIndexMapper indices;
 
681
    Qt::SortOrder order = sortOrder(parent);
 
682
 
 
683
    if (matchHint(part, parent, &hint)) {
 
684
        if (!hint.isValid())
 
685
            return KexiMatchData();
 
686
        indices = hint.indices;
 
687
    } else {
 
688
        indices = indexHint(part, parent, order);
 
689
    }
 
690
 
 
691
    // binary search the model within 'indices' for 'part' under 'parent'
 
692
    int high = indices.to() + 1;
 
693
    int low = indices.from() - 1;
 
694
    int probe;
 
695
    QModelIndex probeIndex;
 
696
    QString probeData;
 
697
 
 
698
    while (high - low > 1)
 
699
    {
 
700
        probe = (high + low) / 2;
 
701
        probeIndex = model->index(probe, c->column, parent);
 
702
        probeData = model->data(probeIndex, c->role).toString();
 
703
        const int cmp = QString::compare(probeData, part, c->cs);
 
704
        if ((order == Qt::AscendingOrder && cmp >= 0)
 
705
            || (order == Qt::DescendingOrder && cmp < 0)) {
 
706
            high = probe;
 
707
        } else {
 
708
            low = probe;
 
709
        }
 
710
    }
 
711
 
 
712
    if ((order == Qt::AscendingOrder && low == indices.to())
 
713
        || (order == Qt::DescendingOrder && high == indices.from())) { // not found
 
714
        saveInCache(part, parent, KexiMatchData());
 
715
        return KexiMatchData();
 
716
    }
 
717
 
 
718
    probeIndex = model->index(order == Qt::AscendingOrder ? low+1 : high-1, c->column, parent);
 
719
    probeData = model->data(probeIndex, c->role).toString();
 
720
    if (!probeData.startsWith(part, c->cs)) {
 
721
        saveInCache(part, parent, KexiMatchData());
 
722
        return KexiMatchData();
 
723
    }
 
724
 
 
725
    const bool exactMatch = QString::compare(probeData, part, c->cs) == 0;
 
726
    int emi =  exactMatch ? (order == Qt::AscendingOrder ? low+1 : high-1) : -1;
 
727
 
 
728
    int from = 0;
 
729
    int to = 0;
 
730
    if (order == Qt::AscendingOrder) {
 
731
        from = low + 1;
 
732
        high = indices.to() + 1;
 
733
        low = from;
 
734
    } else {
 
735
        to = high - 1;
 
736
        low = indices.from() - 1;
 
737
        high = to;
 
738
    }
 
739
 
 
740
    while (high - low > 1)
 
741
    {
 
742
        probe = (high + low) / 2;
 
743
        probeIndex = model->index(probe, c->column, parent);
 
744
        probeData = model->data(probeIndex, c->role).toString();
 
745
        const bool startsWith = probeData.startsWith(part, c->cs);
 
746
        if ((order == Qt::AscendingOrder && startsWith)
 
747
            || (order == Qt::DescendingOrder && !startsWith)) {
 
748
            low = probe;
 
749
        } else {
 
750
            high = probe;
 
751
        }
 
752
    }
 
753
 
 
754
    KexiMatchData m(order == Qt::AscendingOrder ? KexiIndexMapper(from, high - 1) : KexiIndexMapper(low+1, to), emi, false);
 
755
    saveInCache(part, parent, m);
 
756
    return m;
 
757
}
 
758
 
 
759
////////////////////////////////////////////////////////////////////////////////////////
 
760
int QUnsortedModelEngine::buildIndices(const QString& str, const QModelIndex& parent, int n,
 
761
                                      const KexiIndexMapper& indices, KexiMatchData* m)
 
762
{
 
763
    Q_ASSERT(m->partial);
 
764
    Q_ASSERT(n != -1 || m->exactMatchIndex == -1);
 
765
    const QAbstractItemModel *model = c->proxy->sourceModel();
 
766
    int i, count = 0;
 
767
 
 
768
    MatchFunction matchFunction = c->substringCompletion ? &matchSubstring : &matchPrefix;
 
769
    for (i = 0; i < indices.count() && count != n; ++i) {
 
770
        QModelIndex idx = model->index(indices[i], c->column, parent);
 
771
        QString data = model->data(idx, c->role).toString();
 
772
        if (!matchFunction(data, str, c->cs) || !(model->flags(idx) & Qt::ItemIsSelectable))
 
773
            continue;
 
774
        m->indices.append(indices[i]);
 
775
        ++count;
 
776
        if (m->exactMatchIndex == -1 && QString::compare(data, str, c->cs) == 0) {
 
777
            m->exactMatchIndex = indices[i];
 
778
            if (n == -1)
 
779
                return indices[i];
 
780
        }
 
781
    }
 
782
    return indices[i-1];
 
783
}
 
784
 
 
785
void QUnsortedModelEngine::filterOnDemand(int n)
 
786
{
 
787
    Q_ASSERT(matchCount());
 
788
    if (!curMatch.partial)
 
789
        return;
 
790
    Q_ASSERT(n >= -1);
 
791
    const QAbstractItemModel *model = c->proxy->sourceModel();
 
792
    int lastRow = model->rowCount(curParent) - 1;
 
793
    KexiIndexMapper im(curMatch.indices.last() + 1, lastRow);
 
794
    int lastIndex = buildIndices(curParts.last(), curParent, n, im, &curMatch);
 
795
    curMatch.partial = (lastRow != lastIndex);
 
796
    saveInCache(curParts.last(), curParent, curMatch);
 
797
}
 
798
 
 
799
KexiMatchData QUnsortedModelEngine::filter(const QString& part, const QModelIndex& parent, int n)
 
800
{
 
801
    KexiMatchData hint;
 
802
 
 
803
    QVector<int> v;
 
804
    KexiIndexMapper im(v);
 
805
    KexiMatchData m(im, -1, true);
 
806
 
 
807
    const QAbstractItemModel *model = c->proxy->sourceModel();
 
808
    bool foundInCache = lookupCache(part, parent, &m);
 
809
 
 
810
    if (!foundInCache) {
 
811
        if (matchHint(part, parent, &hint) && !hint.isValid())
 
812
            return KexiMatchData();
 
813
    }
 
814
 
 
815
    if (!foundInCache && !hint.isValid()) {
 
816
        const int lastRow = model->rowCount(parent) - 1;
 
817
        KexiIndexMapper all(0, lastRow);
 
818
        int lastIndex = buildIndices(part, parent, n, all, &m);
 
819
        m.partial = (lastIndex != lastRow);
 
820
    } else {
 
821
        if (!foundInCache) { // build from hint as much as we can
 
822
            buildIndices(part, parent, INT_MAX, hint.indices, &m);
 
823
            m.partial = hint.partial;
 
824
        }
 
825
        if (m.partial && ((n == -1 && m.exactMatchIndex == -1) || (m.indices.count() < n))) {
 
826
            // need more and have more
 
827
            const int lastRow = model->rowCount(parent) - 1;
 
828
            KexiIndexMapper rest(hint.indices.last() + 1, lastRow);
 
829
            int want = n == -1 ? -1 : n - m.indices.count();
 
830
            int lastIndex = buildIndices(part, parent, want, rest, &m);
 
831
            m.partial = (lastRow != lastIndex);
 
832
        }
 
833
    }
 
834
 
 
835
    saveInCache(part, parent, m);
 
836
    return m;
 
837
}
 
838
 
 
839
///////////////////////////////////////////////////////////////////////////////
 
840
KexiCompleterPrivate::KexiCompleterPrivate(KexiCompleter *qq)
 
841
: widget(0), proxy(0), popup(0), cs(Qt::CaseSensitive), substringCompletion(false),
 
842
  role(Qt::EditRole), column(0), maxVisibleItems(7), sorting(KexiCompleter::UnsortedModel),
 
843
  wrap(true), eatFocusOut(true),
 
844
  hiddenBecauseNoMatch(false), q(qq)
 
845
{
 
846
}
 
847
 
 
848
void KexiCompleterPrivate::init(QAbstractItemModel *m)
 
849
{
 
850
    proxy = new KexiCompletionModel(this, q);
 
851
    QObject::connect(proxy, SIGNAL(rowsAdded()), q, SLOT(_q_autoResizePopup()));
 
852
    q->setModel(m);
 
853
#ifdef QT_NO_LISTVIEW
 
854
    q->setCompletionMode(KexiCompleter::InlineCompletion);
 
855
#else
 
856
    q->setCompletionMode(KexiCompleter::PopupCompletion);
 
857
#endif // QT_NO_LISTVIEW
 
858
}
 
859
 
 
860
void KexiCompleterPrivate::setCurrentIndex(QModelIndex index, bool select)
 
861
{
 
862
    if (!q->popup())
 
863
        return;
 
864
    if (!select) {
 
865
        popup->selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
 
866
    } else {
 
867
        if (!index.isValid())
 
868
            popup->selectionModel()->clear();
 
869
        else
 
870
            popup->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select
 
871
                                                            | QItemSelectionModel::Rows);
 
872
    }
 
873
    index = popup->selectionModel()->currentIndex();
 
874
    if (!index.isValid())
 
875
        popup->scrollToTop();
 
876
    else
 
877
        popup->scrollTo(index, QAbstractItemView::PositionAtTop);
 
878
}
 
879
 
 
880
void KexiCompleterPrivate::_q_completionSelected(const QItemSelection& selection)
 
881
{
 
882
    QModelIndex index;
 
883
    if (!selection.indexes().isEmpty())
 
884
        index = selection.indexes().first();
 
885
 
 
886
    _q_complete(index, true);
 
887
}
 
888
 
 
889
void KexiCompleterPrivate::_q_complete(QModelIndex index, bool highlighted)
 
890
{
 
891
    QString completion;
 
892
 
 
893
    if (!index.isValid() || (!proxy->showAll && (index.row() >= proxy->engine->matchCount()))) {
 
894
        completion = prefix;
 
895
    } else {
 
896
        if (!(index.flags() & Qt::ItemIsEnabled))
 
897
            return;
 
898
        QModelIndex si = proxy->mapToSource(index);
 
899
        si = si.sibling(si.row(), column); // for clicked()
 
900
        completion = q->pathFromIndex(si);
 
901
#ifndef QT_NO_DIRMODEL
 
902
        // add a trailing separator in inline
 
903
        if (mode == KexiCompleter::InlineCompletion) {
 
904
            if (qobject_cast<QDirModel *>(proxy->sourceModel()) && QFileInfo(completion).isDir())
 
905
                completion += QDir::separator();
 
906
        }
 
907
#endif
 
908
#ifndef QT_NO_FILESYSTEMMODEL
 
909
        // add a trailing separator in inline
 
910
        if (mode == KexiCompleter::InlineCompletion) {
 
911
            if (qobject_cast<QFileSystemModel *>(proxy->sourceModel()) && QFileInfo(completion).isDir())
 
912
                completion += QDir::separator();
 
913
        }
 
914
#endif
 
915
    }
 
916
 
 
917
    if (highlighted) {
 
918
        emit q->highlighted(index);
 
919
        emit q->highlighted(completion);
 
920
    } else {
 
921
        emit q->activated(index);
 
922
        emit q->activated(completion);
 
923
    }
 
924
}
 
925
 
 
926
void KexiCompleterPrivate::_q_autoResizePopup()
 
927
{
 
928
    if (!popup || !popup->isVisible())
 
929
        return;
 
930
    showPopup(popupRect);
 
931
}
 
932
 
 
933
static void adjustPopupGeometry(QWidget *popupWidget, QWidget *widget, int widthHint,
 
934
                                int heightHint, const QRect &currentRect)
 
935
{
 
936
    const QRect screen = QApplication::desktop()->availableGeometry(widget);
 
937
    const Qt::LayoutDirection dir = widget->layoutDirection();
 
938
    QPoint pos;
 
939
    int rh, w;
 
940
    int h = heightHint;
 
941
 
 
942
    if (currentRect.isValid()) {
 
943
        rh = currentRect.height();
 
944
        w = currentRect.width();
 
945
        pos = widget->mapToGlobal(dir == Qt::RightToLeft ? currentRect.bottomRight() : currentRect.bottomLeft());
 
946
    } else {
 
947
        rh = widget->height();
 
948
        pos = widget->mapToGlobal(QPoint(0, widget->height() - 2));
 
949
        w = widget->width();
 
950
    }
 
951
    if (widthHint > w) {
 
952
        w = widthHint;
 
953
    }
 
954
 
 
955
    if (w > screen.width())
 
956
        w = screen.width();
 
957
    if ((pos.x() + w) > (screen.x() + screen.width()))
 
958
        pos.setX(screen.x() + screen.width() - w);
 
959
    if (pos.x() < screen.x())
 
960
        pos.setX(screen.x());
 
961
 
 
962
    int top = pos.y() - rh - screen.top() + 2;
 
963
    int bottom = screen.bottom() - pos.y();
 
964
    h = qMax(h, popupWidget->minimumHeight());
 
965
    if (h > bottom) {
 
966
        h = qMin(qMax(top, bottom), h);
 
967
 
 
968
        if (top > bottom)
 
969
            pos.setY(pos.y() - h - rh + 2);
 
970
    }
 
971
 
 
972
    popupWidget->setGeometry(pos.x(), pos.y(), w, h);
 
973
}
 
974
 
 
975
void KexiCompleterPrivate::showPopup(const QRect& rect)
 
976
{
 
977
    int widthHint = popup->sizeHintForColumn(0);
 
978
    QScrollBar *vsb = popup->verticalScrollBar();
 
979
    if (vsb) {
 
980
        widthHint += vsb->sizeHint().width() + 3 + 3;
 
981
    }
 
982
    int heightHint = (popup->sizeHintForRow(0) * qMin(maxVisibleItems, popup->model()->rowCount()) + 3) + 3;
 
983
    QScrollBar *hsb = popup->horizontalScrollBar();
 
984
    if (hsb && hsb->isVisible()) {
 
985
        heightHint += hsb->sizeHint().height();
 
986
    }
 
987
    adjustPopupGeometry(popup, widget, widthHint, heightHint, rect);
 
988
    if (!popup->isVisible())
 
989
        popup->show();
 
990
}
 
991
 
 
992
void KexiCompleterPrivate::_q_fileSystemModelDirectoryLoaded(const QString &path)
 
993
{
 
994
    // Slot called when QFileSystemModel has finished loading.
 
995
    // If we hide the popup because there was no match because the model was not loaded yet,
 
996
    // we re-start the completion when we get the results
 
997
    if (hiddenBecauseNoMatch
 
998
        && prefix.startsWith(path) && prefix != (path + QLatin1Char('/'))
 
999
        && widget) {
 
1000
        q->complete();
 
1001
    }
 
1002
}
 
1003
 
 
1004
/*!
 
1005
    Constructs a completer object with the given \a parent.
 
1006
*/
 
1007
KexiCompleter::KexiCompleter(QObject *parent)
 
1008
: QObject(parent), d(new KexiCompleterPrivate(this))
 
1009
{
 
1010
    d->init();
 
1011
}
 
1012
 
 
1013
/*!
 
1014
    Constructs a completer object with the given \a parent that provides completions
 
1015
    from the specified \a model.
 
1016
*/
 
1017
KexiCompleter::KexiCompleter(QAbstractItemModel *model, QObject *parent)
 
1018
    : QObject(parent), d(new KexiCompleterPrivate(this))
 
1019
{
 
1020
    d->init(model);
 
1021
}
 
1022
 
 
1023
#ifndef QT_NO_STRINGLISTMODEL
 
1024
/*!
 
1025
    Constructs a KexiCompleter object with the given \a parent that uses the specified
 
1026
    \a list as a source of possible completions.
 
1027
*/
 
1028
KexiCompleter::KexiCompleter(const QStringList& list, QObject *parent)
 
1029
    : QObject(parent), d(new KexiCompleterPrivate(this))
 
1030
{
 
1031
    d->init(new QStringListModel(list, this));
 
1032
}
 
1033
#endif // QT_NO_STRINGLISTMODEL
 
1034
 
 
1035
/*!
 
1036
    Destroys the completer object.
 
1037
*/
 
1038
KexiCompleter::~KexiCompleter()
 
1039
{
 
1040
    delete d;
 
1041
}
 
1042
 
 
1043
/*!
 
1044
    Sets the widget for which completion are provided for to \a widget. This
 
1045
    function is automatically called when a KexiCompleter is set on a QLineEdit
 
1046
    using QLineEdit::setCompleter() or on a QComboBox using
 
1047
    QComboBox::setCompleter(). The widget needs to be set explicitly when
 
1048
    providing completions for custom widgets.
 
1049
 
 
1050
    \sa widget(), setModel(), setPopup()
 
1051
 */
 
1052
void KexiCompleter::setWidget(QWidget *widget)
 
1053
{
 
1054
    if (widget && d->widget == widget)
 
1055
        return;
 
1056
    if (d->widget)
 
1057
        d->widget->removeEventFilter(this);
 
1058
    d->widget = widget;
 
1059
    if (d->widget)
 
1060
        d->widget->installEventFilter(this);
 
1061
    if (d->popup) {
 
1062
        d->popup->hide();
 
1063
        d->popup->setFocusProxy(d->widget);
 
1064
    }
 
1065
}
 
1066
 
 
1067
/*!
 
1068
    Returns the widget for which the completer object is providing completions.
 
1069
 
 
1070
    \sa setWidget()
 
1071
 */
 
1072
QWidget *KexiCompleter::widget() const
 
1073
{
 
1074
    return d->widget;
 
1075
}
 
1076
 
 
1077
/*!
 
1078
    Sets the model which provides completions to \a model. The \a model can
 
1079
    be list model or a tree model. If a model has been already previously set
 
1080
    and it has the KexiCompleter as its parent, it is deleted.
 
1081
 
 
1082
    For convenience, if \a model is a QFileSystemModel, KexiCompleter switches its
 
1083
    caseSensitivity to Qt::CaseInsensitive on Windows and Qt::CaseSensitive
 
1084
    on other platforms.
 
1085
 
 
1086
    \sa completionModel(), modelSorting, {Handling Tree Models}
 
1087
*/
 
1088
void KexiCompleter::setModel(QAbstractItemModel *model)
 
1089
{
 
1090
    QAbstractItemModel *oldModel = d->proxy->sourceModel();
 
1091
    d->proxy->setSourceModel(model);
 
1092
    if (d->popup)
 
1093
        setPopup(d->popup); // set the model and make new connections
 
1094
    if (oldModel && oldModel->QObject::parent() == this)
 
1095
        delete oldModel;
 
1096
#ifndef QT_NO_DIRMODEL
 
1097
    if (qobject_cast<QDirModel *>(model)) {
 
1098
#if (defined(Q_OS_WIN) && !defined(Q_OS_WINCE)) || defined(Q_OS_SYMBIAN)
 
1099
        setCaseSensitivity(Qt::CaseInsensitive);
 
1100
#else
 
1101
        setCaseSensitivity(Qt::CaseSensitive);
 
1102
#endif
 
1103
    }
 
1104
#endif // QT_NO_DIRMODEL
 
1105
#ifndef QT_NO_FILESYSTEMMODEL
 
1106
    QFileSystemModel *fsModel = qobject_cast<QFileSystemModel *>(model);
 
1107
    if (fsModel) {
 
1108
#if (defined(Q_OS_WIN) && !defined(Q_OS_WINCE)) || defined(Q_OS_SYMBIAN)
 
1109
        setCaseSensitivity(Qt::CaseInsensitive);
 
1110
#else
 
1111
        setCaseSensitivity(Qt::CaseSensitive);
 
1112
#endif
 
1113
        setCompletionRole(QFileSystemModel::FileNameRole);
 
1114
        connect(fsModel, SIGNAL(directoryLoaded(QString)), this, SLOT(_q_fileSystemModelDirectoryLoaded(QString)));
 
1115
    }
 
1116
#endif // QT_NO_FILESYSTEMMODEL
 
1117
}
 
1118
 
 
1119
/*!
 
1120
    Returns the model that provides completion strings.
 
1121
 
 
1122
    \sa completionModel()
 
1123
*/
 
1124
QAbstractItemModel *KexiCompleter::model() const
 
1125
{
 
1126
    return d->proxy->sourceModel();
 
1127
}
 
1128
 
 
1129
/*!
 
1130
    \enum KexiCompleter::CompletionMode
 
1131
 
 
1132
    This enum specifies how completions are provided to the user.
 
1133
 
 
1134
    \value PopupCompletion            Current completions are displayed in a popup window.
 
1135
    \value InlineCompletion           Completions appear inline (as selected text).
 
1136
    \value UnfilteredPopupCompletion  All possible completions are displayed in a popup window with the most likely suggestion indicated as current.
 
1137
 
 
1138
    \sa setCompletionMode()
 
1139
*/
 
1140
 
 
1141
/*!
 
1142
    \property KexiCompleter::completionMode
 
1143
    \brief how the completions are provided to the user
 
1144
 
 
1145
    The default value is KexiCompleter::PopupCompletion.
 
1146
*/
 
1147
void KexiCompleter::setCompletionMode(KexiCompleter::CompletionMode mode)
 
1148
{
 
1149
    d->mode = mode;
 
1150
    d->proxy->setFiltered(mode != KexiCompleter::UnfilteredPopupCompletion);
 
1151
 
 
1152
    if (mode == KexiCompleter::InlineCompletion) {
 
1153
        if (d->widget)
 
1154
            d->widget->removeEventFilter(this);
 
1155
        if (d->popup) {
 
1156
            d->popup->deleteLater();
 
1157
            d->popup = 0;
 
1158
        }
 
1159
    } else {
 
1160
        if (d->widget)
 
1161
            d->widget->installEventFilter(this);
 
1162
    }
 
1163
}
 
1164
 
 
1165
KexiCompleter::CompletionMode KexiCompleter::completionMode() const
 
1166
{
 
1167
    return d->mode;
 
1168
}
 
1169
 
 
1170
/*!
 
1171
    Sets the popup used to display completions to \a popup. KexiCompleter takes
 
1172
    ownership of the view.
 
1173
 
 
1174
    A QListView is automatically created when the completionMode() is set to
 
1175
    KexiCompleter::PopupCompletion or KexiCompleter::UnfilteredPopupCompletion. The
 
1176
    default popup displays the completionColumn().
 
1177
 
 
1178
    Ensure that this function is called before the view settings are modified.
 
1179
    This is required since view's properties may require that a model has been
 
1180
    set on the view (for example, hiding columns in the view requires a model
 
1181
    to be set on the view).
 
1182
 
 
1183
    \sa popup()
 
1184
*/
 
1185
void KexiCompleter::setPopup(QAbstractItemView *popup)
 
1186
{
 
1187
    Q_ASSERT(popup != 0);
 
1188
    if (d->popup) {
 
1189
        QObject::disconnect(d->popup->selectionModel(), 0, this, 0);
 
1190
        QObject::disconnect(d->popup, 0, this, 0);
 
1191
    }
 
1192
    if (d->popup != popup)
 
1193
        delete d->popup;
 
1194
    if (popup->model() != d->proxy)
 
1195
        popup->setModel(d->proxy);
 
1196
#if defined(Q_OS_MAC) && !defined(QT_MAC_USE_COCOA)
 
1197
     popup->show();
 
1198
#else
 
1199
     popup->hide();
 
1200
#endif
 
1201
 
 
1202
    Qt::FocusPolicy origPolicy = Qt::NoFocus;
 
1203
    if (d->widget)
 
1204
        origPolicy = d->widget->focusPolicy();
 
1205
    popup->setParent(0, Qt::Popup);
 
1206
    popup->setFocusPolicy(Qt::NoFocus);
 
1207
    if (d->widget)
 
1208
        d->widget->setFocusPolicy(origPolicy);
 
1209
 
 
1210
    popup->setFocusProxy(d->widget);
 
1211
    popup->installEventFilter(this);
 
1212
    popup->setItemDelegate(new KexiCompleterItemDelegate(popup));
 
1213
#ifndef QT_NO_LISTVIEW
 
1214
    if (QListView *listView = qobject_cast<QListView *>(popup)) {
 
1215
        listView->setModelColumn(d->column);
 
1216
    }
 
1217
#endif
 
1218
 
 
1219
    QObject::connect(popup, SIGNAL(clicked(QModelIndex)),
 
1220
                     this, SLOT(_q_complete(QModelIndex)));
 
1221
    QObject::connect(this, SIGNAL(activated(QModelIndex)),
 
1222
                     popup, SLOT(hide()));
 
1223
 
 
1224
    QObject::connect(popup->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
 
1225
                     this, SLOT(_q_completionSelected(QItemSelection)));
 
1226
    d->popup = popup;
 
1227
}
 
1228
 
 
1229
/*!
 
1230
    Returns the popup used to display completions.
 
1231
 
 
1232
    \sa setPopup()
 
1233
*/
 
1234
QAbstractItemView *KexiCompleter::popup() const
 
1235
{
 
1236
#ifndef QT_NO_LISTVIEW
 
1237
    if (!d->popup && completionMode() != KexiCompleter::InlineCompletion) {
 
1238
        QListView *listView = new QListView;
 
1239
        listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
 
1240
        listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
 
1241
        listView->setSelectionBehavior(QAbstractItemView::SelectRows);
 
1242
        listView->setSelectionMode(QAbstractItemView::SingleSelection);
 
1243
        listView->setModelColumn(d->column);
 
1244
        KexiCompleter *that = const_cast<KexiCompleter*>(this);
 
1245
        that->setPopup(listView);
 
1246
    }
 
1247
#endif // QT_NO_LISTVIEW
 
1248
    return d->popup;
 
1249
}
 
1250
 
 
1251
/*!
 
1252
  \reimp
 
1253
*/
 
1254
bool KexiCompleter::event(QEvent *ev)
 
1255
{
 
1256
    return QObject::event(ev);
 
1257
}
 
1258
 
 
1259
/*!
 
1260
  \reimp
 
1261
*/
 
1262
bool KexiCompleter::eventFilter(QObject *o, QEvent *e)
 
1263
{
 
1264
    if (d->eatFocusOut && o == d->widget && e->type() == QEvent::FocusOut) {
 
1265
        d->hiddenBecauseNoMatch = false;
 
1266
        if (d->popup && d->popup->isVisible())
 
1267
            return true;
 
1268
    }
 
1269
 
 
1270
    if (o != d->popup)
 
1271
        return QObject::eventFilter(o, e);
 
1272
 
 
1273
    switch (e->type()) {
 
1274
    case QEvent::KeyPress: {
 
1275
        QKeyEvent *ke = static_cast<QKeyEvent *>(e);
 
1276
 
 
1277
        QModelIndex curIndex = d->popup->currentIndex();
 
1278
        QModelIndexList selList = d->popup->selectionModel()->selectedIndexes();
 
1279
 
 
1280
        const int key = ke->key();
 
1281
        // In UnFilteredPopup mode, select the current item
 
1282
        if ((key == Qt::Key_Up || key == Qt::Key_Down) && selList.isEmpty() && curIndex.isValid()
 
1283
            && d->mode == KexiCompleter::UnfilteredPopupCompletion) {
 
1284
              d->setCurrentIndex(curIndex);
 
1285
              return true;
 
1286
        }
 
1287
 
 
1288
        // Handle popup navigation keys. These are hardcoded because up/down might make the
 
1289
        // widget do something else (lineedit cursor moves to home/end on mac, for instance)
 
1290
        switch (key) {
 
1291
        case Qt::Key_End:
 
1292
        case Qt::Key_Home:
 
1293
            if (ke->modifiers() & Qt::ControlModifier)
 
1294
                return false;
 
1295
            break;
 
1296
 
 
1297
        case Qt::Key_Up:
 
1298
            if (!curIndex.isValid()) {
 
1299
                int rowCount = d->proxy->rowCount();
 
1300
                QModelIndex lastIndex = d->proxy->index(rowCount - 1, d->column);
 
1301
                d->setCurrentIndex(lastIndex);
 
1302
                return true;
 
1303
            } else if (curIndex.row() == 0) {
 
1304
                if (d->wrap)
 
1305
                    d->setCurrentIndex(QModelIndex());
 
1306
                return true;
 
1307
            }
 
1308
            return false;
 
1309
 
 
1310
        case Qt::Key_Down:
 
1311
            if (!curIndex.isValid()) {
 
1312
                QModelIndex firstIndex = d->proxy->index(0, d->column);
 
1313
                d->setCurrentIndex(firstIndex);
 
1314
                return true;
 
1315
            } else if (curIndex.row() == d->proxy->rowCount() - 1) {
 
1316
                if (d->wrap)
 
1317
                    d->setCurrentIndex(QModelIndex());
 
1318
                return true;
 
1319
            }
 
1320
            return false;
 
1321
 
 
1322
        case Qt::Key_PageUp:
 
1323
        case Qt::Key_PageDown:
 
1324
            return false;
 
1325
        }
 
1326
 
 
1327
        // Send the event to the widget. If the widget accepted the event, do nothing
 
1328
        // If the widget did not accept the event, provide a default implementation
 
1329
        d->eatFocusOut = false;
 
1330
        (static_cast<QObject *>(d->widget))->event(ke);
 
1331
        d->eatFocusOut = true;
 
1332
        if (!d->widget || e->isAccepted() || !d->popup->isVisible()) {
 
1333
            // widget lost focus, hide the popup
 
1334
            if (d->widget && (!d->widget->hasFocus()
 
1335
#ifdef QT_KEYPAD_NAVIGATION
 
1336
                || (QApplication::keypadNavigationEnabled() && !d->widget->hasEditFocus())
 
1337
#endif
 
1338
                ))
 
1339
                d->popup->hide();
 
1340
            if (e->isAccepted())
 
1341
                return true;
 
1342
        }
 
1343
 
 
1344
        // default implementation for keys not handled by the widget when popup is open
 
1345
        switch (key) {
 
1346
#ifdef QT_KEYPAD_NAVIGATION
 
1347
        case Qt::Key_Select:
 
1348
            if (!QApplication::keypadNavigationEnabled())
 
1349
                break;
 
1350
#endif
 
1351
        case Qt::Key_Return:
 
1352
        case Qt::Key_Enter:
 
1353
        case Qt::Key_Tab:
 
1354
            d->popup->hide();
 
1355
            if (curIndex.isValid())
 
1356
                d->_q_complete(curIndex);
 
1357
            break;
 
1358
 
 
1359
        case Qt::Key_F4:
 
1360
            if (ke->modifiers() & Qt::AltModifier)
 
1361
                d->popup->hide();
 
1362
            break;
 
1363
 
 
1364
        case Qt::Key_Backtab:
 
1365
        case Qt::Key_Escape:
 
1366
            d->popup->hide();
 
1367
            break;
 
1368
 
 
1369
        default:
 
1370
            break;
 
1371
        }
 
1372
 
 
1373
        return true;
 
1374
    }
 
1375
 
 
1376
#ifdef QT_KEYPAD_NAVIGATION
 
1377
    case QEvent::KeyRelease: {
 
1378
        QKeyEvent *ke = static_cast<QKeyEvent *>(e);
 
1379
        if (QApplication::keypadNavigationEnabled() && ke->key() == Qt::Key_Back) {
 
1380
            // Send the event to the 'widget'. This is what we did for KeyPress, so we need
 
1381
            // to do the same for KeyRelease, in case the widget's KeyPress event set
 
1382
            // up something (such as a timer) that is relying on also receiving the
 
1383
            // key release. I see this as a bug in Qt, and should really set it up for all
 
1384
            // the affected keys. However, it is difficult to tell how this will affect
 
1385
            // existing code, and I can't test for every combination!
 
1386
            d->eatFocusOut = false;
 
1387
            static_cast<QObject *>(d->widget)->event(ke);
 
1388
            d->eatFocusOut = true;
 
1389
        }
 
1390
        break;
 
1391
    }
 
1392
#endif
 
1393
 
 
1394
    case QEvent::MouseButtonPress: {
 
1395
#ifdef QT_KEYPAD_NAVIGATION
 
1396
        if (QApplication::keypadNavigationEnabled()) {
 
1397
            // if we've clicked in the widget (or its descendant), let it handle the click
 
1398
            QWidget *source = qobject_cast<QWidget *>(o);
 
1399
            if (source) {
 
1400
                QPoint pos = source->mapToGlobal((static_cast<QMouseEvent *>(e))->pos());
 
1401
                QWidget *target = QApplication::widgetAt(pos);
 
1402
                if (target && (d->widget->isAncestorOf(target) ||
 
1403
                    target == d->widget)) {
 
1404
                    d->eatFocusOut = false;
 
1405
                    static_cast<QObject *>(target)->event(e);
 
1406
                    d->eatFocusOut = true;
 
1407
                    return true;
 
1408
                }
 
1409
            }
 
1410
        }
 
1411
#endif
 
1412
        if (!d->popup->underMouse()) {
 
1413
            d->popup->hide();
 
1414
            return true;
 
1415
        }
 
1416
        }
 
1417
        return false;
 
1418
 
 
1419
    case QEvent::InputMethod:
 
1420
    case QEvent::ShortcutOverride:
 
1421
        QApplication::sendEvent(d->widget, e);
 
1422
        break;
 
1423
 
 
1424
    default:
 
1425
        return false;
 
1426
    }
 
1427
    return false;
 
1428
}
 
1429
 
 
1430
/*!
 
1431
    For KexiCompleter::PopupCompletion and KexiCompleter::UnfilteredPopupCompletion
 
1432
    modes, calling this function displays the popup displaying the current
 
1433
    completions. By default, if \a rect is not specified, the popup is displayed
 
1434
    on the bottom of the widget(). If \a rect is specified the popup is
 
1435
    displayed on the left edge of the rectangle.
 
1436
 
 
1437
    For KexiCompleter::InlineCompletion mode, the highlighted() signal is fired
 
1438
    with the current completion.
 
1439
*/
 
1440
void KexiCompleter::complete(const QRect& rect)
 
1441
{
 
1442
    QModelIndex idx = d->proxy->currentIndex(false);
 
1443
    d->hiddenBecauseNoMatch = false;
 
1444
    if (d->mode == KexiCompleter::InlineCompletion) {
 
1445
        if (idx.isValid())
 
1446
            d->_q_complete(idx, true);
 
1447
        return;
 
1448
    }
 
1449
 
 
1450
    Q_ASSERT(d->widget != 0);
 
1451
    if ((d->mode == KexiCompleter::PopupCompletion && !idx.isValid())
 
1452
        || (d->mode == KexiCompleter::UnfilteredPopupCompletion && d->proxy->rowCount() == 0)) {
 
1453
        if (d->popup)
 
1454
            d->popup->hide(); // no suggestion, hide
 
1455
        d->hiddenBecauseNoMatch = true;
 
1456
        return;
 
1457
    }
 
1458
 
 
1459
    popup();
 
1460
    if (d->mode == KexiCompleter::UnfilteredPopupCompletion)
 
1461
        d->setCurrentIndex(idx, false);
 
1462
 
 
1463
    d->showPopup(rect);
 
1464
    d->popupRect = rect;
 
1465
}
 
1466
 
 
1467
/*!
 
1468
    Sets the current row to the \a row specified. Returns true if successful;
 
1469
    otherwise returns false.
 
1470
 
 
1471
    This function may be used along with currentCompletion() to iterate
 
1472
    through all the possible completions.
 
1473
 
 
1474
    \sa currentCompletion(), completionCount()
 
1475
*/
 
1476
bool KexiCompleter::setCurrentRow(int row)
 
1477
{
 
1478
    return d->proxy->setCurrentRow(row);
 
1479
}
 
1480
 
 
1481
/*!
 
1482
    Returns the current row.
 
1483
 
 
1484
    \sa setCurrentRow()
 
1485
*/
 
1486
int KexiCompleter::currentRow() const
 
1487
{
 
1488
    return d->proxy->currentRow();
 
1489
}
 
1490
 
 
1491
/*!
 
1492
    Returns the number of completions for the current prefix. For an unsorted
 
1493
    model with a large number of items this can be expensive. Use setCurrentRow()
 
1494
    and currentCompletion() to iterate through all the completions.
 
1495
*/
 
1496
int KexiCompleter::completionCount() const
 
1497
{
 
1498
    return d->proxy->completionCount();
 
1499
}
 
1500
 
 
1501
/*!
 
1502
    \enum KexiCompleter::ModelSorting
 
1503
 
 
1504
    This enum specifies how the items in the model are sorted.
 
1505
 
 
1506
    \value UnsortedModel                    The model is unsorted.
 
1507
    \value CaseSensitivelySortedModel       The model is sorted case sensitively.
 
1508
    \value CaseInsensitivelySortedModel     The model is sorted case insensitively.
 
1509
 
 
1510
    \sa setModelSorting()
 
1511
*/
 
1512
 
 
1513
/*!
 
1514
    \property KexiCompleter::modelSorting
 
1515
    \brief the way the model is sorted
 
1516
 
 
1517
    By default, no assumptions are made about the order of the items
 
1518
    in the model that provides the completions.
 
1519
 
 
1520
    If the model's data for the completionColumn() and completionRole() is sorted in
 
1521
    ascending order, you can set this property to \l CaseSensitivelySortedModel
 
1522
    or \l CaseInsensitivelySortedModel. On large models, this can lead to
 
1523
    significant performance improvements because the completer object can
 
1524
    then use a binary search algorithm instead of linear search algorithm.
 
1525
 
 
1526
    The sort order (i.e ascending or descending order) of the model is determined
 
1527
    dynamically by inspecting the contents of the model.
 
1528
 
 
1529
    \bold{Note:} The performance improvements described above cannot take place
 
1530
    when the completer's \l caseSensitivity is different to the case sensitivity
 
1531
    used by the model's when sorting.
 
1532
 
 
1533
    \sa setCaseSensitivity(), KexiCompleter::ModelSorting
 
1534
*/
 
1535
void KexiCompleter::setModelSorting(KexiCompleter::ModelSorting sorting)
 
1536
{
 
1537
    if (d->sorting == sorting)
 
1538
        return;
 
1539
    d->sorting = sorting;
 
1540
    d->proxy->createEngine();
 
1541
    d->proxy->invalidate();
 
1542
}
 
1543
 
 
1544
KexiCompleter::ModelSorting KexiCompleter::modelSorting() const
 
1545
{
 
1546
    return d->sorting;
 
1547
}
 
1548
 
 
1549
/*!
 
1550
    \property KexiCompleter::completionColumn
 
1551
    \brief the column in the model in which completions are searched for.
 
1552
 
 
1553
    If the popup() is a QListView, it is automatically setup to display
 
1554
    this column.
 
1555
 
 
1556
    By default, the match column is 0.
 
1557
 
 
1558
    \sa completionRole, caseSensitivity
 
1559
*/
 
1560
void KexiCompleter::setCompletionColumn(int column)
 
1561
{
 
1562
    if (d->column == column)
 
1563
        return;
 
1564
#ifndef QT_NO_LISTVIEW
 
1565
    if (QListView *listView = qobject_cast<QListView *>(d->popup))
 
1566
        listView->setModelColumn(column);
 
1567
#endif
 
1568
    d->column = column;
 
1569
    d->proxy->invalidate();
 
1570
}
 
1571
 
 
1572
int KexiCompleter::completionColumn() const
 
1573
{
 
1574
    return d->column;
 
1575
}
 
1576
 
 
1577
/*!
 
1578
    \property KexiCompleter::completionRole
 
1579
    \brief the item role to be used to query the contents of items for matching.
 
1580
 
 
1581
    The default role is Qt::EditRole.
 
1582
 
 
1583
    \sa completionColumn, caseSensitivity
 
1584
*/
 
1585
void KexiCompleter::setCompletionRole(int role)
 
1586
{
 
1587
    if (d->role == role)
 
1588
        return;
 
1589
    d->role = role;
 
1590
    d->proxy->invalidate();
 
1591
}
 
1592
 
 
1593
int KexiCompleter::completionRole() const
 
1594
{
 
1595
    return d->role;
 
1596
}
 
1597
 
 
1598
/*!
 
1599
    \property KexiCompleter::wrapAround
 
1600
    \brief the completions wrap around when navigating through items
 
1601
    \since 4.3
 
1602
 
 
1603
    The default is true.
 
1604
*/
 
1605
void KexiCompleter::setWrapAround(bool wrap)
 
1606
{
 
1607
    if (d->wrap == wrap)
 
1608
        return;
 
1609
    d->wrap = wrap;
 
1610
}
 
1611
 
 
1612
bool KexiCompleter::wrapAround() const
 
1613
{
 
1614
    return d->wrap;
 
1615
}
 
1616
 
 
1617
/*!
 
1618
    \property KexiCompleter::maxVisibleItems
 
1619
    \brief the maximum allowed size on screen of the completer, measured in items
 
1620
    \since 4.6
 
1621
 
 
1622
    By default, this property has a value of 7.
 
1623
*/
 
1624
int KexiCompleter::maxVisibleItems() const
 
1625
{
 
1626
    return d->maxVisibleItems;
 
1627
}
 
1628
 
 
1629
void KexiCompleter::setMaxVisibleItems(int maxItems)
 
1630
{
 
1631
    if (maxItems < 0) {
 
1632
        qWarning("KexiCompleter::setMaxVisibleItems: "
 
1633
                 "Invalid max visible items (%d) must be >= 0", maxItems);
 
1634
        return;
 
1635
    }
 
1636
    d->maxVisibleItems = maxItems;
 
1637
}
 
1638
 
 
1639
/*!
 
1640
    \property KexiCompleter::caseSensitivity
 
1641
    \brief the case sensitivity of the matching
 
1642
 
 
1643
    The default is Qt::CaseSensitive.
 
1644
 
 
1645
    \sa substringCompletion, completionColumn, completionRole, modelSorting
 
1646
*/
 
1647
void KexiCompleter::setCaseSensitivity(Qt::CaseSensitivity cs)
 
1648
{
 
1649
    if (d->cs == cs)
 
1650
        return;
 
1651
    d->cs = cs;
 
1652
    d->proxy->createEngine();
 
1653
    d->proxy->invalidate();
 
1654
}
 
1655
 
 
1656
Qt::CaseSensitivity KexiCompleter::caseSensitivity() const
 
1657
{
 
1658
    return d->cs;
 
1659
}
 
1660
 
 
1661
/*!
 
1662
    \property KexiCompleter::substringCompletion
 
1663
    \brief the completion uses any substring matching
 
1664
 
 
1665
    If true the completion uses any substring matching.
 
1666
    If false prefix matching is used. The default is false.
 
1667
 
 
1668
    \sa caseSensitivity
 
1669
*/
 
1670
void KexiCompleter::setSubstringCompletion(bool substringCompletion)
 
1671
{
 
1672
    if (d->substringCompletion == substringCompletion)
 
1673
        return;
 
1674
    d->substringCompletion = substringCompletion;
 
1675
    d->proxy->invalidate();
 
1676
}
 
1677
 
 
1678
bool KexiCompleter::substringCompletion() const
 
1679
{
 
1680
    return d->substringCompletion;
 
1681
}
 
1682
 
 
1683
/*!
 
1684
    \property KexiCompleter::completionPrefix
 
1685
    \brief the completion prefix used to provide completions.
 
1686
 
 
1687
    The completionModel() is updated to reflect the list of possible
 
1688
    matches for \a prefix.
 
1689
*/
 
1690
void KexiCompleter::setCompletionPrefix(const QString &prefix)
 
1691
{
 
1692
    d->prefix = prefix;
 
1693
    d->proxy->filter(splitPath(prefix));
 
1694
}
 
1695
 
 
1696
QString KexiCompleter::completionPrefix() const
 
1697
{
 
1698
    return d->prefix;
 
1699
}
 
1700
 
 
1701
/*!
 
1702
    Returns the model index of the current completion in the completionModel().
 
1703
 
 
1704
    \sa setCurrentRow(), currentCompletion(), model()
 
1705
*/
 
1706
QModelIndex KexiCompleter::currentIndex() const
 
1707
{
 
1708
    return d->proxy->currentIndex(false);
 
1709
}
 
1710
 
 
1711
/*!
 
1712
    Returns the current completion string. This includes the \l completionPrefix.
 
1713
    When used alongside setCurrentRow(), it can be used to iterate through
 
1714
    all the matches.
 
1715
 
 
1716
    \sa setCurrentRow(), currentIndex()
 
1717
*/
 
1718
QString KexiCompleter::currentCompletion() const
 
1719
{
 
1720
    return pathFromIndex(d->proxy->currentIndex(true));
 
1721
}
 
1722
 
 
1723
/*!
 
1724
    Returns the completion model. The completion model is a read-only list model
 
1725
    that contains all the possible matches for the current completion prefix.
 
1726
    The completion model is auto-updated to reflect the current completions.
 
1727
 
 
1728
    \note The return value of this function is defined to be an QAbstractItemModel
 
1729
    purely for generality. This actual kind of model returned is an instance of an
 
1730
    QAbstractProxyModel subclass.
 
1731
 
 
1732
    \sa completionPrefix, model()
 
1733
*/
 
1734
QAbstractItemModel *KexiCompleter::completionModel() const
 
1735
{
 
1736
    return d->proxy;
 
1737
}
 
1738
 
 
1739
/*!
 
1740
    Returns the path for the given \a index. The completer object uses this to
 
1741
    obtain the completion text from the underlying model.
 
1742
 
 
1743
    The default implementation returns the \l{Qt::EditRole}{edit role} of the
 
1744
    item for list models. It returns the absolute file path if the model is a
 
1745
    QFileSystemModel.
 
1746
 
 
1747
    \sa splitPath()
 
1748
*/
 
1749
 
 
1750
QString KexiCompleter::pathFromIndex(const QModelIndex& index) const
 
1751
{
 
1752
    if (!index.isValid())
 
1753
        return QString();
 
1754
 
 
1755
    QAbstractItemModel *sourceModel = d->proxy->sourceModel();
 
1756
    if (!sourceModel)
 
1757
        return QString();
 
1758
    bool isDirModel = false;
 
1759
    bool isFsModel = false;
 
1760
#ifndef QT_NO_DIRMODEL
 
1761
    isDirModel = qobject_cast<QDirModel *>(d->proxy->sourceModel()) != 0;
 
1762
#endif
 
1763
#ifndef QT_NO_FILESYSTEMMODEL
 
1764
    isFsModel = qobject_cast<QFileSystemModel *>(d->proxy->sourceModel()) != 0;
 
1765
#endif
 
1766
    if (!isDirModel && !isFsModel)
 
1767
        return sourceModel->data(index, d->role).toString();
 
1768
 
 
1769
    QModelIndex idx = index;
 
1770
    QStringList list;
 
1771
    do {
 
1772
        QString t;
 
1773
        if (isDirModel)
 
1774
            t = sourceModel->data(idx, Qt::EditRole).toString();
 
1775
#ifndef QT_NO_FILESYSTEMMODEL
 
1776
        else
 
1777
            t = sourceModel->data(idx, QFileSystemModel::FileNameRole).toString();
 
1778
#endif
 
1779
        list.prepend(t);
 
1780
        QModelIndex parent = idx.parent();
 
1781
        idx = parent.sibling(parent.row(), index.column());
 
1782
    } while (idx.isValid());
 
1783
 
 
1784
#if (!defined(Q_OS_WIN) || defined(Q_OS_WINCE)) && !defined(Q_OS_SYMBIAN)
 
1785
    if (list.count() == 1) // only the separator or some other text
 
1786
        return list[0];
 
1787
    list[0].clear(); // the join below will provide the separator
 
1788
#endif
 
1789
 
 
1790
    return list.join(QDir::separator());
 
1791
}
 
1792
 
 
1793
/*!
 
1794
    Splits the given \a path into strings that are used to match at each level
 
1795
    in the model().
 
1796
 
 
1797
    The default implementation of splitPath() splits a file system path based on
 
1798
    QDir::separator() when the sourceModel() is a QFileSystemModel.
 
1799
 
 
1800
    When used with list models, the first item in the returned list is used for
 
1801
    matching.
 
1802
 
 
1803
    \sa pathFromIndex(), {Handling Tree Models}
 
1804
*/
 
1805
QStringList KexiCompleter::splitPath(const QString& path) const
 
1806
{
 
1807
    bool isDirModel = false;
 
1808
    bool isFsModel = false;
 
1809
#ifndef QT_NO_DIRMODEL
 
1810
    isDirModel = qobject_cast<QDirModel *>(d->proxy->sourceModel()) != 0;
 
1811
#endif
 
1812
#ifndef QT_NO_FILESYSTEMMODEL
 
1813
#ifdef QT_NO_DIRMODEL
 
1814
#endif
 
1815
    isFsModel = qobject_cast<QFileSystemModel *>(d->proxy->sourceModel()) != 0;
 
1816
#endif
 
1817
 
 
1818
    if ((!isDirModel && !isFsModel) || path.isEmpty())
 
1819
        return QStringList(completionPrefix());
 
1820
 
 
1821
    QString pathCopy = QDir::toNativeSeparators(path);
 
1822
    QString sep = QDir::separator();
 
1823
#if defined(Q_OS_SYMBIAN)
 
1824
    if (pathCopy == QLatin1String("\\"))
 
1825
        return QStringList(pathCopy);
 
1826
#elif defined(Q_OS_WIN) && !defined(Q_OS_WINCE)
 
1827
    if (pathCopy == QLatin1String("\\") || pathCopy == QLatin1String("\\\\"))
 
1828
        return QStringList(pathCopy);
 
1829
    QString doubleSlash(QLatin1String("\\\\"));
 
1830
    if (pathCopy.startsWith(doubleSlash))
 
1831
        pathCopy.remove(0, 2);
 
1832
    else
 
1833
        doubleSlash.clear();
 
1834
#endif
 
1835
 
 
1836
    QRegularExpression re(QLatin1Char('[') + QRegularExpression::escape(sep) + QLatin1Char(']'));
 
1837
    QStringList parts = pathCopy.split(re);
 
1838
 
 
1839
#if defined(Q_OS_SYMBIAN)
 
1840
    // Do nothing
 
1841
#elif defined(Q_OS_WIN) && !defined(Q_OS_WINCE)
 
1842
    if (!doubleSlash.isEmpty())
 
1843
        parts[0].prepend(doubleSlash);
 
1844
#else
 
1845
    if (pathCopy[0] == sep[0]) // readd the "/" at the beginning as the split removed it
 
1846
        parts[0] = QDir::fromNativeSeparators(QString(sep[0]));
 
1847
#endif
 
1848
 
 
1849
    return parts;
 
1850
}
 
1851
 
 
1852
/*!
 
1853
    \fn void KexiCompleter::activated(const QModelIndex& index)
 
1854
 
 
1855
    This signal is sent when an item in the popup() is activated by the user.
 
1856
    (by clicking or pressing return). The item's \a index in the completionModel()
 
1857
    is given.
 
1858
 
 
1859
*/
 
1860
 
 
1861
/*!
 
1862
    \fn void KexiCompleter::activated(const QString &text)
 
1863
 
 
1864
    This signal is sent when an item in the popup() is activated by the user (by
 
1865
    clicking or pressing return). The item's \a text is given.
 
1866
 
 
1867
*/
 
1868
 
 
1869
/*!
 
1870
    \fn void KexiCompleter::highlighted(const QModelIndex& index)
 
1871
 
 
1872
    This signal is sent when an item in the popup() is highlighted by
 
1873
    the user. It is also sent if complete() is called with the completionMode()
 
1874
    set to KexiCompleter::InlineCompletion. The item's \a index in the completionModel()
 
1875
    is given.
 
1876
*/
 
1877
 
 
1878
/*!
 
1879
    \fn void KexiCompleter::highlighted(const QString &text)
 
1880
 
 
1881
    This signal is sent when an item in the popup() is highlighted by
 
1882
    the user. It is also sent if complete() is called with the completionMode()
 
1883
    set to KexiCompleter::InlineCompletion. The item's \a text is given.
 
1884
*/
 
1885
 
 
1886
void KexiCompleter::_q_complete(const QModelIndex& index)
 
1887
{
 
1888
    d->_q_complete(index);
 
1889
}
 
1890
 
 
1891
void KexiCompleter::_q_completionSelected(const QItemSelection& selection)
 
1892
{
 
1893
    d->_q_completionSelected(selection);
 
1894
}
 
1895
 
 
1896
void KexiCompleter::_q_autoResizePopup()
 
1897
{
 
1898
    d->_q_autoResizePopup();
 
1899
}
 
1900
 
 
1901
void KexiCompleter::_q_fileSystemModelDirectoryLoaded(const QString& dir)
 
1902
{
 
1903
    d->_q_fileSystemModelDirectoryLoaded(dir);
 
1904
}
 
1905
 
 
1906
KexiCompletionModelPrivate::KexiCompletionModelPrivate(KexiCompletionModel *q)
 
1907
 : q(q)
 
1908
{
 
1909
}
 
1910
 
 
1911
void KexiCompletionModelPrivate::_q_sourceModelDestroyed()
 
1912
{
 
1913
    q->setSourceModel(KexiAbstractItemModelPrivate::staticEmptyModel());
 
1914
}
 
1915
 
 
1916
#include "KexiCompleter.moc"
 
1917
 
 
1918
#endif // QT_NO_COMPLETER