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
243
244 QString currentTextRole() const;
247 QString textAt(int index) const;
248 bool isValidIndex(int index) const;
249
251 void executePopup(bool complete = false);
252
253 bool handlePress(const QPointF &point, ulong timestamp) override;
254 bool handleRelease(const QPointF &point, ulong timestamp) override;
255
258
259 void itemImplicitWidthChanged(QQuickItem *item) override;
260 void itemImplicitHeightChanged(QQuickItem *item) override;
261 void itemDestroyed(QQuickItem *item) override;
262
263 static inline QString popupName() { return QStringLiteral("popup"); }
264
267 int currentIndex = -1;
269 QString text;
270 QString textRole;
272 bool hasCurrentIndex = false;
273 bool live = true;
274 bool searchPressed = false;
275 bool clearPressed = false;
276 bool searchFlat = false;
277 bool clearFlat = false;
278 bool searchDown = false;
279 bool clearDown = false;
280 bool hasSearchDown = false;
281 bool hasClearDown = false;
282 bool ownModel = false;
283 bool selectTextByMouse = true;
284 QQmlInstanceModel *delegateModel = nullptr;
285 QQmlComponent *delegate = nullptr;
286 QQuickIndicatorButton *searchIndicator = nullptr;
287 QQuickIndicatorButton *clearIndicator = nullptr;
289};
290
291bool QQuickSearchFieldPrivate::isPopupVisible() const
292{
293 return popup && popup->isVisible();
294}
295
297{
298 if (!popup)
299 executePopup(true);
300
301 if (popup && !popup->isVisible())
302 popup->open();
303}
304
306{
307 Q_Q(QQuickSearchField);
308 if (accept) {
310 // hiding the popup on user interaction should always emit activated,
311 // even if the current index didn't change
312 emit q->activated(highlightedIndex);
313 }
314 if (popup && popup->isVisible())
315 popup->close();
316}
317
318void QQuickSearchFieldPrivate::hideOldPopup(QQuickPopup *popup)
319{
320 if (!popup)
321 return;
322
323 qCDebug(lcItemManagement) << "hiding old popup" << popup;
324
325 popup->setVisible(false);
326 popup->setParentItem(nullptr);
327#if QT_CONFIG(accessibility)
328 // Remove the item from the accessibility tree.
329 QQuickAccessibleAttached *accessible = accessibleAttached(popup);
330 if (accessible)
331 accessible->setIgnored(true);
332#endif
333}
334
336{
337 if (isPopupVisible())
338 QGuiApplication::inputMethod()->reset();
339
340#if QT_CONFIG(quick_itemview)
341 QQuickItemView *itemView = popup->findChild<QQuickItemView *>();
342 if (itemView)
343 itemView->setHighlightRangeMode(QQuickItemView::NoHighlightRange);
344#endif
345
347
348#if QT_CONFIG(quick_itemview)
349 if (itemView)
350 itemView->positionViewAtIndex(highlightedIndex, QQuickItemView::Beginning);
351#endif
352}
353
355{
356 Q_Q(QQuickSearchField);
357 popup = nullptr;
358 emit q->popupChanged();
359}
360
362{
363 Q_Q(QQuickSearchField);
364 int index = delegateModel->indexOf(q->sender(), nullptr);
365 if (index != -1) {
367 hidePopup(true);
368 }
369}
370
372{
373 Q_Q(QQuickSearchField);
374
375 QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(q->sender());
376 if (!button || !button->isHovered() || !button->isEnabled()
377 || QQuickAbstractButtonPrivate::get(button)->touchId != -1)
378 return;
379
380 int index = delegateModel->indexOf(button, nullptr);
381 if (index != -1) {
383
384#if QT_CONFIG(quick_itemview)
385 if (QQuickItemView *itemView = popup->findChild<QQuickItemView *>())
386 itemView->positionViewAtIndex(index, QQuickItemView::Contain);
387#endif
388 }
389}
390
391void QQuickSearchFieldPrivate::createdItem(int index, QObject *object)
392{
393 Q_UNUSED(index);
394 Q_Q(QQuickSearchField);
395 QQuickItem *item = qobject_cast<QQuickItem *>(object);
396 if (item && !item->parentItem()) {
397 if (popup)
398 item->setParentItem(popup->contentItem());
399 else
400 item->setParentItem(q);
401 QQuickItemPrivate::get(item)->setCulled(true);
402 }
403
404 QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(object);
405 if (button) {
406 button->setFocusPolicy(Qt::NoFocus);
407 connect(button, &QQuickAbstractButton::clicked, this,
409 connect(button, &QQuickAbstractButton::hoveredChanged, this,
411 }
412}
413
415{
416 Q_Q(QQuickSearchField);
417 if (q->suggestionCount() == 0)
419 // If the suggestionModel has been updated and the current text matches an item in
420 // the model, update currentIndex and highlightedIndex to the index of that item.
421 if (!text.isEmpty()) {
422 for (int idx = 0; idx < q->suggestionCount(); ++idx) {
423 QString t = textAt(idx);
424 if (t == text) {
427 break;
428 }
429 }
430 }
431
432 emit q->suggestionCountChanged();
433}
434
436{
437 Q_Q(QQuickSearchField);
438 if (isPopupVisible()) {
439 if (highlightedIndex < q->suggestionCount() - 1)
441 }
442}
443
445{
446 if (isPopupVisible()) {
447 if (highlightedIndex > 0)
449 }
450}
451
453{
454 Q_Q(QQuickSearchField);
455 if (currentIndex == index)
456 return;
457
458 currentIndex = index;
459 emit q->currentIndexChanged();
460}
461
463{
464 Q_Q(QQuickSearchField);
465 if (currentIndex == index)
466 return;
467
468 currentIndex = index;
469 emit q->currentIndexChanged();
470
472
473 if (activate)
474 emit q->activated(index);
475}
476
478{
479 Q_Q(QQuickSearchField);
480 int index = -1;
481
482 if (isPopupVisible()) {
483 if (currentIndex >= 0)
484 index = currentIndex;
485 else if (q->suggestionCount() > 0)
486 index = 0; // auto-highlight first suggestion
487 }
488
490}
491
493{
494 Q_Q(QQuickSearchField);
495 if (highlightedIndex == index)
496 return;
497
498 highlightedIndex = index;
499 emit q->highlightedIndexChanged();
500
501 if (highlight)
502 emit q->highlighted(index);
503}
504
506{
507 Q_Q(QQuickSearchField);
508
509 if (!contentItem)
510 return;
511
512 const QQuickTextInput *input = qobject_cast<QQuickTextInput *>(contentItem);
513 if (!input)
514 return;
515
516 const int pos = input->cursorPosition();
517 if (cursorPosition == pos)
518 return;
519
520 cursorPosition = pos;
521 emit q->cursorPositionChanged();
522}
523
525{
526 Q_Q(QQuickSearchField);
527 bool ownedOldModel = ownModel;
528 QQmlInstanceModel *oldModel = delegateModel;
529
530 if (oldModel) {
531 disconnect(delegateModel, &QQmlInstanceModel::countChanged, this,
533 disconnect(delegateModel, &QQmlInstanceModel::createdItem, this,
535 }
536
537 ownModel = false;
538 delegateModel = suggestionModel.value<QQmlInstanceModel *>();
539
540 if (!delegateModel && suggestionModel.isValid()) {
541 QQmlDelegateModel *dataModel = new QQmlDelegateModel(qmlContext(q), q);
542 dataModel->setModel(suggestionModel);
543 dataModel->setDelegate(delegate);
544 if (q->isComponentComplete())
545 dataModel->componentComplete();
546
547 ownModel = true;
548 delegateModel = dataModel;
549 }
550
551 if (delegateModel) {
552 connect(delegateModel, &QQmlInstanceModel::countChanged, this,
554 connect(delegateModel, &QQmlInstanceModel::createdItem, this,
556 }
557
558 emit q->delegateModelChanged();
559
560 if (ownedOldModel)
561 delete oldModel;
562}
563
565{
566 return textRole.isEmpty() ? QStringLiteral("modelData") : textRole;
567}
568
570{
571 Q_Q(QQuickSearchField);
572 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(contentItem);
573 if (!input)
574 return;
575
576 const QString textInput = input->text();
577
578 if (text != textInput) {
579 q->setText(textInput);
580 emit q->textEdited();
581
584
585 if (live)
586 emit q->searchTriggered();
587 }
588
589 if (text.isEmpty()) {
590 if (isPopupVisible())
591 hidePopup(false);
592 } else {
593 if (delegateModel && delegateModel->count() > 0) {
594 if (!isPopupVisible())
596 } else {
597 if (isPopupVisible())
598 hidePopup(false);
599 }
600 }
601}
602
604{
605 Q_Q(QQuickSearchField);
606 const QString currentText = textAt(currentIndex);
607
608 if (text != currentText)
609 q->setText(currentText);
610}
611
612QString QQuickSearchFieldPrivate::textAt(int index) const
613{
614 if (!isValidIndex(index))
615 return QString();
616
617 return delegateModel->stringValue(index, currentTextRole());
618}
619
621{
622 return delegateModel && index >= 0 && index < delegateModel->count();
623}
624
626{
627 Q_Q(QQuickSearchField);
628 quickCancelDeferred(q, popupName());
629}
630
632{
633 Q_Q(QQuickSearchField);
634 if (popup.wasExecuted())
635 return;
636
637 if (!popup || complete)
638 quickBeginDeferred(q, popupName(), popup);
639 if (complete)
640 quickCompleteDeferred(q, popupName(), popup);
641}
642
643bool QQuickSearchFieldPrivate::handlePress(const QPointF &point, ulong timestamp)
644{
645 Q_Q(QQuickSearchField);
646 QQuickControlPrivate::handlePress(point, timestamp);
647
648 QQuickItem *si = searchIndicator->indicator();
649 QQuickItem *ci = clearIndicator->indicator();
650 const bool isSearch = si && si->isEnabled() && si->contains(q->mapToItem(si, point));
651 const bool isClear = ci && ci->isEnabled() && ci->contains(q->mapToItem(ci, point));
652
653 if (isSearch) {
654 searchIndicator->setPressed(true);
656 } else if (isClear) {
657 clearIndicator->setPressed(true);
659 }
660
661 return true;
662}
663
664bool QQuickSearchFieldPrivate::handleRelease(const QPointF &point, ulong timestamp)
665{
666 QQuickControlPrivate::handleRelease(point, timestamp);
667 if (searchIndicator->isPressed())
668 searchIndicator->setPressed(false);
669 else if (clearIndicator->isPressed())
670 clearIndicator->setPressed(false);
671 return true;
672}
673
675{
676 Q_Q(QQuickSearchField);
677
678 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(contentItem);
679 if (!input)
680 return;
681
682 input->forceActiveFocus();
683 emit q->searchButtonPressed();
684}
685
687{
688 Q_Q(QQuickSearchField);
689
690 if (text.isEmpty())
691 return;
692
693 // if text is not null then clear text, also reset highlightedIndex and currentIndex
694 if (!text.isEmpty()) {
697 q->setText(QString());
698
699 if (isPopupVisible())
700 hidePopup(false);
701
702 emit q->clearButtonPressed();
703 }
704}
705
707{
708 QQuickControlPrivate::itemImplicitWidthChanged(item);
709 if (item == searchIndicator->indicator())
710 emit searchIndicator->implicitIndicatorWidthChanged();
711 if (item == clearIndicator->indicator())
712 emit clearIndicator->implicitIndicatorWidthChanged();
713}
714
716{
717 QQuickControlPrivate::itemImplicitHeightChanged(item);
718 if (item == searchIndicator->indicator())
719 emit searchIndicator->implicitIndicatorHeightChanged();
720 if (item == clearIndicator->indicator())
721 emit clearIndicator->implicitIndicatorHeightChanged();
722}
723
725{
726 QQuickControlPrivate::itemDestroyed(item);
727 if (item == searchIndicator->indicator())
728 searchIndicator->setIndicator(nullptr);
729 if (item == clearIndicator->indicator())
730 clearIndicator->setIndicator(nullptr);
731}
732
733QQuickSearchField::QQuickSearchField(QQuickItem *parent)
734 : QQuickControl(*(new QQuickSearchFieldPrivate), parent)
735{
736 Q_D(QQuickSearchField);
737 d->searchIndicator = new QQuickIndicatorButton(this);
738 d->clearIndicator = new QQuickIndicatorButton(this);
739
740 setFocusPolicy(Qt::StrongFocus);
741 setFlag(QQuickItem::ItemIsFocusScope);
742 setAcceptedMouseButtons(Qt::LeftButton);
743#if QT_CONFIG(cursor)
744 setCursor(Qt::ArrowCursor);
745#endif
746
747 d->init();
748}
749
750QQuickSearchField::~QQuickSearchField()
751{
752 Q_D(QQuickSearchField);
753 d->removeImplicitSizeListener(d->searchIndicator->indicator());
754 d->removeImplicitSizeListener(d->clearIndicator->indicator());
755
756 if (d->popup) {
757 QObjectPrivate::disconnect(d->popup.data(), &QQuickPopup::visibleChanged, d,
758 &QQuickSearchFieldPrivate::popupVisibleChanged);
759 d->hideOldPopup(d->popup);
760 d->popup = nullptr;
761 }
762}
763
764/*!
765 \qmlproperty model QtQuick.Controls::SearchField::suggestionModel
766
767 This property holds the data model used to display search suggestions in the popup menu.
768
769 \code
770 SearchField {
771 textRole: "age"
772 suggestionModel: ListModel {
773 ListElement { name: "Karen"; age: "66" }
774 ListElement { name: "Jim"; age: "32" }
775 ListElement { name: "Pamela"; age: "28" }
776 }
777 }
778 \endcode
779
780 \sa textRole
781 */
782
783QVariant QQuickSearchField::suggestionModel() const
784{
785 Q_D(const QQuickSearchField);
786 return d->suggestionModel;
787}
788
789void QQuickSearchField::setSuggestionModel(const QVariant &model)
790{
791 Q_D(QQuickSearchField);
792
793 QVariant suggestionModel;
794 if (QJSValue *value = get_if<QJSValue>(&suggestionModel))
795 suggestionModel = value->toVariant();
796 else
797 suggestionModel = model;
798
799 if (d->suggestionModel == suggestionModel)
800 return;
801
802 d->suggestionModel = suggestionModel;
803 d->createDelegateModel();
804 emit suggestionCountChanged();
805 emit suggestionModelChanged();
806}
807
808/*!
809 \readonly
810 \qmlproperty model QtQuick.Controls::SearchField::delegateModel
811
812 This property holds the model that provides delegate instances for the search field.
813
814 It is typically assigned to a \l ListView in the \l {Popup::}{contentItem}
815 of the \l popup.
816
817 */
818QQmlInstanceModel *QQuickSearchField::delegateModel() const
819{
820 Q_D(const QQuickSearchField);
821 return d->delegateModel;
822}
823
824/*!
825 \readonly
826 \qmlproperty int QtQuick.Controls::SearchField::suggestionCount
827
828 This property holds the number of suggestions to display from the suggestion model.
829 */
830int QQuickSearchField::suggestionCount() const
831{
832 Q_D(const QQuickSearchField);
833 return d->delegateModel ? d->delegateModel->count() : 0;
834}
835
836/*!
837 \qmlproperty int QtQuick.Controls::SearchField::currentIndex
838
839 This property holds the index of the currently selected suggestion in the popup list.
840
841 Its value is \c -1 when no suggestion is selected.
842
843 currentIndex is not modified automatically when the model changes or when the user types
844 or edits text. It is updated only when the user explicitly selects a suggestion, either
845 by clicking an item in the popup, or by pressing Enter on a highlighted item.
846
847 currentIndex can be set; for example, to display the first item in the model at startup.
848 Before doing so, ensure that the model is not empty:
849
850 \snippet qtquickcontrols-searchfield-currentIndex.qml currentIndex
851
852 \sa activated(), text, highlightedIndex
853 */
854int QQuickSearchField::currentIndex() const
855{
856 Q_D(const QQuickSearchField);
857 return d->currentIndex;
858}
859
860void QQuickSearchField::setCurrentIndex(int index)
861{
862 Q_D(QQuickSearchField);
863 d->hasCurrentIndex = true;
864 d->setCurrentIndex(index);
865}
866
867/*!
868 \readonly
869 \qmlproperty int QtQuick.Controls::SearchField::highlightedIndex
870
871 This property holds the index of the currently highlighted item in
872 the popup list.
873
874 When the highlighted item is activated, the popup closes, \l currentIndex
875 is updated to match \c highlightedIndex, and this property is reset to
876 \c -1, indicating that no item is currently highlighted.
877
878 \sa highlighted(), currentIndex
879*/
880int QQuickSearchField::highlightedIndex() const
881{
882 Q_D(const QQuickSearchField);
883 return d->highlightedIndex;
884}
885
886/*!
887 \qmlproperty string QtQuick.Controls::SearchField::text
888
889 This property holds the current input text in the search field.
890
891 Text is bound to the user input, triggering suggestion updates or search logic.
892
893 \sa searchTriggered(), textEdited()
894 */
895QString QQuickSearchField::text() const
896{
897 Q_D(const QQuickSearchField);
898 return d->text;
899}
900
901void QQuickSearchField::setText(const QString &text)
902{
903 Q_D(QQuickSearchField);
904 if (d->text == text)
905 return;
906
907 d->text = text;
908 emit textChanged();
909}
910
911/*!
912 \qmlproperty string QtQuick.Controls::SearchField::textRole
913
914 This property holds the model role used to display items in the suggestion model
915 shown in the popup list.
916
917 When the model has multiple roles, \c textRole can be set to determine
918 which role should be displayed.
919 */
920QString QQuickSearchField::textRole() const
921{
922 Q_D(const QQuickSearchField);
923 return d->textRole;
924}
925
926void QQuickSearchField::setTextRole(const QString &textRole)
927{
928 Q_D(QQuickSearchField);
929 if (d->textRole == textRole)
930 return;
931
932 d->textRole = textRole;
933}
934
935/*!
936 \qmlproperty bool QtQuick.Controls::SearchField::live
937
938 This property holds a boolean value that determines whether the search is triggered
939 on every text edit.
940
941 When set to \c true, the \l searchTriggered() signal is emitted on each text change,
942 allowing you to respond to every keystroke.
943 When set to \c false, the \l searchTriggered() is only emitted when the user presses
944 the Enter or Return key.
945
946 \sa searchTriggered()
947 */
948
949bool QQuickSearchField::isLive() const
950{
951 Q_D(const QQuickSearchField);
952 return d->live;
953}
954
955void QQuickSearchField::setLive(const bool live)
956{
957 Q_D(QQuickSearchField);
958
959 if (d->live == live)
960 return;
961
962 d->live = live;
963}
964
965/*!
966 \include qquickindicatorbutton.qdocinc {properties} {SearchField} {searchIndicator}
967
968 This property holds the search indicator. Pressing it triggers
969 \l searchButtonPressed.
970
971 It is exposed so that styles and applications can customize it through its
972 internal properties (for example, replacing or removing the \c searchIndicator
973 via \c searchIndicator.indicator, or reacting to interaction state such as
974 pressed and hovered).
975
976 \sa {SearchField's Indicators}
977 */
978QQuickIndicatorButton *QQuickSearchField::searchIndicator() const
979{
980 Q_D(const QQuickSearchField);
981 return d->searchIndicator;
982}
983
984/*!
985 \include qquickindicatorbutton.qdocinc {properties} {SearchField} {clearIndicator}
986
987 This property holds the clear indicator. Pressing it triggers
988 \l clearButtonPressed.
989
990 It is exposed so that styles and applications can customize it through its
991 internal properties (for example, replacing or removing the \c clearIndicator
992 via \c clearIndicator.indicator, or reacting to interaction state such as
993 pressed and hovered).
994
995 \sa {SearchField's Indicators}
996*/
997QQuickIndicatorButton *QQuickSearchField::clearIndicator() const
998{
999 Q_D(const QQuickSearchField);
1000 return d->clearIndicator;
1001}
1002
1003
1004/*!
1005 \qmlproperty Popup QtQuick.Controls::SearchField::popup
1006
1007 This property holds the popup.
1008
1009 The popup can be opened or closed manually, if necessary:
1010
1011 \code
1012 onSpecialEvent: searchField.popup.close()
1013 \endcode
1014 */
1015QQuickPopup *QQuickSearchField::popup() const
1016{
1017 QQuickSearchFieldPrivate *d = const_cast<QQuickSearchFieldPrivate *>(d_func());
1018 if (!d->popup)
1019 d->executePopup(isComponentComplete());
1020 return d->popup;
1021}
1022
1023void QQuickSearchField::setPopup(QQuickPopup *popup)
1024{
1025 Q_D(QQuickSearchField);
1026 if (d->popup == popup)
1027 return;
1028
1029 if (!d->popup.isExecuting())
1030 d->cancelPopup();
1031
1032 if (d->popup) {
1033 QObjectPrivate::disconnect(d->popup.data(), &QQuickPopup::destroyed, d,
1034 &QQuickSearchFieldPrivate::popupDestroyed);
1035 QObjectPrivate::disconnect(d->popup.data(), &QQuickPopup::visibleChanged, d,
1036 &QQuickSearchFieldPrivate::popupVisibleChanged);
1037 QQuickSearchFieldPrivate::hideOldPopup(d->popup);
1038 }
1039
1040 if (popup) {
1041#if QT_CONFIG(wayland)
1042 QQuickPopupPrivate::get(popup)->extendedWindowType = QNativeInterface::Private::QWaylandWindow::ComboBox;
1043#endif
1044 QQuickPopupPrivate::get(popup)->allowVerticalFlip = true;
1045 popup->setClosePolicy(QQuickPopup::CloseOnEscape | QQuickPopup::CloseOnPressOutsideParent);
1046 QObjectPrivate::connect(popup, &QQuickPopup::visibleChanged, d,
1047 &QQuickSearchFieldPrivate::popupVisibleChanged);
1048 // QQuickPopup does not derive from QQuickItemChangeListener, so we cannot use
1049 // QQuickItemChangeListener::itemDestroyed so we have to use QObject::destroyed
1050 QObjectPrivate::connect(popup, &QQuickPopup::destroyed, d,
1051 &QQuickSearchFieldPrivate::popupDestroyed);
1052
1053#if QT_CONFIG(quick_itemview)
1054 if (QQuickItemView *itemView = popup->findChild<QQuickItemView *>())
1055 itemView->setHighlightRangeMode(QQuickItemView::NoHighlightRange);
1056#endif
1057 }
1058
1059 d->popup = popup;
1060 if (!d->popup.isExecuting())
1061 emit popupChanged();
1062}
1063
1064/*!
1065 \qmlproperty Component QtQuick.Controls::SearchField::delegate
1066
1067 This property holds a delegate that presents an item in the search field popup.
1068
1069 It is recommended to use \l ItemDelegate (or any other \l AbstractButton
1070 derivatives) as the delegate. This ensures that the interaction works as
1071 expected, and the popup will automatically close when appropriate. When
1072 other types are used as the delegate, the popup must be closed manually.
1073 For example, if \l MouseArea is used:
1074
1075 \code
1076 delegate: Rectangle {
1077 // ...
1078 MouseArea {
1079 // ...
1080 onClicked: searchField.popup.close()
1081 }
1082 }
1083 \endcode
1084
1085 \include delegate-ownership.qdocinc {no-ownership-since-6.11} {SearchField}
1086*/
1087QQmlComponent *QQuickSearchField::delegate() const
1088{
1089 Q_D(const QQuickSearchField);
1090 return d->delegate;
1091}
1092
1093void QQuickSearchField::setDelegate(QQmlComponent *delegate)
1094{
1095 Q_D(QQuickSearchField);
1096 if (d->delegate == delegate)
1097 return;
1098
1099 d->delegate = delegate;
1100 QQmlDelegateModel *delegateModel = qobject_cast<QQmlDelegateModel *>(d->delegateModel);
1101 if (delegateModel)
1102 delegateModel->setDelegate(d->delegate);
1103 emit delegateChanged();
1104}
1105
1106/*!
1107 \qmlproperty string QtQuick.Controls::SearchField::placeholderText
1108 \since 6.12
1109
1110 This property holds the hint that is displayed in the SearchField before the user
1111 enters text.
1112*/
1113QString QQuickSearchField::placeholderText() const
1114{
1115 Q_D(const QQuickSearchField);
1116 return d->placeholderText;
1117}
1118
1119void QQuickSearchField::setPlaceholderText(const QString &text)
1120{
1121 Q_D(QQuickSearchField);
1122 if (d->placeholderText == text)
1123 return;
1124
1125 d->placeholderText = text;
1126 emit placeholderTextChanged();
1127}
1128
1129/*!
1130 \qmlproperty bool QtQuick.Controls::SearchField::selectTextByMouse
1131 \since 6.12
1132
1133 This property holds whether the text can be selected with the mouse.
1134
1135 The default value is \c true.
1136*/
1137bool QQuickSearchField::selectTextByMouse() const
1138{
1139 Q_D(const QQuickSearchField);
1140 return d->selectTextByMouse;
1141}
1142
1143void QQuickSearchField::setSelectTextByMouse(const bool selectable)
1144{
1145 Q_D(QQuickSearchField);
1146 if (d->selectTextByMouse == selectable)
1147 return;
1148
1149 d->selectTextByMouse = selectable;
1150 emit selectTextByMouseChanged();
1151}
1152
1153/*!
1154 \readonly
1155 \qmlproperty string QtQuick.Controls::SearchField::selectedText
1156 \since 6.12
1157
1158 This read-only property holds the currently selected text.
1159*/
1160QString QQuickSearchField::selectedText() const
1161{
1162 Q_D(const QQuickSearchField);
1163 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(d->contentItem);
1164 if (!input)
1165 return QString();
1166 return input->selectedText();
1167}
1168
1169/*!
1170 \readonly
1171 \qmlproperty int QtQuick.Controls::SearchField::selectionStart
1172 \since 6.12
1173
1174 The cursor position before the first character in the current selection.
1175
1176 This property is read-only. To change the selection, use select(start, end),
1177 selectAll(), or selectWord().
1178
1179 \sa selectionEnd, cursorPosition, selectedText
1180*/
1181int QQuickSearchField::selectionStart() const
1182{
1183 Q_D(const QQuickSearchField);
1184 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(d->contentItem);
1185 if (!input)
1186 return 0;
1187 return input->selectionStart();
1188}
1189
1190/*!
1191 \readonly
1192 \qmlproperty int QtQuick.Controls::SearchField::selectionEnd
1193 \since 6.12
1194
1195 The cursor position after the last character in the current selection.
1196
1197 This property is read-only. To change the selection, use select(start, end),
1198 selectAll(), or selectWord().
1199
1200 \sa selectionStart, cursorPosition, selectedText
1201*/
1202int QQuickSearchField::selectionEnd() const
1203{
1204 Q_D(const QQuickSearchField);
1205 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(d->contentItem);
1206 if (!input)
1207 return 0;
1208 return input->selectionEnd();
1209}
1210
1211/*!
1212 \qmlproperty int QtQuick.Controls::SearchField::cursorPosition
1213 \since 6.12
1214
1215 The position of the cursor in the text field. The cursor is positioned between
1216 characters.
1217
1218 \note The \e characters in this case refer to the string of \l QChar objects,
1219 therefore 16-bit Unicode characters, and the position is considered an index
1220 into this string. This does not necessarily correspond to individual graphemes
1221 in the writing system, as a single grapheme may be represented by multiple
1222 Unicode characters, such as in the case of surrogate pairs, linguistic
1223 ligatures or diacritics.
1224*/
1225int QQuickSearchField::cursorPosition() const
1226{
1227 Q_D(const QQuickSearchField);
1228 return d->cursorPosition;
1229}
1230
1231void QQuickSearchField::setCursorPosition(int position)
1232{
1233 Q_D(QQuickSearchField);
1234 if (d->cursorPosition == position)
1235 return;
1236
1237 d->cursorPosition = position;
1238
1239 if (QQuickTextInput *input = qobject_cast<QQuickTextInput *>(d->contentItem)) {
1240 if (input->cursorPosition() != position)
1241 input->setCursorPosition(position);
1242 }
1243
1244 emit cursorPositionChanged();
1245}
1246
1247/*!
1248 \qmlmethod void QtQuick.Controls::SearchField::selectAll()
1249 \since 6.12
1250
1251 Selects all text in the text field of the control.
1252*/
1253void QQuickSearchField::selectAll()
1254{
1255 Q_D(QQuickSearchField);
1256 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(d->contentItem);
1257 if (!input)
1258 return;
1259 input->selectAll();
1260}
1261
1262/*!
1263 \qmlmethod void QtQuick.Controls::SearchField::select(int start, int end)
1264 \since 6.12
1265
1266 Causes the text from \a start to \a end to be selected.
1267
1268 If either \a start or \a end is out of range, the selection is not changed.
1269
1270 After calling this, selectionStart will become the lesser
1271 and selectionEnd will become the greater (regardless of the order passed
1272 to this method).
1273
1274 \sa selectionStart, selectionEnd
1275*/
1276void QQuickSearchField::select(int start, int end)
1277{
1278 Q_D(QQuickSearchField);
1279 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(d->contentItem);
1280 if (!input)
1281 return;
1282 input->select(start, end);
1283}
1284
1285/*!
1286 \qmlmethod void QtQuick.Controls::SearchField::selectWord()
1287 \since 6.12
1288
1289 Causes the word closest to the current cursor position to be selected.
1290
1291 \sa cursorPosition, selectedText
1292*/
1293void QQuickSearchField::selectWord()
1294{
1295 Q_D(QQuickSearchField);
1296 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(d->contentItem);
1297 if (!input)
1298 return;
1299 input->selectWord();
1300}
1301
1302/*!
1303 \qmlmethod void QtQuick.Controls::SearchField::deselect()
1304 \since 6.12
1305
1306 Removes the active text selection.
1307
1308 \sa selectedText, selectionStart, selectionEnd
1309*/
1310void QQuickSearchField::deselect()
1311{
1312 Q_D(QQuickSearchField);
1313 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(d->contentItem);
1314 if (!input)
1315 return;
1316 input->deselect();
1317}
1318
1319bool QQuickSearchField::eventFilter(QObject *object, QEvent *event)
1320{
1321 Q_D(QQuickSearchField);
1322
1323 switch (event->type()) {
1324 case QEvent::MouseButtonRelease: {
1325 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(d->contentItem);
1326 if (input->hasFocus() && !d->text.isEmpty() && !d->isPopupVisible()
1327 && (d->delegateModel && d->delegateModel->count() > 0)
1328 && static_cast<QMouseEvent*>(event)->button() == Qt::LeftButton) {
1329 d->showPopup();
1330 }
1331 break;
1332 }
1333 case QEvent::FocusOut: {
1334 const bool hasActiveFocus = d->popup && d->popup->hasActiveFocus();
1335 const bool usingPopupWindows =
1336 d->popup ? QQuickPopupPrivate::get(d->popup)->usePopupWindow() : false;
1337 if (qGuiApp->focusObject() != this && !(hasActiveFocus && !usingPopupWindows))
1338 d->hidePopup(false);
1339 break;
1340 }
1341 default:
1342 break;
1343 }
1344 return QQuickControl::eventFilter(object, event);
1345}
1346
1347void QQuickSearchField::focusInEvent(QFocusEvent *event)
1348{
1349 Q_D(QQuickSearchField);
1350 QQuickControl::focusInEvent(event);
1351
1352 if (!d->contentItem)
1353 return;
1354
1355 const auto reason = event->reason();
1356 switch (reason) {
1357 case Qt::TabFocusReason:
1358 case Qt::BacktabFocusReason:
1359 case Qt::ShortcutFocusReason:
1360 case Qt::OtherFocusReason:
1361 if (reason != Qt::OtherFocusReason || !d->isPopupVisible())
1362 d->contentItem->forceActiveFocus(reason);
1363 break;
1364 default:
1365 break;
1366 }
1367}
1368
1369void QQuickSearchField::focusOutEvent(QFocusEvent *event)
1370{
1371 Q_D(QQuickSearchField);
1372 QQuickControl::focusOutEvent(event);
1373
1374 const bool hasActiveFocus = d->popup && d->popup->hasActiveFocus();
1375 const bool usingPopupWindows = d->popup && QQuickPopupPrivate::get(d->popup)->usePopupWindow();
1376
1377 if (qGuiApp->focusObject() != d->contentItem && !(hasActiveFocus && !usingPopupWindows))
1378 d->hidePopup(false);
1379}
1380
1381void QQuickSearchField::hoverEnterEvent(QHoverEvent *event)
1382{
1383 Q_D(QQuickSearchField);
1384 QQuickControl::hoverEnterEvent(event);
1385 QQuickItem *si = d->searchIndicator->indicator();
1386 QQuickItem *ci = d->clearIndicator->indicator();
1387 d->searchIndicator->setHovered(si && si->isEnabled() && si->contains(mapToItem(si, event->position())));
1388 d->clearIndicator->setHovered(ci && ci->isEnabled() && ci->contains(mapToItem(ci, event->position())));
1389 event->ignore();
1390}
1391
1392void QQuickSearchField::hoverMoveEvent(QHoverEvent *event)
1393{
1394 Q_D(QQuickSearchField);
1395 QQuickControl::hoverMoveEvent(event);
1396 QQuickItem *si = d->searchIndicator->indicator();
1397 QQuickItem *ci = d->clearIndicator->indicator();
1398 d->searchIndicator->setHovered(si && si->isEnabled() && si->contains(mapToItem(si, event->position())));
1399 d->clearIndicator->setHovered(ci && ci->isEnabled() && ci->contains(mapToItem(ci, event->position())));
1400 event->ignore();
1401}
1402
1403void QQuickSearchField::hoverLeaveEvent(QHoverEvent *event)
1404{
1405 Q_D(QQuickSearchField);
1406 QQuickControl::hoverLeaveEvent(event);
1407 d->searchIndicator->setHovered(false);
1408 d->clearIndicator->setHovered(false);
1409 event->ignore();
1410}
1411
1412void QQuickSearchField::keyPressEvent(QKeyEvent *event)
1413{
1414 Q_D(QQuickSearchField);
1415
1416 const auto key = event->key();
1417 const bool hasModel = !d->suggestionModel.isNull();
1418 const bool hasText = !d->text.isEmpty();
1419
1420 switch (key) {
1421 case Qt::Key_Escape:
1422 case Qt::Key_Back:
1423 if (d->isPopupVisible()) {
1424 d->hidePopup(false);
1425 event->accept();
1426 } else if (hasText) {
1427 setText(QString());
1428 event->accept();
1429 }
1430 break;
1431 case Qt::Key_Return:
1432 case Qt::Key_Enter:
1433 if (d->isPopupVisible())
1434 d->hidePopup(true);
1435 emit accepted();
1436 emit searchTriggered();
1437 event->accept();
1438 break;
1439 case Qt::Key_Up:
1440 if (hasModel && hasText) {
1441 d->decreaseCurrentIndex();
1442 event->accept();
1443 }
1444 break;
1445 case Qt::Key_Down:
1446 if (hasModel && hasText) {
1447 d->increaseCurrentIndex();
1448 event->accept();
1449 }
1450 break;
1451 case Qt::Key_Home:
1452 if (d->isPopupVisible()) {
1453 d->setHighlightedIndex(0, Highlight);
1454 event->accept();
1455 }
1456 break;
1457 case Qt::Key_End:
1458 if (d->isPopupVisible()) {
1459 d->setHighlightedIndex(suggestionCount() - 1, Highlight);
1460 event->accept();
1461 }
1462 break;
1463 default:
1464 QQuickControl::keyPressEvent(event);
1465 break;
1466 }
1467}
1468
1469void QQuickSearchField::classBegin()
1470{
1471 Q_D(QQuickSearchField);
1472 QQuickControl::classBegin();
1473
1474 QQmlContext *context = qmlContext(this);
1475 if (context) {
1476 QQmlEngine::setContextForObject(d->searchIndicator, context);
1477 QQmlEngine::setContextForObject(d->clearIndicator, context);
1478 }
1479}
1480
1481void QQuickSearchField::componentComplete()
1482{
1483 Q_D(QQuickSearchField);
1484 QQuickIndicatorButtonPrivate::get(d->searchIndicator)->executeIndicator(true);
1485 QQuickIndicatorButtonPrivate::get(d->clearIndicator)->executeIndicator(true);
1486 QQuickControl::componentComplete();
1487
1488 if (d->popup)
1489 d->executePopup(true);
1490
1491 if (d->delegateModel && d->ownModel)
1492 static_cast<QQmlDelegateModel *>(d->delegateModel)->componentComplete();
1493}
1494
1495void QQuickSearchField::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem)
1496{
1497 Q_D(QQuickSearchField);
1498 if (oldItem) {
1499 oldItem->removeEventFilter(this);
1500 if (QQuickTextInput *oldInput = qobject_cast<QQuickTextInput *>(oldItem)) {
1501 QObjectPrivate::disconnect(oldInput, &QQuickTextInput::textChanged, d,
1502 &QQuickSearchFieldPrivate::updateText);
1503 QObjectPrivate::disconnect(oldInput, &QQuickTextInput::cursorPositionChanged,
1504 d, &QQuickSearchFieldPrivate::updateCursorPosition);
1505 }
1506 }
1507
1508 if (newItem) {
1509 newItem->installEventFilter(this);
1510 if (QQuickTextInput *newInput = qobject_cast<QQuickTextInput *>(newItem)) {
1511 QObjectPrivate::connect(newInput, &QQuickTextInput::textChanged, d,
1512 &QQuickSearchFieldPrivate::updateText);
1513 QObjectPrivate::connect(newInput, &QQuickTextInput::cursorPositionChanged,
1514 d, &QQuickSearchFieldPrivate::updateCursorPosition);
1515 }
1516 #if QT_CONFIG(cursor)
1517 newItem->setCursor(Qt::IBeamCursor);
1518 #endif
1519 }
1520}
1521
1522void QQuickSearchField::itemChange(ItemChange change, const ItemChangeData &data)
1523{
1524 Q_D(QQuickSearchField);
1525 QQuickControl::itemChange(change, data);
1526 if (change == ItemVisibleHasChanged && !data.boolValue) {
1527 d->hidePopup(false);
1528 d->setCurrentItemAtIndex(-1, NoActivate);
1529 }
1530}
1531
1532QT_END_NAMESPACE
1533
1534#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.