Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquicksearchfield.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
7#include <private/qquickindicatorbutton_p.h>
8#include <QtQuickTemplates2/private/qquicktextfield_p.h>
11#include <private/qqmldelegatemodel_p.h>
14#include <QtQml/qqmlcomponent.h>
15#include <QtQuick/private/qquickaccessibleattached_p.h>
16#if QT_CONFIG(quick_itemview)
17# include <QtQuick/private/qquickitemview_p.h>
18#endif
19
21
22/*!
23 \qmltype SearchField
24 \inherits Control
25 //! \nativetype QQuickSearchField
26 \inqmlmodule QtQuick.Controls
27 \since 6.10
28 \ingroup qtquickcontrols-input
29 \ingroup qtquickcontrols-focusscopes
30 \brief A specialized input field designed to use for search functionality.
31
32 SearchField is a specialized input field designed to use for search functionality.
33 The control includes a text field, search and clear icons, and a popup that
34 displays suggestions or search results.
35
36 \image qtquickcontrols-searchfield.gif
37
38 \section1 SearchField Model Roles
39
40 SearchField is able to visualize standard \l {qml-data-models}{data models}
41 that provide the \c modelData role:
42 \list
43 \li models that have only one role
44 \li models that do not have named roles (JavaScript array, integer)
45 \endlist
46
47 When using models that have multiple named roles, SearchField must be configured
48 to use a specific \l {textRole}{text role} for its \l {text}{text}
49 and \l delegate instances.
50
51 \code
52 ListModel {
53 id : fruitModel
54 ListElement { name: "Apple"; color: "green" }
55 ListElement { name: "Cherry"; color: "red" }
56 ListElement { name: "Banana"; color: "yellow" }
57 ListElement { name: "Orange"; color: "orange" }
58 ListElement { name: "WaterMelon"; color: "pink" }
59 }
60
61 SortFilterProxyModel {
62 id: fruitFilter
63 model: fruitModel
64 sorters: [
65 RoleSorter {
66 roleName: "name"
67 }
68 ]
69 filters: [
70 FunctionFilter {
71 component CustomData: QtObject { property string name }
72 property var regExp: new RegExp(fruitSearch.text, "i")
73 onRegExpChanged: invalidate()
74 function filter(data: CustomData): bool {
75 return regExp.test(data.name);
76 }
77 }
78 ]
79 }
80
81 SearchField {
82 id: fruitSearch
83 suggestionModel: fruitFilter
84 textRole: "name"
85 anchors.horizontalCenter: parent.horizontalCenter
86 }
87 \endcode
88 */
89
90/*!
91 \qmlsignal void QtQuick.Controls::SearchField::activated(int index)
92
93 This signal is emitted when the item at \a index is activated by the user.
94
95 An item is activated when it is selected while the popup is open,
96 causing the popup to close (and \l currentIndex to change).
97 The \l currentIndex property is set to \a index.
98
99 \sa currentIndex
100*/
101
102
103/*!
104 \qmlsignal void QtQuick.Controls::SearchField::highlighted(int index)
105
106 This signal is emitted when the item at \a index in the popup list is highlighted by the user.
107
108 The highlighted signal is only emitted when the popup is open and an item
109 is highlighted, but not necessarily \l activated.
110
111 \sa highlightedIndex
112*/
113
114/*!
115 \qmlsignal void QtQuick.Controls::SearchField::accepted()
116
117 This signal is emitted when the user confirms their input by pressing
118 the Enter or Return key.
119
120 This signal is typically used to trigger a search or action based on
121 the final text input, and it indicates the user's intention to complete
122 or submit the query.
123
124 \sa searchTriggered()
125 */
126
127/*!
128 \qmlsignal void QtQuick.Controls::SearchField::searchTriggered()
129
130 This signal is emitted when a search action is initiated.
131
132 It occurs in two cases:
133 1. When the Enter or Return key is pressed, it will be emitted together
134 with accepted() signal
135 2. When the text is edited and if the \l live property is set to \c true,
136 this signal will be emitted.
137
138 This signal is ideal for initiating searches both on-demand and in real-time as
139 the user types, depending on the desired interaction model.
140
141 \sa accepted(), textEdited()
142 */
143
144/*!
145 \qmlsignal void QtQuick.Controls::SearchField::textEdited()
146
147 This signal is emitted every time the user modifies the text in the
148 search field, typically with each keystroke.
149
150 \sa searchTriggered()
151 */
152
153namespace {
156}
157
159{
160public:
161 Q_DECLARE_PUBLIC(QQuickSearchField)
162
163 bool isPopupVisible() const;
164 void showPopup();
165 void hidePopup(bool accept);
166 static void hideOldPopup(QQuickPopup *popup);
169
172
173 void createdItem(int index, QObject *object);
175
178 void setCurrentIndex(int index);
179 void setCurrentItemAtIndex(int index, Activation activate);
181 void setHighlightedIndex(int index, Highlighting highlight);
182
184
185 QString currentTextRole() const;
186 void selectAll();
189 QString textAt(int index) const;
190 bool isValidIndex(int index) const;
191
193 void executePopup(bool complete = false);
194
195 bool handlePress(const QPointF &point, ulong timestamp) override;
196 bool handleRelease(const QPointF &point, ulong timestamp) override;
197
200
201 void itemImplicitWidthChanged(QQuickItem *item) override;
202 void itemImplicitHeightChanged(QQuickItem *item) override;
203 void itemDestroyed(QQuickItem *item) override;
204
205 static inline QString popupName() { return QStringLiteral("popup"); }
206
208 bool hasCurrentIndex = false;
210 int currentIndex = -1;
211 QString text;
212 QString textRole;
213 bool live = true;
214 bool searchPressed = false;
215 bool clearPressed = false;
216 bool searchFlat = false;
217 bool clearFlat = false;
218 bool searchDown = false;
219 bool clearDown = false;
220 bool hasSearchDown = false;
221 bool hasClearDown = false;
222 bool ownModel = false;
223 QQmlInstanceModel *delegateModel = nullptr;
224 QQmlComponent *delegate = nullptr;
225 QQuickIndicatorButton *searchIndicator = nullptr;
226 QQuickIndicatorButton *clearIndicator = nullptr;
228};
229
230bool QQuickSearchFieldPrivate::isPopupVisible() const
231{
232 return popup && popup->isVisible();
233}
234
236{
237 if (!popup)
238 executePopup(true);
239
240 if (popup && !popup->isVisible())
241 popup->open();
242}
243
245{
246 Q_Q(QQuickSearchField);
247 if (accept) {
249 // hiding the popup on user interaction should always emit activated,
250 // even if the current index didn't change
251 emit q->activated(highlightedIndex);
252 }
253 if (popup && popup->isVisible())
254 popup->close();
255}
256
257void QQuickSearchFieldPrivate::hideOldPopup(QQuickPopup *popup)
258{
259 if (!popup)
260 return;
261
262 qCDebug(lcItemManagement) << "hiding old popup" << popup;
263
264 popup->setVisible(false);
265 popup->setParentItem(nullptr);
266#if QT_CONFIG(accessibility)
267 // Remove the item from the accessibility tree.
268 QQuickAccessibleAttached *accessible = accessibleAttached(popup);
269 if (accessible)
270 accessible->setIgnored(true);
271#endif
272}
273
275{
276 if (isPopupVisible())
277 QGuiApplication::inputMethod()->reset();
278
279#if QT_CONFIG(quick_itemview)
280 QQuickItemView *itemView = popup->findChild<QQuickItemView *>();
281 if (itemView)
282 itemView->setHighlightRangeMode(QQuickItemView::NoHighlightRange);
283#endif
284
286
287#if QT_CONFIG(quick_itemview)
288 if (itemView)
289 itemView->positionViewAtIndex(highlightedIndex, QQuickItemView::Beginning);
290#endif
291}
292
294{
295 Q_Q(QQuickSearchField);
296 popup = nullptr;
297 emit q->popupChanged();
298}
299
301{
302 Q_Q(QQuickSearchField);
303 int index = delegateModel->indexOf(q->sender(), nullptr);
304 if (index != -1) {
306 hidePopup(true);
307 }
308}
309
311{
312 Q_Q(QQuickSearchField);
313
314 QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(q->sender());
315 if (!button || !button->isHovered() || !button->isEnabled()
316 || QQuickAbstractButtonPrivate::get(button)->touchId != -1)
317 return;
318
319 int index = delegateModel->indexOf(button, nullptr);
320 if (index != -1) {
322
323#if QT_CONFIG(quick_itemview)
324 if (QQuickItemView *itemView = popup->findChild<QQuickItemView *>())
325 itemView->positionViewAtIndex(index, QQuickItemView::Contain);
326#endif
327 }
328}
329
330void QQuickSearchFieldPrivate::createdItem(int index, QObject *object)
331{
332 Q_UNUSED(index);
333 Q_Q(QQuickSearchField);
334 QQuickItem *item = qobject_cast<QQuickItem *>(object);
335 if (item && !item->parentItem()) {
336 if (popup)
337 item->setParentItem(popup->contentItem());
338 else
339 item->setParentItem(q);
340 QQuickItemPrivate::get(item)->setCulled(true);
341 }
342
343 QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(object);
344 if (button) {
345 button->setFocusPolicy(Qt::NoFocus);
346 connect(button, &QQuickAbstractButton::clicked, this,
347 &QQuickSearchFieldPrivate::itemClicked);
348 connect(button, &QQuickAbstractButton::hoveredChanged, this,
349 &QQuickSearchFieldPrivate::itemHovered);
350 }
351}
352
354{
355 Q_Q(QQuickSearchField);
356 if (q->suggestionCount() == 0)
358 // If the suggestionModel has been updated and the current text matches an item in
359 // the model, update currentIndex and highlightedIndex to the index of that item.
360 if (!text.isEmpty()) {
361 for (int idx = 0; idx < q->suggestionCount(); ++idx) {
362 QString t = textAt(idx);
363 if (t == text) {
366 break;
367 }
368 }
369 }
370
371 emit q->suggestionCountChanged();
372}
373
375{
376 Q_Q(QQuickSearchField);
377 if (isPopupVisible()) {
378 if (highlightedIndex < q->suggestionCount() - 1)
380 }
381}
382
384{
385 if (isPopupVisible()) {
386 if (highlightedIndex > 0)
388 }
389}
390
392{
393 Q_Q(QQuickSearchField);
394 if (currentIndex == index)
395 return;
396
397 currentIndex = index;
398 emit q->currentIndexChanged();
399}
400
402{
403 Q_Q(QQuickSearchField);
404 if (currentIndex == index)
405 return;
406
407 currentIndex = index;
408 emit q->currentIndexChanged();
409
411
412 if (activate)
413 emit q->activated(index);
414}
415
417{
418 Q_Q(QQuickSearchField);
419 int index = -1;
420
421 if (isPopupVisible()) {
422 if (currentIndex >= 0)
423 index = currentIndex;
424 else if (q->suggestionCount() > 0)
425 index = 0; // auto-highlight first suggestion
426 }
427
429}
430
432{
433 Q_Q(QQuickSearchField);
434 if (highlightedIndex == index)
435 return;
436
437 highlightedIndex = index;
438 emit q->highlightedIndexChanged();
439
440 if (highlight)
441 emit q->highlighted(index);
442}
443
445{
446 Q_Q(QQuickSearchField);
447 bool ownedOldModel = ownModel;
448 QQmlInstanceModel *oldModel = delegateModel;
449
450 if (oldModel) {
451 disconnect(delegateModel, &QQmlInstanceModel::countChanged, this,
452 &QQuickSearchFieldPrivate::suggestionCountChanged);
453 disconnect(delegateModel, &QQmlInstanceModel::createdItem, this,
454 &QQuickSearchFieldPrivate::createdItem);
455 }
456
457 ownModel = false;
458 delegateModel = suggestionModel.value<QQmlInstanceModel *>();
459
460 if (!delegateModel && suggestionModel.isValid()) {
461 QQmlDelegateModel *dataModel = new QQmlDelegateModel(qmlContext(q), q);
462 dataModel->setModel(suggestionModel);
463 dataModel->setDelegate(delegate);
464 if (q->isComponentComplete())
465 dataModel->componentComplete();
466
467 ownModel = true;
468 delegateModel = dataModel;
469 }
470
471 if (delegateModel) {
472 connect(delegateModel, &QQmlInstanceModel::countChanged, this,
473 &QQuickSearchFieldPrivate::suggestionCountChanged);
474 connect(delegateModel, &QQmlInstanceModel::createdItem, this,
475 &QQuickSearchFieldPrivate::createdItem);
476 }
477
478 emit q->delegateModelChanged();
479
480 if (ownedOldModel)
481 delete oldModel;
482}
483
485{
486 return textRole.isEmpty() ? QStringLiteral("modelData") : textRole;
487}
488
490{
491 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(contentItem);
492 if (!input)
493 return;
494 input->selectAll();
495}
496
498{
499 Q_Q(QQuickSearchField);
500 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(contentItem);
501 if (!input)
502 return;
503
504 const QString textInput = input->text();
505
506 if (text != textInput) {
507 q->setText(textInput);
508 emit q->textEdited();
509
512
513 if (live)
514 emit q->searchTriggered();
515 }
516
517 if (text.isEmpty()) {
518 if (isPopupVisible())
519 hidePopup(false);
520 } else {
521 if (delegateModel && delegateModel->count() > 0) {
522 if (!isPopupVisible())
524 } else {
525 if (isPopupVisible())
526 hidePopup(false);
527 }
528 }
529}
530
532{
533 Q_Q(QQuickSearchField);
534 const QString currentText = textAt(currentIndex);
535
536 if (text != currentText)
537 q->setText(currentText);
538}
539
540QString QQuickSearchFieldPrivate::textAt(int index) const
541{
542 if (!isValidIndex(index))
543 return QString();
544
545 return delegateModel->stringValue(index, currentTextRole());
546}
547
549{
550 return delegateModel && index >= 0 && index < delegateModel->count();
551}
552
554{
555 Q_Q(QQuickSearchField);
556 quickCancelDeferred(q, popupName());
557}
558
560{
561 Q_Q(QQuickSearchField);
562 if (popup.wasExecuted())
563 return;
564
565 if (!popup || complete)
566 quickBeginDeferred(q, popupName(), popup);
567 if (complete)
568 quickCompleteDeferred(q, popupName(), popup);
569}
570
571bool QQuickSearchFieldPrivate::handlePress(const QPointF &point, ulong timestamp)
572{
573 Q_Q(QQuickSearchField);
574 QQuickControlPrivate::handlePress(point, timestamp);
575
576 QQuickItem *si = searchIndicator->indicator();
577 QQuickItem *ci = clearIndicator->indicator();
578 const bool isSearch = si && si->isEnabled() && si->contains(q->mapToItem(si, point));
579 const bool isClear = ci && ci->isEnabled() && ci->contains(q->mapToItem(ci, point));
580
581 if (isSearch) {
582 searchIndicator->setPressed(true);
584 } else if (isClear) {
585 clearIndicator->setPressed(true);
587 }
588
589 return true;
590}
591
592bool QQuickSearchFieldPrivate::handleRelease(const QPointF &point, ulong timestamp)
593{
594 QQuickControlPrivate::handleRelease(point, timestamp);
595 if (searchIndicator->isPressed())
596 searchIndicator->setPressed(false);
597 else if (clearIndicator->isPressed())
598 clearIndicator->setPressed(false);
599 return true;
600}
601
603{
604 Q_Q(QQuickSearchField);
605
606 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(contentItem);
607 if (!input)
608 return;
609
610 input->forceActiveFocus();
611 emit q->searchButtonPressed();
612}
613
615{
616 Q_Q(QQuickSearchField);
617
618 if (text.isEmpty())
619 return;
620
621 // if text is not null then clear text, also reset highlightedIndex and currentIndex
622 if (!text.isEmpty()) {
625 q->setText(QString());
626
627 if (isPopupVisible())
628 hidePopup(false);
629
630 emit q->clearButtonPressed();
631 }
632}
633
635{
636 QQuickControlPrivate::itemImplicitWidthChanged(item);
637 if (item == searchIndicator->indicator())
638 emit searchIndicator->implicitIndicatorWidthChanged();
639 if (item == clearIndicator->indicator())
640 emit clearIndicator->implicitIndicatorWidthChanged();
641}
642
644{
645 QQuickControlPrivate::itemImplicitHeightChanged(item);
646 if (item == searchIndicator->indicator())
647 emit searchIndicator->implicitIndicatorHeightChanged();
648 if (item == clearIndicator->indicator())
649 emit clearIndicator->implicitIndicatorHeightChanged();
650}
651
653{
654 QQuickControlPrivate::itemDestroyed(item);
655 if (item == searchIndicator->indicator())
656 searchIndicator->setIndicator(nullptr);
657 if (item == clearIndicator->indicator())
658 clearIndicator->setIndicator(nullptr);
659}
660
661QQuickSearchField::QQuickSearchField(QQuickItem *parent)
662 : QQuickControl(*(new QQuickSearchFieldPrivate), parent)
663{
664 Q_D(QQuickSearchField);
665 d->searchIndicator = new QQuickIndicatorButton(this);
666 d->clearIndicator = new QQuickIndicatorButton(this);
667
668 setFocusPolicy(Qt::StrongFocus);
669 setFlag(QQuickItem::ItemIsFocusScope);
670 setAcceptedMouseButtons(Qt::LeftButton);
671#if QT_CONFIG(cursor)
672 setCursor(Qt::ArrowCursor);
673#endif
674
675 d->init();
676}
677
678QQuickSearchField::~QQuickSearchField()
679{
680 Q_D(QQuickSearchField);
681 d->removeImplicitSizeListener(d->searchIndicator->indicator());
682 d->removeImplicitSizeListener(d->clearIndicator->indicator());
683
684 if (d->popup) {
685 QObjectPrivate::disconnect(d->popup.data(), &QQuickPopup::visibleChanged, d,
686 &QQuickSearchFieldPrivate::popupVisibleChanged);
687 d->hideOldPopup(d->popup);
688 d->popup = nullptr;
689 }
690}
691
692/*!
693 \qmlproperty model QtQuick.Controls::SearchField::suggestionModel
694
695 This property holds the data model used to display search suggestions in the popup menu.
696
697 \code
698 SearchField {
699 textRole: "age"
700 suggestionModel: ListModel {
701 ListElement { name: "Karen"; age: "66" }
702 ListElement { name: "Jim"; age: "32" }
703 ListElement { name: "Pamela"; age: "28" }
704 }
705 }
706 \endcode
707
708 \sa textRole
709 */
710
711QVariant QQuickSearchField::suggestionModel() const
712{
713 Q_D(const QQuickSearchField);
714 return d->suggestionModel;
715}
716
717void QQuickSearchField::setSuggestionModel(const QVariant &model)
718{
719 Q_D(QQuickSearchField);
720
721 QVariant suggestionModel = model;
722 if (suggestionModel.userType() == qMetaTypeId<QJSValue>())
723 suggestionModel = get<QJSValue>(std::move(suggestionModel)).toVariant();
724
725 if (d->suggestionModel == suggestionModel)
726 return;
727
728 d->suggestionModel = suggestionModel;
729 d->createDelegateModel();
730 emit suggestionCountChanged();
731 emit suggestionModelChanged();
732}
733
734/*!
735 \readonly
736 \qmlproperty model QtQuick.Controls::SearchField::delegateModel
737
738 This property holds the model that provides delegate instances for the search field.
739
740 It is typically assigned to a \l ListView in the \l {Popup::}{contentItem}
741 of the \l popup.
742
743 */
744QQmlInstanceModel *QQuickSearchField::delegateModel() const
745{
746 Q_D(const QQuickSearchField);
747 return d->delegateModel;
748}
749
750/*!
751 \readonly
752 \qmlproperty int QtQuick.Controls::SearchField::suggestionCount
753
754 This property holds the number of suggestions to display from the suggestion model.
755 */
756int QQuickSearchField::suggestionCount() const
757{
758 Q_D(const QQuickSearchField);
759 return d->delegateModel ? d->delegateModel->count() : 0;
760}
761
762/*!
763 \qmlproperty int QtQuick.Controls::SearchField::currentIndex
764
765 This property holds the index of the currently selected suggestion in the popup list.
766
767 The default value is \c -1 when count is \c 0, and \c 0 otherwise.
768
769 \sa activated(), text, highlightedIndex
770 */
771int QQuickSearchField::currentIndex() const
772{
773 Q_D(const QQuickSearchField);
774 return d->currentIndex;
775}
776
777void QQuickSearchField::setCurrentIndex(int index)
778{
779 Q_D(QQuickSearchField);
780 d->hasCurrentIndex = true;
781 d->setCurrentIndex(index);
782}
783
784/*!
785 \readonly
786 \qmlproperty int QtQuick.Controls::SearchField::highlightedIndex
787
788 This property holds the index of the currently highlighted item in
789 the popup list.
790
791 When the highlighted item is activated, the popup closes, \l currentIndex
792 is updated to match \c highlightedIndex, and this property is reset to
793 \c -1, indicating that no item is currently highlighted.
794
795 \sa highlighted(), currentIndex
796*/
797int QQuickSearchField::highlightedIndex() const
798{
799 Q_D(const QQuickSearchField);
800 return d->highlightedIndex;
801}
802
803/*!
804 \qmlproperty string QtQuick.Controls::SearchField::text
805
806 This property holds the current input text in the search field.
807
808 Text is bound to the user input, triggering suggestion updates or search logic.
809
810 \sa searchTriggered(), textEdited()
811 */
812QString QQuickSearchField::text() const
813{
814 Q_D(const QQuickSearchField);
815 return d->text;
816}
817
818void QQuickSearchField::setText(const QString &text)
819{
820 Q_D(QQuickSearchField);
821 if (d->text == text)
822 return;
823
824 d->text = text;
825 emit textChanged();
826}
827
828/*!
829 \qmlproperty string QtQuick.Controls::SearchField::textRole
830
831 This property holds the model role used to display items in the suggestion model
832 shown in the popup list.
833
834 When the model has multiple roles, \c textRole can be set to determine
835 which role should be displayed.
836 */
837QString QQuickSearchField::textRole() const
838{
839 Q_D(const QQuickSearchField);
840 return d->textRole;
841}
842
843void QQuickSearchField::setTextRole(const QString &textRole)
844{
845 Q_D(QQuickSearchField);
846 if (d->textRole == textRole)
847 return;
848
849 d->textRole = textRole;
850}
851
852/*!
853 \qmlproperty bool QtQuick.Controls::SearchField::live
854
855 This property holds a boolean value that determines whether the search is triggered
856 on every text edit.
857
858 When set to \c true, the \l searchTriggered() signal is emitted on each text change,
859 allowing you to respond to every keystroke.
860 When set to \c false, the \l searchTriggered() is only emitted when the user presses
861 the Enter or Return key.
862
863 \sa searchTriggered()
864 */
865
866bool QQuickSearchField::isLive() const
867{
868 Q_D(const QQuickSearchField);
869 return d->live;
870}
871
872void QQuickSearchField::setLive(const bool live)
873{
874 Q_D(QQuickSearchField);
875
876 if (d->live == live)
877 return;
878
879 d->live = live;
880}
881
882/*!
883 \qmlproperty real QtQuick.Controls::SearchField::searchIndicator
884 \readonly
885
886 This property holds the search indicator.
887 */
888QQuickIndicatorButton *QQuickSearchField::searchIndicator() const
889{
890 Q_D(const QQuickSearchField);
891 return d->searchIndicator;
892}
893
894/*!
895 \qmlproperty real QtQuick.Controls::SearchField::clearIndicator
896 \readonly
897
898 This property holds the clear indicator.
899*/
900QQuickIndicatorButton *QQuickSearchField::clearIndicator() const
901{
902 Q_D(const QQuickSearchField);
903 return d->clearIndicator;
904}
905
906
907/*!
908 \qmlproperty Popup QtQuick.Controls::SearchField::popup
909
910 This property holds the popup.
911
912 The popup can be opened or closed manually, if necessary:
913
914 \code
915 onSpecialEvent: searchField.popup.close()
916 \endcode
917 */
918QQuickPopup *QQuickSearchField::popup() const
919{
920 QQuickSearchFieldPrivate *d = const_cast<QQuickSearchFieldPrivate *>(d_func());
921 if (!d->popup)
922 d->executePopup(isComponentComplete());
923 return d->popup;
924}
925
926void QQuickSearchField::setPopup(QQuickPopup *popup)
927{
928 Q_D(QQuickSearchField);
929 if (d->popup == popup)
930 return;
931
932 if (!d->popup.isExecuting())
933 d->cancelPopup();
934
935 if (d->popup) {
936 QObjectPrivate::disconnect(d->popup.data(), &QQuickPopup::destroyed, d,
937 &QQuickSearchFieldPrivate::popupDestroyed);
938 QObjectPrivate::disconnect(d->popup.data(), &QQuickPopup::visibleChanged, d,
939 &QQuickSearchFieldPrivate::popupVisibleChanged);
940 QQuickSearchFieldPrivate::hideOldPopup(d->popup);
941 }
942
943 if (popup) {
944 QQuickPopupPrivate::get(popup)->allowVerticalFlip = true;
945 popup->setClosePolicy(QQuickPopup::CloseOnEscape | QQuickPopup::CloseOnPressOutsideParent);
946 QObjectPrivate::connect(popup, &QQuickPopup::visibleChanged, d,
947 &QQuickSearchFieldPrivate::popupVisibleChanged);
948 // QQuickPopup does not derive from QQuickItemChangeListener, so we cannot use
949 // QQuickItemChangeListener::itemDestroyed so we have to use QObject::destroyed
950 QObjectPrivate::connect(popup, &QQuickPopup::destroyed, d,
951 &QQuickSearchFieldPrivate::popupDestroyed);
952
953#if QT_CONFIG(quick_itemview)
954 if (QQuickItemView *itemView = popup->findChild<QQuickItemView *>())
955 itemView->setHighlightRangeMode(QQuickItemView::NoHighlightRange);
956#endif
957 }
958
959 d->popup = popup;
960 if (!d->popup.isExecuting())
961 emit popupChanged();
962}
963
964/*!
965 \qmlproperty Component QtQuick.Controls::SearchField::delegate
966
967 This property holds a delegate that presents an item in the search field popup.
968
969 It is recommended to use \l ItemDelegate (or any other \l AbstractButton
970 derivatives) as the delegate. This ensures that the interaction works as
971 expected, and the popup will automatically close when appropriate. When
972 other types are used as the delegate, the popup must be closed manually.
973 For example, if \l MouseArea is used:
974
975 \code
976 delegate: Rectangle {
977 // ...
978 MouseArea {
979 // ...
980 onClicked: searchField.popup.close()
981 }
982 }
983 \endcode
984
985 \include delegate-ownership.qdocinc {no-ownership-since-6.11} {SearchField}
986*/
987QQmlComponent *QQuickSearchField::delegate() const
988{
989 Q_D(const QQuickSearchField);
990 return d->delegate;
991}
992
993void QQuickSearchField::setDelegate(QQmlComponent *delegate)
994{
995 Q_D(QQuickSearchField);
996 if (d->delegate == delegate)
997 return;
998
999 d->delegate = delegate;
1000 QQmlDelegateModel *delegateModel = qobject_cast<QQmlDelegateModel *>(d->delegateModel);
1001 if (delegateModel)
1002 delegateModel->setDelegate(d->delegate);
1003 emit delegateChanged();
1004}
1005
1006bool QQuickSearchField::eventFilter(QObject *object, QEvent *event)
1007{
1008 Q_D(QQuickSearchField);
1009
1010 switch (event->type()) {
1011 case QEvent::MouseButtonRelease: {
1012 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(d->contentItem);
1013 if (input->hasFocus()) {
1014 if (!d->text.isEmpty() && !d->isPopupVisible() && (d->delegateModel && d->delegateModel->count() > 0))
1015 d->showPopup();
1016 }
1017 break;
1018 }
1019 case QEvent::FocusOut: {
1020 const bool hasActiveFocus = d->popup && d->popup->hasActiveFocus();
1021 const bool usingPopupWindows =
1022 d->popup ? QQuickPopupPrivate::get(d->popup)->usePopupWindow() : false;
1023 if (qGuiApp->focusObject() != this && !(hasActiveFocus && !usingPopupWindows))
1024 d->hidePopup(false);
1025 break;
1026 }
1027 default:
1028 break;
1029 }
1030 return QQuickControl::eventFilter(object, event);
1031}
1032
1033void QQuickSearchField::focusInEvent(QFocusEvent *event)
1034{
1035 Q_D(QQuickSearchField);
1036 QQuickControl::focusInEvent(event);
1037
1038 if ((event->reason() == Qt::TabFocusReason || event->reason() == Qt::BacktabFocusReason
1039 || event->reason() == Qt::ShortcutFocusReason)
1040 && d->contentItem)
1041 d->contentItem->forceActiveFocus(event->reason());
1042}
1043
1044void QQuickSearchField::focusOutEvent(QFocusEvent *event)
1045{
1046 Q_D(QQuickSearchField);
1047 QQuickControl::focusOutEvent(event);
1048
1049 const bool hasActiveFocus = d->popup && d->popup->hasActiveFocus();
1050 const bool usingPopupWindows = d->popup && QQuickPopupPrivate::get(d->popup)->usePopupWindow();
1051
1052 if (qGuiApp->focusObject() != d->contentItem && !(hasActiveFocus && !usingPopupWindows))
1053 d->hidePopup(false);
1054}
1055
1056void QQuickSearchField::hoverEnterEvent(QHoverEvent *event)
1057{
1058 Q_D(QQuickSearchField);
1059 QQuickControl::hoverEnterEvent(event);
1060 QQuickItem *si = d->searchIndicator->indicator();
1061 QQuickItem *ci = d->clearIndicator->indicator();
1062 d->searchIndicator->setHovered(si && si->isEnabled() && si->contains(mapToItem(si, event->position())));
1063 d->clearIndicator->setHovered(ci && ci->isEnabled() && ci->contains(mapToItem(ci, event->position())));
1064 event->ignore();
1065}
1066
1067void QQuickSearchField::hoverMoveEvent(QHoverEvent *event)
1068{
1069 Q_D(QQuickSearchField);
1070 QQuickControl::hoverMoveEvent(event);
1071 QQuickItem *si = d->searchIndicator->indicator();
1072 QQuickItem *ci = d->clearIndicator->indicator();
1073 d->searchIndicator->setHovered(si && si->isEnabled() && si->contains(mapToItem(si, event->position())));
1074 d->clearIndicator->setHovered(ci && ci->isEnabled() && ci->contains(mapToItem(ci, event->position())));
1075 event->ignore();
1076}
1077
1078void QQuickSearchField::hoverLeaveEvent(QHoverEvent *event)
1079{
1080 Q_D(QQuickSearchField);
1081 QQuickControl::hoverLeaveEvent(event);
1082 d->searchIndicator->setHovered(false);
1083 d->clearIndicator->setHovered(false);
1084 event->ignore();
1085}
1086
1087void QQuickSearchField::keyPressEvent(QKeyEvent *event)
1088{
1089 Q_D(QQuickSearchField);
1090
1091 const auto key = event->key();
1092
1093 if (!d->suggestionModel.isNull() && !d->text.isEmpty()) {
1094 switch (key) {
1095 case Qt::Key_Escape:
1096 case Qt::Key_Back:
1097 if (d->isPopupVisible()) {
1098 d->hidePopup(false);
1099 event->accept();
1100 } else {
1101 setText(QString());
1102 }
1103 break;
1104 case Qt::Key_Return:
1105 case Qt::Key_Enter:
1106 if (d->isPopupVisible())
1107 d->hidePopup(true);
1108 emit accepted();
1109 emit searchTriggered();
1110 event->accept();
1111 break;
1112 case Qt::Key_Up:
1113 d->decreaseCurrentIndex();
1114 event->accept();
1115 break;
1116 case Qt::Key_Down:
1117 d->increaseCurrentIndex();
1118 event->accept();
1119 break;
1120 case Qt::Key_Home:
1121 if (d->isPopupVisible())
1122 d->setHighlightedIndex(0, Highlight);
1123 event->accept();
1124 break;
1125 case Qt::Key_End:
1126 if (d->isPopupVisible())
1127 d->setHighlightedIndex(suggestionCount() - 1, Highlight);
1128 event->accept();
1129 break;
1130 default:
1131 QQuickControl::keyPressEvent(event);
1132 break;
1133 }
1134 }
1135}
1136
1137void QQuickSearchField::classBegin()
1138{
1139 Q_D(QQuickSearchField);
1140 QQuickControl::classBegin();
1141
1142 QQmlContext *context = qmlContext(this);
1143 if (context) {
1144 QQmlEngine::setContextForObject(d->searchIndicator, context);
1145 QQmlEngine::setContextForObject(d->clearIndicator, context);
1146 }
1147}
1148
1149void QQuickSearchField::componentComplete()
1150{
1151 Q_D(QQuickSearchField);
1152 QQuickIndicatorButtonPrivate::get(d->searchIndicator)->executeIndicator(true);
1153 QQuickIndicatorButtonPrivate::get(d->clearIndicator)->executeIndicator(true);
1154 QQuickControl::componentComplete();
1155
1156 if (d->popup)
1157 d->executePopup(true);
1158
1159 if (d->delegateModel && d->ownModel)
1160 static_cast<QQmlDelegateModel *>(d->delegateModel)->componentComplete();
1161}
1162
1163void QQuickSearchField::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem)
1164{
1165 Q_D(QQuickSearchField);
1166 if (oldItem) {
1167 oldItem->removeEventFilter(this);
1168 if (QQuickTextInput *oldInput = qobject_cast<QQuickTextInput *>(oldItem)) {
1169 QObjectPrivate::disconnect(oldInput, &QQuickTextInput::textChanged, d,
1170 &QQuickSearchFieldPrivate::updateText);
1171 }
1172 }
1173
1174 if (newItem) {
1175 newItem->installEventFilter(this);
1176 if (QQuickTextInput *newInput = qobject_cast<QQuickTextInput *>(newItem)) {
1177 QObjectPrivate::connect(newInput, &QQuickTextInput::textChanged, d,
1178 &QQuickSearchFieldPrivate::updateText);
1179 }
1180 #if QT_CONFIG(cursor)
1181 newItem->setCursor(Qt::IBeamCursor);
1182 #endif
1183 }
1184}
1185
1186void QQuickSearchField::itemChange(ItemChange change, const ItemChangeData &data)
1187{
1188 Q_D(QQuickSearchField);
1189 QQuickControl::itemChange(change, data);
1190 if (change == ItemVisibleHasChanged && !data.boolValue) {
1191 d->hidePopup(false);
1192 d->setCurrentItemAtIndex(-1, NoActivate);
1193 }
1194}
1195
1196QT_END_NAMESPACE
1197
1198#include "moc_qquicksearchfield_p.cpp"
void createdItem(int index, QObject *object)
QQuickIndicatorButton * clearIndicator
bool handleRelease(const QPointF &point, ulong timestamp) override
void itemDestroyed(QQuickItem *item) override
bool isValidIndex(int index) const
static void hideOldPopup(QQuickPopup *popup)
QQuickDeferredPointer< QQuickPopup > popup
void itemImplicitWidthChanged(QQuickItem *item) override
void itemImplicitHeightChanged(QQuickItem *item) override
void setCurrentItemAtIndex(int index, Activation activate)
bool handlePress(const QPointF &point, ulong timestamp) override
void setHighlightedIndex(int index, Highlighting highlight)
QString textAt(int index) const
void executePopup(bool complete=false)
QQmlInstanceModel * delegateModel
QQuickIndicatorButton * searchIndicator