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 \note The iOS style does not provide a built-in popup for SearchField in
37 order to preserve the native look and feel. If a popup is still wanted,
38 it has to be defined by the user.
39
40 \image qtquickcontrols-searchfield.gif
41 {Search field with search icon and clear button}
42
43 \section1 SearchField's Indicators
44
45 SearchField provides two optional embedded indicator buttons: \l searchIndicator and
46 \l clearIndicator.
47
48 These are not indicators in the sense of \l BusyIndicator or \l ProgressBar. Instead,
49 they are interactive controls embedded into the field (similar to the up/down buttons
50 in \l SpinBox). Pressing \l searchIndicator triggers \l searchButtonPressed, and pressing
51 \l clearIndicator triggers \l clearButtonPressed.
52
53 In addition to exposing the actions, the indicator buttons provide interaction state
54 (pressed/hovered/focused, etc.) that can be used by styles.
55
56 \section2 Customizing indicator content
57
58 The \l searchIndicator and \l clearIndicator properties are read-only. Customization is
59 supported through their internal properties.
60
61 In particular, the button's visual content is provided by its \c indicator item, which is
62 writable. This allows the default content to be replaced or removed entirely.
63
64 For example, to remove both indicator icons:
65
66 \code
67 SearchField {
68 searchIndicator.indicator: null
69 clearIndicator.indicator: null
70 }
71 \endcode
72
73 This is a supported customization scenario. Different SearchField variants may omit one
74 of the buttons (for example, providing only a search button) or replace the indicator
75 content with an alternative item (for example, a microphone icon to trigger speech input).
76
77 \sa searchIndicator, clearIndicator, searchButtonPressed, clearButtonPressed
78
79 \section1 SearchField Model Roles
80
81 SearchField is able to visualize standard \l {qml-data-models}{data models}
82 that provide the \c modelData role:
83 \list
84 \li models that have only one role
85 \li models that do not have named roles (JavaScript array, integer)
86 \endlist
87
88 When using models that have multiple named roles, SearchField must be configured
89 to use a specific \l {textRole}{text role} for its \l {text}{text}
90 and \l delegate instances.
91
92 \code
93 ListModel {
94 id : fruitModel
95 ListElement { name: "Apple"; color: "green" }
96 ListElement { name: "Cherry"; color: "red" }
97 ListElement { name: "Banana"; color: "yellow" }
98 ListElement { name: "Orange"; color: "orange" }
99 ListElement { name: "WaterMelon"; color: "pink" }
100 }
101
102 SortFilterProxyModel {
103 id: fruitFilter
104 model: fruitModel
105 sorters: [
106 RoleSorter {
107 roleName: "name"
108 }
109 ]
110 filters: [
111 FunctionFilter {
112 component CustomData: QtObject { property string name }
113 property var regExp: new RegExp(fruitSearch.text, "i")
114 onRegExpChanged: invalidate()
115 function filter(data: CustomData): bool {
116 return regExp.test(data.name);
117 }
118 }
119 ]
120 }
121
122 SearchField {
123 id: fruitSearch
124 suggestionModel: fruitFilter
125 textRole: "name"
126 anchors.horizontalCenter: parent.horizontalCenter
127 }
128 \endcode
129 */
130
131/*!
132 \qmlsignal void QtQuick.Controls::SearchField::activated(int index)
133
134 This signal is emitted when the item at \a index is activated by the user.
135
136 An item is activated when it is selected while the popup is open,
137 causing the popup to close (and \l currentIndex to change).
138 The \l currentIndex property is set to \a index.
139
140 \sa currentIndex
141*/
142
143
144/*!
145 \qmlsignal void QtQuick.Controls::SearchField::highlighted(int index)
146
147 This signal is emitted when the item at \a index in the popup list is highlighted by the user.
148
149 The highlighted signal is only emitted when the popup is open and an item
150 is highlighted, but not necessarily \l activated.
151
152 \sa highlightedIndex
153*/
154
155/*!
156 \qmlsignal void QtQuick.Controls::SearchField::accepted()
157
158 This signal is emitted when the user confirms their input by pressing
159 the Enter or Return key.
160
161 This signal is typically used to trigger a search or action based on
162 the final text input, and it indicates the user's intention to complete
163 or submit the query.
164
165 \sa searchTriggered()
166 */
167
168/*!
169 \qmlsignal void QtQuick.Controls::SearchField::searchTriggered()
170
171 This signal is emitted when a search action is initiated.
172
173 It occurs in two cases:
174 1. When the Enter or Return key is pressed, it will be emitted together
175 with accepted() signal
176 2. When the text is edited and if the \l live property is set to \c true,
177 this signal will be emitted.
178
179 This signal is ideal for initiating searches both on-demand and in real-time as
180 the user types, depending on the desired interaction model.
181
182 \sa accepted(), textEdited()
183 */
184
185/*!
186 \qmlsignal void QtQuick.Controls::SearchField::textEdited()
187
188 This signal is emitted every time the user modifies the text in the
189 search field, typically with each keystroke.
190
191 \sa searchTriggered()
192 */
193
194/*!
195 \qmlsignal void QtQuick.Controls::SearchField::searchButtonPressed()
196
197 This signal is emitted when the search button is pressed.
198
199 \sa clearButtonPressed()
200*/
201
202/*!
203 \qmlsignal void QtQuick.Controls::SearchField::clearButtonPressed()
204
205 This signal is emitted when the clear button is pressed.
206
207 \sa searchButtonPressed()
208*/
209
210namespace {
213}
214
216{
217public:
218 Q_DECLARE_PUBLIC(QQuickSearchField)
219
220 bool isPopupVisible() const;
221 void showPopup();
222 void hidePopup(bool accept);
223 static void hideOldPopup(QQuickPopup *popup);
226
229
230 void createdItem(int index, QObject *object);
232
235 void setCurrentIndex(int index);
236 void setCurrentItemAtIndex(int index, Activation activate);
238 void setHighlightedIndex(int index, Highlighting highlight);
239
241
242 QString currentTextRole() const;
243 void selectAll();
246 QString textAt(int index) const;
247 bool isValidIndex(int index) const;
248
250 void executePopup(bool complete = false);
251
252 bool handlePress(const QPointF &point, ulong timestamp) override;
253 bool handleRelease(const QPointF &point, ulong timestamp) override;
254
257
258 void itemImplicitWidthChanged(QQuickItem *item) override;
259 void itemImplicitHeightChanged(QQuickItem *item) override;
260 void itemDestroyed(QQuickItem *item) override;
261
262 static inline QString popupName() { return QStringLiteral("popup"); }
263
265 bool hasCurrentIndex = false;
267 int currentIndex = -1;
268 QString text;
269 QString textRole;
271 bool live = true;
272 bool searchPressed = false;
273 bool clearPressed = false;
274 bool searchFlat = false;
275 bool clearFlat = false;
276 bool searchDown = false;
277 bool clearDown = false;
278 bool hasSearchDown = false;
279 bool hasClearDown = false;
280 bool ownModel = false;
281 QQmlInstanceModel *delegateModel = nullptr;
282 QQmlComponent *delegate = nullptr;
283 QQuickIndicatorButton *searchIndicator = nullptr;
284 QQuickIndicatorButton *clearIndicator = nullptr;
286};
287
288bool QQuickSearchFieldPrivate::isPopupVisible() const
289{
290 return popup && popup->isVisible();
291}
292
294{
295 if (!popup)
296 executePopup(true);
297
298 if (popup && !popup->isVisible())
299 popup->open();
300}
301
303{
304 Q_Q(QQuickSearchField);
305 if (accept) {
307 // hiding the popup on user interaction should always emit activated,
308 // even if the current index didn't change
309 emit q->activated(highlightedIndex);
310 }
311 if (popup && popup->isVisible())
312 popup->close();
313}
314
315void QQuickSearchFieldPrivate::hideOldPopup(QQuickPopup *popup)
316{
317 if (!popup)
318 return;
319
320 qCDebug(lcItemManagement) << "hiding old popup" << popup;
321
322 popup->setVisible(false);
323 popup->setParentItem(nullptr);
324#if QT_CONFIG(accessibility)
325 // Remove the item from the accessibility tree.
326 QQuickAccessibleAttached *accessible = accessibleAttached(popup);
327 if (accessible)
328 accessible->setIgnored(true);
329#endif
330}
331
333{
334 if (isPopupVisible())
335 QGuiApplication::inputMethod()->reset();
336
337#if QT_CONFIG(quick_itemview)
338 QQuickItemView *itemView = popup->findChild<QQuickItemView *>();
339 if (itemView)
340 itemView->setHighlightRangeMode(QQuickItemView::NoHighlightRange);
341#endif
342
344
345#if QT_CONFIG(quick_itemview)
346 if (itemView)
347 itemView->positionViewAtIndex(highlightedIndex, QQuickItemView::Beginning);
348#endif
349}
350
352{
353 Q_Q(QQuickSearchField);
354 popup = nullptr;
355 emit q->popupChanged();
356}
357
359{
360 Q_Q(QQuickSearchField);
361 int index = delegateModel->indexOf(q->sender(), nullptr);
362 if (index != -1) {
364 hidePopup(true);
365 }
366}
367
369{
370 Q_Q(QQuickSearchField);
371
372 QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(q->sender());
373 if (!button || !button->isHovered() || !button->isEnabled()
374 || QQuickAbstractButtonPrivate::get(button)->touchId != -1)
375 return;
376
377 int index = delegateModel->indexOf(button, nullptr);
378 if (index != -1) {
380
381#if QT_CONFIG(quick_itemview)
382 if (QQuickItemView *itemView = popup->findChild<QQuickItemView *>())
383 itemView->positionViewAtIndex(index, QQuickItemView::Contain);
384#endif
385 }
386}
387
388void QQuickSearchFieldPrivate::createdItem(int index, QObject *object)
389{
390 Q_UNUSED(index);
391 Q_Q(QQuickSearchField);
392 QQuickItem *item = qobject_cast<QQuickItem *>(object);
393 if (item && !item->parentItem()) {
394 if (popup)
395 item->setParentItem(popup->contentItem());
396 else
397 item->setParentItem(q);
398 QQuickItemPrivate::get(item)->setCulled(true);
399 }
400
401 QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(object);
402 if (button) {
403 button->setFocusPolicy(Qt::NoFocus);
404 connect(button, &QQuickAbstractButton::clicked, this,
406 connect(button, &QQuickAbstractButton::hoveredChanged, this,
408 }
409}
410
412{
413 Q_Q(QQuickSearchField);
414 if (q->suggestionCount() == 0)
416 // If the suggestionModel has been updated and the current text matches an item in
417 // the model, update currentIndex and highlightedIndex to the index of that item.
418 if (!text.isEmpty()) {
419 for (int idx = 0; idx < q->suggestionCount(); ++idx) {
420 QString t = textAt(idx);
421 if (t == text) {
424 break;
425 }
426 }
427 }
428
429 emit q->suggestionCountChanged();
430}
431
433{
434 Q_Q(QQuickSearchField);
435 if (isPopupVisible()) {
436 if (highlightedIndex < q->suggestionCount() - 1)
438 }
439}
440
442{
443 if (isPopupVisible()) {
444 if (highlightedIndex > 0)
446 }
447}
448
450{
451 Q_Q(QQuickSearchField);
452 if (currentIndex == index)
453 return;
454
455 currentIndex = index;
456 emit q->currentIndexChanged();
457}
458
460{
461 Q_Q(QQuickSearchField);
462 if (currentIndex == index)
463 return;
464
465 currentIndex = index;
466 emit q->currentIndexChanged();
467
469
470 if (activate)
471 emit q->activated(index);
472}
473
475{
476 Q_Q(QQuickSearchField);
477 int index = -1;
478
479 if (isPopupVisible()) {
480 if (currentIndex >= 0)
481 index = currentIndex;
482 else if (q->suggestionCount() > 0)
483 index = 0; // auto-highlight first suggestion
484 }
485
487}
488
490{
491 Q_Q(QQuickSearchField);
492 if (highlightedIndex == index)
493 return;
494
495 highlightedIndex = index;
496 emit q->highlightedIndexChanged();
497
498 if (highlight)
499 emit q->highlighted(index);
500}
501
503{
504 Q_Q(QQuickSearchField);
505 bool ownedOldModel = ownModel;
506 QQmlInstanceModel *oldModel = delegateModel;
507
508 if (oldModel) {
509 disconnect(delegateModel, &QQmlInstanceModel::countChanged, this,
511 disconnect(delegateModel, &QQmlInstanceModel::createdItem, this,
513 }
514
515 ownModel = false;
516 delegateModel = suggestionModel.value<QQmlInstanceModel *>();
517
518 if (!delegateModel && suggestionModel.isValid()) {
519 QQmlDelegateModel *dataModel = new QQmlDelegateModel(qmlContext(q), q);
520 dataModel->setModel(suggestionModel);
521 dataModel->setDelegate(delegate);
522 if (q->isComponentComplete())
523 dataModel->componentComplete();
524
525 ownModel = true;
526 delegateModel = dataModel;
527 }
528
529 if (delegateModel) {
530 connect(delegateModel, &QQmlInstanceModel::countChanged, this,
532 connect(delegateModel, &QQmlInstanceModel::createdItem, this,
534 }
535
536 emit q->delegateModelChanged();
537
538 if (ownedOldModel)
539 delete oldModel;
540}
541
543{
544 return textRole.isEmpty() ? QStringLiteral("modelData") : textRole;
545}
546
548{
549 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(contentItem);
550 if (!input)
551 return;
552 input->selectAll();
553}
554
556{
557 Q_Q(QQuickSearchField);
558 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(contentItem);
559 if (!input)
560 return;
561
562 const QString textInput = input->text();
563
564 if (text != textInput) {
565 q->setText(textInput);
566 emit q->textEdited();
567
570
571 if (live)
572 emit q->searchTriggered();
573 }
574
575 if (text.isEmpty()) {
576 if (isPopupVisible())
577 hidePopup(false);
578 } else {
579 if (delegateModel && delegateModel->count() > 0) {
580 if (!isPopupVisible())
582 } else {
583 if (isPopupVisible())
584 hidePopup(false);
585 }
586 }
587}
588
590{
591 Q_Q(QQuickSearchField);
592 const QString currentText = textAt(currentIndex);
593
594 if (text != currentText)
595 q->setText(currentText);
596}
597
598QString QQuickSearchFieldPrivate::textAt(int index) const
599{
600 if (!isValidIndex(index))
601 return QString();
602
603 return delegateModel->stringValue(index, currentTextRole());
604}
605
607{
608 return delegateModel && index >= 0 && index < delegateModel->count();
609}
610
612{
613 Q_Q(QQuickSearchField);
614 quickCancelDeferred(q, popupName());
615}
616
618{
619 Q_Q(QQuickSearchField);
620 if (popup.wasExecuted())
621 return;
622
623 if (!popup || complete)
624 quickBeginDeferred(q, popupName(), popup);
625 if (complete)
626 quickCompleteDeferred(q, popupName(), popup);
627}
628
629bool QQuickSearchFieldPrivate::handlePress(const QPointF &point, ulong timestamp)
630{
631 Q_Q(QQuickSearchField);
632 QQuickControlPrivate::handlePress(point, timestamp);
633
634 QQuickItem *si = searchIndicator->indicator();
635 QQuickItem *ci = clearIndicator->indicator();
636 const bool isSearch = si && si->isEnabled() && si->contains(q->mapToItem(si, point));
637 const bool isClear = ci && ci->isEnabled() && ci->contains(q->mapToItem(ci, point));
638
639 if (isSearch) {
640 searchIndicator->setPressed(true);
642 } else if (isClear) {
643 clearIndicator->setPressed(true);
645 }
646
647 return true;
648}
649
650bool QQuickSearchFieldPrivate::handleRelease(const QPointF &point, ulong timestamp)
651{
652 QQuickControlPrivate::handleRelease(point, timestamp);
653 if (searchIndicator->isPressed())
654 searchIndicator->setPressed(false);
655 else if (clearIndicator->isPressed())
656 clearIndicator->setPressed(false);
657 return true;
658}
659
661{
662 Q_Q(QQuickSearchField);
663
664 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(contentItem);
665 if (!input)
666 return;
667
668 input->forceActiveFocus();
669 emit q->searchButtonPressed();
670}
671
673{
674 Q_Q(QQuickSearchField);
675
676 if (text.isEmpty())
677 return;
678
679 // if text is not null then clear text, also reset highlightedIndex and currentIndex
680 if (!text.isEmpty()) {
683 q->setText(QString());
684
685 if (isPopupVisible())
686 hidePopup(false);
687
688 emit q->clearButtonPressed();
689 }
690}
691
693{
694 QQuickControlPrivate::itemImplicitWidthChanged(item);
695 if (item == searchIndicator->indicator())
696 emit searchIndicator->implicitIndicatorWidthChanged();
697 if (item == clearIndicator->indicator())
698 emit clearIndicator->implicitIndicatorWidthChanged();
699}
700
702{
703 QQuickControlPrivate::itemImplicitHeightChanged(item);
704 if (item == searchIndicator->indicator())
705 emit searchIndicator->implicitIndicatorHeightChanged();
706 if (item == clearIndicator->indicator())
707 emit clearIndicator->implicitIndicatorHeightChanged();
708}
709
711{
712 QQuickControlPrivate::itemDestroyed(item);
713 if (item == searchIndicator->indicator())
714 searchIndicator->setIndicator(nullptr);
715 if (item == clearIndicator->indicator())
716 clearIndicator->setIndicator(nullptr);
717}
718
719QQuickSearchField::QQuickSearchField(QQuickItem *parent)
720 : QQuickControl(*(new QQuickSearchFieldPrivate), parent)
721{
722 Q_D(QQuickSearchField);
723 d->searchIndicator = new QQuickIndicatorButton(this);
724 d->clearIndicator = new QQuickIndicatorButton(this);
725
726 setFocusPolicy(Qt::StrongFocus);
727 setFlag(QQuickItem::ItemIsFocusScope);
728 setAcceptedMouseButtons(Qt::LeftButton);
729#if QT_CONFIG(cursor)
730 setCursor(Qt::ArrowCursor);
731#endif
732
733 d->init();
734}
735
736QQuickSearchField::~QQuickSearchField()
737{
738 Q_D(QQuickSearchField);
739 d->removeImplicitSizeListener(d->searchIndicator->indicator());
740 d->removeImplicitSizeListener(d->clearIndicator->indicator());
741
742 if (d->popup) {
743 QObjectPrivate::disconnect(d->popup.data(), &QQuickPopup::visibleChanged, d,
744 &QQuickSearchFieldPrivate::popupVisibleChanged);
745 d->hideOldPopup(d->popup);
746 d->popup = nullptr;
747 }
748}
749
750/*!
751 \qmlproperty model QtQuick.Controls::SearchField::suggestionModel
752
753 This property holds the data model used to display search suggestions in the popup menu.
754
755 \code
756 SearchField {
757 textRole: "age"
758 suggestionModel: ListModel {
759 ListElement { name: "Karen"; age: "66" }
760 ListElement { name: "Jim"; age: "32" }
761 ListElement { name: "Pamela"; age: "28" }
762 }
763 }
764 \endcode
765
766 \sa textRole
767 */
768
769QVariant QQuickSearchField::suggestionModel() const
770{
771 Q_D(const QQuickSearchField);
772 return d->suggestionModel;
773}
774
775void QQuickSearchField::setSuggestionModel(const QVariant &model)
776{
777 Q_D(QQuickSearchField);
778
779 QVariant suggestionModel;
780 if (QJSValue *value = get_if<QJSValue>(&suggestionModel))
781 suggestionModel = value->toVariant();
782 else
783 suggestionModel = model;
784
785 if (d->suggestionModel == suggestionModel)
786 return;
787
788 d->suggestionModel = suggestionModel;
789 d->createDelegateModel();
790 emit suggestionCountChanged();
791 emit suggestionModelChanged();
792}
793
794/*!
795 \readonly
796 \qmlproperty model QtQuick.Controls::SearchField::delegateModel
797
798 This property holds the model that provides delegate instances for the search field.
799
800 It is typically assigned to a \l ListView in the \l {Popup::}{contentItem}
801 of the \l popup.
802
803 */
804QQmlInstanceModel *QQuickSearchField::delegateModel() const
805{
806 Q_D(const QQuickSearchField);
807 return d->delegateModel;
808}
809
810/*!
811 \readonly
812 \qmlproperty int QtQuick.Controls::SearchField::suggestionCount
813
814 This property holds the number of suggestions to display from the suggestion model.
815 */
816int QQuickSearchField::suggestionCount() const
817{
818 Q_D(const QQuickSearchField);
819 return d->delegateModel ? d->delegateModel->count() : 0;
820}
821
822/*!
823 \qmlproperty int QtQuick.Controls::SearchField::currentIndex
824
825 This property holds the index of the currently selected suggestion in the popup list.
826
827 Its value is \c -1 when no suggestion is selected.
828
829 currentIndex is not modified automatically when the model changes or when the user types
830 or edits text. It is updated only when the user explicitly selects a suggestion, either
831 by clicking an item in the popup, or by pressing Enter on a highlighted item.
832
833 currentIndex can be set; for example, to display the first item in the model at startup.
834 Before doing so, ensure that the model is not empty:
835
836 \snippet qtquickcontrols-searchfield-currentIndex.qml currentIndex
837
838 \sa activated(), text, highlightedIndex
839 */
840int QQuickSearchField::currentIndex() const
841{
842 Q_D(const QQuickSearchField);
843 return d->currentIndex;
844}
845
846void QQuickSearchField::setCurrentIndex(int index)
847{
848 Q_D(QQuickSearchField);
849 d->hasCurrentIndex = true;
850 d->setCurrentIndex(index);
851}
852
853/*!
854 \readonly
855 \qmlproperty int QtQuick.Controls::SearchField::highlightedIndex
856
857 This property holds the index of the currently highlighted item in
858 the popup list.
859
860 When the highlighted item is activated, the popup closes, \l currentIndex
861 is updated to match \c highlightedIndex, and this property is reset to
862 \c -1, indicating that no item is currently highlighted.
863
864 \sa highlighted(), currentIndex
865*/
866int QQuickSearchField::highlightedIndex() const
867{
868 Q_D(const QQuickSearchField);
869 return d->highlightedIndex;
870}
871
872/*!
873 \qmlproperty string QtQuick.Controls::SearchField::text
874
875 This property holds the current input text in the search field.
876
877 Text is bound to the user input, triggering suggestion updates or search logic.
878
879 \sa searchTriggered(), textEdited()
880 */
881QString QQuickSearchField::text() const
882{
883 Q_D(const QQuickSearchField);
884 return d->text;
885}
886
887void QQuickSearchField::setText(const QString &text)
888{
889 Q_D(QQuickSearchField);
890 if (d->text == text)
891 return;
892
893 d->text = text;
894 emit textChanged();
895}
896
897/*!
898 \qmlproperty string QtQuick.Controls::SearchField::textRole
899
900 This property holds the model role used to display items in the suggestion model
901 shown in the popup list.
902
903 When the model has multiple roles, \c textRole can be set to determine
904 which role should be displayed.
905 */
906QString QQuickSearchField::textRole() const
907{
908 Q_D(const QQuickSearchField);
909 return d->textRole;
910}
911
912void QQuickSearchField::setTextRole(const QString &textRole)
913{
914 Q_D(QQuickSearchField);
915 if (d->textRole == textRole)
916 return;
917
918 d->textRole = textRole;
919}
920
921/*!
922 \qmlproperty bool QtQuick.Controls::SearchField::live
923
924 This property holds a boolean value that determines whether the search is triggered
925 on every text edit.
926
927 When set to \c true, the \l searchTriggered() signal is emitted on each text change,
928 allowing you to respond to every keystroke.
929 When set to \c false, the \l searchTriggered() is only emitted when the user presses
930 the Enter or Return key.
931
932 \sa searchTriggered()
933 */
934
935bool QQuickSearchField::isLive() const
936{
937 Q_D(const QQuickSearchField);
938 return d->live;
939}
940
941void QQuickSearchField::setLive(const bool live)
942{
943 Q_D(QQuickSearchField);
944
945 if (d->live == live)
946 return;
947
948 d->live = live;
949}
950
951/*!
952 \include qquickindicatorbutton.qdocinc {properties} {SearchField} {searchIndicator}
953
954 This property holds the search indicator. Pressing it triggers
955 \l searchButtonPressed.
956
957 It is exposed so that styles and applications can customize it through its
958 internal properties (for example, replacing or removing the \c searchIndicator
959 via \c searchIndicator.indicator, or reacting to interaction state such as
960 pressed and hovered).
961
962 \sa {SearchField's Indicators}
963 */
964QQuickIndicatorButton *QQuickSearchField::searchIndicator() const
965{
966 Q_D(const QQuickSearchField);
967 return d->searchIndicator;
968}
969
970/*!
971 \include qquickindicatorbutton.qdocinc {properties} {SearchField} {clearIndicator}
972
973 This property holds the clear indicator. Pressing it triggers
974 \l clearButtonPressed.
975
976 It is exposed so that styles and applications can customize it through its
977 internal properties (for example, replacing or removing the \c clearIndicator
978 via \c clearIndicator.indicator, or reacting to interaction state such as
979 pressed and hovered).
980
981 \sa {SearchField's Indicators}
982*/
983QQuickIndicatorButton *QQuickSearchField::clearIndicator() const
984{
985 Q_D(const QQuickSearchField);
986 return d->clearIndicator;
987}
988
989
990/*!
991 \qmlproperty Popup QtQuick.Controls::SearchField::popup
992
993 This property holds the popup.
994
995 The popup can be opened or closed manually, if necessary:
996
997 \code
998 onSpecialEvent: searchField.popup.close()
999 \endcode
1000 */
1001QQuickPopup *QQuickSearchField::popup() const
1002{
1003 QQuickSearchFieldPrivate *d = const_cast<QQuickSearchFieldPrivate *>(d_func());
1004 if (!d->popup)
1005 d->executePopup(isComponentComplete());
1006 return d->popup;
1007}
1008
1009void QQuickSearchField::setPopup(QQuickPopup *popup)
1010{
1011 Q_D(QQuickSearchField);
1012 if (d->popup == popup)
1013 return;
1014
1015 if (!d->popup.isExecuting())
1016 d->cancelPopup();
1017
1018 if (d->popup) {
1019 QObjectPrivate::disconnect(d->popup.data(), &QQuickPopup::destroyed, d,
1020 &QQuickSearchFieldPrivate::popupDestroyed);
1021 QObjectPrivate::disconnect(d->popup.data(), &QQuickPopup::visibleChanged, d,
1022 &QQuickSearchFieldPrivate::popupVisibleChanged);
1023 QQuickSearchFieldPrivate::hideOldPopup(d->popup);
1024 }
1025
1026 if (popup) {
1027 QQuickPopupPrivate::get(popup)->allowVerticalFlip = true;
1028 popup->setClosePolicy(QQuickPopup::CloseOnEscape | QQuickPopup::CloseOnPressOutsideParent);
1029 QObjectPrivate::connect(popup, &QQuickPopup::visibleChanged, d,
1030 &QQuickSearchFieldPrivate::popupVisibleChanged);
1031 // QQuickPopup does not derive from QQuickItemChangeListener, so we cannot use
1032 // QQuickItemChangeListener::itemDestroyed so we have to use QObject::destroyed
1033 QObjectPrivate::connect(popup, &QQuickPopup::destroyed, d,
1034 &QQuickSearchFieldPrivate::popupDestroyed);
1035
1036#if QT_CONFIG(quick_itemview)
1037 if (QQuickItemView *itemView = popup->findChild<QQuickItemView *>())
1038 itemView->setHighlightRangeMode(QQuickItemView::NoHighlightRange);
1039#endif
1040 }
1041
1042 d->popup = popup;
1043 if (!d->popup.isExecuting())
1044 emit popupChanged();
1045}
1046
1047/*!
1048 \qmlproperty Component QtQuick.Controls::SearchField::delegate
1049
1050 This property holds a delegate that presents an item in the search field popup.
1051
1052 It is recommended to use \l ItemDelegate (or any other \l AbstractButton
1053 derivatives) as the delegate. This ensures that the interaction works as
1054 expected, and the popup will automatically close when appropriate. When
1055 other types are used as the delegate, the popup must be closed manually.
1056 For example, if \l MouseArea is used:
1057
1058 \code
1059 delegate: Rectangle {
1060 // ...
1061 MouseArea {
1062 // ...
1063 onClicked: searchField.popup.close()
1064 }
1065 }
1066 \endcode
1067
1068 \include delegate-ownership.qdocinc {no-ownership-since-6.11} {SearchField}
1069*/
1070QQmlComponent *QQuickSearchField::delegate() const
1071{
1072 Q_D(const QQuickSearchField);
1073 return d->delegate;
1074}
1075
1076void QQuickSearchField::setDelegate(QQmlComponent *delegate)
1077{
1078 Q_D(QQuickSearchField);
1079 if (d->delegate == delegate)
1080 return;
1081
1082 d->delegate = delegate;
1083 QQmlDelegateModel *delegateModel = qobject_cast<QQmlDelegateModel *>(d->delegateModel);
1084 if (delegateModel)
1085 delegateModel->setDelegate(d->delegate);
1086 emit delegateChanged();
1087}
1088
1089/*!
1090 \qmlproperty string QtQuick.Controls::SearchField::placeholderText
1091 \since 6.12
1092
1093 This property holds the hint that is displayed in the SearchField before the user
1094 enters text.
1095*/
1096QString QQuickSearchField::placeholderText() const
1097{
1098 Q_D(const QQuickSearchField);
1099 return d->placeholderText;
1100}
1101
1102void QQuickSearchField::setPlaceholderText(const QString &text)
1103{
1104 Q_D(QQuickSearchField);
1105 if (d->placeholderText == text)
1106 return;
1107
1108 d->placeholderText = text;
1109 emit placeholderTextChanged();
1110}
1111
1112bool QQuickSearchField::eventFilter(QObject *object, QEvent *event)
1113{
1114 Q_D(QQuickSearchField);
1115
1116 switch (event->type()) {
1117 case QEvent::MouseButtonRelease: {
1118 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(d->contentItem);
1119 if (input->hasFocus()) {
1120 if (!d->text.isEmpty() && !d->isPopupVisible() && (d->delegateModel && d->delegateModel->count() > 0))
1121 d->showPopup();
1122 }
1123 break;
1124 }
1125 case QEvent::FocusOut: {
1126 const bool hasActiveFocus = d->popup && d->popup->hasActiveFocus();
1127 const bool usingPopupWindows =
1128 d->popup ? QQuickPopupPrivate::get(d->popup)->usePopupWindow() : false;
1129 if (qGuiApp->focusObject() != this && !(hasActiveFocus && !usingPopupWindows))
1130 d->hidePopup(false);
1131 break;
1132 }
1133 default:
1134 break;
1135 }
1136 return QQuickControl::eventFilter(object, event);
1137}
1138
1139void QQuickSearchField::focusInEvent(QFocusEvent *event)
1140{
1141 Q_D(QQuickSearchField);
1142 QQuickControl::focusInEvent(event);
1143
1144 if (!d->contentItem)
1145 return;
1146
1147 const auto reason = event->reason();
1148 switch (reason) {
1149 case Qt::TabFocusReason:
1150 case Qt::BacktabFocusReason:
1151 case Qt::ShortcutFocusReason:
1152 case Qt::OtherFocusReason:
1153 if (reason != Qt::OtherFocusReason || !d->isPopupVisible())
1154 d->contentItem->forceActiveFocus(reason);
1155 break;
1156 default:
1157 break;
1158 }
1159}
1160
1161void QQuickSearchField::focusOutEvent(QFocusEvent *event)
1162{
1163 Q_D(QQuickSearchField);
1164 QQuickControl::focusOutEvent(event);
1165
1166 const bool hasActiveFocus = d->popup && d->popup->hasActiveFocus();
1167 const bool usingPopupWindows = d->popup && QQuickPopupPrivate::get(d->popup)->usePopupWindow();
1168
1169 if (qGuiApp->focusObject() != d->contentItem && !(hasActiveFocus && !usingPopupWindows))
1170 d->hidePopup(false);
1171}
1172
1173void QQuickSearchField::hoverEnterEvent(QHoverEvent *event)
1174{
1175 Q_D(QQuickSearchField);
1176 QQuickControl::hoverEnterEvent(event);
1177 QQuickItem *si = d->searchIndicator->indicator();
1178 QQuickItem *ci = d->clearIndicator->indicator();
1179 d->searchIndicator->setHovered(si && si->isEnabled() && si->contains(mapToItem(si, event->position())));
1180 d->clearIndicator->setHovered(ci && ci->isEnabled() && ci->contains(mapToItem(ci, event->position())));
1181 event->ignore();
1182}
1183
1184void QQuickSearchField::hoverMoveEvent(QHoverEvent *event)
1185{
1186 Q_D(QQuickSearchField);
1187 QQuickControl::hoverMoveEvent(event);
1188 QQuickItem *si = d->searchIndicator->indicator();
1189 QQuickItem *ci = d->clearIndicator->indicator();
1190 d->searchIndicator->setHovered(si && si->isEnabled() && si->contains(mapToItem(si, event->position())));
1191 d->clearIndicator->setHovered(ci && ci->isEnabled() && ci->contains(mapToItem(ci, event->position())));
1192 event->ignore();
1193}
1194
1195void QQuickSearchField::hoverLeaveEvent(QHoverEvent *event)
1196{
1197 Q_D(QQuickSearchField);
1198 QQuickControl::hoverLeaveEvent(event);
1199 d->searchIndicator->setHovered(false);
1200 d->clearIndicator->setHovered(false);
1201 event->ignore();
1202}
1203
1204void QQuickSearchField::keyPressEvent(QKeyEvent *event)
1205{
1206 Q_D(QQuickSearchField);
1207
1208 const auto key = event->key();
1209
1210 if (!d->suggestionModel.isNull() && !d->text.isEmpty()) {
1211 switch (key) {
1212 case Qt::Key_Escape:
1213 case Qt::Key_Back:
1214 if (d->isPopupVisible()) {
1215 d->hidePopup(false);
1216 event->accept();
1217 } else {
1218 setText(QString());
1219 }
1220 break;
1221 case Qt::Key_Return:
1222 case Qt::Key_Enter:
1223 if (d->isPopupVisible())
1224 d->hidePopup(true);
1225 emit accepted();
1226 emit searchTriggered();
1227 event->accept();
1228 break;
1229 case Qt::Key_Up:
1230 d->decreaseCurrentIndex();
1231 event->accept();
1232 break;
1233 case Qt::Key_Down:
1234 d->increaseCurrentIndex();
1235 event->accept();
1236 break;
1237 case Qt::Key_Home:
1238 if (d->isPopupVisible())
1239 d->setHighlightedIndex(0, Highlight);
1240 event->accept();
1241 break;
1242 case Qt::Key_End:
1243 if (d->isPopupVisible())
1244 d->setHighlightedIndex(suggestionCount() - 1, Highlight);
1245 event->accept();
1246 break;
1247 default:
1248 QQuickControl::keyPressEvent(event);
1249 break;
1250 }
1251 }
1252}
1253
1254void QQuickSearchField::classBegin()
1255{
1256 Q_D(QQuickSearchField);
1257 QQuickControl::classBegin();
1258
1259 QQmlContext *context = qmlContext(this);
1260 if (context) {
1261 QQmlEngine::setContextForObject(d->searchIndicator, context);
1262 QQmlEngine::setContextForObject(d->clearIndicator, context);
1263 }
1264}
1265
1266void QQuickSearchField::componentComplete()
1267{
1268 Q_D(QQuickSearchField);
1269 QQuickIndicatorButtonPrivate::get(d->searchIndicator)->executeIndicator(true);
1270 QQuickIndicatorButtonPrivate::get(d->clearIndicator)->executeIndicator(true);
1271 QQuickControl::componentComplete();
1272
1273 if (d->popup)
1274 d->executePopup(true);
1275
1276 if (d->delegateModel && d->ownModel)
1277 static_cast<QQmlDelegateModel *>(d->delegateModel)->componentComplete();
1278}
1279
1280void QQuickSearchField::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem)
1281{
1282 Q_D(QQuickSearchField);
1283 if (oldItem) {
1284 oldItem->removeEventFilter(this);
1285 if (QQuickTextInput *oldInput = qobject_cast<QQuickTextInput *>(oldItem)) {
1286 QObjectPrivate::disconnect(oldInput, &QQuickTextInput::textChanged, d,
1287 &QQuickSearchFieldPrivate::updateText);
1288 }
1289 }
1290
1291 if (newItem) {
1292 newItem->installEventFilter(this);
1293 if (QQuickTextInput *newInput = qobject_cast<QQuickTextInput *>(newItem)) {
1294 QObjectPrivate::connect(newInput, &QQuickTextInput::textChanged, d,
1295 &QQuickSearchFieldPrivate::updateText);
1296 }
1297 #if QT_CONFIG(cursor)
1298 newItem->setCursor(Qt::IBeamCursor);
1299 #endif
1300 }
1301}
1302
1303void QQuickSearchField::itemChange(ItemChange change, const ItemChangeData &data)
1304{
1305 Q_D(QQuickSearchField);
1306 QQuickControl::itemChange(change, data);
1307 if (change == ItemVisibleHasChanged && !data.boolValue) {
1308 d->hidePopup(false);
1309 d->setCurrentItemAtIndex(-1, NoActivate);
1310 }
1311}
1312
1313QT_END_NAMESPACE
1314
1315#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
Combined button and popup list for selecting options.