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"