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

« back to all changes in this revision

Viewing changes to src/main/KexiSearchLineEdit.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
/* This file is part of the KDE project
 
2
   Copyright (C) 2011-2016 Jarosław Staniek <staniek@kde.org>
 
3
   Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
 
4
 
 
5
   This program is free software; you can redistribute it and/or
 
6
   modify it under the terms of the GNU Library General Public
 
7
   License as published by the Free Software Foundation; either
 
8
   version 2 of the License, or (at your option) any later version.
 
9
 
 
10
   This program is distributed in the hope that it will be useful,
 
11
   but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
13
   Library General Public License for more details.
 
14
 
 
15
   You should have received a copy of the GNU Library General Public License
 
16
   along with this program; see the file COPYING.  If not, write to
 
17
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 
18
 * Boston, MA 02110-1301, USA.
 
19
*/
 
20
 
 
21
#include "KexiSearchLineEdit.h"
 
22
#include <KexiSearchableModel.h>
 
23
 
 
24
#include <KLocalizedString>
 
25
 
 
26
#include <kexiutils/completer/KexiCompleter.h>
 
27
#include <kexiutils/KexiTester.h>
 
28
 
 
29
#include <QDebug>
 
30
#include <QShortcut>
 
31
#include <QKeySequence>
 
32
#include <QTreeView>
 
33
#include <QAbstractProxyModel>
 
34
#include <QInputMethodEvent>
 
35
#include <QStyledItemDelegate>
 
36
#include <QTextLayout>
 
37
#include <QPainter>
 
38
 
 
39
class SearchableObject
 
40
{
 
41
public:
 
42
    KexiSearchableModel *model;
 
43
    int index;
 
44
};
 
45
 
 
46
class KexiSearchLineEditCompleterPopupModel : public QAbstractListModel
 
47
{
 
48
    Q_OBJECT
 
49
public:
 
50
    explicit KexiSearchLineEditCompleterPopupModel(QObject *parent = 0);
 
51
    ~KexiSearchLineEditCompleterPopupModel();
 
52
    virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
 
53
    virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
 
54
    virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
 
55
    void addSearchableModel(KexiSearchableModel *model);
 
56
private:
 
57
    class Private;
 
58
    Private * const d;
 
59
};
 
60
 
 
61
class KexiSearchLineEditCompleterPopupModel::Private
 
62
{
 
63
public:
 
64
    Private()
 
65
     : cachedCount(-1)
 
66
    {
 
67
    }
 
68
    ~Private() {
 
69
        qDeleteAll(searchableObjects);
 
70
    }
 
71
    void updateCachedCount() {
 
72
        if (searchableModels.isEmpty()) {
 
73
            return;
 
74
        }
 
75
        cachedCount = 0;
 
76
        foreach (KexiSearchableModel* searchableModel, searchableModels) {
 
77
            cachedCount += searchableModel->searchableObjectCount();
 
78
        }
 
79
    }
 
80
    int cachedCount;
 
81
    QList<KexiSearchableModel*> searchableModels;
 
82
    QMap<int, SearchableObject*> searchableObjects;
 
83
};
 
84
 
 
85
KexiSearchLineEditCompleterPopupModel::KexiSearchLineEditCompleterPopupModel(QObject *parent)
 
86
 : QAbstractListModel(parent), d(new Private)
 
87
{
 
88
}
 
89
 
 
90
KexiSearchLineEditCompleterPopupModel::~KexiSearchLineEditCompleterPopupModel()
 
91
{
 
92
    delete d;
 
93
}
 
94
 
 
95
int KexiSearchLineEditCompleterPopupModel::rowCount(const QModelIndex &parent) const
 
96
{
 
97
    Q_UNUSED(parent);
 
98
    if (d->cachedCount < 0) {
 
99
        d->updateCachedCount();
 
100
    }
 
101
    return d->cachedCount;
 
102
}
 
103
 
 
104
QVariant KexiSearchLineEditCompleterPopupModel::data(const QModelIndex &index, int role) const
 
105
{
 
106
    const int row = index.row();
 
107
    if (d->cachedCount <= row) {
 
108
        return QVariant();
 
109
    }
 
110
    SearchableObject *object = static_cast<SearchableObject*>(index.internalPointer());
 
111
    QModelIndex sourceIndex = object->model->sourceIndexForSearchableObject(object->index);
 
112
    return object->model->searchableData(sourceIndex, role);
 
113
}
 
114
 
 
115
QModelIndex KexiSearchLineEditCompleterPopupModel::index(int row, int column,
 
116
                                                         const QModelIndex &parent) const
 
117
{
 
118
    //qDebug() << row;
 
119
    if (!hasIndex(row, column, parent)) {
 
120
        qDebug() << "!hasIndex";
 
121
        return QModelIndex();
 
122
    }
 
123
 
 
124
    int r = row;
 
125
    SearchableObject *sobject = d->searchableObjects.value(row);
 
126
    if (!sobject) {
 
127
        foreach (KexiSearchableModel* searchableModel, d->searchableModels) {
 
128
            const int count = searchableModel->searchableObjectCount();
 
129
            if (r < count) {
 
130
                sobject = new SearchableObject;
 
131
                sobject->model = searchableModel;
 
132
                sobject->index = r;
 
133
                d->searchableObjects.insert(row, sobject);
 
134
                break;
 
135
            }
 
136
            else {
 
137
                r -= count;
 
138
            }
 
139
        }
 
140
    }
 
141
    if (!sobject) {
 
142
        return QModelIndex();
 
143
    }
 
144
    return createIndex(row, column, sobject);
 
145
}
 
146
 
 
147
void KexiSearchLineEditCompleterPopupModel::addSearchableModel(KexiSearchableModel *model)
 
148
{
 
149
    d->searchableModels.removeAll(model);
 
150
    d->searchableModels.append(model);
 
151
    d->updateCachedCount();
 
152
}
 
153
 
 
154
// ----
 
155
 
 
156
class KexiSearchLineEditCompleter : public KexiCompleter
 
157
{
 
158
    Q_OBJECT
 
159
public:
 
160
    explicit KexiSearchLineEditCompleter(QObject *parent = 0) : KexiCompleter(parent) {
 
161
        setCompletionRole(Qt::DisplayRole);
 
162
    }
 
163
 
 
164
    virtual QString pathFromIndex(const QModelIndex &index) const {
 
165
        if (!index.isValid())
 
166
            return QString();
 
167
        SearchableObject *object = static_cast<SearchableObject*>(index.internalPointer());
 
168
        QModelIndex sourceIndex = object->model->sourceIndexForSearchableObject(object->index);
 
169
        return object->model->pathFromIndex(sourceIndex);
 
170
    }
 
171
};
 
172
 
 
173
// ----
 
174
 
 
175
class KexiSearchLineEditPopupItemDelegate;
 
176
 
 
177
class KexiSearchLineEdit::Private
 
178
{
 
179
public:
 
180
    explicit Private(KexiSearchLineEdit *_q)
 
181
     : q(_q), clearShortcut(QKeySequence(Qt::Key_Escape), _q),
 
182
       recentlyHighlightedModel(0)
 
183
    {
 
184
        // make Escape key clear the search box
 
185
        QObject::connect(&clearShortcut, SIGNAL(activated()),
 
186
                         q, SLOT(slotClearShortcutActivated()));
 
187
    }
 
188
 
 
189
    void highlightSearchableObject(const QPair<QModelIndex, KexiSearchableModel*> &source)
 
190
    {
 
191
        source.second->highlightSearchableObject(source.first);
 
192
        recentlyHighlightedModel = source.second;
 
193
    }
 
194
 
 
195
    void removeHighlightingForSearchableObject()
 
196
    {
 
197
        if (recentlyHighlightedModel) {
 
198
            recentlyHighlightedModel->highlightSearchableObject(QModelIndex());
 
199
            recentlyHighlightedModel = 0;
 
200
        }
 
201
    }
 
202
 
 
203
    KexiSearchLineEditCompleter *completer;
 
204
    QTreeView *popupTreeView;
 
205
    KexiSearchLineEditCompleterPopupModel *model;
 
206
    KexiSearchLineEditPopupItemDelegate *delegate;
 
207
    QPointer<QWidget> previouslyFocusedWidget;
 
208
 
 
209
private:
 
210
    KexiSearchLineEdit *q;
 
211
    QShortcut clearShortcut;
 
212
    KexiSearchableModel *recentlyHighlightedModel;
 
213
};
 
214
 
 
215
// ----
 
216
 
 
217
static QSizeF viewItemTextLayout(QTextLayout &textLayout, int lineWidth)
 
218
{
 
219
    qreal height = 0;
 
220
    qreal widthUsed = 0;
 
221
    textLayout.beginLayout();
 
222
    while (true) {
 
223
        QTextLine line = textLayout.createLine();
 
224
        if (!line.isValid())
 
225
            break;
 
226
        line.setLineWidth(lineWidth);
 
227
        line.setPosition(QPointF(0, height));
 
228
        height += line.height();
 
229
        widthUsed = qMax(widthUsed, line.naturalTextWidth());
 
230
    }
 
231
    textLayout.endLayout();
 
232
    return QSizeF(widthUsed, height);
 
233
}
 
234
 
 
235
class KexiSearchLineEditPopupItemDelegate : public QStyledItemDelegate
 
236
{
 
237
    Q_OBJECT
 
238
public:
 
239
    KexiSearchLineEditPopupItemDelegate(QObject *parent, KexiCompleter *completer)
 
240
     : QStyledItemDelegate(parent), highlightMatchingSubstrings(true), m_completer(completer)
 
241
    {
 
242
    }
 
243
 
 
244
    //! Implemented to improve width hint
 
245
    QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
 
246
    {
 
247
        QSize size(QStyledItemDelegate::sizeHint(option, index));
 
248
        QStyleOptionViewItemV4 v4 = option;
 
249
        QStyledItemDelegate::initStyleOption(&v4, index);
 
250
        const QSize s = v4.widget->style()->sizeFromContents(QStyle::CT_ItemViewItem, &v4, size, v4.widget);
 
251
        size.setWidth(s.width());
 
252
        return size;
 
253
    }
 
254
 
 
255
    virtual void paint(QPainter *painter, const QStyleOptionViewItem &option,
 
256
                       const QModelIndex &index) const
 
257
    {
 
258
        QStyledItemDelegate::paint(painter, option, index);
 
259
        QStyleOptionViewItemV4 v4 = option;
 
260
        QStyledItemDelegate::initStyleOption(&v4, index);
 
261
        // like in QCommonStyle::paint():
 
262
        if (!v4.text.isEmpty()) {
 
263
            painter->save();
 
264
            painter->setClipRect(v4.rect);
 
265
            QPalette::ColorGroup cg = v4.state & QStyle::State_Enabled
 
266
                                    ? QPalette::Normal : QPalette::Disabled;
 
267
            if (cg == QPalette::Normal && !(v4.state & QStyle::State_Active)) {
 
268
                cg = QPalette::Inactive;
 
269
            }
 
270
            if (v4.state & QStyle::State_Selected) {
 
271
                painter->setPen(v4.palette.color(cg, QPalette::HighlightedText));
 
272
            }
 
273
            else {
 
274
                painter->setPen(v4.palette.color(cg, QPalette::Text));
 
275
            }
 
276
            QRect textRect = v4.widget->style()->subElementRect(QStyle::SE_ItemViewItemText,
 
277
                                                                &v4, v4.widget);
 
278
            viewItemDrawText(painter, &v4, textRect);
 
279
            painter->restore();
 
280
        }
 
281
    }
 
282
    bool highlightMatchingSubstrings;
 
283
 
 
284
protected:
 
285
    // bits from qcommonstyle.cpp
 
286
    void viewItemDrawText(QPainter *p, const QStyleOptionViewItemV4 *option, const QRect &rect) const
 
287
    {
 
288
        const QWidget *widget = option->widget;
 
289
        const int textMargin = widget->style()->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, widget) + 1;
 
290
 
 
291
        QRect textRect = rect.adjusted(textMargin, 0, -textMargin, 0); // remove width padding
 
292
        const bool wrapText = option->features & QStyleOptionViewItemV2::WrapText;
 
293
        QTextOption textOption;
 
294
        textOption.setWrapMode(wrapText ? QTextOption::WordWrap : QTextOption::ManualWrap);
 
295
        textOption.setTextDirection(option->direction);
 
296
        textOption.setAlignment(QStyle::visualAlignment(option->direction, option->displayAlignment));
 
297
        QTextLayout textLayout;
 
298
        textLayout.setTextOption(textOption);
 
299
        textLayout.setFont(option->font);
 
300
        QString text = option->text;
 
301
        textLayout.setText(text);
 
302
 
 
303
        if (highlightMatchingSubstrings) {
 
304
            QList<QTextLayout::FormatRange> formats;
 
305
            QString substring = m_completer->completionPrefix();
 
306
            QColor underLineColor(p->pen().color());
 
307
            underLineColor.setAlpha(128);
 
308
            QTextLayout::FormatRange formatRange;
 
309
            formatRange.format.setFontUnderline(true);
 
310
            formatRange.format.setUnderlineColor(underLineColor);
 
311
 
 
312
            for (int i = 0; i < text.length();) {
 
313
                i = text.indexOf(substring, i, Qt::CaseInsensitive);
 
314
                if (i == -1)
 
315
                    break;
 
316
                formatRange.length = substring.length();
 
317
                formatRange.start = i;
 
318
                formats.append(formatRange);
 
319
                i += formatRange.length;
 
320
            }
 
321
            textLayout.setAdditionalFormats(formats);
 
322
        }
 
323
        viewItemTextLayout(textLayout, textRect.width());
 
324
 
 
325
        const int lineCount = textLayout.lineCount();
 
326
        QPointF position = textRect.topLeft();
 
327
        for (int i = 0; i < lineCount; ++i) {
 
328
            const QTextLine line = textLayout.lineAt(i);
 
329
            const QPointF adjustPos(0, qreal(textRect.height() - line.rect().height()) / 2.0);
 
330
            line.draw(p, position + adjustPos);
 
331
            position.setY(position.y() + line.y() + line.ascent());
 
332
        }
 
333
    }
 
334
 
 
335
    virtual void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const
 
336
    {
 
337
        QStyledItemDelegate::initStyleOption(option, index);
 
338
        QStyleOptionViewItemV4 *v4 = qstyleoption_cast<QStyleOptionViewItemV4*>(option);
 
339
        if (v4) {
 
340
            v4->text.clear();
 
341
        }
 
342
    }
 
343
    KexiCompleter *m_completer;
 
344
};
 
345
 
 
346
// ----
 
347
 
 
348
//! @internal Style-dependent fixes for the left margin, probably needed because of the limited
 
349
//! width of the line edit - it's placed in tab bar's corner widget.
 
350
static void fixLeftMargin(QLineEdit *lineEdit)
 
351
{
 
352
    int add = 0;
 
353
    const QByteArray st(lineEdit->style()->objectName().toLatin1());
 
354
    if (st == "breeze" || st == "gtk+") {
 
355
        add = 4; // like QLineEditIconButton::IconMargin
 
356
    }
 
357
    else if (st == "fusion") {
 
358
        add = 2;
 
359
    }
 
360
    if (add != 0) {
 
361
        QMargins margins(lineEdit->textMargins());
 
362
        margins.setLeft(margins.left() + add);
 
363
        lineEdit->setTextMargins(margins);
 
364
    }
 
365
}
 
366
 
 
367
// ----
 
368
 
 
369
KexiSearchLineEdit::KexiSearchLineEdit(QWidget *parent)
 
370
 : QLineEdit(parent), d(new Private(this))
 
371
{
 
372
    d->completer = new KexiSearchLineEditCompleter(this);
 
373
    d->popupTreeView = new QTreeView;
 
374
    kexiTester() << KexiTestObject(d->popupTreeView, "globalSearch.treeView");
 
375
 
 
376
    d->completer->setPopup(d->popupTreeView);
 
377
    d->completer->setModel(d->model = new KexiSearchLineEditCompleterPopupModel(d->completer));
 
378
    d->completer->setCaseSensitivity(Qt::CaseInsensitive);
 
379
    d->completer->setSubstringCompletion(true);
 
380
    d->completer->setMaxVisibleItems(12);
 
381
    // Use unsorted model, sorting is handled in the source model itself.
 
382
    // Moreover, sorting KexiCompleter::CaseInsensitivelySortedModel breaks
 
383
    // filtering so only table names are displayed.
 
384
    d->completer->setModelSorting(KexiCompleter::UnsortedModel);
 
385
 
 
386
    d->popupTreeView->setHeaderHidden(true);
 
387
    d->popupTreeView->setRootIsDecorated(false);
 
388
    d->popupTreeView->setItemDelegate(
 
389
        d->delegate = new KexiSearchLineEditPopupItemDelegate(d->popupTreeView, d->completer));
 
390
 
 
391
    // forked initialization like in QLineEdit::setCompleter:
 
392
    d->completer->setWidget(this);
 
393
    if (hasFocus()) {
 
394
        connectCompleter();
 
395
    }
 
396
 
 
397
    setFocusPolicy(Qt::NoFocus); // We cannot focus set any policy here.
 
398
                                 // Qt::ClickFocus would make it impossible to find
 
399
                                 // previously focus widget in KexiSearchLineEdit::setFocus().
 
400
                                 // We need this information to focus back when pressing Escape key.
 
401
    setClearButtonEnabled(true);
 
402
    setPlaceholderText(xi18n("Search"));
 
403
    fixLeftMargin(this);
 
404
}
 
405
 
 
406
KexiSearchLineEdit::~KexiSearchLineEdit()
 
407
{
 
408
    delete d;
 
409
}
 
410
 
 
411
void KexiSearchLineEdit::connectCompleter()
 
412
{
 
413
    connect(d->completer, SIGNAL(activated(QString)),
 
414
            this, SLOT(setText(QString)));
 
415
    connect(d->completer, SIGNAL(activated(QModelIndex)),
 
416
            this, SLOT(slotCompletionActivated(QModelIndex)));
 
417
    connect(d->completer, SIGNAL(highlighted(QString)),
 
418
            this, SLOT(slotCompletionHighlighted(QString)));
 
419
    connect(d->completer, SIGNAL(highlighted(QModelIndex)),
 
420
            this, SLOT(slotCompletionHighlighted(QModelIndex)));
 
421
}
 
422
 
 
423
void KexiSearchLineEdit::disconnectCompleter()
 
424
{
 
425
    disconnect(d->completer, 0, this, 0);
 
426
}
 
427
 
 
428
void KexiSearchLineEdit::slotClearShortcutActivated()
 
429
{
 
430
    //qDebug() << (QWidget*)d->previouslyFocusedWidget << text();
 
431
    d->removeHighlightingForSearchableObject();
 
432
    if (text().isEmpty() && d->previouslyFocusedWidget) {
 
433
        // after second Escape, go back to previously focused widget
 
434
        d->previouslyFocusedWidget->setFocus();
 
435
        d->previouslyFocusedWidget = 0;
 
436
    }
 
437
    else {
 
438
        clear();
 
439
    }
 
440
}
 
441
 
 
442
void KexiSearchLineEdit::addSearchableModel(KexiSearchableModel *model)
 
443
{
 
444
    d->model->addSearchableModel(model);
 
445
}
 
446
 
 
447
QPair<QModelIndex, KexiSearchableModel*> KexiSearchLineEdit::mapCompletionIndexToSource(const QModelIndex &index) const
 
448
{
 
449
    QModelIndex realIndex
 
450
        = qobject_cast<QAbstractProxyModel*>(d->completer->completionModel())->mapToSource(index);
 
451
    if (!realIndex.isValid()) {
 
452
        return qMakePair(QModelIndex(), static_cast<KexiSearchableModel*>(0));
 
453
    }
 
454
    SearchableObject *object = static_cast<SearchableObject*>(realIndex.internalPointer());
 
455
    if (!object) {
 
456
        return qMakePair(QModelIndex(), static_cast<KexiSearchableModel*>(0));
 
457
    }
 
458
    return qMakePair(object->model->sourceIndexForSearchableObject(object->index), object->model);
 
459
}
 
460
 
 
461
void KexiSearchLineEdit::slotCompletionHighlighted(const QString &newText)
 
462
{
 
463
    if (d->completer->completionMode() != KexiCompleter::InlineCompletion) {
 
464
        setText(newText);
 
465
    }
 
466
    else {
 
467
        int p = cursorPosition();
 
468
        QString t = text();
 
469
        setText(t.left(p) + newText.mid(p));
 
470
        end(false);
 
471
        cursorBackward(text().length() - p, true);
 
472
    }
 
473
}
 
474
 
 
475
void KexiSearchLineEdit::slotCompletionHighlighted(const QModelIndex &index)
 
476
{
 
477
    QPair<QModelIndex, KexiSearchableModel*> source = mapCompletionIndexToSource(index);
 
478
    if (!source.first.isValid())
 
479
        return;
 
480
    //qDebug() << source.second->searchableData(source.first, Qt::EditRole);
 
481
    d->highlightSearchableObject(source);
 
482
}
 
483
 
 
484
void KexiSearchLineEdit::slotCompletionActivated(const QModelIndex &index)
 
485
{
 
486
    QPair<QModelIndex, KexiSearchableModel*> source = mapCompletionIndexToSource(index);
 
487
    if (!source.first.isValid())
 
488
        return;
 
489
    //qDebug() << source.second->searchableData(source.first, Qt::EditRole);
 
490
 
 
491
    d->highlightSearchableObject(source);
 
492
    d->removeHighlightingForSearchableObject();
 
493
    if (source.second->activateSearchableObject(source.first)) {
 
494
        clear();
 
495
    }
 
496
}
 
497
 
 
498
// forked bits from QLineEdit::inputMethodEvent()
 
499
void KexiSearchLineEdit::inputMethodEvent(QInputMethodEvent *e)
 
500
{
 
501
    QLineEdit::inputMethodEvent(e);
 
502
    if (isReadOnly() || !e->isAccepted())
 
503
        return;
 
504
    if (!e->commitString().isEmpty()) {
 
505
        complete(Qt::Key_unknown);
 
506
    }
 
507
}
 
508
 
 
509
void KexiSearchLineEdit::setFocus()
 
510
{
 
511
    //qDebug() << "d->previouslyFocusedWidget:" << (QWidget*)d->previouslyFocusedWidget
 
512
    //         << "window()->focusWidget():" << window()->focusWidget();
 
513
    if (!d->previouslyFocusedWidget && window()->focusWidget() != this) {
 
514
        d->previouslyFocusedWidget = window()->focusWidget();
 
515
    }
 
516
    QLineEdit::setFocus();
 
517
}
 
518
 
 
519
// forked bits from QLineEdit::focusInEvent()
 
520
void KexiSearchLineEdit::focusInEvent(QFocusEvent *e)
 
521
{
 
522
    //qDebug() << "d->previouslyFocusedWidget:" << (QWidget*)d->previouslyFocusedWidget
 
523
    //         << "window()->focusWidget():" << window()->focusWidget();
 
524
    if (!d->previouslyFocusedWidget && window()->focusWidget() != this) {
 
525
        d->previouslyFocusedWidget = window()->focusWidget();
 
526
    }
 
527
    QLineEdit::focusInEvent(e);
 
528
    d->completer->setWidget(this);
 
529
    connectCompleter();
 
530
    update();
 
531
}
 
532
 
 
533
// forked bits from QLineEdit::focusOutEvent()
 
534
void KexiSearchLineEdit::focusOutEvent(QFocusEvent *e)
 
535
{
 
536
    QLineEdit::focusOutEvent(e);
 
537
    disconnectCompleter();
 
538
    update();
 
539
    if (e->reason() == Qt::TabFocusReason || e->reason() == Qt::BacktabFocusReason) {
 
540
        // go back to previously focused widget
 
541
        if (d->previouslyFocusedWidget) {
 
542
            d->previouslyFocusedWidget->setFocus();
 
543
        }
 
544
        e->accept();
 
545
    }
 
546
    d->previouslyFocusedWidget = 0;
 
547
    d->removeHighlightingForSearchableObject();
 
548
}
 
549
 
 
550
// forked bits from QLineControl::processKeyEvent()
 
551
void KexiSearchLineEdit::keyPressEvent(QKeyEvent *event)
 
552
{
 
553
    bool inlineCompletionAccepted = false;
 
554
 
 
555
    //qDebug() << event->key() << (QWidget*)d->previouslyFocusedWidget;
 
556
 
 
557
    KexiCompleter::CompletionMode completionMode = d->completer->completionMode();
 
558
    if ((completionMode == KexiCompleter::PopupCompletion
 
559
            || completionMode == KexiCompleter::UnfilteredPopupCompletion)
 
560
        && d->completer->popup()
 
561
        && d->completer->popup()->isVisible()) {
 
562
        // The following keys are forwarded by the completer to the widget
 
563
        // Ignoring the events lets the completer provide suitable default behavior
 
564
        switch (event->key()) {
 
565
        case Qt::Key_Escape:
 
566
            event->ignore();
 
567
            return;
 
568
#ifdef QT_KEYPAD_NAVIGATION
 
569
        case Qt::Key_Select:
 
570
            if (!QApplication::keypadNavigationEnabled())
 
571
                break;
 
572
            d->completer->popup()->hide(); // just hide. will end up propagating to parent
 
573
#endif
 
574
        default:
 
575
            break; // normal key processing
 
576
        }
 
577
    } else if (completionMode == KexiCompleter::InlineCompletion) {
 
578
        switch (event->key()) {
 
579
        case Qt::Key_Enter:
 
580
        case Qt::Key_Return:
 
581
        case Qt::Key_F4:
 
582
#ifdef QT_KEYPAD_NAVIGATION
 
583
        case Qt::Key_Select:
 
584
            if (!QApplication::keypadNavigationEnabled())
 
585
                break;
 
586
#endif
 
587
            if (!d->completer->currentCompletion().isEmpty() && hasSelectedText()
 
588
                && textAfterSelection().isEmpty())
 
589
            {
 
590
                setText(d->completer->currentCompletion());
 
591
                inlineCompletionAccepted = true;
 
592
            }
 
593
        default:
 
594
            break; // normal key processing
 
595
        }
 
596
    }
 
597
 
 
598
    if (d->completer->popup() && !d->completer->popup()->isVisible()
 
599
        && (event->key() == Qt::Key_F4 || event->key() == Qt::Key_Down))
 
600
    {
 
601
        // go back to completing when popup is closed and F4/Down pressed
 
602
        d->completer->complete();
 
603
    }
 
604
    else if (d->completer->popup() && d->completer->popup()->isVisible()
 
605
        && event->key() == Qt::Key_F4)
 
606
    {
 
607
        // hide popup if F4 pressed
 
608
        d->completer->popup()->hide();
 
609
    }
 
610
 
 
611
    if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) {
 
612
        if (d->completer->popup() && !d->completer->popup()->isVisible()) {
 
613
            d->completer->setCompletionPrefix(text());
 
614
        }
 
615
        if (d->completer->completionCount() == 1) {
 
616
            // single item on the completion list, select it automatically
 
617
            d->completer->setCurrentRow(0);
 
618
            slotCompletionActivated(d->completer->currentIndex());
 
619
            event->accept();
 
620
            if (d->completer->popup()) {
 
621
                d->completer->popup()->hide();
 
622
            }
 
623
            return;
 
624
        }
 
625
        //qDebug() << "currentRow:" << d->completer->currentRow();
 
626
        //qDebug() << "currentIndex:" << d->completer->currentIndex().isValid();
 
627
        //qDebug() << "currentCompletion:" << d->completer->currentCompletion();
 
628
        if (d->completer->popup() && d->completer->completionCount() > 1) {
 
629
            //qDebug() << "11111" << d->completer->completionPrefix()
 
630
            //          << d->completer->completionCount();
 
631
 
 
632
            // more than one item on completion list, find exact match, if found, accept
 
633
            for (int i = 0; i < d->completer->completionCount(); i++) {
 
634
                //qDebug() << d->completer->completionModel()->index(i, 0, QModelIndex()).data(Qt::EditRole).toString();
 
635
                if (d->completer->completionPrefix()
 
636
                    == d->completer->completionModel()->index(i, 0, QModelIndex()).data(Qt::EditRole).toString())
 
637
                {
 
638
                    d->completer->setCurrentRow(i);
 
639
                    slotCompletionActivated(d->completer->currentIndex());
 
640
                    event->accept();
 
641
                    d->completer->popup()->hide();
 
642
                    return;
 
643
                }
 
644
            }
 
645
            // exactly matching item not found
 
646
            bool selectedItem = !d->completer->popup()->selectionModel()->selectedIndexes().isEmpty();
 
647
            if (!selectedItem || !d->completer->popup()->isVisible()) {
 
648
                if (!d->completer->popup()->isVisible()) {
 
649
                    // there is no matching text, go back to completing
 
650
                    d->completer->complete();
 
651
                }
 
652
                // do not hide
 
653
                event->accept();
 
654
                return;
 
655
            }
 
656
        }
 
657
        // applying completion since there is item selected
 
658
        d->completer->popup()->hide();
 
659
        connectCompleter();
 
660
        QLineEdit::keyPressEvent(event); /* executes this:
 
661
                                            if (hasAcceptableInput() || fixup()) {
 
662
                                                emit returnPressed();
 
663
                                                emit editingFinished();
 
664
                                            } */
 
665
        if (inlineCompletionAccepted)
 
666
            event->accept();
 
667
        else
 
668
            event->ignore();
 
669
        return;
 
670
    }
 
671
 
 
672
    if (event == QKeySequence::MoveToNextChar) {
 
673
#if defined(Q_OS_WIN)
 
674
        if (hasSelectedText()
 
675
            && d->completer->completionMode() == KexiCompleter::InlineCompletion)
 
676
        {
 
677
            int selEnd = selectionEnd();
 
678
            if (selEnd >= 0) {
 
679
                setCursorPosition(selEnd);
 
680
            }
 
681
            event->accept();
 
682
            return;
 
683
        }
 
684
#endif
 
685
    }
 
686
    else if (event == QKeySequence::MoveToPreviousChar) {
 
687
#if defined(Q_OS_WIN)
 
688
        if (hasSelectedText()
 
689
            && d->completer->completionMode() == KexiCompleter::InlineCompletion)
 
690
        {
 
691
            int selStart = selectionStart();
 
692
            if (selStart >= 0) {
 
693
                setCursorPosition(selStart);
 
694
            }
 
695
            event->accept();
 
696
            return;
 
697
        }
 
698
#endif
 
699
    }
 
700
    else {
 
701
        if (event->modifiers() & Qt::ControlModifier) {
 
702
            switch (event->key()) {
 
703
            case Qt::Key_Up:
 
704
            case Qt::Key_Down:
 
705
                complete(event->key());
 
706
                return;
 
707
            default:;
 
708
            }
 
709
        } else { // ### check for *no* modifier
 
710
            switch (event->key()) {
 
711
            case Qt::Key_Backspace:
 
712
                if (!isReadOnly()) {
 
713
                    backspace();
 
714
                    complete(Qt::Key_Backspace);
 
715
                    return;
 
716
                }
 
717
                break;
 
718
            case Qt::Key_Delete:
 
719
                if (!isReadOnly()) {
 
720
                    QLineEdit::keyPressEvent(event);
 
721
                    complete(Qt::Key_Delete);
 
722
                    return;
 
723
                }
 
724
                break;
 
725
            default:;
 
726
            }
 
727
        }
 
728
    }
 
729
 
 
730
    if (!isReadOnly()) {
 
731
        QString t = event->text();
 
732
        if (!t.isEmpty() && t.at(0).isPrint()) {
 
733
            QLineEdit::keyPressEvent(event);
 
734
            complete(event->key());
 
735
            return;
 
736
        }
 
737
    }
 
738
 
 
739
    QLineEdit::keyPressEvent(event);
 
740
}
 
741
 
 
742
void KexiSearchLineEdit::changeEvent(QEvent *event)
 
743
{
 
744
    QLineEdit::changeEvent(event);
 
745
    if (event->type() == QEvent::StyleChange) {
 
746
        fixLeftMargin(this);
 
747
    }
 
748
}
 
749
 
 
750
// forked bits from QLineControl::advanceToEnabledItem()
 
751
// iterating forward(dir=1)/backward(dir=-1) from the
 
752
// current row based. dir=0 indicates a new completion prefix was set.
 
753
bool KexiSearchLineEdit::advanceToEnabledItem(int dir)
 
754
{
 
755
    int start = d->completer->currentRow();
 
756
    if (start == -1)
 
757
        return false;
 
758
    int i = start + dir;
 
759
    if (dir == 0)
 
760
        dir = 1;
 
761
    do {
 
762
        if (!d->completer->setCurrentRow(i)) {
 
763
            if (!d->completer->wrapAround())
 
764
                break;
 
765
            i = i > 0 ? 0 : d->completer->completionCount() - 1;
 
766
        } else {
 
767
            QModelIndex currentIndex = d->completer->currentIndex();
 
768
            if (d->completer->completionModel()->flags(currentIndex) & Qt::ItemIsEnabled)
 
769
                return true;
 
770
            i += dir;
 
771
        }
 
772
    } while (i != start);
 
773
 
 
774
    d->completer->setCurrentRow(start); // restore
 
775
    return false;
 
776
}
 
777
 
 
778
QString KexiSearchLineEdit::textBeforeSelection() const
 
779
{
 
780
    return hasSelectedText() ? text().left(selectionStart()) : QString();
 
781
}
 
782
 
 
783
QString KexiSearchLineEdit::textAfterSelection() const
 
784
{
 
785
    return hasSelectedText() ? text().mid(selectionEnd()) : QString();
 
786
}
 
787
 
 
788
int KexiSearchLineEdit::selectionEnd() const
 
789
{
 
790
    return hasSelectedText() ?
 
791
        (selectionStart() + selectedText().length()) : -1;
 
792
}
 
793
 
 
794
// forked bits from QLineControl::complete()
 
795
void KexiSearchLineEdit::complete(int key)
 
796
{
 
797
    if (isReadOnly() || echoMode() != QLineEdit::Normal)
 
798
        return;
 
799
 
 
800
    QString text = this->text();
 
801
    if (d->completer->completionMode() == KexiCompleter::InlineCompletion) {
 
802
        if (key == Qt::Key_Backspace)
 
803
            return;
 
804
        int n = 0;
 
805
        if (key == Qt::Key_Up || key == Qt::Key_Down) {
 
806
            if (textAfterSelection().length())
 
807
                return;
 
808
            QString prefix = hasSelectedText() ? textBeforeSelection() : text;
 
809
            if (text.compare(d->completer->currentCompletion(), d->completer->caseSensitivity()) != 0
 
810
                || prefix.compare(d->completer->completionPrefix(), d->completer->caseSensitivity()) != 0) {
 
811
                d->completer->setCompletionPrefix(prefix);
 
812
            } else {
 
813
                n = (key == Qt::Key_Up) ? -1 : +1;
 
814
            }
 
815
        } else {
 
816
            d->completer->setCompletionPrefix(text);
 
817
        }
 
818
        if (!advanceToEnabledItem(n))
 
819
            return;
 
820
    } else {
 
821
#ifndef QT_KEYPAD_NAVIGATION
 
822
        if (text.isEmpty()) {
 
823
            d->completer->popup()->hide();
 
824
            return;
 
825
        }
 
826
#endif
 
827
        d->completer->setCompletionPrefix(text);
 
828
    }
 
829
 
 
830
    d->popupTreeView->resizeColumnToContents(0);
 
831
    d->completer->complete();
 
832
}
 
833
 
 
834
bool KexiSearchLineEdit::highlightMatchingSubstrings() const
 
835
{
 
836
    return d->delegate->highlightMatchingSubstrings;
 
837
}
 
838
 
 
839
void KexiSearchLineEdit::setHighlightMatchingSubstrings(bool highlight)
 
840
{
 
841
    d->delegate->highlightMatchingSubstrings = highlight;
 
842
}
 
843
 
 
844
#include "KexiSearchLineEdit.moc"