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