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