File indexing completed on 2024-05-12 16:40:07

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