File indexing completed on 2024-05-19 12:54:55

0001 /* This file is part of the KDE project
0002    Copyright (C) 2011-2016 Jarosław Staniek <staniek@kde.org>
0003    Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
0004 
0005    This program is free software; you can redistribute it and/or
0006    modify it under the terms of the GNU Library General Public
0007    License as published by the Free Software Foundation; either
0008    version 2 of the License, or (at your option) any later version.
0009 
0010    This program is distributed in the hope that it will be useful,
0011    but WITHOUT ANY WARRANTY; without even the implied warranty of
0012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013    Library General Public License for more details.
0014 
0015    You should have received a copy of the GNU Library General Public License
0016    along with this program; see the file COPYING.  If not, write to
0017    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0018  * Boston, MA 02110-1301, USA.
0019 */
0020 
0021 #include "KexiSearchLineEdit.h"
0022 #include <KexiSearchableModel.h>
0023 
0024 #include <KLocalizedString>
0025 #include <KActionCollection>
0026 
0027 #include <kexiutils/completer/KexiCompleter.h>
0028 #include <kexiutils/KexiTester.h>
0029 #include <KexiMainWindowIface.h>
0030 
0031 #include <QDebug>
0032 #include <QShortcut>
0033 #include <QKeySequence>
0034 #include <QTreeView>
0035 #include <QAbstractProxyModel>
0036 #include <QInputMethodEvent>
0037 #include <QStyledItemDelegate>
0038 #include <QTextLayout>
0039 #include <QPainter>
0040 #include <QFontMetrics>
0041 
0042 class SearchableObject
0043 {
0044 public:
0045     KexiSearchableModel *model;
0046     int index;
0047 };
0048 
0049 class KexiSearchLineEditCompleterPopupModel : public QAbstractListModel
0050 {
0051     Q_OBJECT
0052 public:
0053     explicit KexiSearchLineEditCompleterPopupModel(QObject *parent = 0);
0054     ~KexiSearchLineEditCompleterPopupModel();
0055     virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
0056     virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
0057     virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
0058 
0059 public Q_SLOTS:
0060     //! Adds a new model or updates information (model items) about existing one
0061     void addSearchableModel(KexiSearchableModel *model);
0062 
0063     //! Removes existing model
0064     void removeSearchableModel(KexiSearchableModel *model);
0065 
0066 private:
0067     class Private;
0068     Private * const d;
0069 };
0070 
0071 class Q_DECL_HIDDEN KexiSearchLineEditCompleterPopupModel::Private
0072 {
0073 public:
0074     Private()
0075      : cachedCount(-1)
0076     {
0077     }
0078     ~Private() {
0079         qDeleteAll(searchableObjects);
0080     }
0081     void removeSearchableModel(KexiSearchableModel *model) {
0082         if (searchableModels.removeAll(model) == 0) {
0083             return;
0084         }
0085         QMutableMapIterator<int, SearchableObject *> it(searchableObjects);
0086         while (it.hasNext()) {
0087             it.next();
0088             if (it.value()->model == model) {
0089                 it.remove();
0090             }
0091         }
0092     }
0093     void updateCachedCount() {
0094         cachedCount = 0;
0095         foreach (KexiSearchableModel* searchableModel, searchableModels) {
0096             cachedCount += searchableModel->searchableObjectCount();
0097         }
0098     }
0099     int cachedCount;
0100     QList<KexiSearchableModel*> searchableModels;
0101     QMap<int, SearchableObject*> searchableObjects;
0102 };
0103 
0104 KexiSearchLineEditCompleterPopupModel::KexiSearchLineEditCompleterPopupModel(QObject *parent)
0105  : QAbstractListModel(parent), d(new Private)
0106 {
0107 }
0108 
0109 KexiSearchLineEditCompleterPopupModel::~KexiSearchLineEditCompleterPopupModel()
0110 {
0111     delete d;
0112 }
0113 
0114 int KexiSearchLineEditCompleterPopupModel::rowCount(const QModelIndex &parent) const
0115 {
0116     Q_UNUSED(parent);
0117     if (d->cachedCount < 0) {
0118         d->updateCachedCount();
0119     }
0120     return d->cachedCount;
0121 }
0122 
0123 QVariant KexiSearchLineEditCompleterPopupModel::data(const QModelIndex &index, int role) const
0124 {
0125     const int row = index.row();
0126     if (d->cachedCount <= row) {
0127         return QVariant();
0128     }
0129     SearchableObject *object = static_cast<SearchableObject*>(index.internalPointer());
0130     QModelIndex sourceIndex = object->model->sourceIndexForSearchableObject(object->index);
0131     return object->model->searchableData(sourceIndex, role);
0132 }
0133 
0134 QModelIndex KexiSearchLineEditCompleterPopupModel::index(int row, int column,
0135                                                          const QModelIndex &parent) const
0136 {
0137     //qDebug() << row;
0138     if (!hasIndex(row, column, parent)) {
0139         qDebug() << "!hasIndex";
0140         return QModelIndex();
0141     }
0142 
0143     int r = row;
0144     SearchableObject *sobject = d->searchableObjects.value(row);
0145     if (!sobject) {
0146         foreach (KexiSearchableModel* searchableModel, d->searchableModels) {
0147             const int count = searchableModel->searchableObjectCount();
0148             if (r < count) {
0149                 sobject = new SearchableObject;
0150                 sobject->model = searchableModel;
0151                 sobject->index = r;
0152                 d->searchableObjects.insert(row, sobject);
0153                 break;
0154             }
0155             else {
0156                 r -= count;
0157             }
0158         }
0159     }
0160     if (!sobject) {
0161         return QModelIndex();
0162     }
0163     return createIndex(row, column, sobject);
0164 }
0165 
0166 void KexiSearchLineEditCompleterPopupModel::addSearchableModel(KexiSearchableModel *model)
0167 {
0168     if (!model) {
0169         return;
0170     }
0171     beginResetModel();
0172     d->removeSearchableModel(model);
0173     d->searchableModels.append(model);
0174     connect(model->deleteNotifier(), &KexiSearchableModelDeleteNotifier::aboutToDelete, this,
0175             &KexiSearchLineEditCompleterPopupModel::removeSearchableModel, Qt::UniqueConnection);
0176     d->updateCachedCount();
0177     endResetModel();
0178 }
0179 
0180 void KexiSearchLineEditCompleterPopupModel::removeSearchableModel(KexiSearchableModel *model)
0181 {
0182     if (!model || !d->searchableModels.contains(model)) {
0183         return;
0184     }
0185     beginResetModel();
0186     d->removeSearchableModel(model);
0187     d->updateCachedCount();
0188     endResetModel();
0189 }
0190 
0191 // ----
0192 
0193 class KexiSearchLineEditCompleter : public KexiCompleter
0194 {
0195     Q_OBJECT
0196 public:
0197     explicit KexiSearchLineEditCompleter(QObject *parent = 0) : KexiCompleter(parent) {
0198         setCompletionRole(Qt::DisplayRole);
0199     }
0200 
0201     virtual QString pathFromIndex(const QModelIndex &index) const override {
0202         if (!index.isValid())
0203             return QString();
0204         SearchableObject *object = static_cast<SearchableObject*>(index.internalPointer());
0205         QModelIndex sourceIndex = object->model->sourceIndexForSearchableObject(object->index);
0206         return object->model->pathFromIndex(sourceIndex);
0207     }
0208 };
0209 
0210 // ----
0211 
0212 class KexiSearchLineEditPopupItemDelegate;
0213 
0214 class Q_DECL_HIDDEN KexiSearchLineEdit::Private
0215 {
0216 public:
0217     explicit Private(KexiSearchLineEdit *_q)
0218      : q(_q), clearShortcut(QKeySequence(Qt::Key_Escape), _q),
0219        recentlyHighlightedModel(0)
0220     {
0221         // make Escape key clear the search box
0222         QObject::connect(&clearShortcut, SIGNAL(activated()),
0223                          q, SLOT(slotClearShortcutActivated()));
0224     }
0225 
0226     void highlightSearchableObject(const QPair<QModelIndex, KexiSearchableModel*> &source)
0227     {
0228         source.second->highlightSearchableObject(source.first);
0229         recentlyHighlightedModel = source.second;
0230     }
0231 
0232     void removeHighlightingForSearchableObject()
0233     {
0234         if (recentlyHighlightedModel) {
0235             recentlyHighlightedModel->highlightSearchableObject(QModelIndex());
0236             recentlyHighlightedModel = 0;
0237         }
0238     }
0239 
0240     KexiSearchLineEditCompleter *completer;
0241     QTreeView *popupTreeView;
0242     KexiSearchLineEditCompleterPopupModel *model;
0243     KexiSearchLineEditPopupItemDelegate *delegate;
0244     QPointer<QWidget> previouslyFocusedWidget;
0245 
0246 private:
0247     KexiSearchLineEdit *q;
0248     QShortcut clearShortcut;
0249     KexiSearchableModel *recentlyHighlightedModel;
0250 };
0251 
0252 // ----
0253 
0254 static QSizeF viewItemTextLayout(QTextLayout &textLayout, int lineWidth)
0255 {
0256     qreal height = 0;
0257     qreal widthUsed = 0;
0258     textLayout.beginLayout();
0259     while (true) {
0260         QTextLine line = textLayout.createLine();
0261         if (!line.isValid())
0262             break;
0263         line.setLineWidth(lineWidth);
0264         line.setPosition(QPointF(0, height));
0265         height += line.height();
0266         widthUsed = qMax(widthUsed, line.naturalTextWidth());
0267     }
0268     textLayout.endLayout();
0269     return QSizeF(widthUsed, height);
0270 }
0271 
0272 class KexiSearchLineEditPopupItemDelegate : public QStyledItemDelegate
0273 {
0274     Q_OBJECT
0275 public:
0276     KexiSearchLineEditPopupItemDelegate(QObject *parent, KexiCompleter *completer)
0277      : QStyledItemDelegate(parent), highlightMatchingSubstrings(true), m_completer(completer)
0278     {
0279     }
0280 
0281     //! Implemented to improve width hint
0282     QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
0283     {
0284         QSize size(QStyledItemDelegate::sizeHint(option, index));
0285         QStyleOptionViewItem v4 = option;
0286         QStyledItemDelegate::initStyleOption(&v4, index);
0287         const QSize s = v4.widget->style()->sizeFromContents(QStyle::CT_ItemViewItem, &v4, size, v4.widget);
0288         size.setWidth(s.width());
0289         return size;
0290     }
0291 
0292     virtual void paint(QPainter *painter, const QStyleOptionViewItem &option,
0293                        const QModelIndex &index) const override
0294     {
0295         QStyledItemDelegate::paint(painter, option, index);
0296         QStyleOptionViewItem v4 = option;
0297         QStyledItemDelegate::initStyleOption(&v4, index);
0298         // like in QCommonStyle::paint():
0299         if (!v4.text.isEmpty()) {
0300             painter->save();
0301             painter->setClipRect(v4.rect);
0302             QPalette::ColorGroup cg = (v4.state & QStyle::State_Enabled)
0303                                     ? QPalette::Normal : QPalette::Disabled;
0304             if (cg == QPalette::Normal && !(v4.state & QStyle::State_Active)) {
0305                 cg = QPalette::Inactive;
0306             }
0307             if (v4.state & QStyle::State_Selected) {
0308                 painter->setPen(v4.palette.color(cg, QPalette::HighlightedText));
0309             }
0310             else {
0311                 painter->setPen(v4.palette.color(cg, QPalette::Text));
0312             }
0313             QRect textRect = v4.widget->style()->subElementRect(QStyle::SE_ItemViewItemText,
0314                                                                 &v4, v4.widget);
0315             viewItemDrawText(painter, &v4, textRect);
0316             painter->restore();
0317         }
0318     }
0319     bool highlightMatchingSubstrings;
0320 
0321 protected:
0322     // bits from qcommonstyle.cpp
0323     void viewItemDrawText(QPainter *p, const QStyleOptionViewItem *option, const QRect &rect) const
0324     {
0325         const QWidget *widget = option->widget;
0326         const int textMargin = widget->style()->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, widget) + 1;
0327 
0328         QRect textRect = rect.adjusted(textMargin, 0, -textMargin, 0); // remove width padding
0329         const bool wrapText = option->features & QStyleOptionViewItem::WrapText;
0330         QTextOption textOption;
0331         textOption.setWrapMode(wrapText ? QTextOption::WordWrap : QTextOption::ManualWrap);
0332         textOption.setTextDirection(option->direction);
0333         textOption.setAlignment(QStyle::visualAlignment(option->direction, option->displayAlignment));
0334         QTextLayout textLayout;
0335         textLayout.setTextOption(textOption);
0336         textLayout.setFont(option->font);
0337         QString text = option->text;
0338         textLayout.setText(text);
0339 
0340         if (highlightMatchingSubstrings) {
0341             QList<QTextLayout::FormatRange> formats;
0342             QString substring = m_completer->completionPrefix();
0343             QColor underLineColor(p->pen().color());
0344             underLineColor.setAlpha(128);
0345             QTextLayout::FormatRange formatRange;
0346             formatRange.format.setFontUnderline(true);
0347             formatRange.format.setUnderlineColor(underLineColor);
0348 
0349             for (int i = 0; i < text.length();) {
0350                 i = text.indexOf(substring, i, Qt::CaseInsensitive);
0351                 if (i == -1)
0352                     break;
0353                 formatRange.length = substring.length();
0354                 formatRange.start = i;
0355                 formats.append(formatRange);
0356                 i += formatRange.length;
0357             }
0358             textLayout.setAdditionalFormats(formats);
0359         }
0360         viewItemTextLayout(textLayout, textRect.width());
0361 
0362         const int lineCount = textLayout.lineCount();
0363         QPointF position = textRect.topLeft();
0364         for (int i = 0; i < lineCount; ++i) {
0365             const QTextLine line = textLayout.lineAt(i);
0366             const QPointF adjustPos(0, qreal(textRect.height() - line.rect().height()) / 2.0);
0367             line.draw(p, position + adjustPos);
0368             position.setY(position.y() + line.y() + line.ascent());
0369         }
0370     }
0371 
0372     virtual void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const override
0373     {
0374         QStyledItemDelegate::initStyleOption(option, index);
0375         QStyleOptionViewItem *v4 = qstyleoption_cast<QStyleOptionViewItem*>(option);
0376         if (v4) {
0377             v4->text.clear();
0378         }
0379     }
0380     KexiCompleter *m_completer;
0381 };
0382 
0383 // ----
0384 
0385 //! @internal Style-dependent fixes for the left margin, probably needed because of the limited
0386 //! width of the line edit - it's placed in tab bar's corner widget.
0387 static void fixLeftMargin(QLineEdit *lineEdit)
0388 {
0389     int add = 0;
0390     const QByteArray st(lineEdit->style()->objectName().toLatin1());
0391     if (st == "breeze" || st == "gtk+") {
0392         add = 4; // like QLineEditIconButton::IconMargin
0393     }
0394     else if (st == "fusion") {
0395         add = 2;
0396     }
0397     if (add != 0) {
0398         QMargins margins(lineEdit->textMargins());
0399         margins.setLeft(margins.left() + add);
0400         lineEdit->setTextMargins(margins);
0401     }
0402 }
0403 
0404 // ----
0405 
0406 KexiSearchLineEdit::KexiSearchLineEdit(QWidget *parent)
0407  : QLineEdit(parent), d(new Private(this))
0408 {
0409     d->completer = new KexiSearchLineEditCompleter(this);
0410     d->popupTreeView = new QTreeView;
0411     kexiTester() << KexiTestObject(d->popupTreeView, "globalSearch.treeView");
0412 
0413     d->completer->setPopup(d->popupTreeView);
0414     d->completer->setModel(d->model = new KexiSearchLineEditCompleterPopupModel(d->completer));
0415     d->completer->setCaseSensitivity(Qt::CaseInsensitive);
0416     d->completer->setSubstringCompletion(true);
0417     d->completer->setMaxVisibleItems(12);
0418     // Use unsorted model, sorting is handled in the source model itself.
0419     // Moreover, sorting KexiCompleter::CaseInsensitivelySortedModel breaks
0420     // filtering so only table names are displayed.
0421     d->completer->setModelSorting(KexiCompleter::UnsortedModel);
0422 
0423     d->popupTreeView->setHeaderHidden(true);
0424     d->popupTreeView->setRootIsDecorated(false);
0425     d->popupTreeView->setItemDelegate(
0426         d->delegate = new KexiSearchLineEditPopupItemDelegate(d->popupTreeView, d->completer));
0427 
0428     // forked initialization like in QLineEdit::setCompleter:
0429     d->completer->setWidget(this);
0430     if (hasFocus()) {
0431         connectCompleter();
0432     }
0433 
0434     setFocusPolicy(Qt::NoFocus); // We cannot focus set any policy here.
0435                                  // Qt::ClickFocus would make it impossible to find
0436                                  // previously focus widget in KexiSearchLineEdit::setFocus().
0437                                  // We need this information to focus back when pressing Escape key.
0438     setClearButtonEnabled(true);
0439     QAction *action_tools_locate = KexiMainWindowIface::global()->actionCollection()->action("tools_locate");
0440     setPlaceholderText(xi18nc("Tools->Locate textbox' placeholder text with shortcut", "Locate (%1)",
0441                        action_tools_locate->shortcut().toString(QKeySequence::NativeText)));
0442     fixLeftMargin(this);
0443     setMinimumWidth(fontMetrics().width("  " + placeholderText() + "    " + "    " + "      "));
0444 }
0445 
0446 KexiSearchLineEdit::~KexiSearchLineEdit()
0447 {
0448     delete d;
0449 }
0450 
0451 void KexiSearchLineEdit::connectCompleter()
0452 {
0453     connect(d->completer, SIGNAL(activated(QString)),
0454             this, SLOT(setText(QString)));
0455     connect(d->completer, SIGNAL(activated(QModelIndex)),
0456             this, SLOT(slotCompletionActivated(QModelIndex)));
0457     connect(d->completer, SIGNAL(highlighted(QString)),
0458             this, SLOT(slotCompletionHighlighted(QString)));
0459     connect(d->completer, SIGNAL(highlighted(QModelIndex)),
0460             this, SLOT(slotCompletionHighlighted(QModelIndex)));
0461 }
0462 
0463 void KexiSearchLineEdit::disconnectCompleter()
0464 {
0465     disconnect(d->completer, 0, this, 0);
0466 }
0467 
0468 void KexiSearchLineEdit::slotClearShortcutActivated()
0469 {
0470     //qDebug() << (QWidget*)d->previouslyFocusedWidget << text();
0471     d->removeHighlightingForSearchableObject();
0472     if (text().isEmpty() && d->previouslyFocusedWidget) {
0473         // after second Escape, go back to previously focused widget
0474         d->previouslyFocusedWidget->setFocus();
0475         d->previouslyFocusedWidget = 0;
0476     }
0477     else {
0478         clear();
0479     }
0480 }
0481 
0482 void KexiSearchLineEdit::addSearchableModel(KexiSearchableModel *model)
0483 {
0484     d->model->addSearchableModel(model);
0485 }
0486 
0487 void KexiSearchLineEdit::removeSearchableModel(KexiSearchableModel *model)
0488 {
0489     d->model->removeSearchableModel(model);
0490 }
0491 
0492 QPair<QModelIndex, KexiSearchableModel*> KexiSearchLineEdit::mapCompletionIndexToSource(const QModelIndex &index) const
0493 {
0494     QModelIndex realIndex
0495         = qobject_cast<QAbstractProxyModel*>(d->completer->completionModel())->mapToSource(index);
0496     if (!realIndex.isValid()) {
0497         return qMakePair(QModelIndex(), static_cast<KexiSearchableModel*>(0));
0498     }
0499     SearchableObject *object = static_cast<SearchableObject*>(realIndex.internalPointer());
0500     if (!object) {
0501         return qMakePair(QModelIndex(), static_cast<KexiSearchableModel*>(0));
0502     }
0503     return qMakePair(object->model->sourceIndexForSearchableObject(object->index), object->model);
0504 }
0505 
0506 void KexiSearchLineEdit::slotCompletionHighlighted(const QString &newText)
0507 {
0508     if (d->completer->completionMode() != KexiCompleter::InlineCompletion) {
0509         setText(newText);
0510     }
0511     else {
0512         int p = cursorPosition();
0513         QString t = text();
0514         setText(t.left(p) + newText.mid(p));
0515         end(false);
0516         cursorBackward(text().length() - p, true);
0517     }
0518 }
0519 
0520 void KexiSearchLineEdit::slotCompletionHighlighted(const QModelIndex &index)
0521 {
0522     QPair<QModelIndex, KexiSearchableModel*> source = mapCompletionIndexToSource(index);
0523     if (!source.first.isValid())
0524         return;
0525     //qDebug() << source.second->searchableData(source.first, Qt::EditRole);
0526     d->highlightSearchableObject(source);
0527 }
0528 
0529 void KexiSearchLineEdit::slotCompletionActivated(const QModelIndex &index)
0530 {
0531     QPair<QModelIndex, KexiSearchableModel*> source = mapCompletionIndexToSource(index);
0532     if (!source.first.isValid())
0533         return;
0534     //qDebug() << source.second->searchableData(source.first, Qt::EditRole);
0535 
0536     d->highlightSearchableObject(source);
0537     d->removeHighlightingForSearchableObject();
0538     if (source.second->activateSearchableObject(source.first)) {
0539         clear();
0540     }
0541 }
0542 
0543 // forked bits from QLineEdit::inputMethodEvent()
0544 void KexiSearchLineEdit::inputMethodEvent(QInputMethodEvent *e)
0545 {
0546     QLineEdit::inputMethodEvent(e);
0547     if (isReadOnly() || !e->isAccepted())
0548         return;
0549     if (!e->commitString().isEmpty()) {
0550         complete(Qt::Key_unknown);
0551     }
0552 }
0553 
0554 void KexiSearchLineEdit::setFocus()
0555 {
0556     //qDebug() << "d->previouslyFocusedWidget:" << (QWidget*)d->previouslyFocusedWidget
0557     //         << "window()->focusWidget():" << window()->focusWidget();
0558     if (!d->previouslyFocusedWidget && window()->focusWidget() != this) {
0559         d->previouslyFocusedWidget = window()->focusWidget();
0560     }
0561     QLineEdit::setFocus();
0562 }
0563 
0564 // forked bits from QLineEdit::focusInEvent()
0565 void KexiSearchLineEdit::focusInEvent(QFocusEvent *e)
0566 {
0567     //qDebug() << "d->previouslyFocusedWidget:" << (QWidget*)d->previouslyFocusedWidget
0568     //         << "window()->focusWidget():" << window()->focusWidget();
0569     if (!d->previouslyFocusedWidget && window()->focusWidget() != this) {
0570         d->previouslyFocusedWidget = window()->focusWidget();
0571     }
0572     QLineEdit::focusInEvent(e);
0573     d->completer->setWidget(this);
0574     connectCompleter();
0575     update();
0576 }
0577 
0578 // forked bits from QLineEdit::focusOutEvent()
0579 void KexiSearchLineEdit::focusOutEvent(QFocusEvent *e)
0580 {
0581     QLineEdit::focusOutEvent(e);
0582     disconnectCompleter();
0583     update();
0584     if (e->reason() == Qt::TabFocusReason || e->reason() == Qt::BacktabFocusReason) {
0585         // go back to previously focused widget
0586         if (d->previouslyFocusedWidget) {
0587             d->previouslyFocusedWidget->setFocus();
0588         }
0589         e->accept();
0590     }
0591     d->previouslyFocusedWidget = 0;
0592     d->removeHighlightingForSearchableObject();
0593 }
0594 
0595 // forked bits from QLineControl::processKeyEvent()
0596 void KexiSearchLineEdit::keyPressEvent(QKeyEvent *event)
0597 {
0598     bool inlineCompletionAccepted = false;
0599 
0600     //qDebug() << event->key() << (QWidget*)d->previouslyFocusedWidget;
0601 
0602     KexiCompleter::CompletionMode completionMode = d->completer->completionMode();
0603     if ((completionMode == KexiCompleter::PopupCompletion
0604             || completionMode == KexiCompleter::UnfilteredPopupCompletion)
0605         && d->completer->popup()
0606         && d->completer->popup()->isVisible()) {
0607         // The following keys are forwarded by the completer to the widget
0608         // Ignoring the events lets the completer provide suitable default behavior
0609         switch (event->key()) {
0610         case Qt::Key_Escape:
0611             event->ignore();
0612             return;
0613 #ifdef QT_KEYPAD_NAVIGATION
0614         case Qt::Key_Select:
0615             if (!QApplication::keypadNavigationEnabled())
0616                 break;
0617             d->completer->popup()->hide(); // just hide. will end up propagating to parent
0618 #endif
0619         default:
0620             break; // normal key processing
0621         }
0622     } else if (completionMode == KexiCompleter::InlineCompletion) {
0623         switch (event->key()) {
0624         case Qt::Key_Enter:
0625         case Qt::Key_Return:
0626         case Qt::Key_F4:
0627 #ifdef QT_KEYPAD_NAVIGATION
0628         case Qt::Key_Select:
0629             if (!QApplication::keypadNavigationEnabled())
0630                 break;
0631 #endif
0632             if (!d->completer->currentCompletion().isEmpty() && hasSelectedText()
0633                 && textAfterSelection().isEmpty())
0634             {
0635                 setText(d->completer->currentCompletion());
0636                 inlineCompletionAccepted = true;
0637             }
0638         default:
0639             break; // normal key processing
0640         }
0641     }
0642 
0643     if (d->completer->popup() && !d->completer->popup()->isVisible()
0644         && (event->key() == Qt::Key_F4 || event->key() == Qt::Key_Down))
0645     {
0646         // go back to completing when popup is closed and F4/Down pressed
0647         d->completer->complete();
0648     }
0649     else if (d->completer->popup() && d->completer->popup()->isVisible()
0650         && event->key() == Qt::Key_F4)
0651     {
0652         // hide popup if F4 pressed
0653         d->completer->popup()->hide();
0654     }
0655 
0656     if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) {
0657         if (d->completer->popup() && !d->completer->popup()->isVisible()) {
0658             d->completer->setCompletionPrefix(text());
0659         }
0660         if (d->completer->completionCount() == 1) {
0661             // single item on the completion list, select it automatically
0662             d->completer->setCurrentRow(0);
0663             slotCompletionActivated(d->completer->currentIndex());
0664             event->accept();
0665             if (d->completer->popup()) {
0666                 d->completer->popup()->hide();
0667             }
0668             return;
0669         }
0670         //qDebug() << "currentRow:" << d->completer->currentRow();
0671         //qDebug() << "currentIndex:" << d->completer->currentIndex().isValid();
0672         //qDebug() << "currentCompletion:" << d->completer->currentCompletion();
0673         if (d->completer->popup() && d->completer->completionCount() > 1) {
0674             //qDebug() << "11111" << d->completer->completionPrefix()
0675             //          << d->completer->completionCount();
0676 
0677             // more than one item on completion list, find exact match, if found, accept
0678             for (int i = 0; i < d->completer->completionCount(); i++) {
0679                 //qDebug() << d->completer->completionModel()->index(i, 0, QModelIndex()).data(Qt::EditRole).toString();
0680                 if (d->completer->completionPrefix()
0681                     == d->completer->completionModel()->index(i, 0, QModelIndex()).data(Qt::EditRole).toString())
0682                 {
0683                     d->completer->setCurrentRow(i);
0684                     slotCompletionActivated(d->completer->currentIndex());
0685                     event->accept();
0686                     d->completer->popup()->hide();
0687                     return;
0688                 }
0689             }
0690             // exactly matching item not found
0691             bool selectedItem = !d->completer->popup()->selectionModel()->selectedIndexes().isEmpty();
0692             if (!selectedItem || !d->completer->popup()->isVisible()) {
0693                 if (!d->completer->popup()->isVisible()) {
0694                     // there is no matching text, go back to completing
0695                     d->completer->complete();
0696                 }
0697                 // do not hide
0698                 event->accept();
0699                 return;
0700             }
0701         }
0702         // applying completion since there is item selected
0703         d->completer->popup()->hide();
0704         connectCompleter();
0705         QLineEdit::keyPressEvent(event); /* executes this:
0706                                             if (hasAcceptableInput() || fixup()) {
0707                                                 emit returnPressed();
0708                                                 emit editingFinished();
0709                                             } */
0710         if (inlineCompletionAccepted)
0711             event->accept();
0712         else
0713             event->ignore();
0714         return;
0715     }
0716 
0717     if (event == QKeySequence::MoveToNextChar) {
0718 #if defined(Q_OS_WIN)
0719         if (hasSelectedText()
0720             && d->completer->completionMode() == KexiCompleter::InlineCompletion)
0721         {
0722             int selEnd = selectionEnd();
0723             if (selEnd >= 0) {
0724                 setCursorPosition(selEnd);
0725             }
0726             event->accept();
0727             return;
0728         }
0729 #endif
0730     }
0731     else if (event == QKeySequence::MoveToPreviousChar) {
0732 #if defined(Q_OS_WIN)
0733         if (hasSelectedText()
0734             && d->completer->completionMode() == KexiCompleter::InlineCompletion)
0735         {
0736             int selStart = selectionStart();
0737             if (selStart >= 0) {
0738                 setCursorPosition(selStart);
0739             }
0740             event->accept();
0741             return;
0742         }
0743 #endif
0744     }
0745     else {
0746         if (event->modifiers() & Qt::ControlModifier) {
0747             switch (event->key()) {
0748             case Qt::Key_Up:
0749             case Qt::Key_Down:
0750                 complete(event->key());
0751                 return;
0752             default:;
0753             }
0754         } else { // ### check for *no* modifier
0755             switch (event->key()) {
0756             case Qt::Key_Backspace:
0757                 if (!isReadOnly()) {
0758                     backspace();
0759                     complete(Qt::Key_Backspace);
0760                     return;
0761                 }
0762                 break;
0763             case Qt::Key_Delete:
0764                 if (!isReadOnly()) {
0765                     QLineEdit::keyPressEvent(event);
0766                     complete(Qt::Key_Delete);
0767                     return;
0768                 }
0769                 break;
0770             default:;
0771             }
0772         }
0773     }
0774 
0775     if (!isReadOnly()) {
0776         QString t = event->text();
0777         if (!t.isEmpty() && t.at(0).isPrint()) {
0778             QLineEdit::keyPressEvent(event);
0779             complete(event->key());
0780             return;
0781         }
0782     }
0783 
0784     QLineEdit::keyPressEvent(event);
0785 }
0786 
0787 void KexiSearchLineEdit::changeEvent(QEvent *event)
0788 {
0789     QLineEdit::changeEvent(event);
0790     if (event->type() == QEvent::StyleChange) {
0791         fixLeftMargin(this);
0792     }
0793 }
0794 
0795 // forked bits from QLineControl::advanceToEnabledItem()
0796 // iterating forward(dir=1)/backward(dir=-1) from the
0797 // current row based. dir=0 indicates a new completion prefix was set.
0798 bool KexiSearchLineEdit::advanceToEnabledItem(int dir)
0799 {
0800     int start = d->completer->currentRow();
0801     if (start == -1)
0802         return false;
0803     int i = start + dir;
0804     if (dir == 0)
0805         dir = 1;
0806     do {
0807         if (!d->completer->setCurrentRow(i)) {
0808             if (!d->completer->wrapAround())
0809                 break;
0810             i = i > 0 ? 0 : d->completer->completionCount() - 1;
0811         } else {
0812             QModelIndex currentIndex = d->completer->currentIndex();
0813             if (d->completer->completionModel()->flags(currentIndex) & Qt::ItemIsEnabled)
0814                 return true;
0815             i += dir;
0816         }
0817     } while (i != start);
0818 
0819     d->completer->setCurrentRow(start); // restore
0820     return false;
0821 }
0822 
0823 QString KexiSearchLineEdit::textBeforeSelection() const
0824 {
0825     return hasSelectedText() ? text().left(selectionStart()) : QString();
0826 }
0827 
0828 QString KexiSearchLineEdit::textAfterSelection() const
0829 {
0830     return hasSelectedText() ? text().mid(selectionEnd()) : QString();
0831 }
0832 
0833 int KexiSearchLineEdit::selectionEnd() const
0834 {
0835     return hasSelectedText() ?
0836         (selectionStart() + selectedText().length()) : -1;
0837 }
0838 
0839 // forked bits from QLineControl::complete()
0840 void KexiSearchLineEdit::complete(int key)
0841 {
0842     if (isReadOnly() || echoMode() != QLineEdit::Normal)
0843         return;
0844 
0845     QString text = this->text();
0846     if (d->completer->completionMode() == KexiCompleter::InlineCompletion) {
0847         if (key == Qt::Key_Backspace)
0848             return;
0849         int n = 0;
0850         if (key == Qt::Key_Up || key == Qt::Key_Down) {
0851             if (textAfterSelection().length())
0852                 return;
0853             QString prefix = hasSelectedText() ? textBeforeSelection() : text;
0854             if (text.compare(d->completer->currentCompletion(), d->completer->caseSensitivity()) != 0
0855                 || prefix.compare(d->completer->completionPrefix(), d->completer->caseSensitivity()) != 0) {
0856                 d->completer->setCompletionPrefix(prefix);
0857             } else {
0858                 n = (key == Qt::Key_Up) ? -1 : +1;
0859             }
0860         } else {
0861             d->completer->setCompletionPrefix(text);
0862         }
0863         if (!advanceToEnabledItem(n))
0864             return;
0865     } else {
0866 #ifndef QT_KEYPAD_NAVIGATION
0867         if (text.isEmpty()) {
0868             d->completer->popup()->hide();
0869             return;
0870         }
0871 #endif
0872         d->completer->setCompletionPrefix(text);
0873     }
0874 
0875     d->popupTreeView->resizeColumnToContents(0);
0876     d->completer->complete();
0877 }
0878 
0879 bool KexiSearchLineEdit::highlightMatchingSubstrings() const
0880 {
0881     return d->delegate->highlightMatchingSubstrings;
0882 }
0883 
0884 void KexiSearchLineEdit::setHighlightMatchingSubstrings(bool highlight)
0885 {
0886     d->delegate->highlightMatchingSubstrings = highlight;
0887 }
0888 
0889 #include "KexiSearchLineEdit.moc"