1 /****************************************************************************
2 **
3 ** Copyright (C) 2017 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL3$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or later as published by the Free
28 ** Software Foundation and appearing in the file LICENSE.GPL included in
29 ** the packaging of this file. Please review the following information to
30 ** ensure the GNU General Public License version 2.0 requirements will be
31 ** met: http://www.gnu.org/licenses/gpl-2.0.html.
32 **
33 ** $QT_END_LICENSE$
34 **
35 ****************************************************************************/
36 
37 #include "qquickcombobox_p.h"
38 #include "qquickcontrol_p_p.h"
39 #include "qquickabstractbutton_p.h"
40 #include "qquickabstractbutton_p_p.h"
41 #include "qquickpopup_p_p.h"
42 #include "qquickdeferredexecute_p_p.h"
43 
44 #include <QtCore/qregularexpression.h>
45 #include <QtCore/qabstractitemmodel.h>
46 #include <QtCore/qglobal.h>
47 #include <QtGui/qinputmethod.h>
48 #include <QtGui/qguiapplication.h>
49 #include <QtGui/qpa/qplatformtheme.h>
50 #include <QtQml/qjsvalue.h>
51 #include <QtQml/qqmlcontext.h>
52 #include <QtQml/private/qlazilyallocated_p.h>
53 #include <private/qqmldelegatemodel_p.h>
54 #include <QtQuick/private/qquickaccessibleattached_p.h>
55 #include <QtQuick/private/qquickevents_p_p.h>
56 #include <QtQuick/private/qquicktextinput_p.h>
57 #include <QtQuick/private/qquickitemview_p.h>
58 
59 QT_BEGIN_NAMESPACE
60 
61 /*!
62     \qmltype ComboBox
63     \inherits Control
64 //!     \instantiates QQuickComboBox
65     \inqmlmodule QtQuick.Controls
66     \since 5.7
67     \ingroup qtquickcontrols2-input
68     \ingroup qtquickcontrols2-focusscopes
69     \brief Combined button and popup list for selecting options.
70 
71     \image qtquickcontrols2-combobox.gif
72 
73     ComboBox is a combined button and popup list. It provides a means of
74     presenting a list of options to the user in a way that takes up the
75     minimum amount of screen space.
76 
77     ComboBox is populated with a data model. The data model is commonly
78     a JavaScript array, a \l ListModel or an integer, but other types
79     of \l {qml-data-models}{data models} are also supported.
80 
81     \code
82     ComboBox {
83         model: ["First", "Second", "Third"]
84     }
85     \endcode
86 
87     \section1 Editable ComboBox
88 
89     ComboBox can be made \l editable. An editable combo box auto-completes
90     its text based on what is available in the model.
91 
92     The following example demonstrates appending content to an editable
93     combo box by reacting to the \l accepted signal.
94 
95     \snippet qtquickcontrols2-combobox-accepted.qml combobox
96 
97     \section1 ComboBox Model Roles
98 
99     ComboBox is able to visualize standard \l {qml-data-models}{data models}
100     that provide the \c modelData role:
101     \list
102     \li models that have only one role
103     \li models that do not have named roles (JavaScript array, integer)
104     \endlist
105 
106     When using models that have multiple named roles, ComboBox must be configured
107     to use a specific \l {textRole}{text role} for its \l {displayText}{display text}
108     and \l delegate instances. If you want to use a role of the model item
109     that corresponds to the text role, set \l valueRole. The \l currentValue
110     property and \l indexOfValue() method can then be used to get information
111     about those values.
112 
113     For example:
114 
115     \snippet qtquickcontrols2-combobox-valuerole.qml file
116 
117     \note If ComboBox is assigned a data model that has multiple named roles, but
118     \l textRole is not defined, ComboBox is unable to visualize it and throws a
119     \c {ReferenceError: modelData is not defined}.
120 
121     \sa {Customizing ComboBox}, {Input Controls}, {Focus Management in Qt Quick Controls}
122 */
123 
124 /*!
125     \qmlsignal void QtQuick.Controls::ComboBox::activated(int index)
126 
127     This signal is emitted when the item at \a index is activated by the user.
128 
129     An item is activated when it is selected while the popup is open,
130     causing the popup to close (and \l currentIndex to change),
131     or while the popup is closed and the combo box is navigated via
132     keyboard, causing the \l currentIndex to change.
133     The \l currentIndex property is set to \a index.
134 
135     \sa currentIndex
136 */
137 
138 /*!
139     \qmlsignal void QtQuick.Controls::ComboBox::highlighted(int index)
140 
141     This signal is emitted when the item at \a index in the popup list is highlighted by the user.
142 
143     The highlighted signal is only emitted when the popup is open and an item
144     is highlighted, but not necessarily \l activated.
145 
146     \sa highlightedIndex
147 */
148 
149 /*!
150     \since QtQuick.Controls 2.2 (Qt 5.9)
151     \qmlsignal void QtQuick.Controls::ComboBox::accepted()
152 
153     This signal is emitted when the \uicontrol Return or \uicontrol Enter key is pressed
154     on an \l editable combo box.
155 
156     You can handle this signal in order to add the newly entered
157     item to the model, for example:
158 
159     \snippet qtquickcontrols2-combobox-accepted.qml combobox
160 
161     Before the signal is emitted, a check is done to see if the string
162     exists in the model. If it does, \l currentIndex will be set to its index,
163     and \l currentText to the string itself.
164 
165     After the signal has been emitted, and if the first check failed (that is,
166     the item did not exist), another check will be done to see if the item was
167     added by the signal handler. If it was, the \l currentIndex and
168     \l currentText are updated accordingly. Otherwise, they will be set to
169     \c -1 and \c "", respectively.
170 
171     \note If there is a \l validator set on the combo box, the signal will only be
172           emitted if the input is in an acceptable state.
173 */
174 
175 namespace {
176     enum Activation { NoActivate, Activate };
177     enum Highlighting { NoHighlight, Highlight };
178 }
179 
180 class QQuickComboBoxDelegateModel : public QQmlDelegateModel
181 {
182 public:
183     explicit QQuickComboBoxDelegateModel(QQuickComboBox *combo);
184     QVariant variantValue(int index, const QString &role) override;
185 
186 private:
187     QQuickComboBox *combo = nullptr;
188 };
189 
QQuickComboBoxDelegateModel(QQuickComboBox * combo)190 QQuickComboBoxDelegateModel::QQuickComboBoxDelegateModel(QQuickComboBox *combo)
191     : QQmlDelegateModel(qmlContext(combo), combo),
192       combo(combo)
193 {
194 }
195 
variantValue(int index,const QString & role)196 QVariant QQuickComboBoxDelegateModel::variantValue(int index, const QString &role)
197 {
198     const QVariant model = combo->model();
199     if (model.userType() == QMetaType::QVariantList) {
200         QVariant object = model.toList().value(index);
201         if (object.userType() == QMetaType::QVariantMap) {
202             const QVariantMap data = object.toMap();
203             if (data.count() == 1 && role == QLatin1String("modelData"))
204                 return data.first();
205             return data.value(role);
206         } else if (object.userType() == QMetaType::QObjectStar) {
207             const QObject *data = object.value<QObject *>();
208             if (data && role != QLatin1String("modelData"))
209                 return data->property(role.toUtf8());
210         }
211     }
212     return QQmlDelegateModel::variantValue(index, role);
213 }
214 
215 class QQuickComboBoxPrivate : public QQuickControlPrivate
216 {
217     Q_DECLARE_PUBLIC(QQuickComboBox)
218 
219 public:
220     bool isPopupVisible() const;
221     void showPopup();
222     void hidePopup(bool accept);
223     void togglePopup(bool accept);
224     void popupVisibleChanged();
225 
226     void itemClicked();
227     void itemHovered();
228 
229     void createdItem(int index, QObject *object);
230     void modelUpdated();
231     void countChanged();
232 
233     void updateEditText();
234     void updateCurrentText();
235     void updateCurrentValue();
236     void updateCurrentTextAndValue();
237 
238     bool isValidIndex(int index) const;
239 
240     void acceptInput();
241     QString tryComplete(const QString &inputText);
242 
243     void incrementCurrentIndex();
244     void decrementCurrentIndex();
245     void setCurrentIndex(int index, Activation activate);
246     void updateHighlightedIndex();
247     void setHighlightedIndex(int index, Highlighting highlight);
248 
249     void keySearch(const QString &text);
250     int match(int start, const QString &text, Qt::MatchFlags flags) const;
251 
252     void createDelegateModel();
253 
254     void handlePress(const QPointF &point) override;
255     void handleMove(const QPointF &point) override;
256     void handleRelease(const QPointF &point) override;
257     void handleUngrab() override;
258 
259     void cancelIndicator();
260     void executeIndicator(bool complete = false);
261 
262     void cancelPopup();
263     void executePopup(bool complete = false);
264 
265     void itemImplicitWidthChanged(QQuickItem *item) override;
266     void itemImplicitHeightChanged(QQuickItem *item) override;
267 
268     static void hideOldPopup(QQuickPopup *popup);
269 
270     bool flat = false;
271     bool down = false;
272     bool hasDown = false;
273     bool pressed = false;
274     bool ownModel = false;
275     bool keyNavigating = false;
276     bool hasDisplayText = false;
277     bool hasCurrentIndex = false;
278     int highlightedIndex = -1;
279     int currentIndex = -1;
280     QVariant model;
281     QString textRole;
282     QString currentText;
283     QString displayText;
284     QString valueRole;
285     QVariant currentValue;
286     QQuickItem *pressedItem = nullptr;
287     QQmlInstanceModel *delegateModel = nullptr;
288     QQmlComponent *delegate = nullptr;
289     QQuickDeferredPointer<QQuickItem> indicator;
290     QQuickDeferredPointer<QQuickPopup> popup;
291 
292     struct ExtraData {
293         bool editable = false;
294         bool accepting = false;
295         bool allowComplete = false;
296         bool selectTextByMouse = false;
297         Qt::InputMethodHints inputMethodHints = Qt::ImhNone;
298         QString editText;
299         QValidator *validator = nullptr;
300     };
301     QLazilyAllocated<ExtraData> extra;
302 };
303 
isPopupVisible() const304 bool QQuickComboBoxPrivate::isPopupVisible() const
305 {
306     return popup && popup->isVisible();
307 }
308 
showPopup()309 void QQuickComboBoxPrivate::showPopup()
310 {
311     if (!popup)
312         executePopup(true);
313 
314     if (popup && !popup->isVisible())
315         popup->open();
316 }
317 
hidePopup(bool accept)318 void QQuickComboBoxPrivate::hidePopup(bool accept)
319 {
320     Q_Q(QQuickComboBox);
321     if (accept) {
322         q->setCurrentIndex(highlightedIndex);
323         emit q->activated(currentIndex);
324     }
325     if (popup && popup->isVisible())
326         popup->close();
327 }
328 
togglePopup(bool accept)329 void QQuickComboBoxPrivate::togglePopup(bool accept)
330 {
331     if (!popup || !popup->isVisible())
332         showPopup();
333     else
334         hidePopup(accept);
335 }
336 
popupVisibleChanged()337 void QQuickComboBoxPrivate::popupVisibleChanged()
338 {
339     Q_Q(QQuickComboBox);
340     if (isPopupVisible())
341         QGuiApplication::inputMethod()->reset();
342 
343     QQuickItemView *itemView = popup->findChild<QQuickItemView *>();
344     if (itemView)
345         itemView->setHighlightRangeMode(QQuickItemView::NoHighlightRange);
346 
347     updateHighlightedIndex();
348 
349     if (itemView)
350         itemView->positionViewAtIndex(highlightedIndex, QQuickItemView::Beginning);
351 
352     if (!hasDown) {
353         q->setDown(pressed || isPopupVisible());
354         hasDown = false;
355     }
356 }
357 
itemClicked()358 void QQuickComboBoxPrivate::itemClicked()
359 {
360     Q_Q(QQuickComboBox);
361     int index = delegateModel->indexOf(q->sender(), nullptr);
362     if (index != -1) {
363         setHighlightedIndex(index, Highlight);
364         hidePopup(true);
365     }
366 }
367 
itemHovered()368 void QQuickComboBoxPrivate::itemHovered()
369 {
370     Q_Q(QQuickComboBox);
371     if (keyNavigating)
372         return;
373 
374     QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(q->sender());
375     if (!button || !button->isHovered() || QQuickAbstractButtonPrivate::get(button)->touchId != -1)
376         return;
377 
378     int index = delegateModel->indexOf(button, nullptr);
379     if (index != -1) {
380         setHighlightedIndex(index, Highlight);
381 
382         if (QQuickItemView *itemView = popup->findChild<QQuickItemView *>())
383             itemView->positionViewAtIndex(index, QQuickItemView::Contain);
384     }
385 }
386 
createdItem(int index,QObject * object)387 void QQuickComboBoxPrivate::createdItem(int index, QObject *object)
388 {
389     Q_Q(QQuickComboBox);
390     QQuickItem *item = qobject_cast<QQuickItem *>(object);
391     if (item && !item->parentItem()) {
392         if (popup)
393             item->setParentItem(popup->contentItem());
394         else
395             item->setParentItem(q);
396         QQuickItemPrivate::get(item)->setCulled(true);
397     }
398 
399     QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(object);
400     if (button) {
401         button->setFocusPolicy(Qt::NoFocus);
402         connect(button, &QQuickAbstractButton::clicked, this, &QQuickComboBoxPrivate::itemClicked);
403         connect(button, &QQuickAbstractButton::hoveredChanged, this, &QQuickComboBoxPrivate::itemHovered);
404     }
405 
406     if (index == currentIndex && !q->isEditable())
407         updateCurrentTextAndValue();
408 }
409 
modelUpdated()410 void QQuickComboBoxPrivate::modelUpdated()
411 {
412     if (!extra.isAllocated() || !extra->accepting)
413         updateCurrentTextAndValue();
414 }
415 
countChanged()416 void QQuickComboBoxPrivate::countChanged()
417 {
418     Q_Q(QQuickComboBox);
419     if (q->count() == 0)
420         q->setCurrentIndex(-1);
421     emit q->countChanged();
422 }
423 
updateEditText()424 void QQuickComboBoxPrivate::updateEditText()
425 {
426     Q_Q(QQuickComboBox);
427     QQuickTextInput *input = qobject_cast<QQuickTextInput *>(contentItem);
428     if (!input)
429         return;
430 
431     const QString text = input->text();
432 
433     if (extra.isAllocated() && extra->allowComplete && !text.isEmpty()) {
434         const QString completed = tryComplete(text);
435         if (completed.length() > text.length()) {
436             input->setText(completed);
437             input->select(completed.length(), text.length());
438             return;
439         }
440     }
441     q->setEditText(text);
442 }
443 
updateCurrentText()444 void QQuickComboBoxPrivate::updateCurrentText()
445 {
446     Q_Q(QQuickComboBox);
447     const QString text = q->textAt(currentIndex);
448     if (currentText != text) {
449         currentText = text;
450         if (!hasDisplayText)
451            q->maybeSetAccessibleName(text);
452         emit q->currentTextChanged();
453     }
454     if (!hasDisplayText && displayText != text) {
455         displayText = text;
456         emit q->displayTextChanged();
457     }
458     if (!extra.isAllocated() || !extra->accepting)
459         q->setEditText(currentText);
460 }
461 
updateCurrentValue()462 void QQuickComboBoxPrivate::updateCurrentValue()
463 {
464     Q_Q(QQuickComboBox);
465     const QVariant value = q->valueAt(currentIndex);
466     if (currentValue == value)
467         return;
468 
469     currentValue = value;
470     emit q->currentValueChanged();
471 }
472 
updateCurrentTextAndValue()473 void QQuickComboBoxPrivate::updateCurrentTextAndValue()
474 {
475     updateCurrentText();
476     updateCurrentValue();
477 }
478 
isValidIndex(int index) const479 bool QQuickComboBoxPrivate::isValidIndex(int index) const
480 {
481     return delegateModel && index >= 0 && index < delegateModel->count();
482 }
483 
acceptInput()484 void QQuickComboBoxPrivate::acceptInput()
485 {
486     Q_Q(QQuickComboBox);
487     int idx = q->find(extra.value().editText, Qt::MatchFixedString);
488     if (idx > -1)
489         q->setCurrentIndex(idx);
490 
491     extra.value().accepting = true;
492     emit q->accepted();
493 
494     if (idx == -1)
495         q->setCurrentIndex(q->find(extra.value().editText, Qt::MatchFixedString));
496     extra.value().accepting = false;
497 }
498 
tryComplete(const QString & input)499 QString QQuickComboBoxPrivate::tryComplete(const QString &input)
500 {
501     Q_Q(QQuickComboBox);
502     QString match;
503 
504     const int itemCount = q->count();
505     for (int idx = 0; idx < itemCount; ++idx) {
506         const QString text = q->textAt(idx);
507         if (!text.startsWith(input, Qt::CaseInsensitive))
508             continue;
509 
510         // either the first or the shortest match
511         if (match.isEmpty() || text.length() < match.length())
512             match = text;
513     }
514 
515     if (match.isEmpty())
516         return input;
517 
518     return input + match.mid(input.length());
519 }
520 
setCurrentIndex(int index,Activation activate)521 void QQuickComboBoxPrivate::setCurrentIndex(int index, Activation activate)
522 {
523     Q_Q(QQuickComboBox);
524     if (currentIndex == index)
525         return;
526 
527     currentIndex = index;
528     emit q->currentIndexChanged();
529 
530     if (componentComplete)
531         updateCurrentTextAndValue();
532 
533     if (activate)
534         emit q->activated(index);
535 }
536 
incrementCurrentIndex()537 void QQuickComboBoxPrivate::incrementCurrentIndex()
538 {
539     Q_Q(QQuickComboBox);
540     if (extra.isAllocated())
541         extra->allowComplete = false;
542     if (isPopupVisible()) {
543         if (highlightedIndex < q->count() - 1)
544             setHighlightedIndex(highlightedIndex + 1, Highlight);
545     } else {
546         if (currentIndex < q->count() - 1)
547             setCurrentIndex(currentIndex + 1, Activate);
548     }
549     if (extra.isAllocated())
550         extra->allowComplete = true;
551 }
552 
decrementCurrentIndex()553 void QQuickComboBoxPrivate::decrementCurrentIndex()
554 {
555     if (extra.isAllocated())
556         extra->allowComplete = false;
557     if (isPopupVisible()) {
558         if (highlightedIndex > 0)
559             setHighlightedIndex(highlightedIndex - 1, Highlight);
560     } else {
561         if (currentIndex > 0)
562             setCurrentIndex(currentIndex - 1, Activate);
563     }
564     if (extra.isAllocated())
565         extra->allowComplete = true;
566 }
567 
updateHighlightedIndex()568 void QQuickComboBoxPrivate::updateHighlightedIndex()
569 {
570     setHighlightedIndex(popup->isVisible() ? currentIndex : -1, NoHighlight);
571 }
572 
setHighlightedIndex(int index,Highlighting highlight)573 void QQuickComboBoxPrivate::setHighlightedIndex(int index, Highlighting highlight)
574 {
575     Q_Q(QQuickComboBox);
576     if (highlightedIndex == index)
577         return;
578 
579     highlightedIndex = index;
580     emit q->highlightedIndexChanged();
581 
582     if (highlight)
583         emit q->highlighted(index);
584 }
585 
keySearch(const QString & text)586 void QQuickComboBoxPrivate::keySearch(const QString &text)
587 {
588     const int startIndex = isPopupVisible() ? highlightedIndex : currentIndex;
589     const int index = match(startIndex + 1, text, Qt::MatchStartsWith | Qt::MatchWrap);
590     if (index != -1) {
591         if (isPopupVisible())
592             setHighlightedIndex(index, Highlight);
593         else
594             setCurrentIndex(index, Activate);
595     }
596 }
597 
match(int start,const QString & text,Qt::MatchFlags flags) const598 int QQuickComboBoxPrivate::match(int start, const QString &text, Qt::MatchFlags flags) const
599 {
600     Q_Q(const QQuickComboBox);
601     uint matchType = flags & 0x0F;
602     bool wrap = flags & Qt::MatchWrap;
603     Qt::CaseSensitivity cs = flags & Qt::MatchCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive;
604     QRegularExpression::PatternOptions options = flags & Qt::MatchCaseSensitive ? QRegularExpression::NoPatternOption
605                                                                                 : QRegularExpression::CaseInsensitiveOption;
606     int from = start;
607     int to = q->count();
608 
609     // iterates twice if wrapping
610     for (int i = 0; (wrap && i < 2) || (!wrap && i < 1); ++i) {
611         for (int idx = from; idx < to; ++idx) {
612             QString t = q->textAt(idx);
613             switch (matchType) {
614             case Qt::MatchExactly:
615                 if (t == text)
616                     return idx;
617                 break;
618             case Qt::MatchRegExp: {
619                 QRegularExpression rx(QRegularExpression::anchoredPattern(text), options);
620                 if (rx.match(t).hasMatch())
621                     return idx;
622                 break;
623             }
624             case Qt::MatchWildcard: {
625                 QRegularExpression rx(QRegularExpression::wildcardToRegularExpression(text),
626                                       options);
627                 if (rx.match(t).hasMatch())
628                     return idx;
629                 break;
630             }
631             case Qt::MatchStartsWith:
632                 if (t.startsWith(text, cs))
633                     return idx;
634                 break;
635             case Qt::MatchEndsWith:
636                 if (t.endsWith(text, cs))
637                     return idx;
638                 break;
639             case Qt::MatchFixedString:
640                 if (t.compare(text, cs) == 0)
641                     return idx;
642                 break;
643             case Qt::MatchContains:
644             default:
645                 if (t.contains(text, cs))
646                     return idx;
647                 break;
648             }
649         }
650         // prepare for the next iteration
651         from = 0;
652         to = start;
653     }
654     return -1;
655 }
656 
createDelegateModel()657 void QQuickComboBoxPrivate::createDelegateModel()
658 {
659     Q_Q(QQuickComboBox);
660     bool ownedOldModel = ownModel;
661     QQmlInstanceModel* oldModel = delegateModel;
662     if (oldModel) {
663         disconnect(delegateModel, &QQmlInstanceModel::countChanged, this, &QQuickComboBoxPrivate::countChanged);
664         disconnect(delegateModel, &QQmlInstanceModel::modelUpdated, this, &QQuickComboBoxPrivate::modelUpdated);
665         disconnect(delegateModel, &QQmlInstanceModel::createdItem, this, &QQuickComboBoxPrivate::createdItem);
666     }
667 
668     ownModel = false;
669     delegateModel = model.value<QQmlInstanceModel *>();
670 
671     if (!delegateModel && model.isValid()) {
672         QQmlDelegateModel *dataModel = new QQuickComboBoxDelegateModel(q);
673         dataModel->setModel(model);
674         dataModel->setDelegate(delegate);
675         if (q->isComponentComplete())
676             dataModel->componentComplete();
677 
678         ownModel = true;
679         delegateModel = dataModel;
680     }
681 
682     if (delegateModel) {
683         connect(delegateModel, &QQmlInstanceModel::countChanged, this, &QQuickComboBoxPrivate::countChanged);
684         connect(delegateModel, &QQmlInstanceModel::modelUpdated, this, &QQuickComboBoxPrivate::modelUpdated);
685         connect(delegateModel, &QQmlInstanceModel::createdItem, this, &QQuickComboBoxPrivate::createdItem);
686     }
687 
688     emit q->delegateModelChanged();
689 
690     if (ownedOldModel)
691         delete oldModel;
692 }
693 
handlePress(const QPointF & point)694 void QQuickComboBoxPrivate::handlePress(const QPointF &point)
695 {
696     Q_Q(QQuickComboBox);
697     QQuickControlPrivate::handlePress(point);
698     q->setPressed(true);
699 }
700 
handleMove(const QPointF & point)701 void QQuickComboBoxPrivate::handleMove(const QPointF &point)
702 {
703     Q_Q(QQuickComboBox);
704     QQuickControlPrivate::handleMove(point);
705     q->setPressed(q->contains(point));
706 }
707 
handleRelease(const QPointF & point)708 void QQuickComboBoxPrivate::handleRelease(const QPointF &point)
709 {
710     Q_Q(QQuickComboBox);
711     QQuickControlPrivate::handleRelease(point);
712     if (pressed) {
713         q->setPressed(false);
714         togglePopup(false);
715     }
716 }
717 
handleUngrab()718 void QQuickComboBoxPrivate::handleUngrab()
719 {
720     Q_Q(QQuickComboBox);
721     QQuickControlPrivate::handleUngrab();
722     q->setPressed(false);
723 }
724 
indicatorName()725 static inline QString indicatorName() { return QStringLiteral("indicator"); }
726 
cancelIndicator()727 void QQuickComboBoxPrivate::cancelIndicator()
728 {
729     Q_Q(QQuickComboBox);
730     quickCancelDeferred(q, indicatorName());
731 }
732 
executeIndicator(bool complete)733 void QQuickComboBoxPrivate::executeIndicator(bool complete)
734 {
735     Q_Q(QQuickComboBox);
736     if (indicator.wasExecuted())
737         return;
738 
739     if (!indicator || complete)
740         quickBeginDeferred(q, indicatorName(), indicator);
741     if (complete)
742         quickCompleteDeferred(q, indicatorName(), indicator);
743 }
744 
popupName()745 static inline QString popupName() { return QStringLiteral("popup"); }
746 
cancelPopup()747 void QQuickComboBoxPrivate::cancelPopup()
748 {
749     Q_Q(QQuickComboBox);
750     quickCancelDeferred(q, popupName());
751 }
752 
executePopup(bool complete)753 void QQuickComboBoxPrivate::executePopup(bool complete)
754 {
755     Q_Q(QQuickComboBox);
756     if (popup.wasExecuted())
757         return;
758 
759     if (!popup || complete)
760         quickBeginDeferred(q, popupName(), popup);
761     if (complete)
762         quickCompleteDeferred(q, popupName(), popup);
763 }
764 
itemImplicitWidthChanged(QQuickItem * item)765 void QQuickComboBoxPrivate::itemImplicitWidthChanged(QQuickItem *item)
766 {
767     Q_Q(QQuickComboBox);
768     QQuickControlPrivate::itemImplicitWidthChanged(item);
769     if (item == indicator)
770         emit q->implicitIndicatorWidthChanged();
771 }
772 
itemImplicitHeightChanged(QQuickItem * item)773 void QQuickComboBoxPrivate::itemImplicitHeightChanged(QQuickItem *item)
774 {
775     Q_Q(QQuickComboBox);
776     QQuickControlPrivate::itemImplicitHeightChanged(item);
777     if (item == indicator)
778         emit q->implicitIndicatorHeightChanged();
779 }
780 
hideOldPopup(QQuickPopup * popup)781 void QQuickComboBoxPrivate::hideOldPopup(QQuickPopup *popup)
782 {
783     if (!popup)
784         return;
785 
786     qCDebug(lcItemManagement) << "hiding old popup" << popup;
787 
788     popup->setVisible(false);
789     popup->setParentItem(nullptr);
790 #if QT_CONFIG(accessibility)
791     // Remove the item from the accessibility tree.
792     QQuickAccessibleAttached *accessible = accessibleAttached(popup);
793     if (accessible)
794         accessible->setIgnored(true);
795 #endif
796 }
797 
QQuickComboBox(QQuickItem * parent)798 QQuickComboBox::QQuickComboBox(QQuickItem *parent)
799     : QQuickControl(*(new QQuickComboBoxPrivate), parent)
800 {
801     setFocusPolicy(Qt::StrongFocus);
802     setFlag(QQuickItem::ItemIsFocusScope);
803     setAcceptedMouseButtons(Qt::LeftButton);
804 #if QT_CONFIG(cursor)
805     setCursor(Qt::ArrowCursor);
806 #endif
807     setInputMethodHints(Qt::ImhNoPredictiveText);
808 }
809 
~QQuickComboBox()810 QQuickComboBox::~QQuickComboBox()
811 {
812     Q_D(QQuickComboBox);
813     d->removeImplicitSizeListener(d->indicator);
814     if (d->popup) {
815         // Disconnect visibleChanged() to avoid a spurious highlightedIndexChanged() signal
816         // emission during the destruction of the (visible) popup. (QTBUG-57650)
817         QObjectPrivate::disconnect(d->popup.data(), &QQuickPopup::visibleChanged, d, &QQuickComboBoxPrivate::popupVisibleChanged);
818         QQuickComboBoxPrivate::hideOldPopup(d->popup);
819         d->popup = nullptr;
820     }
821 }
822 
823 /*!
824     \readonly
825     \qmlproperty int QtQuick.Controls::ComboBox::count
826 
827     This property holds the number of items in the combo box.
828 */
count() const829 int QQuickComboBox::count() const
830 {
831     Q_D(const QQuickComboBox);
832     return d->delegateModel ? d->delegateModel->count() : 0;
833 }
834 
835 /*!
836     \qmlproperty model QtQuick.Controls::ComboBox::model
837 
838     This property holds the model providing data for the combo box.
839 
840     \code
841     ComboBox {
842         textRole: "key"
843         model: ListModel {
844             ListElement { key: "First"; value: 123 }
845             ListElement { key: "Second"; value: 456 }
846             ListElement { key: "Third"; value: 789 }
847         }
848     }
849     \endcode
850 
851     \sa textRole, {qml-data-models}{Data Models}
852 */
model() const853 QVariant QQuickComboBox::model() const
854 {
855     Q_D(const QQuickComboBox);
856     return d->model;
857 }
858 
setModel(const QVariant & m)859 void QQuickComboBox::setModel(const QVariant& m)
860 {
861     Q_D(QQuickComboBox);
862     QVariant model = m;
863     if (model.userType() == qMetaTypeId<QJSValue>())
864         model = model.value<QJSValue>().toVariant();
865 
866     if (d->model == model)
867         return;
868 
869     if (QAbstractItemModel* aim = qvariant_cast<QAbstractItemModel *>(d->model)) {
870         QObjectPrivate::disconnect(aim, &QAbstractItemModel::dataChanged,
871             d, QOverload<>::of(&QQuickComboBoxPrivate::updateCurrentTextAndValue));
872     }
873     if (QAbstractItemModel* aim = qvariant_cast<QAbstractItemModel *>(model)) {
874         QObjectPrivate::connect(aim, &QAbstractItemModel::dataChanged,
875             d, QOverload<>::of(&QQuickComboBoxPrivate::updateCurrentTextAndValue));
876     }
877 
878     d->model = model;
879     d->createDelegateModel();
880     emit countChanged();
881     if (isComponentComplete()) {
882         setCurrentIndex(count() > 0 ? 0 : -1);
883         d->updateCurrentTextAndValue();
884     }
885     emit modelChanged();
886 }
887 
888 /*!
889     \internal
890     \qmlproperty model QtQuick.Controls::ComboBox::delegateModel
891 
892     This property holds the model providing delegate instances for the combo box.
893 */
delegateModel() const894 QQmlInstanceModel *QQuickComboBox::delegateModel() const
895 {
896     Q_D(const QQuickComboBox);
897     return d->delegateModel;
898 }
899 
900 
901 /*!
902     \qmlproperty bool QtQuick.Controls::ComboBox::pressed
903 
904     This property holds whether the combo box button is physically pressed.
905     A button can be pressed by either touch or key events.
906 
907     \sa down
908 */
isPressed() const909 bool QQuickComboBox::isPressed() const
910 {
911     Q_D(const QQuickComboBox);
912     return d->pressed;
913 }
914 
setPressed(bool pressed)915 void QQuickComboBox::setPressed(bool pressed)
916 {
917     Q_D(QQuickComboBox);
918     if (d->pressed == pressed)
919         return;
920 
921     d->pressed = pressed;
922     emit pressedChanged();
923 
924     if (!d->hasDown) {
925         setDown(d->pressed || d->isPopupVisible());
926         d->hasDown = false;
927     }
928 }
929 
930 /*!
931     \readonly
932     \qmlproperty int QtQuick.Controls::ComboBox::highlightedIndex
933 
934     This property holds the index of the highlighted item in the combo box popup list.
935 
936     When a highlighted item is activated, the popup is closed, \l currentIndex
937     is set to \c highlightedIndex, and the value of this property is reset to
938     \c -1, as there is no longer a highlighted item.
939 
940     \sa highlighted(), currentIndex
941 */
highlightedIndex() const942 int QQuickComboBox::highlightedIndex() const
943 {
944     Q_D(const QQuickComboBox);
945     return d->highlightedIndex;
946 }
947 
948 /*!
949     \qmlproperty int QtQuick.Controls::ComboBox::currentIndex
950 
951     This property holds the index of the current item in the combo box.
952 
953     The default value is \c -1 when \l count is \c 0, and \c 0 otherwise.
954 
955     \sa activated(), currentText, highlightedIndex
956 */
currentIndex() const957 int QQuickComboBox::currentIndex() const
958 {
959     Q_D(const QQuickComboBox);
960     return d->currentIndex;
961 }
962 
setCurrentIndex(int index)963 void QQuickComboBox::setCurrentIndex(int index)
964 {
965     Q_D(QQuickComboBox);
966     d->hasCurrentIndex = true;
967     d->setCurrentIndex(index, NoActivate);
968 }
969 
970 /*!
971     \readonly
972     \qmlproperty string QtQuick.Controls::ComboBox::currentText
973 
974     This property holds the text of the current item in the combo box.
975 
976     \sa currentIndex, displayText, textRole, editText
977 */
currentText() const978 QString QQuickComboBox::currentText() const
979 {
980     Q_D(const QQuickComboBox);
981     return d->currentText;
982 }
983 
984 /*!
985     \qmlproperty string QtQuick.Controls::ComboBox::displayText
986 
987     This property holds the text that is displayed on the combo box button.
988 
989     By default, the display text presents the current selection. That is,
990     it follows the text of the current item. However, the default display
991     text can be overridden with a custom value.
992 
993     \code
994     ComboBox {
995         currentIndex: 1
996         displayText: "Size: " + currentText
997         model: ["S", "M", "L"]
998     }
999     \endcode
1000 
1001     \sa currentText, textRole
1002 */
displayText() const1003 QString QQuickComboBox::displayText() const
1004 {
1005     Q_D(const QQuickComboBox);
1006     return d->displayText;
1007 }
1008 
setDisplayText(const QString & text)1009 void QQuickComboBox::setDisplayText(const QString &text)
1010 {
1011     Q_D(QQuickComboBox);
1012     d->hasDisplayText = true;
1013     if (d->displayText == text)
1014         return;
1015 
1016     d->displayText = text;
1017     maybeSetAccessibleName(text);
1018     emit displayTextChanged();
1019 }
1020 
resetDisplayText()1021 void QQuickComboBox::resetDisplayText()
1022 {
1023     Q_D(QQuickComboBox);
1024     if (!d->hasDisplayText)
1025         return;
1026 
1027     d->hasDisplayText = false;
1028     d->updateCurrentText();
1029 }
1030 
1031 
1032 /*!
1033     \qmlproperty string QtQuick.Controls::ComboBox::textRole
1034 
1035     This property holds the model role used for populating the combo box.
1036 
1037     When the model has multiple roles, \c textRole can be set to determine
1038     which role should be displayed.
1039 
1040     \sa model, currentText, displayText, {ComboBox Model Roles}
1041 */
textRole() const1042 QString QQuickComboBox::textRole() const
1043 {
1044     Q_D(const QQuickComboBox);
1045     return d->textRole;
1046 }
1047 
setTextRole(const QString & role)1048 void QQuickComboBox::setTextRole(const QString &role)
1049 {
1050     Q_D(QQuickComboBox);
1051     if (d->textRole == role)
1052         return;
1053 
1054     d->textRole = role;
1055     if (isComponentComplete())
1056         d->updateCurrentText();
1057     emit textRoleChanged();
1058 }
1059 
1060 /*!
1061     \since QtQuick.Controls 2.14 (Qt 5.14)
1062     \qmlproperty string QtQuick.Controls::ComboBox::valueRole
1063 
1064     This property holds the model role used for storing the value associated
1065     with each item in the model.
1066 
1067     For an example of how to use this property, see \l {ComboBox Model Roles}.
1068 
1069     \sa model, currentValue
1070 */
valueRole() const1071 QString QQuickComboBox::valueRole() const
1072 {
1073     Q_D(const QQuickComboBox);
1074     return d->valueRole;
1075 }
1076 
setValueRole(const QString & role)1077 void QQuickComboBox::setValueRole(const QString &role)
1078 {
1079     Q_D(QQuickComboBox);
1080     if (d->valueRole == role)
1081         return;
1082 
1083     d->valueRole = role;
1084     if (isComponentComplete())
1085         d->updateCurrentValue();
1086     emit valueRoleChanged();
1087 }
1088 
1089 /*!
1090     \qmlproperty Component QtQuick.Controls::ComboBox::delegate
1091 
1092     This property holds a delegate that presents an item in the combo box popup.
1093 
1094     It is recommended to use \l ItemDelegate (or any other \l AbstractButton
1095     derivatives) as the delegate. This ensures that the interaction works as
1096     expected, and the popup will automatically close when appropriate. When
1097     other types are used as the delegate, the popup must be closed manually.
1098     For example, if \l MouseArea is used:
1099 
1100     \code
1101     delegate: Rectangle {
1102         // ...
1103         MouseArea {
1104             // ...
1105             onClicked: comboBox.popup.close()
1106         }
1107     }
1108     \endcode
1109 
1110     \sa ItemDelegate, {Customizing ComboBox}
1111 */
delegate() const1112 QQmlComponent *QQuickComboBox::delegate() const
1113 {
1114     Q_D(const QQuickComboBox);
1115     return d->delegate;
1116 }
1117 
setDelegate(QQmlComponent * delegate)1118 void QQuickComboBox::setDelegate(QQmlComponent* delegate)
1119 {
1120     Q_D(QQuickComboBox);
1121     if (d->delegate == delegate)
1122         return;
1123 
1124     delete d->delegate;
1125     d->delegate = delegate;
1126     QQmlDelegateModel *delegateModel = qobject_cast<QQmlDelegateModel*>(d->delegateModel);
1127     if (delegateModel)
1128         delegateModel->setDelegate(d->delegate);
1129     emit delegateChanged();
1130 }
1131 
1132 /*!
1133     \qmlproperty Item QtQuick.Controls::ComboBox::indicator
1134 
1135     This property holds the drop indicator item.
1136 
1137     \sa {Customizing ComboBox}
1138 */
indicator() const1139 QQuickItem *QQuickComboBox::indicator() const
1140 {
1141     QQuickComboBoxPrivate *d = const_cast<QQuickComboBoxPrivate *>(d_func());
1142     if (!d->indicator)
1143         d->executeIndicator();
1144     return d->indicator;
1145 }
1146 
setIndicator(QQuickItem * indicator)1147 void QQuickComboBox::setIndicator(QQuickItem *indicator)
1148 {
1149     Q_D(QQuickComboBox);
1150     if (d->indicator == indicator)
1151         return;
1152 
1153     if (!d->indicator.isExecuting())
1154         d->cancelIndicator();
1155 
1156     const qreal oldImplicitIndicatorWidth = implicitIndicatorWidth();
1157     const qreal oldImplicitIndicatorHeight = implicitIndicatorHeight();
1158 
1159     d->removeImplicitSizeListener(d->indicator);
1160     QQuickControlPrivate::hideOldItem(d->indicator);
1161     d->indicator = indicator;
1162     if (indicator) {
1163         if (!indicator->parentItem())
1164             indicator->setParentItem(this);
1165         d->addImplicitSizeListener(indicator);
1166     }
1167 
1168     if (!qFuzzyCompare(oldImplicitIndicatorWidth, implicitIndicatorWidth()))
1169         emit implicitIndicatorWidthChanged();
1170     if (!qFuzzyCompare(oldImplicitIndicatorHeight, implicitIndicatorHeight()))
1171         emit implicitIndicatorHeightChanged();
1172     if (!d->indicator.isExecuting())
1173         emit indicatorChanged();
1174 }
1175 
1176 /*!
1177     \qmlproperty Popup QtQuick.Controls::ComboBox::popup
1178 
1179     This property holds the popup.
1180 
1181     The popup can be opened or closed manually, if necessary:
1182 
1183     \code
1184     onSpecialEvent: comboBox.popup.close()
1185     \endcode
1186 
1187     \sa {Customizing ComboBox}
1188 */
popup() const1189 QQuickPopup *QQuickComboBox::popup() const
1190 {
1191     QQuickComboBoxPrivate *d = const_cast<QQuickComboBoxPrivate *>(d_func());
1192     if (!d->popup)
1193         d->executePopup(isComponentComplete());
1194     return d->popup;
1195 }
1196 
setPopup(QQuickPopup * popup)1197 void QQuickComboBox::setPopup(QQuickPopup *popup)
1198 {
1199     Q_D(QQuickComboBox);
1200     if (d->popup == popup)
1201         return;
1202 
1203     if (!d->popup.isExecuting())
1204         d->cancelPopup();
1205 
1206     if (d->popup) {
1207         QObjectPrivate::disconnect(d->popup.data(), &QQuickPopup::visibleChanged, d, &QQuickComboBoxPrivate::popupVisibleChanged);
1208         QQuickComboBoxPrivate::hideOldPopup(d->popup);
1209     }
1210     if (popup) {
1211         QQuickPopupPrivate::get(popup)->allowVerticalFlip = true;
1212         popup->setClosePolicy(QQuickPopup::CloseOnEscape | QQuickPopup::CloseOnPressOutsideParent);
1213         QObjectPrivate::connect(popup, &QQuickPopup::visibleChanged, d, &QQuickComboBoxPrivate::popupVisibleChanged);
1214 
1215         if (QQuickItemView *itemView = popup->findChild<QQuickItemView *>())
1216             itemView->setHighlightRangeMode(QQuickItemView::NoHighlightRange);
1217     }
1218     d->popup = popup;
1219     if (!d->popup.isExecuting())
1220         emit popupChanged();
1221 }
1222 
1223 /*!
1224     \since QtQuick.Controls 2.1 (Qt 5.8)
1225     \qmlproperty bool QtQuick.Controls::ComboBox::flat
1226 
1227     This property holds whether the combo box button is flat.
1228 
1229     A flat combo box button does not draw a background unless it is interacted
1230     with. In comparison to normal combo boxes, flat combo boxes provide looks
1231     that make them stand out less from the rest of the UI. For instance, when
1232     placing a combo box into a tool bar, it may be desirable to make the combo
1233     box flat so it matches better with the flat looks of tool buttons.
1234 
1235     The default value is \c false.
1236 */
isFlat() const1237 bool QQuickComboBox::isFlat() const
1238 {
1239     Q_D(const QQuickComboBox);
1240     return d->flat;
1241 }
1242 
setFlat(bool flat)1243 void QQuickComboBox::setFlat(bool flat)
1244 {
1245     Q_D(QQuickComboBox);
1246     if (d->flat == flat)
1247         return;
1248 
1249     d->flat = flat;
1250     emit flatChanged();
1251 }
1252 
1253 /*!
1254     \since QtQuick.Controls 2.2 (Qt 5.9)
1255     \qmlproperty bool QtQuick.Controls::ComboBox::down
1256 
1257     This property holds whether the combo box button is visually down.
1258 
1259     Unless explicitly set, this property is \c true when either \c pressed
1260     or \c popup.visible is \c true. To return to the default value, set this
1261     property to \c undefined.
1262 
1263     \sa pressed, popup
1264 */
isDown() const1265 bool QQuickComboBox::isDown() const
1266 {
1267     Q_D(const QQuickComboBox);
1268     return d->down;
1269 }
1270 
setDown(bool down)1271 void QQuickComboBox::setDown(bool down)
1272 {
1273     Q_D(QQuickComboBox);
1274     d->hasDown = true;
1275 
1276     if (d->down == down)
1277         return;
1278 
1279     d->down = down;
1280     emit downChanged();
1281 }
1282 
resetDown()1283 void QQuickComboBox::resetDown()
1284 {
1285     Q_D(QQuickComboBox);
1286     if (!d->hasDown)
1287         return;
1288 
1289     setDown(d->pressed || d->isPopupVisible());
1290     d->hasDown = false;
1291 }
1292 
1293 /*!
1294     \since QtQuick.Controls 2.2 (Qt 5.9)
1295     \qmlproperty bool QtQuick.Controls::ComboBox::editable
1296 
1297     This property holds whether the combo box is editable.
1298 
1299     The default value is \c false.
1300 
1301     \sa validator
1302 */
isEditable() const1303 bool QQuickComboBox::isEditable() const
1304 {
1305     Q_D(const QQuickComboBox);
1306     return d->extra.isAllocated() && d->extra->editable;
1307 }
1308 
setEditable(bool editable)1309 void QQuickComboBox::setEditable(bool editable)
1310 {
1311     Q_D(QQuickComboBox);
1312     if (editable == isEditable())
1313         return;
1314 
1315     if (d->contentItem) {
1316         if (editable) {
1317             d->contentItem->installEventFilter(this);
1318             if (QQuickTextInput *input = qobject_cast<QQuickTextInput *>(d->contentItem)) {
1319                 QObjectPrivate::connect(input, &QQuickTextInput::textChanged, d, &QQuickComboBoxPrivate::updateEditText);
1320                 QObjectPrivate::connect(input, &QQuickTextInput::accepted, d, &QQuickComboBoxPrivate::acceptInput);
1321             }
1322 #if QT_CONFIG(cursor)
1323             d->contentItem->setCursor(Qt::IBeamCursor);
1324 #endif
1325         } else {
1326             d->contentItem->removeEventFilter(this);
1327             if (QQuickTextInput *input = qobject_cast<QQuickTextInput *>(d->contentItem)) {
1328                 QObjectPrivate::disconnect(input, &QQuickTextInput::textChanged, d, &QQuickComboBoxPrivate::updateEditText);
1329                 QObjectPrivate::disconnect(input, &QQuickTextInput::accepted, d, &QQuickComboBoxPrivate::acceptInput);
1330             }
1331 #if QT_CONFIG(cursor)
1332             d->contentItem->unsetCursor();
1333 #endif
1334         }
1335     }
1336 
1337     d->extra.value().editable = editable;
1338     setAccessibleProperty("editable", editable);
1339     emit editableChanged();
1340 }
1341 
1342 /*!
1343     \since QtQuick.Controls 2.2 (Qt 5.9)
1344     \qmlproperty string QtQuick.Controls::ComboBox::editText
1345 
1346     This property holds the text in the text field of an editable combo box.
1347 
1348     \sa editable, currentText, displayText
1349 */
editText() const1350 QString QQuickComboBox::editText() const
1351 {
1352     Q_D(const QQuickComboBox);
1353     return d->extra.isAllocated() ? d->extra->editText : QString();
1354 }
1355 
setEditText(const QString & text)1356 void QQuickComboBox::setEditText(const QString &text)
1357 {
1358     Q_D(QQuickComboBox);
1359     if (text == editText())
1360         return;
1361 
1362     d->extra.value().editText = text;
1363     emit editTextChanged();
1364 }
1365 
resetEditText()1366 void QQuickComboBox::resetEditText()
1367 {
1368     setEditText(QString());
1369 }
1370 
1371 /*!
1372     \since QtQuick.Controls 2.2 (Qt 5.9)
1373     \qmlproperty Validator QtQuick.Controls::ComboBox::validator
1374 
1375     This property holds an input text validator for an editable combo box.
1376 
1377     When a validator is set, the text field will only accept input which
1378     leaves the text property in an intermediate state. The \l accepted signal
1379     will only be emitted if the text is in an acceptable state when the
1380     \uicontrol Return or \uicontrol Enter key is pressed.
1381 
1382     The currently supported validators are \l[QtQuick]{IntValidator},
1383     \l[QtQuick]{DoubleValidator}, and \l[QtQuick]{RegExpValidator}. An
1384     example of using validators is shown below, which allows input of
1385     integers between \c 0 and \c 10 into the text field:
1386 
1387     \code
1388     ComboBox {
1389         model: 10
1390         editable: true
1391         validator: IntValidator {
1392             top: 9
1393             bottom: 0
1394         }
1395     }
1396     \endcode
1397 
1398     \sa acceptableInput, accepted, editable
1399 */
validator() const1400 QValidator *QQuickComboBox::validator() const
1401 {
1402     Q_D(const QQuickComboBox);
1403     return d->extra.isAllocated() ? d->extra->validator : nullptr;
1404 }
1405 
setValidator(QValidator * validator)1406 void QQuickComboBox::setValidator(QValidator *validator)
1407 {
1408     Q_D(QQuickComboBox);
1409     if (validator == QQuickComboBox::validator())
1410         return;
1411 
1412     d->extra.value().validator = validator;
1413 #if QT_CONFIG(validator)
1414     if (validator)
1415         validator->setLocale(d->locale);
1416 #endif
1417     emit validatorChanged();
1418 }
1419 
1420 /*!
1421     \since QtQuick.Controls 2.2 (Qt 5.9)
1422     \qmlproperty flags QtQuick.Controls::ComboBox::inputMethodHints
1423 
1424     Provides hints to the input method about the expected content of the combo box and how it
1425     should operate.
1426 
1427     The default value is \c Qt.ImhNoPredictiveText.
1428 
1429     \include inputmethodhints.qdocinc
1430 */
inputMethodHints() const1431 Qt::InputMethodHints QQuickComboBox::inputMethodHints() const
1432 {
1433     Q_D(const QQuickComboBox);
1434     return d->extra.isAllocated() ? d->extra->inputMethodHints : Qt::ImhNoPredictiveText;
1435 }
1436 
setInputMethodHints(Qt::InputMethodHints hints)1437 void QQuickComboBox::setInputMethodHints(Qt::InputMethodHints hints)
1438 {
1439     Q_D(QQuickComboBox);
1440     if (hints == inputMethodHints())
1441         return;
1442 
1443     d->extra.value().inputMethodHints = hints;
1444     emit inputMethodHintsChanged();
1445 }
1446 
1447 /*!
1448     \since QtQuick.Controls 2.2 (Qt 5.9)
1449     \qmlproperty bool QtQuick.Controls::ComboBox::inputMethodComposing
1450     \readonly
1451 
1452     This property holds whether an editable combo box has partial text input from an input method.
1453 
1454     While it is composing, an input method may rely on mouse or key events from the combo box to
1455     edit or commit the partial text. This property can be used to determine when to disable event
1456     handlers that may interfere with the correct operation of an input method.
1457 */
isInputMethodComposing() const1458 bool QQuickComboBox::isInputMethodComposing() const
1459 {
1460     Q_D(const QQuickComboBox);
1461     return d->contentItem && d->contentItem->property("inputMethodComposing").toBool();
1462 }
1463 
1464 /*!
1465     \since QtQuick.Controls 2.2 (Qt 5.9)
1466     \qmlproperty bool QtQuick.Controls::ComboBox::acceptableInput
1467     \readonly
1468 
1469     This property holds whether the combo box contains acceptable text in the editable text field.
1470 
1471     If a validator has been set, the value is \c true only if the current text is acceptable
1472     to the validator as a final string (not as an intermediate string).
1473 
1474     \sa validator, accepted
1475 */
hasAcceptableInput() const1476 bool QQuickComboBox::hasAcceptableInput() const
1477 {
1478     Q_D(const QQuickComboBox);
1479     return d->contentItem && d->contentItem->property("acceptableInput").toBool();
1480 }
1481 
1482 /*!
1483     \since QtQuick.Controls 2.5 (Qt 5.12)
1484     \qmlproperty real QtQuick.Controls::ComboBox::implicitIndicatorWidth
1485     \readonly
1486 
1487     This property holds the implicit indicator width.
1488 
1489     The value is equal to \c {indicator ? indicator.implicitWidth : 0}.
1490 
1491     This is typically used, together with \l {Control::}{implicitContentWidth} and
1492     \l {Control::}{implicitBackgroundWidth}, to calculate the \l {Item::}{implicitWidth}.
1493 
1494     \sa implicitIndicatorHeight
1495 */
implicitIndicatorWidth() const1496 qreal QQuickComboBox::implicitIndicatorWidth() const
1497 {
1498     Q_D(const QQuickComboBox);
1499     if (!d->indicator)
1500         return 0;
1501     return d->indicator->implicitWidth();
1502 }
1503 
1504 /*!
1505     \since QtQuick.Controls 2.5 (Qt 5.12)
1506     \qmlproperty real QtQuick.Controls::ComboBox::implicitIndicatorHeight
1507     \readonly
1508 
1509     This property holds the implicit indicator height.
1510 
1511     The value is equal to \c {indicator ? indicator.implicitHeight : 0}.
1512 
1513     This is typically used, together with \l {Control::}{implicitContentHeight} and
1514     \l {Control::}{implicitBackgroundHeight}, to calculate the \l {Item::}{implicitHeight}.
1515 
1516     \sa implicitIndicatorWidth
1517 */
implicitIndicatorHeight() const1518 qreal QQuickComboBox::implicitIndicatorHeight() const
1519 {
1520     Q_D(const QQuickComboBox);
1521     if (!d->indicator)
1522         return 0;
1523     return d->indicator->implicitHeight();
1524 }
1525 
1526 /*!
1527     \readonly
1528     \since QtQuick.Controls 2.14 (Qt 5.14)
1529     \qmlproperty string QtQuick.Controls::ComboBox::currentValue
1530 
1531     This property holds the value of the current item in the combo box.
1532 
1533     For an example of how to use this property, see \l {ComboBox Model Roles}.
1534 
1535     \sa currentIndex, currentText, valueRole
1536 */
currentValue() const1537 QVariant QQuickComboBox::currentValue() const
1538 {
1539     Q_D(const QQuickComboBox);
1540     return d->currentValue;
1541 }
1542 
valueAt(int index) const1543 QVariant QQuickComboBox::valueAt(int index) const
1544 {
1545     Q_D(const QQuickComboBox);
1546     if (!d->isValidIndex(index))
1547         return QVariant();
1548 
1549     const QString effectiveValueRole = d->valueRole.isEmpty() ? QStringLiteral("modelData") : d->valueRole;
1550     return d->delegateModel->variantValue(index, effectiveValueRole);
1551 }
1552 
1553 /*!
1554     \since QtQuick.Controls 2.14 (Qt 5.14)
1555     \qmlmethod int QtQuick.Controls::ComboBox::indexOfValue(object value)
1556 
1557     Returns the index of the specified \a value, or \c -1 if no match is found.
1558 
1559     For an example of how to use this method, see \l {ComboBox Model Roles}.
1560 
1561     \sa find(), currentValue, currentIndex, valueRole
1562 */
indexOfValue(const QVariant & value) const1563 int QQuickComboBox::indexOfValue(const QVariant &value) const
1564 {
1565     for (int i = 0; i < count(); ++i) {
1566         const QVariant ourValue = valueAt(i);
1567         if (value == ourValue)
1568             return i;
1569     }
1570     return -1;
1571 }
1572 
1573 /*!
1574     \since QtQuick.Controls 2.15 (Qt 5.15)
1575     \qmlproperty bool QtQuick.Controls::ComboBox::selectTextByMouse
1576 
1577     This property holds whether the text field for an editable ComboBox
1578     can be selected with the mouse.
1579 
1580     The default value is \c false.
1581 */
selectTextByMouse() const1582 bool QQuickComboBox::selectTextByMouse() const
1583 {
1584     Q_D(const QQuickComboBox);
1585     return d->extra.isAllocated() ? d->extra->selectTextByMouse : false;
1586 }
1587 
setSelectTextByMouse(bool canSelect)1588 void QQuickComboBox::setSelectTextByMouse(bool canSelect)
1589 {
1590     Q_D(QQuickComboBox);
1591     if (canSelect == selectTextByMouse())
1592         return;
1593 
1594     d->extra.value().selectTextByMouse = canSelect;
1595     emit selectTextByMouseChanged();
1596 }
1597 /*!
1598     \qmlmethod string QtQuick.Controls::ComboBox::textAt(int index)
1599 
1600     Returns the text for the specified \a index, or an empty string
1601     if the index is out of bounds.
1602 
1603     \sa textRole
1604 */
textAt(int index) const1605 QString QQuickComboBox::textAt(int index) const
1606 {
1607     Q_D(const QQuickComboBox);
1608     if (!d->isValidIndex(index))
1609         return QString();
1610 
1611     const QString effectiveTextRole = d->textRole.isEmpty() ? QStringLiteral("modelData") : d->textRole;
1612     return d->delegateModel->stringValue(index, effectiveTextRole);
1613 }
1614 
1615 /*!
1616     \qmlmethod int QtQuick.Controls::ComboBox::find(string text, enumeration flags)
1617 
1618     Returns the index of the specified \a text, or \c -1 if no match is found.
1619 
1620     The way the search is performed is defined by the specified match \a flags. By default,
1621     combo box performs case sensitive exact matching (\c Qt.MatchExactly). All other match
1622     types are case-insensitive unless the \c Qt.MatchCaseSensitive flag is also specified.
1623 
1624     \value Qt.MatchExactly          The search term matches exactly (default).
1625     \value Qt.MatchRegExp           The search term matches as a regular expression.
1626     \value Qt.MatchWildcard         The search term matches using wildcards.
1627     \value Qt.MatchFixedString      The search term matches as a fixed string.
1628     \value Qt.MatchStartsWith       The search term matches the start of the item.
1629     \value Qt.MatchEndsWidth        The search term matches the end of the item.
1630     \value Qt.MatchContains         The search term is contained in the item.
1631     \value Qt.MatchCaseSensitive    The search is case sensitive.
1632 
1633     \sa textRole
1634 */
find(const QString & text,Qt::MatchFlags flags) const1635 int QQuickComboBox::find(const QString &text, Qt::MatchFlags flags) const
1636 {
1637     Q_D(const QQuickComboBox);
1638     return d->match(0, text, flags);
1639 }
1640 
1641 /*!
1642     \qmlmethod void QtQuick.Controls::ComboBox::incrementCurrentIndex()
1643 
1644     Increments the current index of the combo box, or the highlighted
1645     index if the popup list is visible.
1646 
1647     \sa currentIndex, highlightedIndex
1648 */
incrementCurrentIndex()1649 void QQuickComboBox::incrementCurrentIndex()
1650 {
1651     Q_D(QQuickComboBox);
1652     d->incrementCurrentIndex();
1653 }
1654 
1655 /*!
1656     \qmlmethod void QtQuick.Controls::ComboBox::decrementCurrentIndex()
1657 
1658     Decrements the current index of the combo box, or the highlighted
1659     index if the popup list is visible.
1660 
1661     \sa currentIndex, highlightedIndex
1662 */
decrementCurrentIndex()1663 void QQuickComboBox::decrementCurrentIndex()
1664 {
1665     Q_D(QQuickComboBox);
1666     d->decrementCurrentIndex();
1667 }
1668 
1669 /*!
1670     \since QtQuick.Controls 2.2 (Qt 5.9)
1671     \qmlmethod void QtQuick.Controls::ComboBox::selectAll()
1672 
1673     Selects all the text in the editable text field of the combo box.
1674 
1675     \sa editText
1676 */
selectAll()1677 void QQuickComboBox::selectAll()
1678 {
1679     Q_D(QQuickComboBox);
1680     QQuickTextInput *input = qobject_cast<QQuickTextInput *>(d->contentItem);
1681     if (!input)
1682         return;
1683     input->selectAll();
1684 }
1685 
eventFilter(QObject * object,QEvent * event)1686 bool QQuickComboBox::eventFilter(QObject *object, QEvent *event)
1687 {
1688     Q_D(QQuickComboBox);
1689     switch (event->type()) {
1690     case QEvent::MouseButtonRelease:
1691         if (d->isPopupVisible())
1692             d->hidePopup(false);
1693         break;
1694     case QEvent::KeyPress: {
1695         QKeyEvent *ke = static_cast<QKeyEvent *>(event);
1696         if (d->filterKeyEvent(ke, false))
1697             return true;
1698         event->accept();
1699         if (d->extra.isAllocated())
1700             d->extra->allowComplete = ke->key() != Qt::Key_Backspace && ke->key() != Qt::Key_Delete;
1701         break;
1702     }
1703     case QEvent::FocusOut:
1704         if (qGuiApp->focusObject() != this && (!d->popup || !d->popup->hasActiveFocus())) {
1705             // Only close the popup if focus was transferred somewhere else
1706             // than to the popup or the popup button (which normally means that
1707             // the user clicked on the popup button to open it, not close it).
1708             d->hidePopup(false);
1709             setPressed(false);
1710 
1711             // The focus left the text field, so if the edit text matches an item in the model,
1712             // change our currentIndex to that. This matches widgets' behavior.
1713             const int indexForEditText = find(d->extra.value().editText, Qt::MatchFixedString);
1714             if (indexForEditText > -1)
1715                 setCurrentIndex(indexForEditText);
1716         }
1717         break;
1718 #if QT_CONFIG(im)
1719     case QEvent::InputMethod:
1720         if (d->extra.isAllocated())
1721             d->extra->allowComplete = !static_cast<QInputMethodEvent*>(event)->commitString().isEmpty();
1722         break;
1723 #endif
1724     default:
1725         break;
1726     }
1727     return QQuickControl::eventFilter(object, event);
1728 }
1729 
focusInEvent(QFocusEvent * event)1730 void QQuickComboBox::focusInEvent(QFocusEvent *event)
1731 {
1732     Q_D(QQuickComboBox);
1733     QQuickControl::focusInEvent(event);
1734     if (d->contentItem && isEditable())
1735         d->contentItem->forceActiveFocus(event->reason());
1736 }
1737 
focusOutEvent(QFocusEvent * event)1738 void QQuickComboBox::focusOutEvent(QFocusEvent *event)
1739 {
1740     Q_D(QQuickComboBox);
1741     QQuickControl::focusOutEvent(event);
1742 
1743     if (qGuiApp->focusObject() != d->contentItem && (!d->popup || !d->popup->hasActiveFocus())) {
1744         // Only close the popup if focus was transferred
1745         // somewhere else than to the popup or the inner line edit (which is
1746         // normally done from QQuickComboBox::focusInEvent).
1747         d->hidePopup(false);
1748         setPressed(false);
1749     }
1750 }
1751 
1752 #if QT_CONFIG(im)
inputMethodEvent(QInputMethodEvent * event)1753 void QQuickComboBox::inputMethodEvent(QInputMethodEvent *event)
1754 {
1755     Q_D(QQuickComboBox);
1756     QQuickControl::inputMethodEvent(event);
1757     if (!isEditable() && !event->commitString().isEmpty())
1758         d->keySearch(event->commitString());
1759     else
1760         event->ignore();
1761 }
1762 #endif
1763 
keyPressEvent(QKeyEvent * event)1764 void QQuickComboBox::keyPressEvent(QKeyEvent *event)
1765 {
1766     Q_D(QQuickComboBox);
1767     QQuickControl::keyPressEvent(event);
1768 
1769     switch (event->key()) {
1770     case Qt::Key_Escape:
1771     case Qt::Key_Back:
1772         if (d->isPopupVisible())
1773             event->accept();
1774         break;
1775     case Qt::Key_Space:
1776         if (!event->isAutoRepeat())
1777             setPressed(true);
1778         event->accept();
1779         break;
1780     case Qt::Key_Enter:
1781     case Qt::Key_Return:
1782         if (d->isPopupVisible())
1783             setPressed(true);
1784         event->accept();
1785         break;
1786     case Qt::Key_Up:
1787         d->keyNavigating = true;
1788         d->decrementCurrentIndex();
1789         event->accept();
1790         break;
1791     case Qt::Key_Down:
1792         d->keyNavigating = true;
1793         d->incrementCurrentIndex();
1794         event->accept();
1795         break;
1796     case Qt::Key_Home:
1797         d->keyNavigating = true;
1798         if (d->isPopupVisible())
1799             d->setHighlightedIndex(0, Highlight);
1800         else
1801             d->setCurrentIndex(0, Activate);
1802         event->accept();
1803         break;
1804     case Qt::Key_End:
1805         d->keyNavigating = true;
1806         if (d->isPopupVisible())
1807             d->setHighlightedIndex(count() - 1, Highlight);
1808         else
1809             d->setCurrentIndex(count() - 1, Activate);
1810         event->accept();
1811         break;
1812     default:
1813         if (!isEditable() && !event->text().isEmpty())
1814             d->keySearch(event->text());
1815         else
1816             event->ignore();
1817         break;
1818     }
1819 }
1820 
keyReleaseEvent(QKeyEvent * event)1821 void QQuickComboBox::keyReleaseEvent(QKeyEvent *event)
1822 {
1823     Q_D(QQuickComboBox);
1824     QQuickControl::keyReleaseEvent(event);
1825     d->keyNavigating = false;
1826     if (event->isAutoRepeat())
1827         return;
1828 
1829     switch (event->key()) {
1830     case Qt::Key_Space:
1831         if (!isEditable())
1832             d->togglePopup(true);
1833         setPressed(false);
1834         event->accept();
1835         break;
1836     case Qt::Key_Enter:
1837     case Qt::Key_Return:
1838         if (!isEditable() || d->isPopupVisible())
1839             d->hidePopup(d->isPopupVisible());
1840         setPressed(false);
1841         event->accept();
1842         break;
1843     case Qt::Key_Escape:
1844     case Qt::Key_Back:
1845         if (d->isPopupVisible()) {
1846             d->hidePopup(false);
1847             setPressed(false);
1848             event->accept();
1849         }
1850         break;
1851     default:
1852         break;
1853     }
1854 }
1855 
1856 #if QT_CONFIG(wheelevent)
wheelEvent(QWheelEvent * event)1857 void QQuickComboBox::wheelEvent(QWheelEvent *event)
1858 {
1859     Q_D(QQuickComboBox);
1860     QQuickControl::wheelEvent(event);
1861     if (d->wheelEnabled && !d->isPopupVisible()) {
1862         if (event->angleDelta().y() > 0)
1863             d->decrementCurrentIndex();
1864         else
1865             d->incrementCurrentIndex();
1866     }
1867 }
1868 #endif
1869 
event(QEvent * e)1870 bool QQuickComboBox::event(QEvent *e)
1871 {
1872     Q_D(QQuickComboBox);
1873     if (e->type() == QEvent::LanguageChange)
1874         d->updateCurrentTextAndValue();
1875     return QQuickControl::event(e);
1876 }
1877 
componentComplete()1878 void QQuickComboBox::componentComplete()
1879 {
1880     Q_D(QQuickComboBox);
1881     d->executeIndicator(true);
1882     QQuickControl::componentComplete();
1883     if (d->popup)
1884         d->executePopup(true);
1885 
1886     if (d->delegateModel && d->ownModel)
1887         static_cast<QQmlDelegateModel *>(d->delegateModel)->componentComplete();
1888 
1889     if (count() > 0) {
1890         if (!d->hasCurrentIndex && d->currentIndex == -1)
1891             setCurrentIndex(0);
1892         else
1893             d->updateCurrentTextAndValue();
1894     }
1895 }
1896 
itemChange(QQuickItem::ItemChange change,const QQuickItem::ItemChangeData & value)1897 void QQuickComboBox::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
1898 {
1899     Q_D(QQuickComboBox);
1900     QQuickControl::itemChange(change, value);
1901     if (change == ItemVisibleHasChanged && !value.boolValue) {
1902         d->hidePopup(false);
1903         setPressed(false);
1904     }
1905 }
1906 
contentItemChange(QQuickItem * newItem,QQuickItem * oldItem)1907 void QQuickComboBox::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem)
1908 {
1909     Q_D(QQuickComboBox);
1910     if (oldItem) {
1911         oldItem->removeEventFilter(this);
1912         if (QQuickTextInput *oldInput = qobject_cast<QQuickTextInput *>(oldItem)) {
1913             QObjectPrivate::disconnect(oldInput, &QQuickTextInput::accepted, d, &QQuickComboBoxPrivate::acceptInput);
1914             QObjectPrivate::disconnect(oldInput, &QQuickTextInput::textChanged, d, &QQuickComboBoxPrivate::updateEditText);
1915             disconnect(oldInput, &QQuickTextInput::inputMethodComposingChanged, this, &QQuickComboBox::inputMethodComposingChanged);
1916             disconnect(oldInput, &QQuickTextInput::acceptableInputChanged, this, &QQuickComboBox::acceptableInputChanged);
1917         }
1918     }
1919     if (newItem && isEditable()) {
1920         newItem->installEventFilter(this);
1921         if (QQuickTextInput *newInput = qobject_cast<QQuickTextInput *>(newItem)) {
1922             QObjectPrivate::connect(newInput, &QQuickTextInput::accepted, d, &QQuickComboBoxPrivate::acceptInput);
1923             QObjectPrivate::connect(newInput, &QQuickTextInput::textChanged, d, &QQuickComboBoxPrivate::updateEditText);
1924             connect(newInput, &QQuickTextInput::inputMethodComposingChanged, this, &QQuickComboBox::inputMethodComposingChanged);
1925             connect(newInput, &QQuickTextInput::acceptableInputChanged, this, &QQuickComboBox::acceptableInputChanged);
1926         }
1927 #if QT_CONFIG(cursor)
1928         newItem->setCursor(Qt::IBeamCursor);
1929 #endif
1930     }
1931 }
1932 
localeChange(const QLocale & newLocale,const QLocale & oldLocale)1933 void QQuickComboBox::localeChange(const QLocale &newLocale, const QLocale &oldLocale)
1934 {
1935     QQuickControl::localeChange(newLocale, oldLocale);
1936 #if QT_CONFIG(validator)
1937     if (QValidator *v = validator())
1938         v->setLocale(newLocale);
1939 #endif
1940 }
1941 
defaultFont() const1942 QFont QQuickComboBox::defaultFont() const
1943 {
1944     return QQuickTheme::font(QQuickTheme::ComboBox);
1945 }
1946 
defaultPalette() const1947 QPalette QQuickComboBox::defaultPalette() const
1948 {
1949     return QQuickTheme::palette(QQuickTheme::ComboBox);
1950 }
1951 
1952 #if QT_CONFIG(accessibility)
accessibleRole() const1953 QAccessible::Role QQuickComboBox::accessibleRole() const
1954 {
1955     return QAccessible::ComboBox;
1956 }
1957 
accessibilityActiveChanged(bool active)1958 void QQuickComboBox::accessibilityActiveChanged(bool active)
1959 {
1960     Q_D(QQuickComboBox);
1961     QQuickControl::accessibilityActiveChanged(active);
1962 
1963     if (active) {
1964         maybeSetAccessibleName(d->hasDisplayText ? d->displayText : d->currentText);
1965         setAccessibleProperty("editable", isEditable());
1966     }
1967 }
1968 #endif //
1969 
1970 QT_END_NAMESPACE
1971