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"