1// Copyright (C) 2017 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 "qquickcombobox_p.h"
5#include "qquickcontrol_p_p.h"
6#include "qquickabstractbutton_p.h"
7#include "qquickabstractbutton_p_p.h"
8#include "qquickpopup_p_p.h"
9#include "qquickdeferredexecute_p_p.h"
10
11#include <QtCore/qregularexpression.h>
12#include <QtCore/qabstractitemmodel.h>
13#include <QtCore/qglobal.h>
14#include <QtGui/qinputmethod.h>
15#include <QtGui/qguiapplication.h>
16#include <QtGui/private/qguiapplication_p.h>
17#include <QtGui/qpa/qplatformtheme.h>
18#include <QtQml/qjsvalue.h>
19#include <QtQml/qqmlcontext.h>
20#include <QtQml/qqmlcomponent.h>
21#include <QtQml/private/qlazilyallocated_p.h>
22#include <private/qqmldelegatemodel_p.h>
23#include <QtQuick/private/qquickaccessibleattached_p.h>
24#include <QtQuick/private/qquickevents_p_p.h>
25#include <QtQuick/private/qquicktextinput_p.h>
26#include <QtQuick/private/qquicktextinput_p_p.h>
27#if QT_CONFIG(quick_itemview)
28#include <QtQuick/private/qquickitemview_p.h>
29#endif
30
31QT_BEGIN_NAMESPACE
32
33Q_STATIC_LOGGING_CATEGORY(lcCalculateWidestTextWidth, "qt.quick.controls.combobox.calculatewidesttextwidth")
34
35/*!
36 \qmltype ComboBox
37 \inherits Control
38//! \nativetype QQuickComboBox
39 \inqmlmodule QtQuick.Controls
40 \since 5.7
41 \ingroup qtquickcontrols-input
42 \ingroup qtquickcontrols-focusscopes
43 \brief Combined button and popup list for selecting options.
44
45 \image qtquickcontrols-combobox.gif
46
47 ComboBox is a combined button and popup list. It provides a means of
48 presenting a list of options to the user in a way that takes up the
49 minimum amount of screen space.
50
51 ComboBox is populated with a data model. The data model is commonly
52 a JavaScript array, a \l ListModel or an integer, but other types
53 of \l {qml-data-models}{data models} are also supported.
54
55 \code
56 ComboBox {
57 model: ["First", "Second", "Third"]
58 }
59 \endcode
60
61 \section1 Editable ComboBox
62
63 ComboBox can be made \l editable. An editable combo box auto-completes
64 its text based on what is available in the model.
65
66 The following example demonstrates appending content to an editable
67 combo box by reacting to the \l accepted signal.
68
69 \snippet qtquickcontrols-combobox-accepted.qml combobox
70
71 \section1 ComboBox's Popup
72
73 By default, clicking outside of ComboBox's popup will close it, and the
74 event is propagated to items lower in the stacking order. To prevent the
75 popup from closing, set its \l {Popup::}{closePolicy}:
76
77 \snippet qtquickcontrols-combobox-popup.qml closePolicy
78
79 To prevent event propagation, set its \l {Popup::}{modal} property to
80 \c true:
81
82 \snippet qtquickcontrols-combobox-popup.qml modal
83
84 \section1 ComboBox Model Roles
85
86 ComboBox is able to visualize standard \l {qml-data-models}{data models}
87 that provide the \c modelData role:
88 \list
89 \li models that have only one role
90 \li models that do not have named roles (JavaScript array, integer)
91 \endlist
92
93 When using models that have multiple named roles, ComboBox must be configured
94 to use a specific \l {textRole}{text role} for its \l {displayText}{display text}
95 and \l delegate instances. If you want to use a role of the model item
96 that corresponds to the text role, set \l valueRole. The \l currentValue
97 property and \l indexOfValue() method can then be used to get information
98 about those values.
99
100 For example:
101
102 \snippet qtquickcontrols-combobox-valuerole.qml file
103
104 \note If ComboBox is assigned a data model that has multiple named roles, but
105 \l textRole is not defined, ComboBox is unable to visualize it and throws a
106 \c {ReferenceError: modelData is not defined}.
107
108 \sa {Customizing ComboBox}, {Input Controls}, {Focus Management in Qt Quick Controls}
109*/
110
111/*!
112 \qmlsignal void QtQuick.Controls::ComboBox::activated(int index)
113
114 This signal is emitted when the item at \a index is activated by the user.
115
116 An item is activated when it is selected while the popup is open,
117 causing the popup to close (and \l currentIndex to change),
118 or while the popup is closed and the combo box is navigated via
119 keyboard, causing the \l currentIndex to change.
120 The \l currentIndex property is set to \a index.
121
122 \sa currentIndex
123*/
124
125/*!
126 \qmlsignal void QtQuick.Controls::ComboBox::highlighted(int index)
127
128 This signal is emitted when the item at \a index in the popup list is highlighted by the user.
129
130 The highlighted signal is only emitted when the popup is open and an item
131 is highlighted, but not necessarily \l activated.
132
133 \sa highlightedIndex
134*/
135
136/*!
137 \since QtQuick.Controls 2.2 (Qt 5.9)
138 \qmlsignal void QtQuick.Controls::ComboBox::accepted()
139
140 This signal is emitted when the \uicontrol Return or \uicontrol Enter key is pressed
141 on an \l editable combo box.
142
143 You can handle this signal in order to add the newly entered
144 item to the model, for example:
145
146 \snippet qtquickcontrols-combobox-accepted.qml combobox
147
148 Before the signal is emitted, a check is done to see if the string
149 exists in the model. If it does, \l currentIndex will be set to its index,
150 and \l currentText to the string itself.
151
152 After the signal has been emitted, and if the first check failed (that is,
153 the item did not exist), another check will be done to see if the item was
154 added by the signal handler. If it was, the \l currentIndex and
155 \l currentText are updated accordingly. Otherwise, they will be set to
156 \c -1 and \c "", respectively.
157
158 \note If there is a \l validator set on the combo box, the signal will only be
159 emitted if the input is in an acceptable state.
160*/
161
162namespace {
163 enum Activation { NoActivate, Activate };
164 enum Highlighting { NoHighlight, Highlight };
165}
166
167// ### Qt7: Remove this class. Use QQmlDelegateModel instead.
168class QQuickComboBoxDelegateModel : public QQmlDelegateModel
169{
170public:
171 explicit QQuickComboBoxDelegateModel(QQuickComboBox *combo);
172 QVariant variantValue(int index, const QString &role) override;
173
174private:
175 QQuickComboBox *combo = nullptr;
176};
177
178QQuickComboBoxDelegateModel::QQuickComboBoxDelegateModel(QQuickComboBox *combo)
179 : QQmlDelegateModel(qmlContext(combo), combo),
180 combo(combo)
181{
182}
183
184QVariant QQuickComboBoxDelegateModel::variantValue(int index, const QString &role)
185{
186 // ### Qt7: Get rid of this. Why do we special case lists of variant maps with
187 // exactly one entry? There are many other ways of producing a list of
188 // map-like things with exactly one entry. And what if some of the maps
189 // in the list have more than one entry? You get inconsistent results.
190 if (role == QLatin1String("modelData")) {
191 const QVariant model = combo->model();
192 if (model.metaType() == QMetaType::fromType<QVariantList>()) {
193 const QVariant object = model.toList().value(i: index);
194 if (object.metaType() == QMetaType::fromType<QVariantMap>()) {
195 const QVariantMap data = object.toMap();
196 if (data.size() == 1)
197 return data.first();
198 }
199 }
200 }
201
202 return QQmlDelegateModel::variantValue(index, role);
203}
204
205class QQuickComboBoxPrivate : public QQuickControlPrivate
206{
207public:
208 Q_DECLARE_PUBLIC(QQuickComboBox)
209
210 /*
211 This defines which criteria is used to determine the current model element.
212 The criteria is set by the currentIndex and currentValue setters.
213 With CurrentValue the currentIndex will be set to the index of the element
214 having the same value and currentText will be set to the text of that element.
215 With CurrentIndex or None (when none is explicitly set)
216 currentText and currentValue are set to the ones of the element at currentIndex.
217 Upon model changes the two other properties (those not set) are updated accordingly.
218 User interaction (choosing an item) doesn't change this criteria.
219 This means that if we start with a model containing the values [foo, bar, baz],
220 the user selecting "bar" (at index 1) and the model then being updated by inserting
221 a new element at the beginning, the end result will depend on currentElementCriteria:
222 - CurrentIndex or None:
223 currentIndex will still be 1 but currentValue will be "foo"
224 - CurrentValue:
225 currentValue will still be "bar" but currentIndex will be 2
226
227 The different criteria also have slight behavior changes upon initialization and
228 model changes. The following table depicts how the currentIndex is set in different
229 cases depending on the model count:
230
231 | CurrentValue | CurrentIndex | None
232 ---------------------------------------------------------------------
233 componentComplete | | | 0 if not empty
234 ---------------------------------------------------------------------
235 countChanged | | -1 if empty
236 ---------------------------------------------------------------------
237 modelChanged | | -1 if empty, 0 if not empty
238
239 Note that with CurrentValue the currentValue will only be changed by user interactions,
240 not by model changes.
241 */
242 enum class CurrentElementCriteria {
243 None,
244 CurrentIndex,
245 CurrentValue,
246 };
247
248 bool isPopupVisible() const;
249 void showPopup();
250 void hidePopup(bool accept);
251 void togglePopup(bool accept);
252 void popupVisibleChanged();
253 void popupDestroyed();
254
255 void itemClicked();
256 void itemHovered();
257
258 void createdItem(int index, QObject *object);
259 void modelUpdated();
260 void countChanged();
261
262 QString effectiveTextRole() const;
263 void updateEditText();
264 void updateCurrentIndex();
265 void updateCurrentText();
266 void updateCurrentValue();
267 void updateCurrentElements();
268 void updateAcceptableInput();
269
270 void onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList<int> &roles);
271
272 bool isValidIndex(int index) const;
273
274 void acceptInput();
275 QString tryComplete(const QString &inputText);
276
277 void setCurrentItemAtIndex(int, Activation activate);
278 void incrementCurrentIndex();
279 void decrementCurrentIndex();
280 void setCurrentIndex(int index);
281 void updateHighlightedIndex();
282 void setHighlightedIndex(int index, Highlighting highlight);
283
284 void keySearch(const QString &text);
285 int match(int start, const QString &text, Qt::MatchFlags flags) const;
286
287 void createDelegateModel();
288
289 bool handlePress(const QPointF &point, ulong timestamp) override;
290 bool handleMove(const QPointF &point, ulong timestamp) override;
291 bool handleRelease(const QPointF &point, ulong timestamp) override;
292 void handleUngrab() override;
293
294 void cancelIndicator();
295 void executeIndicator(bool complete = false);
296
297 void cancelPopup();
298 void executePopup(bool complete = false);
299
300 void itemImplicitWidthChanged(QQuickItem *item) override;
301 void itemImplicitHeightChanged(QQuickItem *item) override;
302 void itemDestroyed(QQuickItem *item) override;
303
304 void setInputMethodHints(Qt::InputMethodHints hints, bool force = false);
305
306 virtual qreal getContentWidth() const override;
307 qreal calculateWidestTextWidth() const;
308 void maybeUpdateImplicitContentWidth();
309
310 static void hideOldPopup(QQuickPopup *popup);
311
312 QPalette defaultPalette() const override { return QQuickTheme::palette(scope: QQuickTheme::ComboBox); }
313
314 bool flat = false;
315 bool down = false;
316 bool hasDown = false;
317 bool pressed = false;
318 bool ownModel = false;
319 bool keyNavigating = false;
320 bool hasDisplayText = false;
321 bool hasCalculatedWidestText = false;
322 CurrentElementCriteria currentElementCriteria = CurrentElementCriteria::None;
323 int highlightedIndex = -1;
324 int currentIndex = -1;
325 QQuickComboBox::ImplicitContentWidthPolicy implicitContentWidthPolicy = QQuickComboBox::ContentItemImplicitWidth;
326 QVariant model;
327 std::optional<QPointer<QObject>> qobjectModelGuard;
328 QString textRole;
329 QString currentText;
330 QString displayText;
331 QString valueRole;
332 QVariant currentValue;
333 QQuickItem *pressedItem = nullptr;
334 QQmlInstanceModel *delegateModel = nullptr;
335 QQmlComponent *delegate = nullptr;
336 QQuickDeferredPointer<QQuickItem> indicator;
337 QQuickDeferredPointer<QQuickPopup> popup;
338 bool m_acceptableInput = true;
339 bool acceptedEscKeyPress = false;
340 bool receivedEscKeyPress = false;
341
342 struct ExtraData {
343 bool editable = false;
344 bool accepting = false;
345 bool allowComplete = false;
346 bool selectTextByMouse = false;
347 Qt::InputMethodHints inputMethodHints = Qt::ImhNone;
348 QString editText;
349#if QT_CONFIG(validator)
350 QValidator *validator = nullptr;
351#endif
352 };
353 QLazilyAllocated<ExtraData> extra;
354};
355
356bool QQuickComboBoxPrivate::isPopupVisible() const
357{
358 return popup && popup->isVisible();
359}
360
361void QQuickComboBoxPrivate::showPopup()
362{
363 if (!popup)
364 executePopup(complete: true);
365
366 if (popup && !popup->isVisible())
367 popup->open();
368}
369
370void QQuickComboBoxPrivate::hidePopup(bool accept)
371{
372 Q_Q(QQuickComboBox);
373 if (accept) {
374 setCurrentItemAtIndex(highlightedIndex, activate: NoActivate);
375 // hiding the popup on user interaction should always emit activated,
376 // even if the current index didn't change
377 emit q->activated(index: highlightedIndex);
378 }
379 if (popup && popup->isVisible())
380 popup->close();
381}
382
383void QQuickComboBoxPrivate::togglePopup(bool accept)
384{
385 if (!popup || !popup->isVisible())
386 showPopup();
387 else
388 hidePopup(accept);
389}
390
391void QQuickComboBoxPrivate::popupVisibleChanged()
392{
393 Q_Q(QQuickComboBox);
394 if (isPopupVisible())
395 QGuiApplication::inputMethod()->reset();
396
397#if QT_CONFIG(quick_itemview)
398 QQuickItemView *itemView = popup->findChild<QQuickItemView *>();
399 if (itemView)
400 itemView->setHighlightRangeMode(QQuickItemView::NoHighlightRange);
401#endif
402
403 updateHighlightedIndex();
404
405#if QT_CONFIG(quick_itemview)
406 if (itemView)
407 itemView->positionViewAtIndex(index: highlightedIndex, mode: QQuickItemView::Beginning);
408#endif
409
410 if (!hasDown) {
411 q->setDown(pressed || isPopupVisible());
412 hasDown = false;
413 }
414}
415
416void QQuickComboBoxPrivate::popupDestroyed()
417{
418 Q_Q(QQuickComboBox);
419 popup = nullptr;
420 emit q->popupChanged();
421}
422
423void QQuickComboBoxPrivate::itemClicked()
424{
425 Q_Q(QQuickComboBox);
426 int index = delegateModel->indexOf(object: q->sender(), objectContext: nullptr);
427 if (index != -1) {
428 setHighlightedIndex(index, highlight: Highlight);
429 hidePopup(accept: true);
430 }
431}
432
433void QQuickComboBoxPrivate::itemHovered()
434{
435 Q_Q(QQuickComboBox);
436 if (keyNavigating)
437 return;
438
439 QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(object: q->sender());
440 if (!button || !button->isHovered() || !button->isEnabled() || QQuickAbstractButtonPrivate::get(button)->touchId != -1)
441 return;
442
443 int index = delegateModel->indexOf(object: button, objectContext: nullptr);
444 if (index != -1) {
445 setHighlightedIndex(index, highlight: Highlight);
446
447#if QT_CONFIG(quick_itemview)
448 if (QQuickItemView *itemView = popup->findChild<QQuickItemView *>())
449 itemView->positionViewAtIndex(index, mode: QQuickItemView::Contain);
450#endif
451 }
452}
453
454void QQuickComboBoxPrivate::createdItem(int index, QObject *object)
455{
456 Q_Q(QQuickComboBox);
457 QQuickItem *item = qobject_cast<QQuickItem *>(o: object);
458 if (item && !item->parentItem()) {
459 if (popup)
460 item->setParentItem(popup->contentItem());
461 else
462 item->setParentItem(q);
463 QQuickItemPrivate::get(item)->setCulled(true);
464 }
465
466 QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(object);
467 if (button) {
468 button->setFocusPolicy(Qt::NoFocus);
469 connect(sender: button, signal: &QQuickAbstractButton::clicked, receiverPrivate: this, slot: &QQuickComboBoxPrivate::itemClicked);
470 connect(sender: button, signal: &QQuickAbstractButton::hoveredChanged, receiverPrivate: this, slot: &QQuickComboBoxPrivate::itemHovered);
471 }
472
473 if (index == currentIndex && !q->isEditable())
474 updateCurrentElements();
475}
476
477void QQuickComboBoxPrivate::modelUpdated()
478{
479 if (componentComplete && (!extra.isAllocated() || !extra->accepting)) {
480 updateCurrentElements();
481
482 if (implicitContentWidthPolicy == QQuickComboBox::WidestText)
483 updateImplicitContentSize();
484 }
485}
486
487void QQuickComboBoxPrivate::countChanged()
488{
489 Q_Q(QQuickComboBox);
490 if (q->count() == 0) {
491 if (currentElementCriteria == CurrentElementCriteria::CurrentValue)
492 updateCurrentElements();
493 else
494 setCurrentItemAtIndex(-1, activate: NoActivate);
495 }
496 emit q->countChanged();
497}
498
499QString QQuickComboBoxPrivate::effectiveTextRole() const
500{
501 return textRole.isEmpty() ? QStringLiteral("modelData") : textRole;
502}
503
504void QQuickComboBoxPrivate::updateEditText()
505{
506 Q_Q(QQuickComboBox);
507 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object: contentItem);
508 if (!input)
509 return;
510
511 const QString text = input->text();
512
513 if (extra.isAllocated() && extra->allowComplete && !text.isEmpty()) {
514 const QString completed = tryComplete(inputText: text);
515 if (completed.size() > text.size()) {
516 input->setText(completed);
517 // This will select the text backwards.
518 input->select(start: completed.size(), end: text.size());
519 return;
520 }
521 }
522 q->setEditText(text);
523}
524
525void QQuickComboBoxPrivate::updateCurrentIndex()
526{
527 Q_Q(QQuickComboBox);
528 const int index = q->indexOfValue(value: currentValue);
529 if (currentIndex == index)
530 return;
531
532 currentIndex = index;
533 emit q->currentIndexChanged();
534}
535
536void QQuickComboBoxPrivate::updateCurrentText()
537{
538 Q_Q(QQuickComboBox);
539 const QString text = q->textAt(index: currentIndex);
540 if (currentText != text) {
541 currentText = text;
542 if (!hasDisplayText)
543 q->maybeSetAccessibleName(name: text);
544 emit q->currentTextChanged();
545 }
546 if (!hasDisplayText && displayText != text) {
547 displayText = text;
548 emit q->displayTextChanged();
549 }
550 if (!extra.isAllocated() || !extra->accepting)
551 q->setEditText(currentText);
552}
553
554void QQuickComboBoxPrivate::updateCurrentValue()
555{
556 Q_Q(QQuickComboBox);
557 const QVariant value = q->valueAt(index: currentIndex);
558 if (currentValue == value)
559 return;
560
561 currentValue = value;
562 emit q->currentValueChanged();
563}
564
565void QQuickComboBoxPrivate::updateCurrentElements()
566{
567 switch (currentElementCriteria) {
568 case CurrentElementCriteria::None:
569 Q_FALLTHROUGH();
570 case CurrentElementCriteria::CurrentIndex:
571 updateCurrentText();
572 updateCurrentValue();
573 break;
574 case CurrentElementCriteria::CurrentValue:
575 updateCurrentIndex();
576 updateCurrentText();
577 break;
578 }
579}
580
581void QQuickComboBoxPrivate::updateAcceptableInput()
582{
583 Q_Q(QQuickComboBox);
584
585 if (!contentItem)
586 return;
587
588 const QQuickTextInput *textInputContentItem = qobject_cast<QQuickTextInput *>(object: contentItem);
589
590 if (!textInputContentItem)
591 return;
592
593 const bool newValue = textInputContentItem->hasAcceptableInput();
594
595 if (m_acceptableInput != newValue) {
596 m_acceptableInput = newValue;
597 emit q->acceptableInputChanged();
598 }
599}
600
601void QQuickComboBoxPrivate::onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList<int> &)
602{
603 const bool currentIndexIsInChangedRange = currentIndex >= topLeft.row() && currentIndex <= bottomRight.row();
604 if (currentIndex == -1 || currentIndexIsInChangedRange)
605 updateCurrentElements();
606}
607
608bool QQuickComboBoxPrivate::isValidIndex(int index) const
609{
610 return delegateModel && index >= 0 && index < delegateModel->count();
611}
612
613void QQuickComboBoxPrivate::acceptInput()
614{
615 Q_Q(QQuickComboBox);
616 int idx = q->find(text: extra.value().editText, flags: Qt::MatchFixedString);
617 if (idx > -1) {
618 // The item that was accepted already exists, so make it the current item.
619 setCurrentItemAtIndex(idx, activate: NoActivate);
620 // After accepting text that matches an existing entry, the selection should be cleared.
621 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object: contentItem);
622 if (input) {
623 const auto text = input->text();
624 input->select(start: text.size(), end: text.size());
625 }
626 }
627
628 extra.value().accepting = true;
629 emit q->accepted();
630
631 // The user might have added the item since it didn't exist, so check again
632 // to see if we can select that new item.
633 if (idx == -1) {
634 idx = q->find(text: extra.value().editText, flags: Qt::MatchFixedString);
635 setCurrentItemAtIndex(idx, activate: NoActivate);
636 }
637 extra.value().accepting = false;
638}
639
640QString QQuickComboBoxPrivate::tryComplete(const QString &input)
641{
642 Q_Q(QQuickComboBox);
643 QString match;
644
645 const int itemCount = q->count();
646 for (int idx = 0; idx < itemCount; ++idx) {
647 const QString text = q->textAt(index: idx);
648 if (!text.startsWith(s: input, cs: Qt::CaseInsensitive))
649 continue;
650
651 // either the first or the shortest match
652 if (match.isEmpty() || text.size() < match.size())
653 match = text;
654 }
655
656 if (match.isEmpty())
657 return input;
658
659 return input + match.mid(position: input.size());
660}
661
662void QQuickComboBoxPrivate::setCurrentIndex(int index)
663{
664 Q_Q(QQuickComboBox);
665 if (currentIndex == index)
666 return;
667
668 currentIndex = index;
669 emit q->currentIndexChanged();
670
671 if (componentComplete)
672 updateCurrentElements();
673}
674
675void QQuickComboBoxPrivate::setCurrentItemAtIndex(int index, Activation activate)
676{
677 Q_Q(QQuickComboBox);
678 if (currentIndex == index)
679 return;
680
681 currentIndex = index;
682 emit q->currentIndexChanged();
683
684 updateCurrentText();
685 updateCurrentValue();
686
687 if (activate)
688 emit q->activated(index);
689}
690
691void QQuickComboBoxPrivate::incrementCurrentIndex()
692{
693 Q_Q(QQuickComboBox);
694 if (extra.isAllocated())
695 extra->allowComplete = false;
696 if (isPopupVisible()) {
697 if (highlightedIndex < q->count() - 1)
698 setHighlightedIndex(index: highlightedIndex + 1, highlight: Highlight);
699 } else {
700 if (currentIndex < q->count() - 1)
701 setCurrentItemAtIndex(index: currentIndex + 1, activate: Activate);
702 }
703 if (extra.isAllocated())
704 extra->allowComplete = true;
705}
706
707void QQuickComboBoxPrivate::decrementCurrentIndex()
708{
709 if (extra.isAllocated())
710 extra->allowComplete = false;
711 if (isPopupVisible()) {
712 if (highlightedIndex > 0)
713 setHighlightedIndex(index: highlightedIndex - 1, highlight: Highlight);
714 } else {
715 if (currentIndex > 0)
716 setCurrentItemAtIndex(index: currentIndex - 1, activate: Activate);
717 }
718 if (extra.isAllocated())
719 extra->allowComplete = true;
720}
721
722void QQuickComboBoxPrivate::updateHighlightedIndex()
723{
724 setHighlightedIndex(index: popup->isVisible() ? currentIndex : -1, highlight: NoHighlight);
725}
726
727void QQuickComboBoxPrivate::setHighlightedIndex(int index, Highlighting highlight)
728{
729 Q_Q(QQuickComboBox);
730 if (highlightedIndex == index)
731 return;
732
733 highlightedIndex = index;
734 emit q->highlightedIndexChanged();
735
736 if (highlight)
737 emit q->highlighted(index);
738}
739
740void QQuickComboBoxPrivate::keySearch(const QString &text)
741{
742 const int startIndex = isPopupVisible() ? highlightedIndex : currentIndex;
743 const int index = match(start: startIndex + 1, text, flags: Qt::MatchStartsWith | Qt::MatchWrap);
744 if (index != -1) {
745 if (isPopupVisible())
746 setHighlightedIndex(index, highlight: Highlight);
747 else
748 setCurrentItemAtIndex(index, activate: Activate);
749 }
750}
751
752int QQuickComboBoxPrivate::match(int start, const QString &text, Qt::MatchFlags flags) const
753{
754 Q_Q(const QQuickComboBox);
755 uint matchType = flags & 0x0F;
756 bool wrap = flags & Qt::MatchWrap;
757 Qt::CaseSensitivity cs = flags & Qt::MatchCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive;
758 QRegularExpression::PatternOptions options = flags & Qt::MatchCaseSensitive ? QRegularExpression::NoPatternOption
759 : QRegularExpression::CaseInsensitiveOption;
760 int from = start;
761 int to = q->count();
762
763 // iterates twice if wrapping
764 for (int i = 0; (wrap && i < 2) || (!wrap && i < 1); ++i) {
765 for (int idx = from; idx < to; ++idx) {
766 QString t = q->textAt(index: idx);
767 switch (matchType) {
768 case Qt::MatchExactly:
769 if (t == text)
770 return idx;
771 break;
772 case Qt::MatchRegularExpression: {
773 QRegularExpression rx(QRegularExpression::anchoredPattern(expression: text), options);
774 if (rx.match(subject: t).hasMatch())
775 return idx;
776 break;
777 }
778 case Qt::MatchWildcard: {
779 QRegularExpression rx(QRegularExpression::wildcardToRegularExpression(str: text),
780 options);
781 if (rx.match(subject: t).hasMatch())
782 return idx;
783 break;
784 }
785 case Qt::MatchStartsWith:
786 if (t.startsWith(s: text, cs))
787 return idx;
788 break;
789 case Qt::MatchEndsWith:
790 if (t.endsWith(s: text, cs))
791 return idx;
792 break;
793 case Qt::MatchFixedString:
794 if (t.compare(s: text, cs) == 0)
795 return idx;
796 break;
797 case Qt::MatchContains:
798 default:
799 if (t.contains(s: text, cs))
800 return idx;
801 break;
802 }
803 }
804 // prepare for the next iteration
805 from = 0;
806 to = start;
807 }
808 return -1;
809}
810
811void QQuickComboBoxPrivate::createDelegateModel()
812{
813 Q_Q(QQuickComboBox);
814 bool ownedOldModel = ownModel;
815 QQmlInstanceModel* oldModel = delegateModel;
816 if (oldModel) {
817 disconnect(sender: delegateModel, signal: &QQmlInstanceModel::countChanged, receiverPrivate: this, slot: &QQuickComboBoxPrivate::countChanged);
818 disconnect(sender: delegateModel, signal: &QQmlInstanceModel::modelUpdated, receiverPrivate: this, slot: &QQuickComboBoxPrivate::modelUpdated);
819 disconnect(sender: delegateModel, signal: &QQmlInstanceModel::createdItem, receiverPrivate: this, slot: &QQuickComboBoxPrivate::createdItem);
820 }
821
822 ownModel = false;
823 delegateModel = model.value<QQmlInstanceModel *>();
824
825 if (!delegateModel && model.isValid()) {
826 QQmlDelegateModel *dataModel = new QQuickComboBoxDelegateModel(q);
827 dataModel->setModel(model);
828 dataModel->setDelegate(delegate);
829 if (q->isComponentComplete())
830 dataModel->componentComplete();
831
832 ownModel = true;
833 delegateModel = dataModel;
834 }
835
836 if (delegateModel) {
837 connect(sender: delegateModel, signal: &QQmlInstanceModel::countChanged, receiverPrivate: this, slot: &QQuickComboBoxPrivate::countChanged);
838 connect(sender: delegateModel, signal: &QQmlInstanceModel::modelUpdated, receiverPrivate: this, slot: &QQuickComboBoxPrivate::modelUpdated);
839 connect(sender: delegateModel, signal: &QQmlInstanceModel::createdItem, receiverPrivate: this, slot: &QQuickComboBoxPrivate::createdItem);
840 }
841
842 emit q->delegateModelChanged();
843
844 if (ownedOldModel)
845 delete oldModel;
846}
847
848bool QQuickComboBoxPrivate::handlePress(const QPointF &point, ulong timestamp)
849{
850 Q_Q(QQuickComboBox);
851 QQuickControlPrivate::handlePress(point, timestamp);
852 q->setPressed(true);
853 return true;
854}
855
856bool QQuickComboBoxPrivate::handleMove(const QPointF &point, ulong timestamp)
857{
858 Q_Q(QQuickComboBox);
859 QQuickControlPrivate::handleMove(point, timestamp);
860 q->setPressed(q->contains(point));
861 return true;
862}
863
864bool QQuickComboBoxPrivate::handleRelease(const QPointF &point, ulong timestamp)
865{
866 Q_Q(QQuickComboBox);
867 QQuickControlPrivate::handleRelease(point, timestamp);
868 if (pressed) {
869 q->setPressed(false);
870 togglePopup(accept: false);
871 }
872 return true;
873}
874
875void QQuickComboBoxPrivate::handleUngrab()
876{
877 Q_Q(QQuickComboBox);
878 QQuickControlPrivate::handleUngrab();
879 q->setPressed(false);
880}
881
882void QQuickComboBoxPrivate::cancelIndicator()
883{
884 Q_Q(QQuickComboBox);
885 quickCancelDeferred(object: q, property: indicatorName());
886}
887
888void QQuickComboBoxPrivate::executeIndicator(bool complete)
889{
890 Q_Q(QQuickComboBox);
891 if (indicator.wasExecuted())
892 return;
893
894 if (!indicator || complete)
895 quickBeginDeferred(object: q, property: indicatorName(), delegate&: indicator);
896 if (complete)
897 quickCompleteDeferred(object: q, property: indicatorName(), delegate&: indicator);
898}
899
900static inline QString popupName() { return QStringLiteral("popup"); }
901
902void QQuickComboBoxPrivate::cancelPopup()
903{
904 Q_Q(QQuickComboBox);
905 quickCancelDeferred(object: q, property: popupName());
906}
907
908void QQuickComboBoxPrivate::executePopup(bool complete)
909{
910 Q_Q(QQuickComboBox);
911 if (popup.wasExecuted())
912 return;
913
914 if (!popup || complete)
915 quickBeginDeferred(object: q, property: popupName(), delegate&: popup);
916 if (complete)
917 quickCompleteDeferred(object: q, property: popupName(), delegate&: popup);
918}
919
920void QQuickComboBoxPrivate::itemImplicitWidthChanged(QQuickItem *item)
921{
922 Q_Q(QQuickComboBox);
923 QQuickControlPrivate::itemImplicitWidthChanged(item);
924 if (item == indicator)
925 emit q->implicitIndicatorWidthChanged();
926}
927
928void QQuickComboBoxPrivate::setInputMethodHints(Qt::InputMethodHints hints, bool force)
929{
930 Q_Q(QQuickComboBox);
931 if (!force && hints == q->inputMethodHints())
932 return;
933
934 extra.value().inputMethodHints = hints;
935 emit q->inputMethodHintsChanged();
936}
937
938void QQuickComboBoxPrivate::itemImplicitHeightChanged(QQuickItem *item)
939{
940 Q_Q(QQuickComboBox);
941 QQuickControlPrivate::itemImplicitHeightChanged(item);
942 if (item == indicator)
943 emit q->implicitIndicatorHeightChanged();
944}
945
946void QQuickComboBoxPrivate::itemDestroyed(QQuickItem *item)
947{
948 Q_Q(QQuickComboBox);
949 QQuickControlPrivate::itemDestroyed(item);
950 if (item == indicator) {
951 indicator = nullptr;
952 emit q->indicatorChanged();
953 }
954}
955
956qreal QQuickComboBoxPrivate::getContentWidth() const
957{
958 if (componentComplete) {
959 switch (implicitContentWidthPolicy) {
960 case QQuickComboBox::WidestText:
961 return calculateWidestTextWidth();
962 case QQuickComboBox::WidestTextWhenCompleted:
963 if (!hasCalculatedWidestText)
964 return calculateWidestTextWidth();
965 break;
966 default:
967 break;
968 }
969 }
970
971 return QQuickControlPrivate::getContentWidth();
972}
973
974qreal QQuickComboBoxPrivate::calculateWidestTextWidth() const
975{
976 Q_Q(const QQuickComboBox);
977 if (!componentComplete)
978 return 0;
979
980 const int count = q->count();
981 if (count == 0)
982 return 0;
983
984 auto textInput = qobject_cast<QQuickTextInput*>(object: contentItem);
985 if (!textInput)
986 return 0;
987
988 qCDebug(lcCalculateWidestTextWidth) << "calculating widest text from" << count << "items...";
989
990 // Avoid the index check and repeated calls to effectiveTextRole()
991 // that would result from calling textAt() in a loop.
992 const QString textRole = effectiveTextRole();
993 auto textInputPrivate = QQuickTextInputPrivate::get(t: textInput);
994 qreal widest = 0;
995 for (int i = 0; i < count; ++i) {
996 const QString text = delegateModel->stringValue(index: i, role: textRole);
997 const qreal textImplicitWidth = textInputPrivate->calculateImplicitWidthForText(text);
998 widest = qMax(a: widest, b: textImplicitWidth);
999 }
1000
1001 qCDebug(lcCalculateWidestTextWidth) << "... widest text is" << widest;
1002 return widest;
1003}
1004
1005/*!
1006 \internal
1007
1008 If the user requested it (and we haven't already done it, depending on the policy),
1009 update the implicit content width to the largest text in the model.
1010*/
1011void QQuickComboBoxPrivate::maybeUpdateImplicitContentWidth()
1012{
1013 if (!componentComplete)
1014 return;
1015
1016 if (implicitContentWidthPolicy == QQuickComboBox::ContentItemImplicitWidth
1017 || (implicitContentWidthPolicy == QQuickComboBox::WidestTextWhenCompleted && hasCalculatedWidestText))
1018 return;
1019
1020 updateImplicitContentWidth();
1021 hasCalculatedWidestText = true;
1022}
1023
1024void QQuickComboBoxPrivate::hideOldPopup(QQuickPopup *popup)
1025{
1026 if (!popup)
1027 return;
1028
1029 qCDebug(lcItemManagement) << "hiding old popup" << popup;
1030
1031 popup->setVisible(false);
1032 popup->setParentItem(nullptr);
1033#if QT_CONFIG(accessibility)
1034 // Remove the item from the accessibility tree.
1035 QQuickAccessibleAttached *accessible = accessibleAttached(object: popup);
1036 if (accessible)
1037 accessible->setIgnored(true);
1038#endif
1039}
1040
1041QQuickComboBox::QQuickComboBox(QQuickItem *parent)
1042 : QQuickControl(*(new QQuickComboBoxPrivate), parent)
1043{
1044 setFocusPolicy(Qt::StrongFocus);
1045 setFlag(flag: QQuickItem::ItemIsFocusScope);
1046 setAcceptedMouseButtons(Qt::LeftButton);
1047#if QT_CONFIG(cursor)
1048 setCursor(Qt::ArrowCursor);
1049#endif
1050 Q_D(QQuickComboBox);
1051 d->setInputMethodHints(hints: Qt::ImhNoPredictiveText, force: true);
1052 d->setSizePolicy(horizontalPolicy: QLayoutPolicy::Preferred, verticalPolicy: QLayoutPolicy::Fixed);
1053}
1054
1055QQuickComboBox::~QQuickComboBox()
1056{
1057 Q_D(QQuickComboBox);
1058 d->removeImplicitSizeListener(item: d->indicator);
1059 if (d->popup) {
1060 // Disconnect visibleChanged() to avoid a spurious highlightedIndexChanged() signal
1061 // emission during the destruction of the (visible) popup. (QTBUG-57650)
1062 QObjectPrivate::disconnect(sender: d->popup.data(), signal: &QQuickPopup::visibleChanged, receiverPrivate: d, slot: &QQuickComboBoxPrivate::popupVisibleChanged);
1063 QQuickComboBoxPrivate::hideOldPopup(popup: d->popup);
1064 d->popup = nullptr;
1065 }
1066}
1067
1068/*!
1069 \readonly
1070 \qmlproperty int QtQuick.Controls::ComboBox::count
1071
1072 This property holds the number of items in the combo box.
1073*/
1074int QQuickComboBox::count() const
1075{
1076 Q_D(const QQuickComboBox);
1077 return d->delegateModel ? d->delegateModel->count() : 0;
1078}
1079
1080/*!
1081 \qmlproperty model QtQuick.Controls::ComboBox::model
1082
1083 This property holds the model providing data for the combo box.
1084
1085 \code
1086 ComboBox {
1087 textRole: "key"
1088 model: ListModel {
1089 ListElement { key: "First"; value: 123 }
1090 ListElement { key: "Second"; value: 456 }
1091 ListElement { key: "Third"; value: 789 }
1092 }
1093 }
1094 \endcode
1095
1096 \sa textRole, {qml-data-models}{Data Models}
1097*/
1098QVariant QQuickComboBox::model() const
1099{
1100 Q_D(const QQuickComboBox);
1101 if (!d->qobjectModelGuard.has_value()) {
1102 // It's not a QObject-derived model; return it as-is.
1103 return d->model;
1104 }
1105
1106 // It is a QObject-derived model; only return it if it's still alive.
1107 return !d->qobjectModelGuard->isNull() ? d->model : QVariant();
1108}
1109
1110void QQuickComboBox::setModel(const QVariant& m)
1111{
1112 Q_D(QQuickComboBox);
1113 QVariant newModel = m;
1114 if (newModel.userType() == qMetaTypeId<QJSValue>())
1115 newModel = newModel.value<QJSValue>().toVariant();
1116
1117 if (d->model == newModel)
1118 return;
1119
1120 if (d->qobjectModelGuard.has_value() && !d->qobjectModelGuard->isNull()) {
1121 if (QAbstractItemModel* aim = qvariant_cast<QAbstractItemModel *>(v: d->model)) {
1122 QObjectPrivate::disconnect(sender: aim, signal: &QAbstractItemModel::dataChanged,
1123 receiverPrivate: d, slot: &QQuickComboBoxPrivate::onDataChanged);
1124 }
1125 }
1126 if (QObject* newModelAsQObject = qvariant_cast<QObject *>(v: newModel)) {
1127 // Ensure that if the model is destroyed, we don't try to access it.
1128 // We store it in std::optional because model() needs to return the model
1129 // as-is if a non-QObject model is contained within it.
1130 d->qobjectModelGuard = QPointer<QObject>(newModelAsQObject);
1131
1132 if (QAbstractItemModel* aim = qvariant_cast<QAbstractItemModel *>(v: newModel)) {
1133 QObjectPrivate::connect(sender: aim, signal: &QAbstractItemModel::dataChanged,
1134 receiverPrivate: d, slot: &QQuickComboBoxPrivate::onDataChanged);
1135 }
1136 } else {
1137 // It's not a QObject, so we don't need to worry about tracking its lifetime.
1138 d->qobjectModelGuard.reset();
1139 }
1140
1141 d->model = newModel;
1142 d->createDelegateModel();
1143 emit countChanged();
1144 if (isComponentComplete()) {
1145 if (d->currentElementCriteria != QQuickComboBoxPrivate::CurrentElementCriteria::CurrentValue)
1146 d->setCurrentIndex(count() > 0 ? 0 : -1);
1147 d->updateCurrentElements();
1148 }
1149 emit modelChanged();
1150
1151 d->maybeUpdateImplicitContentWidth();
1152}
1153
1154/*!
1155 \readonly
1156 \qmlproperty model QtQuick.Controls::ComboBox::delegateModel
1157
1158 This property holds the model that provides delegate instances for the combo box.
1159
1160 It is typically assigned to a \l ListView in the \l {Popup::}{contentItem}
1161 of the \l popup.
1162
1163 \sa {Customizing ComboBox}
1164*/
1165QQmlInstanceModel *QQuickComboBox::delegateModel() const
1166{
1167 Q_D(const QQuickComboBox);
1168 return d->delegateModel;
1169}
1170
1171
1172/*!
1173 \readonly
1174 \qmlproperty bool QtQuick.Controls::ComboBox::pressed
1175
1176 This property holds whether the combo box button is physically pressed.
1177 A button can be pressed by either touch or key events.
1178
1179 \sa down
1180*/
1181bool QQuickComboBox::isPressed() const
1182{
1183 Q_D(const QQuickComboBox);
1184 return d->pressed;
1185}
1186
1187void QQuickComboBox::setPressed(bool pressed)
1188{
1189 Q_D(QQuickComboBox);
1190 if (d->pressed == pressed)
1191 return;
1192
1193 d->pressed = pressed;
1194 emit pressedChanged();
1195
1196 if (!d->hasDown) {
1197 setDown(d->pressed || d->isPopupVisible());
1198 d->hasDown = false;
1199 }
1200}
1201
1202/*!
1203 \readonly
1204 \qmlproperty int QtQuick.Controls::ComboBox::highlightedIndex
1205
1206 This property holds the index of the highlighted item in the combo box popup list.
1207
1208 When a highlighted item is activated, the popup is closed, \l currentIndex
1209 is set to \c highlightedIndex, and the value of this property is reset to
1210 \c -1, as there is no longer a highlighted item.
1211
1212 \sa highlighted(), currentIndex
1213*/
1214int QQuickComboBox::highlightedIndex() const
1215{
1216 Q_D(const QQuickComboBox);
1217 return d->highlightedIndex;
1218}
1219
1220/*!
1221 \qmlproperty int QtQuick.Controls::ComboBox::currentIndex
1222
1223 This property holds the index of the current item in the combo box.
1224
1225 The default value is \c -1 when \l count is \c 0, and \c 0 otherwise.
1226
1227 \sa activated(), currentText, highlightedIndex
1228*/
1229int QQuickComboBox::currentIndex() const
1230{
1231 Q_D(const QQuickComboBox);
1232 return d->currentIndex;
1233}
1234
1235void QQuickComboBox::setCurrentIndex(int index)
1236{
1237 Q_D(QQuickComboBox);
1238 d->currentElementCriteria = QQuickComboBoxPrivate::CurrentElementCriteria::CurrentIndex;
1239 d->setCurrentIndex(index);
1240}
1241
1242/*!
1243 \readonly
1244 \qmlproperty string QtQuick.Controls::ComboBox::currentText
1245
1246 This property holds the text of the current item in the combo box.
1247
1248 \sa currentIndex, displayText, textRole, editText
1249*/
1250QString QQuickComboBox::currentText() const
1251{
1252 Q_D(const QQuickComboBox);
1253 return d->currentText;
1254}
1255
1256/*!
1257 \qmlproperty string QtQuick.Controls::ComboBox::displayText
1258
1259 This property holds the text that is displayed on the combo box button.
1260
1261 By default, the display text presents the current selection. That is,
1262 it follows the text of the current item. However, the default display
1263 text can be overridden with a custom value.
1264
1265 \code
1266 ComboBox {
1267 currentIndex: 1
1268 displayText: "Size: " + currentText
1269 model: ["S", "M", "L"]
1270 }
1271 \endcode
1272
1273 \sa currentText, textRole
1274*/
1275QString QQuickComboBox::displayText() const
1276{
1277 Q_D(const QQuickComboBox);
1278 return d->displayText;
1279}
1280
1281void QQuickComboBox::setDisplayText(const QString &text)
1282{
1283 Q_D(QQuickComboBox);
1284 d->hasDisplayText = true;
1285 if (d->displayText == text)
1286 return;
1287
1288 d->displayText = text;
1289 maybeSetAccessibleName(name: text);
1290 emit displayTextChanged();
1291}
1292
1293void QQuickComboBox::resetDisplayText()
1294{
1295 Q_D(QQuickComboBox);
1296 if (!d->hasDisplayText)
1297 return;
1298
1299 d->hasDisplayText = false;
1300 d->updateCurrentText();
1301}
1302
1303
1304/*!
1305 \qmlproperty string QtQuick.Controls::ComboBox::textRole
1306
1307 This property holds the model role used for populating the combo box.
1308
1309 When the model has multiple roles, \c textRole can be set to determine
1310 which role should be displayed.
1311
1312 \sa model, currentText, displayText, {ComboBox Model Roles}
1313*/
1314QString QQuickComboBox::textRole() const
1315{
1316 Q_D(const QQuickComboBox);
1317 return d->textRole;
1318}
1319
1320void QQuickComboBox::setTextRole(const QString &role)
1321{
1322 Q_D(QQuickComboBox);
1323 if (d->textRole == role)
1324 return;
1325
1326 d->textRole = role;
1327 if (isComponentComplete())
1328 d->updateCurrentText();
1329 emit textRoleChanged();
1330}
1331
1332/*!
1333 \since QtQuick.Controls 2.14 (Qt 5.14)
1334 \qmlproperty string QtQuick.Controls::ComboBox::valueRole
1335
1336 This property holds the model role used for storing the value associated
1337 with each item in the model.
1338
1339 For an example of how to use this property, see \l {ComboBox Model Roles}.
1340
1341 \sa model, currentValue
1342*/
1343QString QQuickComboBox::valueRole() const
1344{
1345 Q_D(const QQuickComboBox);
1346 return d->valueRole;
1347}
1348
1349void QQuickComboBox::setValueRole(const QString &role)
1350{
1351 Q_D(QQuickComboBox);
1352 if (d->valueRole == role)
1353 return;
1354
1355 d->valueRole = role;
1356 if (isComponentComplete())
1357 d->updateCurrentElements();
1358 emit valueRoleChanged();
1359}
1360
1361/*!
1362 \qmlproperty Component QtQuick.Controls::ComboBox::delegate
1363
1364 This property holds a delegate that presents an item in the combo box popup.
1365
1366 It is recommended to use \l ItemDelegate (or any other \l AbstractButton
1367 derivatives) as the delegate. This ensures that the interaction works as
1368 expected, and the popup will automatically close when appropriate. When
1369 other types are used as the delegate, the popup must be closed manually.
1370 For example, if \l MouseArea is used:
1371
1372 \code
1373 delegate: Rectangle {
1374 // ...
1375 MouseArea {
1376 // ...
1377 onClicked: comboBox.popup.close()
1378 }
1379 }
1380 \endcode
1381
1382 \sa ItemDelegate, {Customizing ComboBox}
1383*/
1384QQmlComponent *QQuickComboBox::delegate() const
1385{
1386 Q_D(const QQuickComboBox);
1387 return d->delegate;
1388}
1389
1390void QQuickComboBox::setDelegate(QQmlComponent* delegate)
1391{
1392 Q_D(QQuickComboBox);
1393 if (d->delegate == delegate)
1394 return;
1395
1396 delete d->delegate;
1397 d->delegate = delegate;
1398 QQmlDelegateModel *delegateModel = qobject_cast<QQmlDelegateModel*>(object: d->delegateModel);
1399 if (delegateModel)
1400 delegateModel->setDelegate(d->delegate);
1401 emit delegateChanged();
1402}
1403
1404/*!
1405 \qmlproperty Item QtQuick.Controls::ComboBox::indicator
1406
1407 This property holds the drop indicator item.
1408
1409 \sa {Customizing ComboBox}
1410*/
1411QQuickItem *QQuickComboBox::indicator() const
1412{
1413 QQuickComboBoxPrivate *d = const_cast<QQuickComboBoxPrivate *>(d_func());
1414 if (!d->indicator)
1415 d->executeIndicator();
1416 return d->indicator;
1417}
1418
1419void QQuickComboBox::setIndicator(QQuickItem *indicator)
1420{
1421 Q_D(QQuickComboBox);
1422 if (d->indicator == indicator)
1423 return;
1424
1425 QQuickControlPrivate::warnIfCustomizationNotSupported(control: this, item: indicator, QStringLiteral("indicator"));
1426
1427 if (!d->indicator.isExecuting())
1428 d->cancelIndicator();
1429
1430 const qreal oldImplicitIndicatorWidth = implicitIndicatorWidth();
1431 const qreal oldImplicitIndicatorHeight = implicitIndicatorHeight();
1432
1433 d->removeImplicitSizeListener(item: d->indicator);
1434 QQuickControlPrivate::hideOldItem(item: d->indicator);
1435 d->indicator = indicator;
1436 if (indicator) {
1437 if (!indicator->parentItem())
1438 indicator->setParentItem(this);
1439 d->addImplicitSizeListener(item: indicator);
1440 }
1441
1442 if (!qFuzzyCompare(p1: oldImplicitIndicatorWidth, p2: implicitIndicatorWidth()))
1443 emit implicitIndicatorWidthChanged();
1444 if (!qFuzzyCompare(p1: oldImplicitIndicatorHeight, p2: implicitIndicatorHeight()))
1445 emit implicitIndicatorHeightChanged();
1446 if (!d->indicator.isExecuting())
1447 emit indicatorChanged();
1448}
1449
1450/*!
1451 \qmlproperty Popup QtQuick.Controls::ComboBox::popup
1452
1453 This property holds the popup.
1454
1455 The popup can be opened or closed manually, if necessary:
1456
1457 \code
1458 onSpecialEvent: comboBox.popup.close()
1459 \endcode
1460
1461 \sa {Customizing ComboBox}
1462*/
1463QQuickPopup *QQuickComboBox::popup() const
1464{
1465 QQuickComboBoxPrivate *d = const_cast<QQuickComboBoxPrivate *>(d_func());
1466 if (!d->popup)
1467 d->executePopup(complete: isComponentComplete());
1468 return d->popup;
1469}
1470
1471void QQuickComboBox::setPopup(QQuickPopup *popup)
1472{
1473 Q_D(QQuickComboBox);
1474 if (d->popup == popup)
1475 return;
1476
1477 if (!d->popup.isExecuting())
1478 d->cancelPopup();
1479
1480 if (d->popup) {
1481 QObjectPrivate::disconnect(sender: d->popup.data(), signal: &QQuickPopup::destroyed, receiverPrivate: d, slot: &QQuickComboBoxPrivate::popupDestroyed);
1482 QObjectPrivate::disconnect(sender: d->popup.data(), signal: &QQuickPopup::visibleChanged, receiverPrivate: d, slot: &QQuickComboBoxPrivate::popupVisibleChanged);
1483 QQuickComboBoxPrivate::hideOldPopup(popup: d->popup);
1484 }
1485 if (popup) {
1486 QQuickPopupPrivate::get(popup)->allowVerticalFlip = true;
1487 popup->setClosePolicy(QQuickPopup::CloseOnEscape | QQuickPopup::CloseOnPressOutsideParent);
1488 QObjectPrivate::connect(sender: popup, signal: &QQuickPopup::visibleChanged, receiverPrivate: d, slot: &QQuickComboBoxPrivate::popupVisibleChanged);
1489 // QQuickPopup does not derive from QQuickItemChangeListener, so we cannot use
1490 // QQuickItemChangeListener::itemDestroyed so we have to use QObject::destroyed
1491 QObjectPrivate::connect(sender: popup, signal: &QQuickPopup::destroyed, receiverPrivate: d, slot: &QQuickComboBoxPrivate::popupDestroyed);
1492
1493#if QT_CONFIG(quick_itemview)
1494 if (QQuickItemView *itemView = popup->findChild<QQuickItemView *>())
1495 itemView->setHighlightRangeMode(QQuickItemView::NoHighlightRange);
1496#endif
1497 }
1498 d->popup = popup;
1499 if (!d->popup.isExecuting())
1500 emit popupChanged();
1501}
1502
1503/*!
1504 \since QtQuick.Controls 2.1 (Qt 5.8)
1505 \qmlproperty bool QtQuick.Controls::ComboBox::flat
1506
1507 This property holds whether the combo box button is flat.
1508
1509 A flat combo box button does not draw a background unless it is interacted
1510 with. In comparison to normal combo boxes, flat combo boxes provide looks
1511 that make them stand out less from the rest of the UI. For instance, when
1512 placing a combo box into a tool bar, it may be desirable to make the combo
1513 box flat so it matches better with the flat looks of tool buttons.
1514
1515 The default value is \c false.
1516*/
1517bool QQuickComboBox::isFlat() const
1518{
1519 Q_D(const QQuickComboBox);
1520 return d->flat;
1521}
1522
1523void QQuickComboBox::setFlat(bool flat)
1524{
1525 Q_D(QQuickComboBox);
1526 if (d->flat == flat)
1527 return;
1528
1529 d->flat = flat;
1530 emit flatChanged();
1531}
1532
1533/*!
1534 \since QtQuick.Controls 2.2 (Qt 5.9)
1535 \qmlproperty bool QtQuick.Controls::ComboBox::down
1536
1537 This property holds whether the combo box button is visually down.
1538
1539 Unless explicitly set, this property is \c true when either \c pressed
1540 or \c popup.visible is \c true. To return to the default value, set this
1541 property to \c undefined.
1542
1543 \sa pressed, popup
1544*/
1545bool QQuickComboBox::isDown() const
1546{
1547 Q_D(const QQuickComboBox);
1548 return d->down;
1549}
1550
1551void QQuickComboBox::setDown(bool down)
1552{
1553 Q_D(QQuickComboBox);
1554 d->hasDown = true;
1555
1556 if (d->down == down)
1557 return;
1558
1559 d->down = down;
1560 emit downChanged();
1561}
1562
1563void QQuickComboBox::resetDown()
1564{
1565 Q_D(QQuickComboBox);
1566 if (!d->hasDown)
1567 return;
1568
1569 setDown(d->pressed || d->isPopupVisible());
1570 d->hasDown = false;
1571}
1572
1573/*!
1574 \since QtQuick.Controls 2.2 (Qt 5.9)
1575 \qmlproperty bool QtQuick.Controls::ComboBox::editable
1576
1577 This property holds whether the combo box is editable.
1578
1579 The default value is \c false.
1580
1581 \sa validator
1582*/
1583bool QQuickComboBox::isEditable() const
1584{
1585 Q_D(const QQuickComboBox);
1586 return d->extra.isAllocated() && d->extra->editable;
1587}
1588
1589void QQuickComboBox::setEditable(bool editable)
1590{
1591 Q_D(QQuickComboBox);
1592 if (editable == isEditable())
1593 return;
1594
1595 if (d->contentItem) {
1596 if (editable) {
1597 d->contentItem->installEventFilter(filterObj: this);
1598 if (QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object: d->contentItem)) {
1599 QObjectPrivate::connect(sender: input, signal: &QQuickTextInput::textChanged, receiverPrivate: d, slot: &QQuickComboBoxPrivate::updateEditText);
1600 QObjectPrivate::connect(sender: input, signal: &QQuickTextInput::accepted, receiverPrivate: d, slot: &QQuickComboBoxPrivate::acceptInput);
1601 }
1602#if QT_CONFIG(cursor)
1603 d->contentItem->setCursor(Qt::IBeamCursor);
1604#endif
1605 } else {
1606 d->contentItem->removeEventFilter(obj: this);
1607 if (QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object: d->contentItem)) {
1608 QObjectPrivate::disconnect(sender: input, signal: &QQuickTextInput::textChanged, receiverPrivate: d, slot: &QQuickComboBoxPrivate::updateEditText);
1609 QObjectPrivate::disconnect(sender: input, signal: &QQuickTextInput::accepted, receiverPrivate: d, slot: &QQuickComboBoxPrivate::acceptInput);
1610 }
1611#if QT_CONFIG(cursor)
1612 d->contentItem->unsetCursor();
1613#endif
1614 }
1615 }
1616
1617 d->extra.value().editable = editable;
1618 setAccessibleProperty(propertyName: "editable", value: editable);
1619 emit editableChanged();
1620}
1621
1622/*!
1623 \since QtQuick.Controls 2.2 (Qt 5.9)
1624 \qmlproperty string QtQuick.Controls::ComboBox::editText
1625
1626 This property holds the text in the text field of an editable combo box.
1627
1628 \sa editable, currentText, displayText
1629*/
1630QString QQuickComboBox::editText() const
1631{
1632 Q_D(const QQuickComboBox);
1633 return d->extra.isAllocated() ? d->extra->editText : QString();
1634}
1635
1636void QQuickComboBox::setEditText(const QString &text)
1637{
1638 Q_D(QQuickComboBox);
1639 if (text == editText())
1640 return;
1641
1642 d->extra.value().editText = text;
1643 emit editTextChanged();
1644}
1645
1646void QQuickComboBox::resetEditText()
1647{
1648 setEditText(QString());
1649}
1650
1651#if QT_CONFIG(validator)
1652/*!
1653 \since QtQuick.Controls 2.2 (Qt 5.9)
1654 \qmlproperty Validator QtQuick.Controls::ComboBox::validator
1655
1656 This property holds an input text validator for an editable combo box.
1657
1658 When a validator is set, the text field will only accept input which
1659 leaves the text property in an intermediate state. The \l accepted signal
1660 will only be emitted if the text is in an acceptable state when the
1661 \uicontrol Return or \uicontrol Enter key is pressed.
1662
1663 The currently supported validators are \l[QtQuick]{IntValidator},
1664 \l[QtQuick]{DoubleValidator}, and \l[QtQuick]{RegularExpressionValidator}. An
1665 example of using validators is shown below, which allows input of
1666 integers between \c 0 and \c 10 into the text field:
1667
1668 \code
1669 ComboBox {
1670 model: 10
1671 editable: true
1672 validator: IntValidator {
1673 top: 9
1674 bottom: 0
1675 }
1676 }
1677 \endcode
1678
1679 \sa acceptableInput, accepted, editable
1680*/
1681QValidator *QQuickComboBox::validator() const
1682{
1683 Q_D(const QQuickComboBox);
1684 return d->extra.isAllocated() ? d->extra->validator : nullptr;
1685}
1686
1687void QQuickComboBox::setValidator(QValidator *validator)
1688{
1689 Q_D(QQuickComboBox);
1690 if (validator == QQuickComboBox::validator())
1691 return;
1692
1693 d->extra.value().validator = validator;
1694#if QT_CONFIG(validator)
1695 if (validator)
1696 validator->setLocale(d->locale);
1697#endif
1698 emit validatorChanged();
1699}
1700#endif
1701
1702/*!
1703 \since QtQuick.Controls 2.2 (Qt 5.9)
1704 \qmlproperty flags QtQuick.Controls::ComboBox::inputMethodHints
1705
1706 Provides hints to the input method about the expected content of the combo box and how it
1707 should operate.
1708
1709 The default value is \c Qt.ImhNoPredictiveText.
1710
1711 \include inputmethodhints.qdocinc
1712*/
1713Qt::InputMethodHints QQuickComboBox::inputMethodHints() const
1714{
1715 Q_D(const QQuickComboBox);
1716 return d->extra.isAllocated() ? d->extra->inputMethodHints : Qt::ImhNoPredictiveText;
1717}
1718
1719void QQuickComboBox::setInputMethodHints(Qt::InputMethodHints hints)
1720{
1721 Q_D(QQuickComboBox);
1722 d->setInputMethodHints(hints);
1723}
1724
1725/*!
1726 \since QtQuick.Controls 2.2 (Qt 5.9)
1727 \qmlproperty bool QtQuick.Controls::ComboBox::inputMethodComposing
1728 \readonly
1729
1730 This property holds whether an editable combo box has partial text input from an input method.
1731
1732 While it is composing, an input method may rely on mouse or key events from the combo box to
1733 edit or commit the partial text. This property can be used to determine when to disable event
1734 handlers that may interfere with the correct operation of an input method.
1735*/
1736bool QQuickComboBox::isInputMethodComposing() const
1737{
1738 Q_D(const QQuickComboBox);
1739 return d->contentItem && d->contentItem->property(name: "inputMethodComposing").toBool();
1740}
1741
1742/*!
1743 \since QtQuick.Controls 2.2 (Qt 5.9)
1744 \qmlproperty bool QtQuick.Controls::ComboBox::acceptableInput
1745 \readonly
1746
1747 This property holds whether the combo box contains acceptable text in the editable text field.
1748
1749 If a validator has been set, the value is \c true only if the current text is acceptable
1750 to the validator as a final string (not as an intermediate string).
1751
1752 \sa validator, accepted
1753*/
1754bool QQuickComboBox::hasAcceptableInput() const
1755{
1756 Q_D(const QQuickComboBox);
1757 return d->m_acceptableInput;
1758}
1759
1760/*!
1761 \since QtQuick.Controls 2.5 (Qt 5.12)
1762 \qmlproperty real QtQuick.Controls::ComboBox::implicitIndicatorWidth
1763 \readonly
1764
1765 This property holds the implicit indicator width.
1766
1767 The value is equal to \c {indicator ? indicator.implicitWidth : 0}.
1768
1769 This is typically used, together with \l {Control::}{implicitContentWidth} and
1770 \l {Control::}{implicitBackgroundWidth}, to calculate the \l {Item::}{implicitWidth}.
1771
1772 \sa implicitIndicatorHeight
1773*/
1774qreal QQuickComboBox::implicitIndicatorWidth() const
1775{
1776 Q_D(const QQuickComboBox);
1777 if (!d->indicator)
1778 return 0;
1779 return d->indicator->implicitWidth();
1780}
1781
1782/*!
1783 \since QtQuick.Controls 2.5 (Qt 5.12)
1784 \qmlproperty real QtQuick.Controls::ComboBox::implicitIndicatorHeight
1785 \readonly
1786
1787 This property holds the implicit indicator height.
1788
1789 The value is equal to \c {indicator ? indicator.implicitHeight : 0}.
1790
1791 This is typically used, together with \l {Control::}{implicitContentHeight} and
1792 \l {Control::}{implicitBackgroundHeight}, to calculate the \l {Item::}{implicitHeight}.
1793
1794 \sa implicitIndicatorWidth
1795*/
1796qreal QQuickComboBox::implicitIndicatorHeight() const
1797{
1798 Q_D(const QQuickComboBox);
1799 if (!d->indicator)
1800 return 0;
1801 return d->indicator->implicitHeight();
1802}
1803
1804/*!
1805 \since QtQuick.Controls 2.14 (Qt 5.14)
1806 \qmlproperty var QtQuick.Controls::ComboBox::currentValue
1807
1808 This property holds the value of the current item in the combo box.
1809 Setting this property will set \l currentIndex to the item with the
1810 corresponding value or \c -1 if it is not found.
1811 Setting both \l currentIndex and \a currentValue declaratively will
1812 result in undefined behavior.
1813 Setting this property with a value that is not unique is not supported.
1814
1815 For an example of how to use this property, see \l {ComboBox Model Roles}.
1816
1817 \sa currentIndex, currentText, valueRole
1818*/
1819QVariant QQuickComboBox::currentValue() const
1820{
1821 Q_D(const QQuickComboBox);
1822 return d->currentValue;
1823}
1824
1825void QQuickComboBox::setCurrentValue(const QVariant &value)
1826{
1827 Q_D(QQuickComboBox);
1828 d->currentElementCriteria = QQuickComboBoxPrivate::CurrentElementCriteria::CurrentValue;
1829 if (value == d->currentValue)
1830 return;
1831
1832 d->currentValue = value;
1833 emit currentValueChanged();
1834
1835 if (d->componentComplete)
1836 d->updateCurrentElements();
1837}
1838
1839/*!
1840 \readonly
1841 \since QtQuick.Controls 2.14 (Qt 5.14)
1842 \qmlmethod var QtQuick.Controls::ComboBox::valueAt(int index)
1843
1844 Returns the value at position \a index in the combo box.
1845
1846 \sa indexOfValue
1847*/
1848QVariant QQuickComboBox::valueAt(int index) const
1849{
1850 Q_D(const QQuickComboBox);
1851 if (!d->isValidIndex(index))
1852 return QVariant();
1853
1854 const QString effectiveValueRole = d->valueRole.isEmpty() ? QStringLiteral("modelData") : d->valueRole;
1855 return d->delegateModel->variantValue(index, effectiveValueRole);
1856}
1857
1858/*!
1859 \since QtQuick.Controls 2.14 (Qt 5.14)
1860 \qmlmethod int QtQuick.Controls::ComboBox::indexOfValue(object value)
1861
1862 Returns the index of the specified \a value, or \c -1 if no match is found.
1863
1864 For an example of how to use this method, see \l {ComboBox Model Roles}.
1865
1866 \include qquickcombobox.qdocinc functions-after-component-completion
1867
1868 \sa find(), currentValue, currentIndex, valueRole, valueAt
1869*/
1870int QQuickComboBox::indexOfValue(const QVariant &value) const
1871{
1872 for (int i = 0; i < count(); ++i) {
1873 const QVariant ourValue = valueAt(index: i);
1874 if (value == ourValue)
1875 return i;
1876 }
1877 return -1;
1878}
1879
1880/*!
1881 \since QtQuick.Controls 2.15 (Qt 5.15)
1882 \qmlproperty bool QtQuick.Controls::ComboBox::selectTextByMouse
1883
1884 This property holds whether the text field for an editable ComboBox
1885 can be selected with the mouse.
1886
1887 The default value is \c false.
1888*/
1889bool QQuickComboBox::selectTextByMouse() const
1890{
1891 Q_D(const QQuickComboBox);
1892 return d->extra.isAllocated() ? d->extra->selectTextByMouse : false;
1893}
1894
1895void QQuickComboBox::setSelectTextByMouse(bool canSelect)
1896{
1897 Q_D(QQuickComboBox);
1898 if (canSelect == selectTextByMouse())
1899 return;
1900
1901 d->extra.value().selectTextByMouse = canSelect;
1902 emit selectTextByMouseChanged();
1903}
1904
1905/*!
1906 \since QtQuick.Controls 6.0 (Qt 6.0)
1907 \qmlproperty enumeration QtQuick.Controls::ComboBox::implicitContentWidthPolicy
1908
1909 This property controls how the \l{Control::}{implicitContentWidth} of the ComboBox is
1910 calculated.
1911
1912 When the width of a ComboBox is not large enough to display text, that text
1913 is elided. Depending on which parts of the text are elided, this can make
1914 selecting an item difficult for the end user. An efficient way of ensuring
1915 that a ComboBox is wide enough to avoid text being elided is to set a width
1916 that is known to be large enough:
1917
1918 \code
1919 width: 300
1920 implicitContentWidthPolicy: ComboBox.ContentItemImplicitWidth
1921 \endcode
1922
1923 However, it is often not possible to know whether or not a hard-coded value
1924 will be large enough, as the size of text depends on many factors, such as
1925 font family, font size, translations, and so on.
1926
1927 implicitContentWidthPolicy provides an easy way to control how the
1928 implicitContentWidth is calculated, which in turn affects the
1929 \l{Item::}{implicitWidth} of the ComboBox and ensures that text will not be elided.
1930
1931 The available values are:
1932
1933 \value ContentItemImplicitWidth
1934 The implicitContentWidth will default to that of the \l{Control::}{contentItem}.
1935 This is the most efficient option, as no extra text layout is done.
1936 \value WidestText
1937 The implicitContentWidth will be set to the implicit width of the
1938 the largest text for the given \l textRole every time the model
1939 changes.
1940 This option should be used with smaller models, as it can be expensive.
1941 \value WidestTextWhenCompleted
1942 The implicitContentWidth will be set to the implicit width of the
1943 the largest text for the given \l textRole once after
1944 \l {QQmlParserStatus::componentComplete()}{component completion}.
1945 This option should be used with smaller models, as it can be expensive.
1946
1947 The default value is \c ContentItemImplicitWidth.
1948
1949 As this property only affects the \c implicitWidth of the ComboBox, setting
1950 an explicit \l{Item::}{width} can still result in eliding.
1951
1952 \note This feature requires the contentItem to be a type derived from
1953 \l TextInput.
1954
1955 \note This feature requires text to be laid out, and can therefore be
1956 expensive for large models or models whose contents are updated
1957 frequently.
1958*/
1959QQuickComboBox::ImplicitContentWidthPolicy QQuickComboBox::implicitContentWidthPolicy() const
1960{
1961 Q_D(const QQuickComboBox);
1962 return d->implicitContentWidthPolicy;
1963}
1964
1965void QQuickComboBox::setImplicitContentWidthPolicy(QQuickComboBox::ImplicitContentWidthPolicy policy)
1966{
1967 Q_D(QQuickComboBox);
1968 if (policy == d->implicitContentWidthPolicy)
1969 return;
1970
1971 d->implicitContentWidthPolicy = policy;
1972 d->maybeUpdateImplicitContentWidth();
1973 emit implicitContentWidthPolicyChanged();
1974}
1975/*!
1976 \qmlmethod string QtQuick.Controls::ComboBox::textAt(int index)
1977
1978 Returns the text for the specified \a index, or an empty string
1979 if the index is out of bounds.
1980
1981 \include qquickcombobox.qdocinc functions-after-component-completion
1982 For example:
1983 \snippet qtquickcontrols-combobox-textat.qml textat
1984
1985 \sa textRole
1986*/
1987QString QQuickComboBox::textAt(int index) const
1988{
1989 Q_D(const QQuickComboBox);
1990 if (!d->isValidIndex(index))
1991 return QString();
1992
1993 return d->delegateModel->stringValue(index, role: d->effectiveTextRole());
1994}
1995
1996/*!
1997 \qmlmethod int QtQuick.Controls::ComboBox::find(string text, enumeration flags)
1998
1999 Returns the index of the specified \a text, or \c -1 if no match is found.
2000
2001 The way the search is performed is defined by the specified match \a flags. By default,
2002 combo box performs case sensitive exact matching (\c Qt.MatchExactly). All other match
2003 types are case-insensitive unless the \c Qt.MatchCaseSensitive flag is also specified.
2004
2005 \value Qt.MatchExactly The search term matches exactly (default).
2006 \value Qt.MatchRegularExpression The search term matches as a regular expression.
2007 \value Qt.MatchWildcard The search term matches using wildcards.
2008 \value Qt.MatchFixedString The search term matches as a fixed string.
2009 \value Qt.MatchStartsWith The search term matches the start of the item.
2010 \value Qt.MatchEndsWith The search term matches the end of the item.
2011 \value Qt.MatchContains The search term is contained in the item.
2012 \value Qt.MatchCaseSensitive The search is case sensitive.
2013
2014 \include qquickcombobox.qdocinc functions-after-component-completion
2015 For example:
2016 \snippet qtquickcontrols-combobox-find.qml find
2017
2018 \sa textRole
2019*/
2020int QQuickComboBox::find(const QString &text, Qt::MatchFlags flags) const
2021{
2022 Q_D(const QQuickComboBox);
2023 return d->match(start: 0, text, flags);
2024}
2025
2026/*!
2027 \qmlmethod void QtQuick.Controls::ComboBox::incrementCurrentIndex()
2028
2029 Increments the current index of the combo box, or the highlighted
2030 index if the popup list is visible.
2031
2032 \sa currentIndex, highlightedIndex
2033*/
2034void QQuickComboBox::incrementCurrentIndex()
2035{
2036 Q_D(QQuickComboBox);
2037 d->incrementCurrentIndex();
2038}
2039
2040/*!
2041 \qmlmethod void QtQuick.Controls::ComboBox::decrementCurrentIndex()
2042
2043 Decrements the current index of the combo box, or the highlighted
2044 index if the popup list is visible.
2045
2046 \sa currentIndex, highlightedIndex
2047*/
2048void QQuickComboBox::decrementCurrentIndex()
2049{
2050 Q_D(QQuickComboBox);
2051 d->decrementCurrentIndex();
2052}
2053
2054/*!
2055 \since QtQuick.Controls 2.2 (Qt 5.9)
2056 \qmlmethod void QtQuick.Controls::ComboBox::selectAll()
2057
2058 Selects all the text in the editable text field of the combo box.
2059
2060 \sa editText
2061*/
2062void QQuickComboBox::selectAll()
2063{
2064 Q_D(QQuickComboBox);
2065 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object: d->contentItem);
2066 if (!input)
2067 return;
2068 input->selectAll();
2069}
2070
2071bool QQuickComboBox::eventFilter(QObject *object, QEvent *event)
2072{
2073 Q_D(QQuickComboBox);
2074 switch (event->type()) {
2075 case QEvent::MouseButtonRelease:
2076 if (d->isPopupVisible())
2077 d->hidePopup(accept: false);
2078 break;
2079 case QEvent::KeyPress: {
2080 QKeyEvent *ke = static_cast<QKeyEvent *>(event);
2081 if (d->filterKeyEvent(ke, post: false))
2082 return true;
2083 event->accept();
2084 if (d->extra.isAllocated())
2085 d->extra->allowComplete = ke->key() != Qt::Key_Backspace && ke->key() != Qt::Key_Delete;
2086 break;
2087 }
2088 case QEvent::FocusOut: {
2089 const bool hasActiveFocus = d->popup && d->popup->hasActiveFocus();
2090 const bool usingPopupWindows =
2091 d->popup ? QQuickPopupPrivate::get(popup: d->popup)->usePopupWindow() : false;
2092 if (qGuiApp->focusObject() != this && !(hasActiveFocus && !usingPopupWindows)) {
2093 // Only close the popup if focus was transferred somewhere else
2094 // than to the popup or the popup button (which normally means that
2095 // the user clicked on the popup button to open it, not close it).
2096 d->hidePopup(accept: false);
2097 setPressed(false);
2098
2099 // The focus left the text field, so if the edit text matches an item in the model,
2100 // change our currentIndex to that. This matches widgets' behavior.
2101 const int indexForEditText = find(text: d->extra.value().editText, flags: Qt::MatchFixedString);
2102 if (indexForEditText > -1)
2103 d->setCurrentItemAtIndex(index: indexForEditText, activate: Activate);
2104 }
2105 break;
2106 }
2107#if QT_CONFIG(im)
2108 case QEvent::InputMethod:
2109 if (d->extra.isAllocated())
2110 d->extra->allowComplete = !static_cast<QInputMethodEvent*>(event)->commitString().isEmpty();
2111 break;
2112#endif
2113 default:
2114 break;
2115 }
2116 return QQuickControl::eventFilter(watched: object, event);
2117}
2118
2119void QQuickComboBox::focusInEvent(QFocusEvent *event)
2120{
2121 Q_D(QQuickComboBox);
2122 QQuickControl::focusInEvent(event);
2123 // Setting focus on TextField should not be done when drop down indicator was clicked
2124 // That is why, if focus is not set with key reason, it should not be passed to textEdit by default.
2125 // Focus on Edit Text should be set only intentionally by user.
2126 if ((event->reason() == Qt::TabFocusReason || event->reason() == Qt::BacktabFocusReason ||
2127 event->reason() == Qt::ShortcutFocusReason) && d->contentItem && isEditable())
2128 d->contentItem->forceActiveFocus(reason: event->reason());
2129}
2130
2131void QQuickComboBox::focusOutEvent(QFocusEvent *event)
2132{
2133 Q_D(QQuickComboBox);
2134 QQuickControl::focusOutEvent(event);
2135
2136 const bool hasActiveFocus = d->popup && d->popup->hasActiveFocus();
2137 const bool usingPopupWindows = d->popup && QQuickPopupPrivate::get(popup: d->popup)->usePopupWindow();
2138 if (qGuiApp->focusObject() != d->contentItem && !(hasActiveFocus && !usingPopupWindows)) {
2139 // Only close the popup if focus was transferred
2140 // somewhere else than to the popup or the inner line edit (which is
2141 // normally done from QQuickComboBox::focusInEvent).
2142 d->hidePopup(accept: false);
2143 setPressed(false);
2144 }
2145}
2146
2147#if QT_CONFIG(im)
2148void QQuickComboBox::inputMethodEvent(QInputMethodEvent *event)
2149{
2150 Q_D(QQuickComboBox);
2151 QQuickControl::inputMethodEvent(event);
2152 if (!isEditable() && !event->commitString().isEmpty())
2153 d->keySearch(text: event->commitString());
2154 else
2155 event->ignore();
2156}
2157#endif
2158
2159void QQuickComboBox::keyPressEvent(QKeyEvent *event)
2160{
2161 Q_D(QQuickComboBox);
2162 QQuickControl::keyPressEvent(event);
2163
2164 const auto key = event->key();
2165 if (!isEditable()) {
2166 const auto buttonPressKeys = QGuiApplicationPrivate::platformTheme()->themeHint(hint: QPlatformTheme::ButtonPressKeys).value<QList<Qt::Key>>();
2167 if (buttonPressKeys.contains(t: key)) {
2168 if (!event->isAutoRepeat())
2169 setPressed(true);
2170 event->accept();
2171 return;
2172 }
2173 }
2174
2175 switch (key) {
2176 case Qt::Key_Escape:
2177 case Qt::Key_Back:
2178 d->acceptedEscKeyPress = d->isPopupVisible();
2179 d->receivedEscKeyPress = true;
2180 if (d->acceptedEscKeyPress) {
2181 d->hidePopup(accept: false);
2182 setPressed(false);
2183 event->accept();
2184 }
2185 break;
2186 case Qt::Key_Enter:
2187 case Qt::Key_Return:
2188 if (d->isPopupVisible())
2189 setPressed(true);
2190 event->accept();
2191 break;
2192 case Qt::Key_Up:
2193 d->keyNavigating = true;
2194 d->decrementCurrentIndex();
2195 event->accept();
2196 break;
2197 case Qt::Key_Down:
2198 d->keyNavigating = true;
2199 d->incrementCurrentIndex();
2200 event->accept();
2201 break;
2202 case Qt::Key_Home:
2203 d->keyNavigating = true;
2204 if (d->isPopupVisible())
2205 d->setHighlightedIndex(index: 0, highlight: Highlight);
2206 else
2207 d->setCurrentItemAtIndex(index: 0, activate: Activate);
2208 event->accept();
2209 break;
2210 case Qt::Key_End:
2211 d->keyNavigating = true;
2212 if (d->isPopupVisible())
2213 d->setHighlightedIndex(index: count() - 1, highlight: Highlight);
2214 else
2215 d->setCurrentItemAtIndex(index: count() - 1, activate: Activate);
2216 event->accept();
2217 break;
2218 default:
2219 if (!isEditable() && !event->text().isEmpty())
2220 d->keySearch(text: event->text());
2221 else
2222 event->ignore();
2223 break;
2224 }
2225}
2226
2227void QQuickComboBox::keyReleaseEvent(QKeyEvent *event)
2228{
2229 Q_D(QQuickComboBox);
2230 QQuickControl::keyReleaseEvent(event);
2231 d->keyNavigating = false;
2232 if (event->isAutoRepeat())
2233 return;
2234
2235 const auto key = event->key();
2236 if (!isEditable()) {
2237 const auto buttonPressKeys = QGuiApplicationPrivate::platformTheme()->themeHint(hint: QPlatformTheme::ButtonPressKeys).value<QList<Qt::Key>>();
2238 if (buttonPressKeys.contains(t: key)) {
2239 if (!isEditable() && isPressed())
2240 d->togglePopup(accept: true);
2241 setPressed(false);
2242 event->accept();
2243 return;
2244 }
2245 }
2246
2247 switch (key) {
2248 case Qt::Key_Enter:
2249 case Qt::Key_Return:
2250 if (!isEditable() || d->isPopupVisible())
2251 d->hidePopup(accept: d->isPopupVisible());
2252 setPressed(false);
2253 event->accept();
2254 break;
2255 case Qt::Key_Escape:
2256 case Qt::Key_Back:
2257 // If QQuickComboBox accepts the key press event, accept the key release event.
2258 // If QQuickComboBox doesn't receive the key press event, but does receive the
2259 // key release event, most likely the popup has popupType == Window and the window was
2260 // closed on key press, resulting in QQuickComboBox only receiving the release event.
2261 if (d->acceptedEscKeyPress || !d->receivedEscKeyPress)
2262 event->accept();
2263 d->acceptedEscKeyPress = false;
2264 d->receivedEscKeyPress = false;
2265 break;
2266 default:
2267 break;
2268 }
2269}
2270
2271#if QT_CONFIG(wheelevent)
2272void QQuickComboBox::wheelEvent(QWheelEvent *event)
2273{
2274 Q_D(QQuickComboBox);
2275 QQuickControl::wheelEvent(event);
2276 if (d->wheelEnabled && !d->isPopupVisible()) {
2277 if (event->angleDelta().y() > 0)
2278 d->decrementCurrentIndex();
2279 else
2280 d->incrementCurrentIndex();
2281 }
2282}
2283#endif
2284
2285bool QQuickComboBox::event(QEvent *e)
2286{
2287 Q_D(QQuickComboBox);
2288 if (e->type() == QEvent::LanguageChange)
2289 d->updateCurrentElements();
2290 return QQuickControl::event(e);
2291}
2292
2293void QQuickComboBox::componentComplete()
2294{
2295 Q_D(QQuickComboBox);
2296 d->executeIndicator(complete: true);
2297 QQuickControl::componentComplete();
2298 if (d->popup)
2299 d->executePopup(complete: true);
2300
2301 if (d->delegateModel && d->ownModel)
2302 static_cast<QQmlDelegateModel *>(d->delegateModel)->componentComplete();
2303
2304 if (count() > 0) {
2305 if (d->currentElementCriteria == QQuickComboBoxPrivate::CurrentElementCriteria::None && d->currentIndex == -1)
2306 d->setCurrentIndex(0);
2307 else
2308 d->updateCurrentElements();
2309
2310 // If the widest text was already calculated in the call to
2311 // QQmlDelegateModel::componentComplete() above, then we shouldn't do it here too.
2312 if (!d->hasCalculatedWidestText)
2313 d->maybeUpdateImplicitContentWidth();
2314 }
2315}
2316
2317void QQuickComboBox::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
2318{
2319 Q_D(QQuickComboBox);
2320 QQuickControl::itemChange(change, value);
2321 if (change == ItemVisibleHasChanged && !value.boolValue) {
2322 d->hidePopup(accept: false);
2323 setPressed(false);
2324 }
2325}
2326
2327void QQuickComboBox::fontChange(const QFont &newFont, const QFont &oldFont)
2328{
2329 Q_D(QQuickComboBox);
2330 QQuickControl::fontChange(newFont, oldFont);
2331 d->maybeUpdateImplicitContentWidth();
2332}
2333
2334void QQuickComboBox::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem)
2335{
2336 Q_D(QQuickComboBox);
2337 if (oldItem) {
2338 oldItem->removeEventFilter(obj: this);
2339 if (QQuickTextInput *oldInput = qobject_cast<QQuickTextInput *>(object: oldItem)) {
2340 QObjectPrivate::disconnect(sender: oldInput, signal: &QQuickTextInput::accepted, receiverPrivate: d, slot: &QQuickComboBoxPrivate::acceptInput);
2341 QObjectPrivate::disconnect(sender: oldInput, signal: &QQuickTextInput::textChanged, receiverPrivate: d, slot: &QQuickComboBoxPrivate::updateEditText);
2342 disconnect(sender: oldInput, signal: &QQuickTextInput::inputMethodComposingChanged, receiver: this, slot: &QQuickComboBox::inputMethodComposingChanged);
2343 QObjectPrivate::disconnect(sender: oldInput, signal: &QQuickTextInput::acceptableInputChanged, receiverPrivate: d, slot: &QQuickComboBoxPrivate::updateAcceptableInput);
2344 }
2345 }
2346 if (newItem && isEditable()) {
2347 newItem->installEventFilter(filterObj: this);
2348 if (QQuickTextInput *newInput = qobject_cast<QQuickTextInput *>(object: newItem)) {
2349 QObjectPrivate::connect(sender: newInput, signal: &QQuickTextInput::accepted, receiverPrivate: d, slot: &QQuickComboBoxPrivate::acceptInput);
2350 QObjectPrivate::connect(sender: newInput, signal: &QQuickTextInput::textChanged, receiverPrivate: d, slot: &QQuickComboBoxPrivate::updateEditText);
2351 connect(sender: newInput, signal: &QQuickTextInput::inputMethodComposingChanged, context: this, slot: &QQuickComboBox::inputMethodComposingChanged);
2352 QObjectPrivate::connect(sender: newInput, signal: &QQuickTextInput::acceptableInputChanged, receiverPrivate: d, slot: &QQuickComboBoxPrivate::updateAcceptableInput);
2353 }
2354#if QT_CONFIG(cursor)
2355 newItem->setCursor(Qt::IBeamCursor);
2356#endif
2357 }
2358
2359 d->updateAcceptableInput();
2360}
2361
2362void QQuickComboBox::localeChange(const QLocale &newLocale, const QLocale &oldLocale)
2363{
2364 QQuickControl::localeChange(newLocale, oldLocale);
2365#if QT_CONFIG(validator)
2366 if (QValidator *v = validator())
2367 v->setLocale(newLocale);
2368#endif
2369}
2370
2371QFont QQuickComboBox::defaultFont() const
2372{
2373 return QQuickTheme::font(scope: QQuickTheme::ComboBox);
2374}
2375
2376#if QT_CONFIG(accessibility)
2377QAccessible::Role QQuickComboBox::accessibleRole() const
2378{
2379 return QAccessible::ComboBox;
2380}
2381
2382void QQuickComboBox::accessibilityActiveChanged(bool active)
2383{
2384 Q_D(QQuickComboBox);
2385 QQuickControl::accessibilityActiveChanged(active);
2386
2387 if (active) {
2388 maybeSetAccessibleName(name: d->hasDisplayText ? d->displayText : d->currentText);
2389 setAccessibleProperty(propertyName: "editable", value: isEditable());
2390 }
2391}
2392#endif //
2393
2394QT_END_NAMESPACE
2395
2396#include "moc_qquickcombobox_p.cpp"
2397

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