// Copyright (C) 2025 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only // Qt-Security score:significant reason:default #include "qquicksearchfield_p.h" #include "qquickcontrol_p_p.h" #include #include #include "qquickpopup_p_p.h" #include "qquickdeferredexecute_p_p.h" #include #include "qquickabstractbutton_p.h" #include "qquickabstractbutton_p_p.h" #include #include #if QT_CONFIG(quick_itemview) # include #endif QT_BEGIN_NAMESPACE /*! \qmltype SearchField \inherits Control //! \nativetype QQuickSearchField \inqmlmodule QtQuick.Controls \since 6.10 \ingroup qtquickcontrols-input \ingroup qtquickcontrols-focusscopes \brief A specialized input field designed to use for search functionality. SearchField is a specialized input field designed to use for search functionality. The control includes a text field, search and clear icons, and a popup that displays suggestions or search results. \image qtquickcontrols-searchfield.gif {Search field with search icon and clear button} \section1 SearchField Model Roles SearchField is able to visualize standard \l {qml-data-models}{data models} that provide the \c modelData role: \list \li models that have only one role \li models that do not have named roles (JavaScript array, integer) \endlist When using models that have multiple named roles, SearchField must be configured to use a specific \l {textRole}{text role} for its \l {text}{text} and \l delegate instances. \code ListModel { id : fruitModel ListElement { name: "Apple"; color: "green" } ListElement { name: "Cherry"; color: "red" } ListElement { name: "Banana"; color: "yellow" } ListElement { name: "Orange"; color: "orange" } ListElement { name: "WaterMelon"; color: "pink" } } SortFilterProxyModel { id: fruitFilter model: fruitModel sorters: [ RoleSorter { roleName: "name" } ] filters: [ FunctionFilter { component CustomData: QtObject { property string name } property var regExp: new RegExp(fruitSearch.text, "i") onRegExpChanged: invalidate() function filter(data: CustomData): bool { return regExp.test(data.name); } } ] } SearchField { id: fruitSearch suggestionModel: fruitFilter textRole: "name" anchors.horizontalCenter: parent.horizontalCenter } \endcode */ /*! \qmlsignal void QtQuick.Controls::SearchField::activated(int index) This signal is emitted when the item at \a index is activated by the user. An item is activated when it is selected while the popup is open, causing the popup to close (and \l currentIndex to change). The \l currentIndex property is set to \a index. \sa currentIndex */ /*! \qmlsignal void QtQuick.Controls::SearchField::highlighted(int index) This signal is emitted when the item at \a index in the popup list is highlighted by the user. The highlighted signal is only emitted when the popup is open and an item is highlighted, but not necessarily \l activated. \sa highlightedIndex */ /*! \qmlsignal void QtQuick.Controls::SearchField::accepted() This signal is emitted when the user confirms their input by pressing the Enter or Return key. This signal is typically used to trigger a search or action based on the final text input, and it indicates the user's intention to complete or submit the query. \sa searchTriggered() */ /*! \qmlsignal void QtQuick.Controls::SearchField::searchTriggered() This signal is emitted when a search action is initiated. It occurs in two cases: 1. When the Enter or Return key is pressed, it will be emitted together with accepted() signal 2. When the text is edited and if the \l live property is set to \c true, this signal will be emitted. This signal is ideal for initiating searches both on-demand and in real-time as the user types, depending on the desired interaction model. \sa accepted(), textEdited() */ /*! \qmlsignal void QtQuick.Controls::SearchField::textEdited() This signal is emitted every time the user modifies the text in the search field, typically with each keystroke. \sa searchTriggered() */ namespace { enum Activation { NoActivate, Activate }; enum Highlighting { NoHighlight, Highlight }; } class QQuickSearchFieldPrivate : public QQuickControlPrivate { public: Q_DECLARE_PUBLIC(QQuickSearchField) bool isPopupVisible() const; void showPopup(); void hidePopup(bool accept); static void hideOldPopup(QQuickPopup *popup); void popupVisibleChanged(); void popupDestroyed(); void itemClicked(); void itemHovered(); void createdItem(int index, QObject *object); void suggestionCountChanged(); void increaseCurrentIndex(); void decreaseCurrentIndex(); void setCurrentIndex(int index); void setCurrentItemAtIndex(int index, Activation activate); void updateHighlightedIndex(); void setHighlightedIndex(int index, Highlighting highlight); void createDelegateModel(); QString currentTextRole() const; void selectAll(); void updateText(); void updateDisplayText(); QString textAt(int index) const; bool isValidIndex(int index) const; void cancelPopup(); void executePopup(bool complete = false); bool handlePress(const QPointF &point, ulong timestamp) override; bool handleRelease(const QPointF &point, ulong timestamp) override; void startSearch(); void startClear(); void itemImplicitWidthChanged(QQuickItem *item) override; void itemImplicitHeightChanged(QQuickItem *item) override; void itemDestroyed(QQuickItem *item) override; static inline QString popupName() { return QStringLiteral("popup"); } QVariant suggestionModel; bool hasCurrentIndex = false; int highlightedIndex = -1; int currentIndex = -1; QString text; QString textRole; bool live = true; bool searchPressed = false; bool clearPressed = false; bool searchFlat = false; bool clearFlat = false; bool searchDown = false; bool clearDown = false; bool hasSearchDown = false; bool hasClearDown = false; bool ownModel = false; QQmlInstanceModel *delegateModel = nullptr; QQmlComponent *delegate = nullptr; QQuickIndicatorButton *searchIndicator = nullptr; QQuickIndicatorButton *clearIndicator = nullptr; QQuickDeferredPointer popup; }; bool QQuickSearchFieldPrivate::isPopupVisible() const { return popup && popup->isVisible(); } void QQuickSearchFieldPrivate::showPopup() { if (!popup) executePopup(true); if (popup && !popup->isVisible()) popup->open(); } void QQuickSearchFieldPrivate::hidePopup(bool accept) { Q_Q(QQuickSearchField); if (accept) { setCurrentItemAtIndex(highlightedIndex, NoActivate); // hiding the popup on user interaction should always emit activated, // even if the current index didn't change emit q->activated(highlightedIndex); } if (popup && popup->isVisible()) popup->close(); } void QQuickSearchFieldPrivate::hideOldPopup(QQuickPopup *popup) { if (!popup) return; qCDebug(lcItemManagement) << "hiding old popup" << popup; popup->setVisible(false); popup->setParentItem(nullptr); #if QT_CONFIG(accessibility) // Remove the item from the accessibility tree. QQuickAccessibleAttached *accessible = accessibleAttached(popup); if (accessible) accessible->setIgnored(true); #endif } void QQuickSearchFieldPrivate::popupVisibleChanged() { if (isPopupVisible()) QGuiApplication::inputMethod()->reset(); #if QT_CONFIG(quick_itemview) QQuickItemView *itemView = popup->findChild(); if (itemView) itemView->setHighlightRangeMode(QQuickItemView::NoHighlightRange); #endif updateHighlightedIndex(); #if QT_CONFIG(quick_itemview) if (itemView) itemView->positionViewAtIndex(highlightedIndex, QQuickItemView::Beginning); #endif } void QQuickSearchFieldPrivate::popupDestroyed() { Q_Q(QQuickSearchField); popup = nullptr; emit q->popupChanged(); } void QQuickSearchFieldPrivate::itemClicked() { Q_Q(QQuickSearchField); int index = delegateModel->indexOf(q->sender(), nullptr); if (index != -1) { setHighlightedIndex(index, Highlight); hidePopup(true); } } void QQuickSearchFieldPrivate::itemHovered() { Q_Q(QQuickSearchField); QQuickAbstractButton *button = qobject_cast(q->sender()); if (!button || !button->isHovered() || !button->isEnabled() || QQuickAbstractButtonPrivate::get(button)->touchId != -1) return; int index = delegateModel->indexOf(button, nullptr); if (index != -1) { setHighlightedIndex(index, Highlight); #if QT_CONFIG(quick_itemview) if (QQuickItemView *itemView = popup->findChild()) itemView->positionViewAtIndex(index, QQuickItemView::Contain); #endif } } void QQuickSearchFieldPrivate::createdItem(int index, QObject *object) { Q_UNUSED(index); Q_Q(QQuickSearchField); QQuickItem *item = qobject_cast(object); if (item && !item->parentItem()) { if (popup) item->setParentItem(popup->contentItem()); else item->setParentItem(q); QQuickItemPrivate::get(item)->setCulled(true); } QQuickAbstractButton *button = qobject_cast(object); if (button) { button->setFocusPolicy(Qt::NoFocus); connect(button, &QQuickAbstractButton::clicked, this, &QQuickSearchFieldPrivate::itemClicked); connect(button, &QQuickAbstractButton::hoveredChanged, this, &QQuickSearchFieldPrivate::itemHovered); } } void QQuickSearchFieldPrivate::suggestionCountChanged() { Q_Q(QQuickSearchField); if (q->suggestionCount() == 0) setCurrentItemAtIndex(-1, NoActivate); // If the suggestionModel has been updated and the current text matches an item in // the model, update currentIndex and highlightedIndex to the index of that item. if (!text.isEmpty()) { for (int idx = 0; idx < q->suggestionCount(); ++idx) { QString t = textAt(idx); if (t == text) { setCurrentItemAtIndex(idx, NoActivate); updateHighlightedIndex(); break; } } } emit q->suggestionCountChanged(); } void QQuickSearchFieldPrivate::increaseCurrentIndex() { Q_Q(QQuickSearchField); if (isPopupVisible()) { if (highlightedIndex < q->suggestionCount() - 1) setHighlightedIndex(highlightedIndex + 1, Highlight); } } void QQuickSearchFieldPrivate::decreaseCurrentIndex() { if (isPopupVisible()) { if (highlightedIndex > 0) setHighlightedIndex(highlightedIndex - 1, Highlight); } } void QQuickSearchFieldPrivate::setCurrentIndex(int index) { Q_Q(QQuickSearchField); if (currentIndex == index) return; currentIndex = index; emit q->currentIndexChanged(); } void QQuickSearchFieldPrivate::setCurrentItemAtIndex(int index, Activation activate) { Q_Q(QQuickSearchField); if (currentIndex == index) return; currentIndex = index; emit q->currentIndexChanged(); updateDisplayText(); if (activate) emit q->activated(index); } void QQuickSearchFieldPrivate::updateHighlightedIndex() { Q_Q(QQuickSearchField); int index = -1; if (isPopupVisible()) { if (currentIndex >= 0) index = currentIndex; else if (q->suggestionCount() > 0) index = 0; // auto-highlight first suggestion } setHighlightedIndex(index, NoHighlight); } void QQuickSearchFieldPrivate::setHighlightedIndex(int index, Highlighting highlight) { Q_Q(QQuickSearchField); if (highlightedIndex == index) return; highlightedIndex = index; emit q->highlightedIndexChanged(); if (highlight) emit q->highlighted(index); } void QQuickSearchFieldPrivate::createDelegateModel() { Q_Q(QQuickSearchField); bool ownedOldModel = ownModel; QQmlInstanceModel *oldModel = delegateModel; if (oldModel) { disconnect(delegateModel, &QQmlInstanceModel::countChanged, this, &QQuickSearchFieldPrivate::suggestionCountChanged); disconnect(delegateModel, &QQmlInstanceModel::createdItem, this, &QQuickSearchFieldPrivate::createdItem); } ownModel = false; delegateModel = suggestionModel.value(); if (!delegateModel && suggestionModel.isValid()) { QQmlDelegateModel *dataModel = new QQmlDelegateModel(qmlContext(q), q); dataModel->setModel(suggestionModel); dataModel->setDelegate(delegate); if (q->isComponentComplete()) dataModel->componentComplete(); ownModel = true; delegateModel = dataModel; } if (delegateModel) { connect(delegateModel, &QQmlInstanceModel::countChanged, this, &QQuickSearchFieldPrivate::suggestionCountChanged); connect(delegateModel, &QQmlInstanceModel::createdItem, this, &QQuickSearchFieldPrivate::createdItem); } emit q->delegateModelChanged(); if (ownedOldModel) delete oldModel; } QString QQuickSearchFieldPrivate::currentTextRole() const { return textRole.isEmpty() ? QStringLiteral("modelData") : textRole; } void QQuickSearchFieldPrivate::selectAll() { QQuickTextInput *input = qobject_cast(contentItem); if (!input) return; input->selectAll(); } void QQuickSearchFieldPrivate::updateText() { Q_Q(QQuickSearchField); QQuickTextInput *input = qobject_cast(contentItem); if (!input) return; const QString textInput = input->text(); if (text != textInput) { q->setText(textInput); emit q->textEdited(); setCurrentIndex(-1); updateHighlightedIndex(); if (live) emit q->searchTriggered(); } if (text.isEmpty()) { if (isPopupVisible()) hidePopup(false); } else { if (delegateModel && delegateModel->count() > 0) { if (!isPopupVisible()) showPopup(); } else { if (isPopupVisible()) hidePopup(false); } } } void QQuickSearchFieldPrivate::updateDisplayText() { Q_Q(QQuickSearchField); const QString currentText = textAt(currentIndex); if (text != currentText) q->setText(currentText); } QString QQuickSearchFieldPrivate::textAt(int index) const { if (!isValidIndex(index)) return QString(); return delegateModel->stringValue(index, currentTextRole()); } bool QQuickSearchFieldPrivate::isValidIndex(int index) const { return delegateModel && index >= 0 && index < delegateModel->count(); } void QQuickSearchFieldPrivate::cancelPopup() { Q_Q(QQuickSearchField); quickCancelDeferred(q, popupName()); } void QQuickSearchFieldPrivate::executePopup(bool complete) { Q_Q(QQuickSearchField); if (popup.wasExecuted()) return; if (!popup || complete) quickBeginDeferred(q, popupName(), popup); if (complete) quickCompleteDeferred(q, popupName(), popup); } bool QQuickSearchFieldPrivate::handlePress(const QPointF &point, ulong timestamp) { Q_Q(QQuickSearchField); QQuickControlPrivate::handlePress(point, timestamp); QQuickItem *si = searchIndicator->indicator(); QQuickItem *ci = clearIndicator->indicator(); const bool isSearch = si && si->isEnabled() && si->contains(q->mapToItem(si, point)); const bool isClear = ci && ci->isEnabled() && ci->contains(q->mapToItem(ci, point)); if (isSearch) { searchIndicator->setPressed(true); startSearch(); } else if (isClear) { clearIndicator->setPressed(true); startClear(); } return true; } bool QQuickSearchFieldPrivate::handleRelease(const QPointF &point, ulong timestamp) { QQuickControlPrivate::handleRelease(point, timestamp); if (searchIndicator->isPressed()) searchIndicator->setPressed(false); else if (clearIndicator->isPressed()) clearIndicator->setPressed(false); return true; } void QQuickSearchFieldPrivate::startSearch() { Q_Q(QQuickSearchField); QQuickTextInput *input = qobject_cast(contentItem); if (!input) return; input->forceActiveFocus(); emit q->searchButtonPressed(); } void QQuickSearchFieldPrivate::startClear() { Q_Q(QQuickSearchField); if (text.isEmpty()) return; // if text is not null then clear text, also reset highlightedIndex and currentIndex if (!text.isEmpty()) { setCurrentIndex(-1); updateHighlightedIndex(); q->setText(QString()); if (isPopupVisible()) hidePopup(false); emit q->clearButtonPressed(); } } void QQuickSearchFieldPrivate::itemImplicitWidthChanged(QQuickItem *item) { QQuickControlPrivate::itemImplicitWidthChanged(item); if (item == searchIndicator->indicator()) emit searchIndicator->implicitIndicatorWidthChanged(); if (item == clearIndicator->indicator()) emit clearIndicator->implicitIndicatorWidthChanged(); } void QQuickSearchFieldPrivate::itemImplicitHeightChanged(QQuickItem *item) { QQuickControlPrivate::itemImplicitHeightChanged(item); if (item == searchIndicator->indicator()) emit searchIndicator->implicitIndicatorHeightChanged(); if (item == clearIndicator->indicator()) emit clearIndicator->implicitIndicatorHeightChanged(); } void QQuickSearchFieldPrivate::itemDestroyed(QQuickItem *item) { QQuickControlPrivate::itemDestroyed(item); if (item == searchIndicator->indicator()) searchIndicator->setIndicator(nullptr); if (item == clearIndicator->indicator()) clearIndicator->setIndicator(nullptr); } QQuickSearchField::QQuickSearchField(QQuickItem *parent) : QQuickControl(*(new QQuickSearchFieldPrivate), parent) { Q_D(QQuickSearchField); d->searchIndicator = new QQuickIndicatorButton(this); d->clearIndicator = new QQuickIndicatorButton(this); setFocusPolicy(Qt::StrongFocus); setFlag(QQuickItem::ItemIsFocusScope); setAcceptedMouseButtons(Qt::LeftButton); #if QT_CONFIG(cursor) setCursor(Qt::ArrowCursor); #endif d->init(); } QQuickSearchField::~QQuickSearchField() { Q_D(QQuickSearchField); d->removeImplicitSizeListener(d->searchIndicator->indicator()); d->removeImplicitSizeListener(d->clearIndicator->indicator()); if (d->popup) { QObjectPrivate::disconnect(d->popup.data(), &QQuickPopup::visibleChanged, d, &QQuickSearchFieldPrivate::popupVisibleChanged); d->hideOldPopup(d->popup); d->popup = nullptr; } } /*! \qmlproperty model QtQuick.Controls::SearchField::suggestionModel This property holds the data model used to display search suggestions in the popup menu. \code SearchField { textRole: "age" suggestionModel: ListModel { ListElement { name: "Karen"; age: "66" } ListElement { name: "Jim"; age: "32" } ListElement { name: "Pamela"; age: "28" } } } \endcode \sa textRole */ QVariant QQuickSearchField::suggestionModel() const { Q_D(const QQuickSearchField); return d->suggestionModel; } void QQuickSearchField::setSuggestionModel(const QVariant &model) { Q_D(QQuickSearchField); QVariant suggestionModel = model; if (suggestionModel.userType() == qMetaTypeId()) suggestionModel = get(std::move(suggestionModel)).toVariant(); if (d->suggestionModel == suggestionModel) return; d->suggestionModel = suggestionModel; d->createDelegateModel(); emit suggestionCountChanged(); emit suggestionModelChanged(); } /*! \readonly \qmlproperty model QtQuick.Controls::SearchField::delegateModel This property holds the model that provides delegate instances for the search field. It is typically assigned to a \l ListView in the \l {Popup::}{contentItem} of the \l popup. */ QQmlInstanceModel *QQuickSearchField::delegateModel() const { Q_D(const QQuickSearchField); return d->delegateModel; } /*! \readonly \qmlproperty int QtQuick.Controls::SearchField::suggestionCount This property holds the number of suggestions to display from the suggestion model. */ int QQuickSearchField::suggestionCount() const { Q_D(const QQuickSearchField); return d->delegateModel ? d->delegateModel->count() : 0; } /*! \qmlproperty int QtQuick.Controls::SearchField::currentIndex This property holds the index of the currently selected suggestion in the popup list. The default value is \c -1 when count is \c 0, and \c 0 otherwise. \sa activated(), text, highlightedIndex */ int QQuickSearchField::currentIndex() const { Q_D(const QQuickSearchField); return d->currentIndex; } void QQuickSearchField::setCurrentIndex(int index) { Q_D(QQuickSearchField); d->hasCurrentIndex = true; d->setCurrentIndex(index); } /*! \readonly \qmlproperty int QtQuick.Controls::SearchField::highlightedIndex This property holds the index of the currently highlighted item in the popup list. When the highlighted item is activated, the popup closes, \l currentIndex is updated to match \c highlightedIndex, and this property is reset to \c -1, indicating that no item is currently highlighted. \sa highlighted(), currentIndex */ int QQuickSearchField::highlightedIndex() const { Q_D(const QQuickSearchField); return d->highlightedIndex; } /*! \qmlproperty string QtQuick.Controls::SearchField::text This property holds the current input text in the search field. Text is bound to the user input, triggering suggestion updates or search logic. \sa searchTriggered(), textEdited() */ QString QQuickSearchField::text() const { Q_D(const QQuickSearchField); return d->text; } void QQuickSearchField::setText(const QString &text) { Q_D(QQuickSearchField); if (d->text == text) return; d->text = text; emit textChanged(); } /*! \qmlproperty string QtQuick.Controls::SearchField::textRole This property holds the model role used to display items in the suggestion model shown in the popup list. When the model has multiple roles, \c textRole can be set to determine which role should be displayed. */ QString QQuickSearchField::textRole() const { Q_D(const QQuickSearchField); return d->textRole; } void QQuickSearchField::setTextRole(const QString &textRole) { Q_D(QQuickSearchField); if (d->textRole == textRole) return; d->textRole = textRole; } /*! \qmlproperty bool QtQuick.Controls::SearchField::live This property holds a boolean value that determines whether the search is triggered on every text edit. When set to \c true, the \l searchTriggered() signal is emitted on each text change, allowing you to respond to every keystroke. When set to \c false, the \l searchTriggered() is only emitted when the user presses the Enter or Return key. \sa searchTriggered() */ bool QQuickSearchField::isLive() const { Q_D(const QQuickSearchField); return d->live; } void QQuickSearchField::setLive(const bool live) { Q_D(QQuickSearchField); if (d->live == live) return; d->live = live; } /*! \qmlproperty real QtQuick.Controls::SearchField::searchIndicator \readonly This property holds the search indicator. */ QQuickIndicatorButton *QQuickSearchField::searchIndicator() const { Q_D(const QQuickSearchField); return d->searchIndicator; } /*! \qmlproperty real QtQuick.Controls::SearchField::clearIndicator \readonly This property holds the clear indicator. */ QQuickIndicatorButton *QQuickSearchField::clearIndicator() const { Q_D(const QQuickSearchField); return d->clearIndicator; } /*! \qmlproperty Popup QtQuick.Controls::SearchField::popup This property holds the popup. The popup can be opened or closed manually, if necessary: \code onSpecialEvent: searchField.popup.close() \endcode */ QQuickPopup *QQuickSearchField::popup() const { QQuickSearchFieldPrivate *d = const_cast(d_func()); if (!d->popup) d->executePopup(isComponentComplete()); return d->popup; } void QQuickSearchField::setPopup(QQuickPopup *popup) { Q_D(QQuickSearchField); if (d->popup == popup) return; if (!d->popup.isExecuting()) d->cancelPopup(); if (d->popup) { QObjectPrivate::disconnect(d->popup.data(), &QQuickPopup::destroyed, d, &QQuickSearchFieldPrivate::popupDestroyed); QObjectPrivate::disconnect(d->popup.data(), &QQuickPopup::visibleChanged, d, &QQuickSearchFieldPrivate::popupVisibleChanged); QQuickSearchFieldPrivate::hideOldPopup(d->popup); } if (popup) { QQuickPopupPrivate::get(popup)->allowVerticalFlip = true; popup->setClosePolicy(QQuickPopup::CloseOnEscape | QQuickPopup::CloseOnPressOutsideParent); QObjectPrivate::connect(popup, &QQuickPopup::visibleChanged, d, &QQuickSearchFieldPrivate::popupVisibleChanged); // QQuickPopup does not derive from QQuickItemChangeListener, so we cannot use // QQuickItemChangeListener::itemDestroyed so we have to use QObject::destroyed QObjectPrivate::connect(popup, &QQuickPopup::destroyed, d, &QQuickSearchFieldPrivate::popupDestroyed); #if QT_CONFIG(quick_itemview) if (QQuickItemView *itemView = popup->findChild()) itemView->setHighlightRangeMode(QQuickItemView::NoHighlightRange); #endif } d->popup = popup; if (!d->popup.isExecuting()) emit popupChanged(); } /*! \qmlproperty Component QtQuick.Controls::SearchField::delegate This property holds a delegate that presents an item in the search field popup. It is recommended to use \l ItemDelegate (or any other \l AbstractButton derivatives) as the delegate. This ensures that the interaction works as expected, and the popup will automatically close when appropriate. When other types are used as the delegate, the popup must be closed manually. For example, if \l MouseArea is used: \code delegate: Rectangle { // ... MouseArea { // ... onClicked: searchField.popup.close() } } \endcode \include delegate-ownership.qdocinc {no-ownership-since-6.11} {SearchField} */ QQmlComponent *QQuickSearchField::delegate() const { Q_D(const QQuickSearchField); return d->delegate; } void QQuickSearchField::setDelegate(QQmlComponent *delegate) { Q_D(QQuickSearchField); if (d->delegate == delegate) return; d->delegate = delegate; QQmlDelegateModel *delegateModel = qobject_cast(d->delegateModel); if (delegateModel) delegateModel->setDelegate(d->delegate); emit delegateChanged(); } bool QQuickSearchField::eventFilter(QObject *object, QEvent *event) { Q_D(QQuickSearchField); switch (event->type()) { case QEvent::MouseButtonRelease: { QQuickTextInput *input = qobject_cast(d->contentItem); if (input->hasFocus()) { if (!d->text.isEmpty() && !d->isPopupVisible() && (d->delegateModel && d->delegateModel->count() > 0)) d->showPopup(); } break; } case QEvent::FocusOut: { const bool hasActiveFocus = d->popup && d->popup->hasActiveFocus(); const bool usingPopupWindows = d->popup ? QQuickPopupPrivate::get(d->popup)->usePopupWindow() : false; if (qGuiApp->focusObject() != this && !(hasActiveFocus && !usingPopupWindows)) d->hidePopup(false); break; } default: break; } return QQuickControl::eventFilter(object, event); } void QQuickSearchField::focusInEvent(QFocusEvent *event) { Q_D(QQuickSearchField); QQuickControl::focusInEvent(event); if ((event->reason() == Qt::TabFocusReason || event->reason() == Qt::BacktabFocusReason || event->reason() == Qt::ShortcutFocusReason) && d->contentItem) d->contentItem->forceActiveFocus(event->reason()); } void QQuickSearchField::focusOutEvent(QFocusEvent *event) { Q_D(QQuickSearchField); QQuickControl::focusOutEvent(event); const bool hasActiveFocus = d->popup && d->popup->hasActiveFocus(); const bool usingPopupWindows = d->popup && QQuickPopupPrivate::get(d->popup)->usePopupWindow(); if (qGuiApp->focusObject() != d->contentItem && !(hasActiveFocus && !usingPopupWindows)) d->hidePopup(false); } void QQuickSearchField::hoverEnterEvent(QHoverEvent *event) { Q_D(QQuickSearchField); QQuickControl::hoverEnterEvent(event); QQuickItem *si = d->searchIndicator->indicator(); QQuickItem *ci = d->clearIndicator->indicator(); d->searchIndicator->setHovered(si && si->isEnabled() && si->contains(mapToItem(si, event->position()))); d->clearIndicator->setHovered(ci && ci->isEnabled() && ci->contains(mapToItem(ci, event->position()))); event->ignore(); } void QQuickSearchField::hoverMoveEvent(QHoverEvent *event) { Q_D(QQuickSearchField); QQuickControl::hoverMoveEvent(event); QQuickItem *si = d->searchIndicator->indicator(); QQuickItem *ci = d->clearIndicator->indicator(); d->searchIndicator->setHovered(si && si->isEnabled() && si->contains(mapToItem(si, event->position()))); d->clearIndicator->setHovered(ci && ci->isEnabled() && ci->contains(mapToItem(ci, event->position()))); event->ignore(); } void QQuickSearchField::hoverLeaveEvent(QHoverEvent *event) { Q_D(QQuickSearchField); QQuickControl::hoverLeaveEvent(event); d->searchIndicator->setHovered(false); d->clearIndicator->setHovered(false); event->ignore(); } void QQuickSearchField::keyPressEvent(QKeyEvent *event) { Q_D(QQuickSearchField); const auto key = event->key(); if (!d->suggestionModel.isNull() && !d->text.isEmpty()) { switch (key) { case Qt::Key_Escape: case Qt::Key_Back: if (d->isPopupVisible()) { d->hidePopup(false); event->accept(); } else { setText(QString()); } break; case Qt::Key_Return: case Qt::Key_Enter: if (d->isPopupVisible()) d->hidePopup(true); emit accepted(); emit searchTriggered(); event->accept(); break; case Qt::Key_Up: d->decreaseCurrentIndex(); event->accept(); break; case Qt::Key_Down: d->increaseCurrentIndex(); event->accept(); break; case Qt::Key_Home: if (d->isPopupVisible()) d->setHighlightedIndex(0, Highlight); event->accept(); break; case Qt::Key_End: if (d->isPopupVisible()) d->setHighlightedIndex(suggestionCount() - 1, Highlight); event->accept(); break; default: QQuickControl::keyPressEvent(event); break; } } } void QQuickSearchField::classBegin() { Q_D(QQuickSearchField); QQuickControl::classBegin(); QQmlContext *context = qmlContext(this); if (context) { QQmlEngine::setContextForObject(d->searchIndicator, context); QQmlEngine::setContextForObject(d->clearIndicator, context); } } void QQuickSearchField::componentComplete() { Q_D(QQuickSearchField); QQuickIndicatorButtonPrivate::get(d->searchIndicator)->executeIndicator(true); QQuickIndicatorButtonPrivate::get(d->clearIndicator)->executeIndicator(true); QQuickControl::componentComplete(); if (d->popup) d->executePopup(true); if (d->delegateModel && d->ownModel) static_cast(d->delegateModel)->componentComplete(); } void QQuickSearchField::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) { Q_D(QQuickSearchField); if (oldItem) { oldItem->removeEventFilter(this); if (QQuickTextInput *oldInput = qobject_cast(oldItem)) { QObjectPrivate::disconnect(oldInput, &QQuickTextInput::textChanged, d, &QQuickSearchFieldPrivate::updateText); } } if (newItem) { newItem->installEventFilter(this); if (QQuickTextInput *newInput = qobject_cast(newItem)) { QObjectPrivate::connect(newInput, &QQuickTextInput::textChanged, d, &QQuickSearchFieldPrivate::updateText); } #if QT_CONFIG(cursor) newItem->setCursor(Qt::IBeamCursor); #endif } } void QQuickSearchField::itemChange(ItemChange change, const ItemChangeData &data) { Q_D(QQuickSearchField); QQuickControl::itemChange(change, data); if (change == ItemVisibleHasChanged && !data.boolValue) { d->hidePopup(false); d->setCurrentItemAtIndex(-1, NoActivate); } } QT_END_NAMESPACE #include "moc_qquicksearchfield_p.cpp"