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

source code of qtdeclarative/src/quicktemplates/qquicksearchfield.cpp