| /**************************************************************************** |
| ** |
| ** Copyright (C) 2017 The Qt Company Ltd. |
| ** Contact: http://www.qt.io/licensing/ |
| ** |
| ** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL3$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see http://www.qt.io/terms-conditions. For further |
| ** information use the contact form at http://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPLv3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or later as published by the Free |
| ** Software Foundation and appearing in the file LICENSE.GPL included in |
| ** the packaging of this file. Please review the following information to |
| ** ensure the GNU General Public License version 2.0 requirements will be |
| ** met: http://www.gnu.org/licenses/gpl-2.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qquickcombobox_p.h" |
| #include "qquickcontrol_p_p.h" |
| #include "qquickabstractbutton_p.h" |
| #include "qquickabstractbutton_p_p.h" |
| #include "qquickpopup_p_p.h" |
| #include "qquickdeferredexecute_p_p.h" |
| |
| #include <QtCore/qregularexpression.h> |
| #include <QtCore/qabstractitemmodel.h> |
| #include <QtCore/qglobal.h> |
| #include <QtGui/qinputmethod.h> |
| #include <QtGui/qguiapplication.h> |
| #include <QtGui/qpa/qplatformtheme.h> |
| #include <QtQml/qjsvalue.h> |
| #include <QtQml/qqmlcontext.h> |
| #include <QtQml/private/qlazilyallocated_p.h> |
| #include <private/qqmldelegatemodel_p.h> |
| #include <QtQuick/private/qquickaccessibleattached_p.h> |
| #include <QtQuick/private/qquickevents_p_p.h> |
| #include <QtQuick/private/qquicktextinput_p.h> |
| #include <QtQuick/private/qquickitemview_p.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| /*! |
| \qmltype ComboBox |
| \inherits Control |
| //! \instantiates QQuickComboBox |
| \inqmlmodule QtQuick.Controls |
| \since 5.7 |
| \ingroup qtquickcontrols2-input |
| \ingroup qtquickcontrols2-focusscopes |
| \brief Combined button and popup list for selecting options. |
| |
| \image qtquickcontrols2-combobox.gif |
| |
| ComboBox is a combined button and popup list. It provides a means of |
| presenting a list of options to the user in a way that takes up the |
| minimum amount of screen space. |
| |
| ComboBox is populated with a data model. The data model is commonly |
| a JavaScript array, a \l ListModel or an integer, but other types |
| of \l {qml-data-models}{data models} are also supported. |
| |
| \code |
| ComboBox { |
| model: ["First", "Second", "Third"] |
| } |
| \endcode |
| |
| \section1 Editable ComboBox |
| |
| ComboBox can be made \l editable. An editable combo box auto-completes |
| its text based on what is available in the model. |
| |
| The following example demonstrates appending content to an editable |
| combo box by reacting to the \l accepted signal. |
| |
| \snippet qtquickcontrols2-combobox-accepted.qml combobox |
| |
| \section1 ComboBox Model Roles |
| |
| ComboBox is able to visualize standard \l {qml-data-models}{data models} |
| that provide the \c modelData role: |
| \list |
| \li models that have only one role |
| \li models that do not have named roles (JavaScript array, integer) |
| \endlist |
| |
| When using models that have multiple named roles, ComboBox must be configured |
| to use a specific \l {textRole}{text role} for its \l {displayText}{display text} |
| and \l delegate instances. If you want to use a role of the model item |
| that corresponds to the text role, set \l valueRole. The \l currentValue |
| property and \l indexOfValue() method can then be used to get information |
| about those values. |
| |
| For example: |
| |
| \snippet qtquickcontrols2-combobox-valuerole.qml file |
| |
| \note If ComboBox is assigned a data model that has multiple named roles, but |
| \l textRole is not defined, ComboBox is unable to visualize it and throws a |
| \c {ReferenceError: modelData is not defined}. |
| |
| \sa {Customizing ComboBox}, {Input Controls}, {Focus Management in Qt Quick Controls} |
| */ |
| |
| /*! |
| \qmlsignal void QtQuick.Controls::ComboBox::activated(int index) |
| |
| This signal is emitted when the item at \a index is activated by the user. |
| |
| An item is activated when it is selected while the popup is open, |
| causing the popup to close (and \l currentIndex to change), |
| or while the popup is closed and the combo box is navigated via |
| keyboard, causing the \l currentIndex to change. |
| The \l currentIndex property is set to \a index. |
| |
| \sa currentIndex |
| */ |
| |
| /*! |
| \qmlsignal void QtQuick.Controls::ComboBox::highlighted(int index) |
| |
| This signal is emitted when the item at \a index in the popup list is highlighted by the user. |
| |
| The highlighted signal is only emitted when the popup is open and an item |
| is highlighted, but not necessarily \l activated. |
| |
| \sa highlightedIndex |
| */ |
| |
| /*! |
| \since QtQuick.Controls 2.2 (Qt 5.9) |
| \qmlsignal void QtQuick.Controls::ComboBox::accepted() |
| |
| This signal is emitted when the \uicontrol Return or \uicontrol Enter key is pressed |
| on an \l editable combo box. |
| |
| You can handle this signal in order to add the newly entered |
| item to the model, for example: |
| |
| \snippet qtquickcontrols2-combobox-accepted.qml combobox |
| |
| Before the signal is emitted, a check is done to see if the string |
| exists in the model. If it does, \l currentIndex will be set to its index, |
| and \l currentText to the string itself. |
| |
| After the signal has been emitted, and if the first check failed (that is, |
| the item did not exist), another check will be done to see if the item was |
| added by the signal handler. If it was, the \l currentIndex and |
| \l currentText are updated accordingly. Otherwise, they will be set to |
| \c -1 and \c "", respectively. |
| |
| \note If there is a \l validator set on the combo box, the signal will only be |
| emitted if the input is in an acceptable state. |
| */ |
| |
| namespace { |
| enum Activation { NoActivate, Activate }; |
| enum Highlighting { NoHighlight, Highlight }; |
| } |
| |
| class QQuickComboBoxDelegateModel : public QQmlDelegateModel |
| { |
| public: |
| explicit QQuickComboBoxDelegateModel(QQuickComboBox *combo); |
| QVariant variantValue(int index, const QString &role) override; |
| |
| private: |
| QQuickComboBox *combo = nullptr; |
| }; |
| |
| QQuickComboBoxDelegateModel::QQuickComboBoxDelegateModel(QQuickComboBox *combo) |
| : QQmlDelegateModel(qmlContext(combo), combo), |
| combo(combo) |
| { |
| } |
| |
| QVariant QQuickComboBoxDelegateModel::variantValue(int index, const QString &role) |
| { |
| const QVariant model = combo->model(); |
| if (model.userType() == QMetaType::QVariantList) { |
| QVariant object = model.toList().value(index); |
| if (object.userType() == QMetaType::QVariantMap) { |
| const QVariantMap data = object.toMap(); |
| if (data.count() == 1 && role == QLatin1String("modelData")) |
| return data.first(); |
| return data.value(role); |
| } else if (object.userType() == QMetaType::QObjectStar) { |
| const QObject *data = object.value<QObject *>(); |
| if (data && role != QLatin1String("modelData")) |
| return data->property(role.toUtf8()); |
| } |
| } |
| return QQmlDelegateModel::variantValue(index, role); |
| } |
| |
| class QQuickComboBoxPrivate : public QQuickControlPrivate |
| { |
| Q_DECLARE_PUBLIC(QQuickComboBox) |
| |
| public: |
| bool isPopupVisible() const; |
| void showPopup(); |
| void hidePopup(bool accept); |
| void togglePopup(bool accept); |
| void popupVisibleChanged(); |
| |
| void itemClicked(); |
| void itemHovered(); |
| |
| void createdItem(int index, QObject *object); |
| void modelUpdated(); |
| void countChanged(); |
| |
| void updateEditText(); |
| void updateCurrentText(); |
| void updateCurrentValue(); |
| void updateCurrentTextAndValue(); |
| |
| bool isValidIndex(int index) const; |
| |
| void acceptInput(); |
| QString tryComplete(const QString &inputText); |
| |
| void incrementCurrentIndex(); |
| void decrementCurrentIndex(); |
| void setCurrentIndex(int index, Activation activate); |
| void updateHighlightedIndex(); |
| void setHighlightedIndex(int index, Highlighting highlight); |
| |
| void keySearch(const QString &text); |
| int match(int start, const QString &text, Qt::MatchFlags flags) const; |
| |
| void createDelegateModel(); |
| |
| void handlePress(const QPointF &point) override; |
| void handleMove(const QPointF &point) override; |
| void handleRelease(const QPointF &point) override; |
| void handleUngrab() override; |
| |
| void cancelIndicator(); |
| void executeIndicator(bool complete = false); |
| |
| void cancelPopup(); |
| void executePopup(bool complete = false); |
| |
| void itemImplicitWidthChanged(QQuickItem *item) override; |
| void itemImplicitHeightChanged(QQuickItem *item) override; |
| |
| static void hideOldPopup(QQuickPopup *popup); |
| |
| bool flat = false; |
| bool down = false; |
| bool hasDown = false; |
| bool pressed = false; |
| bool ownModel = false; |
| bool keyNavigating = false; |
| bool hasDisplayText = false; |
| bool hasCurrentIndex = false; |
| int highlightedIndex = -1; |
| int currentIndex = -1; |
| QVariant model; |
| QString textRole; |
| QString currentText; |
| QString displayText; |
| QString valueRole; |
| QVariant currentValue; |
| QQuickItem *pressedItem = nullptr; |
| QQmlInstanceModel *delegateModel = nullptr; |
| QQmlComponent *delegate = nullptr; |
| QQuickDeferredPointer<QQuickItem> indicator; |
| QQuickDeferredPointer<QQuickPopup> popup; |
| |
| struct ExtraData { |
| bool editable = false; |
| bool accepting = false; |
| bool allowComplete = false; |
| bool selectTextByMouse = false; |
| Qt::InputMethodHints inputMethodHints = Qt::ImhNone; |
| QString editText; |
| QValidator *validator = nullptr; |
| }; |
| QLazilyAllocated<ExtraData> extra; |
| }; |
| |
| bool QQuickComboBoxPrivate::isPopupVisible() const |
| { |
| return popup && popup->isVisible(); |
| } |
| |
| void QQuickComboBoxPrivate::showPopup() |
| { |
| if (!popup) |
| executePopup(true); |
| |
| if (popup && !popup->isVisible()) |
| popup->open(); |
| } |
| |
| void QQuickComboBoxPrivate::hidePopup(bool accept) |
| { |
| Q_Q(QQuickComboBox); |
| if (accept) { |
| q->setCurrentIndex(highlightedIndex); |
| emit q->activated(currentIndex); |
| } |
| if (popup && popup->isVisible()) |
| popup->close(); |
| } |
| |
| void QQuickComboBoxPrivate::togglePopup(bool accept) |
| { |
| if (!popup || !popup->isVisible()) |
| showPopup(); |
| else |
| hidePopup(accept); |
| } |
| |
| void QQuickComboBoxPrivate::popupVisibleChanged() |
| { |
| Q_Q(QQuickComboBox); |
| if (isPopupVisible()) |
| QGuiApplication::inputMethod()->reset(); |
| |
| QQuickItemView *itemView = popup->findChild<QQuickItemView *>(); |
| if (itemView) |
| itemView->setHighlightRangeMode(QQuickItemView::NoHighlightRange); |
| |
| updateHighlightedIndex(); |
| |
| if (itemView) |
| itemView->positionViewAtIndex(highlightedIndex, QQuickItemView::Beginning); |
| |
| if (!hasDown) { |
| q->setDown(pressed || isPopupVisible()); |
| hasDown = false; |
| } |
| } |
| |
| void QQuickComboBoxPrivate::itemClicked() |
| { |
| Q_Q(QQuickComboBox); |
| int index = delegateModel->indexOf(q->sender(), nullptr); |
| if (index != -1) { |
| setHighlightedIndex(index, Highlight); |
| hidePopup(true); |
| } |
| } |
| |
| void QQuickComboBoxPrivate::itemHovered() |
| { |
| Q_Q(QQuickComboBox); |
| if (keyNavigating) |
| return; |
| |
| QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(q->sender()); |
| if (!button || !button->isHovered() || QQuickAbstractButtonPrivate::get(button)->touchId != -1) |
| return; |
| |
| int index = delegateModel->indexOf(button, nullptr); |
| if (index != -1) { |
| setHighlightedIndex(index, Highlight); |
| |
| if (QQuickItemView *itemView = popup->findChild<QQuickItemView *>()) |
| itemView->positionViewAtIndex(index, QQuickItemView::Contain); |
| } |
| } |
| |
| void QQuickComboBoxPrivate::createdItem(int index, QObject *object) |
| { |
| Q_Q(QQuickComboBox); |
| QQuickItem *item = qobject_cast<QQuickItem *>(object); |
| if (item && !item->parentItem()) { |
| if (popup) |
| item->setParentItem(popup->contentItem()); |
| else |
| item->setParentItem(q); |
| QQuickItemPrivate::get(item)->setCulled(true); |
| } |
| |
| QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(object); |
| if (button) { |
| button->setFocusPolicy(Qt::NoFocus); |
| connect(button, &QQuickAbstractButton::clicked, this, &QQuickComboBoxPrivate::itemClicked); |
| connect(button, &QQuickAbstractButton::hoveredChanged, this, &QQuickComboBoxPrivate::itemHovered); |
| } |
| |
| if (index == currentIndex && !q->isEditable()) |
| updateCurrentTextAndValue(); |
| } |
| |
| void QQuickComboBoxPrivate::modelUpdated() |
| { |
| if (!extra.isAllocated() || !extra->accepting) |
| updateCurrentTextAndValue(); |
| } |
| |
| void QQuickComboBoxPrivate::countChanged() |
| { |
| Q_Q(QQuickComboBox); |
| if (q->count() == 0) |
| q->setCurrentIndex(-1); |
| emit q->countChanged(); |
| } |
| |
| void QQuickComboBoxPrivate::updateEditText() |
| { |
| Q_Q(QQuickComboBox); |
| QQuickTextInput *input = qobject_cast<QQuickTextInput *>(contentItem); |
| if (!input) |
| return; |
| |
| const QString text = input->text(); |
| |
| if (extra.isAllocated() && extra->allowComplete && !text.isEmpty()) { |
| const QString completed = tryComplete(text); |
| if (completed.length() > text.length()) { |
| input->setText(completed); |
| input->select(completed.length(), text.length()); |
| return; |
| } |
| } |
| q->setEditText(text); |
| } |
| |
| void QQuickComboBoxPrivate::updateCurrentText() |
| { |
| Q_Q(QQuickComboBox); |
| const QString text = q->textAt(currentIndex); |
| if (currentText != text) { |
| currentText = text; |
| if (!hasDisplayText) |
| q->maybeSetAccessibleName(text); |
| emit q->currentTextChanged(); |
| } |
| if (!hasDisplayText && displayText != text) { |
| displayText = text; |
| emit q->displayTextChanged(); |
| } |
| if (!extra.isAllocated() || !extra->accepting) |
| q->setEditText(currentText); |
| } |
| |
| void QQuickComboBoxPrivate::updateCurrentValue() |
| { |
| Q_Q(QQuickComboBox); |
| const QVariant value = q->valueAt(currentIndex); |
| if (currentValue == value) |
| return; |
| |
| currentValue = value; |
| emit q->currentValueChanged(); |
| } |
| |
| void QQuickComboBoxPrivate::updateCurrentTextAndValue() |
| { |
| updateCurrentText(); |
| updateCurrentValue(); |
| } |
| |
| bool QQuickComboBoxPrivate::isValidIndex(int index) const |
| { |
| return delegateModel && index >= 0 && index < delegateModel->count(); |
| } |
| |
| void QQuickComboBoxPrivate::acceptInput() |
| { |
| Q_Q(QQuickComboBox); |
| int idx = q->find(extra.value().editText, Qt::MatchFixedString); |
| if (idx > -1) |
| q->setCurrentIndex(idx); |
| |
| extra.value().accepting = true; |
| emit q->accepted(); |
| |
| if (idx == -1) |
| q->setCurrentIndex(q->find(extra.value().editText, Qt::MatchFixedString)); |
| extra.value().accepting = false; |
| } |
| |
| QString QQuickComboBoxPrivate::tryComplete(const QString &input) |
| { |
| Q_Q(QQuickComboBox); |
| QString match; |
| |
| const int itemCount = q->count(); |
| for (int idx = 0; idx < itemCount; ++idx) { |
| const QString text = q->textAt(idx); |
| if (!text.startsWith(input, Qt::CaseInsensitive)) |
| continue; |
| |
| // either the first or the shortest match |
| if (match.isEmpty() || text.length() < match.length()) |
| match = text; |
| } |
| |
| if (match.isEmpty()) |
| return input; |
| |
| return input + match.mid(input.length()); |
| } |
| |
| void QQuickComboBoxPrivate::setCurrentIndex(int index, Activation activate) |
| { |
| Q_Q(QQuickComboBox); |
| if (currentIndex == index) |
| return; |
| |
| currentIndex = index; |
| emit q->currentIndexChanged(); |
| |
| if (componentComplete) |
| updateCurrentTextAndValue(); |
| |
| if (activate) |
| emit q->activated(index); |
| } |
| |
| void QQuickComboBoxPrivate::incrementCurrentIndex() |
| { |
| Q_Q(QQuickComboBox); |
| if (extra.isAllocated()) |
| extra->allowComplete = false; |
| if (isPopupVisible()) { |
| if (highlightedIndex < q->count() - 1) |
| setHighlightedIndex(highlightedIndex + 1, Highlight); |
| } else { |
| if (currentIndex < q->count() - 1) |
| setCurrentIndex(currentIndex + 1, Activate); |
| } |
| if (extra.isAllocated()) |
| extra->allowComplete = true; |
| } |
| |
| void QQuickComboBoxPrivate::decrementCurrentIndex() |
| { |
| if (extra.isAllocated()) |
| extra->allowComplete = false; |
| if (isPopupVisible()) { |
| if (highlightedIndex > 0) |
| setHighlightedIndex(highlightedIndex - 1, Highlight); |
| } else { |
| if (currentIndex > 0) |
| setCurrentIndex(currentIndex - 1, Activate); |
| } |
| if (extra.isAllocated()) |
| extra->allowComplete = true; |
| } |
| |
| void QQuickComboBoxPrivate::updateHighlightedIndex() |
| { |
| setHighlightedIndex(popup->isVisible() ? currentIndex : -1, NoHighlight); |
| } |
| |
| void QQuickComboBoxPrivate::setHighlightedIndex(int index, Highlighting highlight) |
| { |
| Q_Q(QQuickComboBox); |
| if (highlightedIndex == index) |
| return; |
| |
| highlightedIndex = index; |
| emit q->highlightedIndexChanged(); |
| |
| if (highlight) |
| emit q->highlighted(index); |
| } |
| |
| void QQuickComboBoxPrivate::keySearch(const QString &text) |
| { |
| const int startIndex = isPopupVisible() ? highlightedIndex : currentIndex; |
| const int index = match(startIndex + 1, text, Qt::MatchStartsWith | Qt::MatchWrap); |
| if (index != -1) { |
| if (isPopupVisible()) |
| setHighlightedIndex(index, Highlight); |
| else |
| setCurrentIndex(index, Activate); |
| } |
| } |
| |
| int QQuickComboBoxPrivate::match(int start, const QString &text, Qt::MatchFlags flags) const |
| { |
| Q_Q(const QQuickComboBox); |
| uint matchType = flags & 0x0F; |
| bool wrap = flags & Qt::MatchWrap; |
| Qt::CaseSensitivity cs = flags & Qt::MatchCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive; |
| QRegularExpression::PatternOptions options = flags & Qt::MatchCaseSensitive ? QRegularExpression::NoPatternOption |
| : QRegularExpression::CaseInsensitiveOption; |
| int from = start; |
| int to = q->count(); |
| |
| // iterates twice if wrapping |
| for (int i = 0; (wrap && i < 2) || (!wrap && i < 1); ++i) { |
| for (int idx = from; idx < to; ++idx) { |
| QString t = q->textAt(idx); |
| switch (matchType) { |
| case Qt::MatchExactly: |
| if (t == text) |
| return idx; |
| break; |
| case Qt::MatchRegExp: { |
| QRegularExpression rx(QRegularExpression::anchoredPattern(text), options); |
| if (rx.match(t).hasMatch()) |
| return idx; |
| break; |
| } |
| case Qt::MatchWildcard: { |
| QRegularExpression rx(QRegularExpression::wildcardToRegularExpression(text), |
| options); |
| if (rx.match(t).hasMatch()) |
| return idx; |
| break; |
| } |
| case Qt::MatchStartsWith: |
| if (t.startsWith(text, cs)) |
| return idx; |
| break; |
| case Qt::MatchEndsWith: |
| if (t.endsWith(text, cs)) |
| return idx; |
| break; |
| case Qt::MatchFixedString: |
| if (t.compare(text, cs) == 0) |
| return idx; |
| break; |
| case Qt::MatchContains: |
| default: |
| if (t.contains(text, cs)) |
| return idx; |
| break; |
| } |
| } |
| // prepare for the next iteration |
| from = 0; |
| to = start; |
| } |
| return -1; |
| } |
| |
| void QQuickComboBoxPrivate::createDelegateModel() |
| { |
| Q_Q(QQuickComboBox); |
| bool ownedOldModel = ownModel; |
| QQmlInstanceModel* oldModel = delegateModel; |
| if (oldModel) { |
| disconnect(delegateModel, &QQmlInstanceModel::countChanged, this, &QQuickComboBoxPrivate::countChanged); |
| disconnect(delegateModel, &QQmlInstanceModel::modelUpdated, this, &QQuickComboBoxPrivate::modelUpdated); |
| disconnect(delegateModel, &QQmlInstanceModel::createdItem, this, &QQuickComboBoxPrivate::createdItem); |
| } |
| |
| ownModel = false; |
| delegateModel = model.value<QQmlInstanceModel *>(); |
| |
| if (!delegateModel && model.isValid()) { |
| QQmlDelegateModel *dataModel = new QQuickComboBoxDelegateModel(q); |
| dataModel->setModel(model); |
| dataModel->setDelegate(delegate); |
| if (q->isComponentComplete()) |
| dataModel->componentComplete(); |
| |
| ownModel = true; |
| delegateModel = dataModel; |
| } |
| |
| if (delegateModel) { |
| connect(delegateModel, &QQmlInstanceModel::countChanged, this, &QQuickComboBoxPrivate::countChanged); |
| connect(delegateModel, &QQmlInstanceModel::modelUpdated, this, &QQuickComboBoxPrivate::modelUpdated); |
| connect(delegateModel, &QQmlInstanceModel::createdItem, this, &QQuickComboBoxPrivate::createdItem); |
| } |
| |
| emit q->delegateModelChanged(); |
| |
| if (ownedOldModel) |
| delete oldModel; |
| } |
| |
| void QQuickComboBoxPrivate::handlePress(const QPointF &point) |
| { |
| Q_Q(QQuickComboBox); |
| QQuickControlPrivate::handlePress(point); |
| q->setPressed(true); |
| } |
| |
| void QQuickComboBoxPrivate::handleMove(const QPointF &point) |
| { |
| Q_Q(QQuickComboBox); |
| QQuickControlPrivate::handleMove(point); |
| q->setPressed(q->contains(point)); |
| } |
| |
| void QQuickComboBoxPrivate::handleRelease(const QPointF &point) |
| { |
| Q_Q(QQuickComboBox); |
| QQuickControlPrivate::handleRelease(point); |
| if (pressed) { |
| q->setPressed(false); |
| togglePopup(false); |
| } |
| } |
| |
| void QQuickComboBoxPrivate::handleUngrab() |
| { |
| Q_Q(QQuickComboBox); |
| QQuickControlPrivate::handleUngrab(); |
| q->setPressed(false); |
| } |
| |
| static inline QString indicatorName() { return QStringLiteral("indicator"); } |
| |
| void QQuickComboBoxPrivate::cancelIndicator() |
| { |
| Q_Q(QQuickComboBox); |
| quickCancelDeferred(q, indicatorName()); |
| } |
| |
| void QQuickComboBoxPrivate::executeIndicator(bool complete) |
| { |
| Q_Q(QQuickComboBox); |
| if (indicator.wasExecuted()) |
| return; |
| |
| if (!indicator || complete) |
| quickBeginDeferred(q, indicatorName(), indicator); |
| if (complete) |
| quickCompleteDeferred(q, indicatorName(), indicator); |
| } |
| |
| static inline QString popupName() { return QStringLiteral("popup"); } |
| |
| void QQuickComboBoxPrivate::cancelPopup() |
| { |
| Q_Q(QQuickComboBox); |
| quickCancelDeferred(q, popupName()); |
| } |
| |
| void QQuickComboBoxPrivate::executePopup(bool complete) |
| { |
| Q_Q(QQuickComboBox); |
| if (popup.wasExecuted()) |
| return; |
| |
| if (!popup || complete) |
| quickBeginDeferred(q, popupName(), popup); |
| if (complete) |
| quickCompleteDeferred(q, popupName(), popup); |
| } |
| |
| void QQuickComboBoxPrivate::itemImplicitWidthChanged(QQuickItem *item) |
| { |
| Q_Q(QQuickComboBox); |
| QQuickControlPrivate::itemImplicitWidthChanged(item); |
| if (item == indicator) |
| emit q->implicitIndicatorWidthChanged(); |
| } |
| |
| void QQuickComboBoxPrivate::itemImplicitHeightChanged(QQuickItem *item) |
| { |
| Q_Q(QQuickComboBox); |
| QQuickControlPrivate::itemImplicitHeightChanged(item); |
| if (item == indicator) |
| emit q->implicitIndicatorHeightChanged(); |
| } |
| |
| void QQuickComboBoxPrivate::hideOldPopup(QQuickPopup *popup) |
| { |
| if (!popup) |
| return; |
| |
| qCDebug(lcItemManagement) << "hiding old popup" << popup; |
| |
| popup->setVisible(false); |
| popup->setParentItem(nullptr); |
| #if QT_CONFIG(accessibility) |
| // Remove the item from the accessibility tree. |
| QQuickAccessibleAttached *accessible = accessibleAttached(popup); |
| if (accessible) |
| accessible->setIgnored(true); |
| #endif |
| } |
| |
| QQuickComboBox::QQuickComboBox(QQuickItem *parent) |
| : QQuickControl(*(new QQuickComboBoxPrivate), parent) |
| { |
| setFocusPolicy(Qt::StrongFocus); |
| setFlag(QQuickItem::ItemIsFocusScope); |
| setAcceptedMouseButtons(Qt::LeftButton); |
| #if QT_CONFIG(cursor) |
| setCursor(Qt::ArrowCursor); |
| #endif |
| setInputMethodHints(Qt::ImhNoPredictiveText); |
| } |
| |
| QQuickComboBox::~QQuickComboBox() |
| { |
| Q_D(QQuickComboBox); |
| d->removeImplicitSizeListener(d->indicator); |
| if (d->popup) { |
| // Disconnect visibleChanged() to avoid a spurious highlightedIndexChanged() signal |
| // emission during the destruction of the (visible) popup. (QTBUG-57650) |
| QObjectPrivate::disconnect(d->popup.data(), &QQuickPopup::visibleChanged, d, &QQuickComboBoxPrivate::popupVisibleChanged); |
| QQuickComboBoxPrivate::hideOldPopup(d->popup); |
| d->popup = nullptr; |
| } |
| } |
| |
| /*! |
| \readonly |
| \qmlproperty int QtQuick.Controls::ComboBox::count |
| |
| This property holds the number of items in the combo box. |
| */ |
| int QQuickComboBox::count() const |
| { |
| Q_D(const QQuickComboBox); |
| return d->delegateModel ? d->delegateModel->count() : 0; |
| } |
| |
| /*! |
| \qmlproperty model QtQuick.Controls::ComboBox::model |
| |
| This property holds the model providing data for the combo box. |
| |
| \code |
| ComboBox { |
| textRole: "key" |
| model: ListModel { |
| ListElement { key: "First"; value: 123 } |
| ListElement { key: "Second"; value: 456 } |
| ListElement { key: "Third"; value: 789 } |
| } |
| } |
| \endcode |
| |
| \sa textRole, {qml-data-models}{Data Models} |
| */ |
| QVariant QQuickComboBox::model() const |
| { |
| Q_D(const QQuickComboBox); |
| return d->model; |
| } |
| |
| void QQuickComboBox::setModel(const QVariant& m) |
| { |
| Q_D(QQuickComboBox); |
| QVariant model = m; |
| if (model.userType() == qMetaTypeId<QJSValue>()) |
| model = model.value<QJSValue>().toVariant(); |
| |
| if (d->model == model) |
| return; |
| |
| if (QAbstractItemModel* aim = qvariant_cast<QAbstractItemModel *>(d->model)) { |
| QObjectPrivate::disconnect(aim, &QAbstractItemModel::dataChanged, |
| d, QOverload<>::of(&QQuickComboBoxPrivate::updateCurrentTextAndValue)); |
| } |
| if (QAbstractItemModel* aim = qvariant_cast<QAbstractItemModel *>(model)) { |
| QObjectPrivate::connect(aim, &QAbstractItemModel::dataChanged, |
| d, QOverload<>::of(&QQuickComboBoxPrivate::updateCurrentTextAndValue)); |
| } |
| |
| d->model = model; |
| d->createDelegateModel(); |
| emit countChanged(); |
| if (isComponentComplete()) { |
| setCurrentIndex(count() > 0 ? 0 : -1); |
| d->updateCurrentTextAndValue(); |
| } |
| emit modelChanged(); |
| } |
| |
| /*! |
| \internal |
| \qmlproperty model QtQuick.Controls::ComboBox::delegateModel |
| |
| This property holds the model providing delegate instances for the combo box. |
| */ |
| QQmlInstanceModel *QQuickComboBox::delegateModel() const |
| { |
| Q_D(const QQuickComboBox); |
| return d->delegateModel; |
| } |
| |
| |
| /*! |
| \qmlproperty bool QtQuick.Controls::ComboBox::pressed |
| |
| This property holds whether the combo box button is physically pressed. |
| A button can be pressed by either touch or key events. |
| |
| \sa down |
| */ |
| bool QQuickComboBox::isPressed() const |
| { |
| Q_D(const QQuickComboBox); |
| return d->pressed; |
| } |
| |
| void QQuickComboBox::setPressed(bool pressed) |
| { |
| Q_D(QQuickComboBox); |
| if (d->pressed == pressed) |
| return; |
| |
| d->pressed = pressed; |
| emit pressedChanged(); |
| |
| if (!d->hasDown) { |
| setDown(d->pressed || d->isPopupVisible()); |
| d->hasDown = false; |
| } |
| } |
| |
| /*! |
| \readonly |
| \qmlproperty int QtQuick.Controls::ComboBox::highlightedIndex |
| |
| This property holds the index of the highlighted item in the combo box popup list. |
| |
| When a highlighted item is activated, the popup is closed, \l currentIndex |
| is set to \c highlightedIndex, and the value of this property is reset to |
| \c -1, as there is no longer a highlighted item. |
| |
| \sa highlighted(), currentIndex |
| */ |
| int QQuickComboBox::highlightedIndex() const |
| { |
| Q_D(const QQuickComboBox); |
| return d->highlightedIndex; |
| } |
| |
| /*! |
| \qmlproperty int QtQuick.Controls::ComboBox::currentIndex |
| |
| This property holds the index of the current item in the combo box. |
| |
| The default value is \c -1 when \l count is \c 0, and \c 0 otherwise. |
| |
| \sa activated(), currentText, highlightedIndex |
| */ |
| int QQuickComboBox::currentIndex() const |
| { |
| Q_D(const QQuickComboBox); |
| return d->currentIndex; |
| } |
| |
| void QQuickComboBox::setCurrentIndex(int index) |
| { |
| Q_D(QQuickComboBox); |
| d->hasCurrentIndex = true; |
| d->setCurrentIndex(index, NoActivate); |
| } |
| |
| /*! |
| \readonly |
| \qmlproperty string QtQuick.Controls::ComboBox::currentText |
| |
| This property holds the text of the current item in the combo box. |
| |
| \sa currentIndex, displayText, textRole, editText |
| */ |
| QString QQuickComboBox::currentText() const |
| { |
| Q_D(const QQuickComboBox); |
| return d->currentText; |
| } |
| |
| /*! |
| \qmlproperty string QtQuick.Controls::ComboBox::displayText |
| |
| This property holds the text that is displayed on the combo box button. |
| |
| By default, the display text presents the current selection. That is, |
| it follows the text of the current item. However, the default display |
| text can be overridden with a custom value. |
| |
| \code |
| ComboBox { |
| currentIndex: 1 |
| displayText: "Size: " + currentText |
| model: ["S", "M", "L"] |
| } |
| \endcode |
| |
| \sa currentText, textRole |
| */ |
| QString QQuickComboBox::displayText() const |
| { |
| Q_D(const QQuickComboBox); |
| return d->displayText; |
| } |
| |
| void QQuickComboBox::setDisplayText(const QString &text) |
| { |
| Q_D(QQuickComboBox); |
| d->hasDisplayText = true; |
| if (d->displayText == text) |
| return; |
| |
| d->displayText = text; |
| maybeSetAccessibleName(text); |
| emit displayTextChanged(); |
| } |
| |
| void QQuickComboBox::resetDisplayText() |
| { |
| Q_D(QQuickComboBox); |
| if (!d->hasDisplayText) |
| return; |
| |
| d->hasDisplayText = false; |
| d->updateCurrentText(); |
| } |
| |
| |
| /*! |
| \qmlproperty string QtQuick.Controls::ComboBox::textRole |
| |
| This property holds the model role used for populating the combo box. |
| |
| When the model has multiple roles, \c textRole can be set to determine |
| which role should be displayed. |
| |
| \sa model, currentText, displayText, {ComboBox Model Roles} |
| */ |
| QString QQuickComboBox::textRole() const |
| { |
| Q_D(const QQuickComboBox); |
| return d->textRole; |
| } |
| |
| void QQuickComboBox::setTextRole(const QString &role) |
| { |
| Q_D(QQuickComboBox); |
| if (d->textRole == role) |
| return; |
| |
| d->textRole = role; |
| if (isComponentComplete()) |
| d->updateCurrentText(); |
| emit textRoleChanged(); |
| } |
| |
| /*! |
| \since QtQuick.Controls 2.14 (Qt 5.14) |
| \qmlproperty string QtQuick.Controls::ComboBox::valueRole |
| |
| This property holds the model role used for storing the value associated |
| with each item in the model. |
| |
| For an example of how to use this property, see \l {ComboBox Model Roles}. |
| |
| \sa model, currentValue |
| */ |
| QString QQuickComboBox::valueRole() const |
| { |
| Q_D(const QQuickComboBox); |
| return d->valueRole; |
| } |
| |
| void QQuickComboBox::setValueRole(const QString &role) |
| { |
| Q_D(QQuickComboBox); |
| if (d->valueRole == role) |
| return; |
| |
| d->valueRole = role; |
| if (isComponentComplete()) |
| d->updateCurrentValue(); |
| emit valueRoleChanged(); |
| } |
| |
| /*! |
| \qmlproperty Component QtQuick.Controls::ComboBox::delegate |
| |
| This property holds a delegate that presents an item in the combo box popup. |
| |
| It is recommended to use \l ItemDelegate (or any other \l AbstractButton |
| derivatives) as the delegate. This ensures that the interaction works as |
| expected, and the popup will automatically close when appropriate. When |
| other types are used as the delegate, the popup must be closed manually. |
| For example, if \l MouseArea is used: |
| |
| \code |
| delegate: Rectangle { |
| // ... |
| MouseArea { |
| // ... |
| onClicked: comboBox.popup.close() |
| } |
| } |
| \endcode |
| |
| \sa ItemDelegate, {Customizing ComboBox} |
| */ |
| QQmlComponent *QQuickComboBox::delegate() const |
| { |
| Q_D(const QQuickComboBox); |
| return d->delegate; |
| } |
| |
| void QQuickComboBox::setDelegate(QQmlComponent* delegate) |
| { |
| Q_D(QQuickComboBox); |
| if (d->delegate == delegate) |
| return; |
| |
| delete d->delegate; |
| d->delegate = delegate; |
| QQmlDelegateModel *delegateModel = qobject_cast<QQmlDelegateModel*>(d->delegateModel); |
| if (delegateModel) |
| delegateModel->setDelegate(d->delegate); |
| emit delegateChanged(); |
| } |
| |
| /*! |
| \qmlproperty Item QtQuick.Controls::ComboBox::indicator |
| |
| This property holds the drop indicator item. |
| |
| \sa {Customizing ComboBox} |
| */ |
| QQuickItem *QQuickComboBox::indicator() const |
| { |
| QQuickComboBoxPrivate *d = const_cast<QQuickComboBoxPrivate *>(d_func()); |
| if (!d->indicator) |
| d->executeIndicator(); |
| return d->indicator; |
| } |
| |
| void QQuickComboBox::setIndicator(QQuickItem *indicator) |
| { |
| Q_D(QQuickComboBox); |
| if (d->indicator == indicator) |
| return; |
| |
| if (!d->indicator.isExecuting()) |
| d->cancelIndicator(); |
| |
| const qreal oldImplicitIndicatorWidth = implicitIndicatorWidth(); |
| const qreal oldImplicitIndicatorHeight = implicitIndicatorHeight(); |
| |
| d->removeImplicitSizeListener(d->indicator); |
| QQuickControlPrivate::hideOldItem(d->indicator); |
| d->indicator = indicator; |
| if (indicator) { |
| if (!indicator->parentItem()) |
| indicator->setParentItem(this); |
| d->addImplicitSizeListener(indicator); |
| } |
| |
| if (!qFuzzyCompare(oldImplicitIndicatorWidth, implicitIndicatorWidth())) |
| emit implicitIndicatorWidthChanged(); |
| if (!qFuzzyCompare(oldImplicitIndicatorHeight, implicitIndicatorHeight())) |
| emit implicitIndicatorHeightChanged(); |
| if (!d->indicator.isExecuting()) |
| emit indicatorChanged(); |
| } |
| |
| /*! |
| \qmlproperty Popup QtQuick.Controls::ComboBox::popup |
| |
| This property holds the popup. |
| |
| The popup can be opened or closed manually, if necessary: |
| |
| \code |
| onSpecialEvent: comboBox.popup.close() |
| \endcode |
| |
| \sa {Customizing ComboBox} |
| */ |
| QQuickPopup *QQuickComboBox::popup() const |
| { |
| QQuickComboBoxPrivate *d = const_cast<QQuickComboBoxPrivate *>(d_func()); |
| if (!d->popup) |
| d->executePopup(isComponentComplete()); |
| return d->popup; |
| } |
| |
| void QQuickComboBox::setPopup(QQuickPopup *popup) |
| { |
| Q_D(QQuickComboBox); |
| if (d->popup == popup) |
| return; |
| |
| if (!d->popup.isExecuting()) |
| d->cancelPopup(); |
| |
| if (d->popup) { |
| QObjectPrivate::disconnect(d->popup.data(), &QQuickPopup::visibleChanged, d, &QQuickComboBoxPrivate::popupVisibleChanged); |
| QQuickComboBoxPrivate::hideOldPopup(d->popup); |
| } |
| if (popup) { |
| QQuickPopupPrivate::get(popup)->allowVerticalFlip = true; |
| popup->setClosePolicy(QQuickPopup::CloseOnEscape | QQuickPopup::CloseOnPressOutsideParent); |
| QObjectPrivate::connect(popup, &QQuickPopup::visibleChanged, d, &QQuickComboBoxPrivate::popupVisibleChanged); |
| |
| if (QQuickItemView *itemView = popup->findChild<QQuickItemView *>()) |
| itemView->setHighlightRangeMode(QQuickItemView::NoHighlightRange); |
| } |
| d->popup = popup; |
| if (!d->popup.isExecuting()) |
| emit popupChanged(); |
| } |
| |
| /*! |
| \since QtQuick.Controls 2.1 (Qt 5.8) |
| \qmlproperty bool QtQuick.Controls::ComboBox::flat |
| |
| This property holds whether the combo box button is flat. |
| |
| A flat combo box button does not draw a background unless it is interacted |
| with. In comparison to normal combo boxes, flat combo boxes provide looks |
| that make them stand out less from the rest of the UI. For instance, when |
| placing a combo box into a tool bar, it may be desirable to make the combo |
| box flat so it matches better with the flat looks of tool buttons. |
| |
| The default value is \c false. |
| */ |
| bool QQuickComboBox::isFlat() const |
| { |
| Q_D(const QQuickComboBox); |
| return d->flat; |
| } |
| |
| void QQuickComboBox::setFlat(bool flat) |
| { |
| Q_D(QQuickComboBox); |
| if (d->flat == flat) |
| return; |
| |
| d->flat = flat; |
| emit flatChanged(); |
| } |
| |
| /*! |
| \since QtQuick.Controls 2.2 (Qt 5.9) |
| \qmlproperty bool QtQuick.Controls::ComboBox::down |
| |
| This property holds whether the combo box button is visually down. |
| |
| Unless explicitly set, this property is \c true when either \c pressed |
| or \c popup.visible is \c true. To return to the default value, set this |
| property to \c undefined. |
| |
| \sa pressed, popup |
| */ |
| bool QQuickComboBox::isDown() const |
| { |
| Q_D(const QQuickComboBox); |
| return d->down; |
| } |
| |
| void QQuickComboBox::setDown(bool down) |
| { |
| Q_D(QQuickComboBox); |
| d->hasDown = true; |
| |
| if (d->down == down) |
| return; |
| |
| d->down = down; |
| emit downChanged(); |
| } |
| |
| void QQuickComboBox::resetDown() |
| { |
| Q_D(QQuickComboBox); |
| if (!d->hasDown) |
| return; |
| |
| setDown(d->pressed || d->isPopupVisible()); |
| d->hasDown = false; |
| } |
| |
| /*! |
| \since QtQuick.Controls 2.2 (Qt 5.9) |
| \qmlproperty bool QtQuick.Controls::ComboBox::editable |
| |
| This property holds whether the combo box is editable. |
| |
| The default value is \c false. |
| |
| \sa validator |
| */ |
| bool QQuickComboBox::isEditable() const |
| { |
| Q_D(const QQuickComboBox); |
| return d->extra.isAllocated() && d->extra->editable; |
| } |
| |
| void QQuickComboBox::setEditable(bool editable) |
| { |
| Q_D(QQuickComboBox); |
| if (editable == isEditable()) |
| return; |
| |
| if (d->contentItem) { |
| if (editable) { |
| d->contentItem->installEventFilter(this); |
| if (QQuickTextInput *input = qobject_cast<QQuickTextInput *>(d->contentItem)) { |
| QObjectPrivate::connect(input, &QQuickTextInput::textChanged, d, &QQuickComboBoxPrivate::updateEditText); |
| QObjectPrivate::connect(input, &QQuickTextInput::accepted, d, &QQuickComboBoxPrivate::acceptInput); |
| } |
| #if QT_CONFIG(cursor) |
| d->contentItem->setCursor(Qt::IBeamCursor); |
| #endif |
| } else { |
| d->contentItem->removeEventFilter(this); |
| if (QQuickTextInput *input = qobject_cast<QQuickTextInput *>(d->contentItem)) { |
| QObjectPrivate::disconnect(input, &QQuickTextInput::textChanged, d, &QQuickComboBoxPrivate::updateEditText); |
| QObjectPrivate::disconnect(input, &QQuickTextInput::accepted, d, &QQuickComboBoxPrivate::acceptInput); |
| } |
| #if QT_CONFIG(cursor) |
| d->contentItem->unsetCursor(); |
| #endif |
| } |
| } |
| |
| d->extra.value().editable = editable; |
| setAccessibleProperty("editable", editable); |
| emit editableChanged(); |
| } |
| |
| /*! |
| \since QtQuick.Controls 2.2 (Qt 5.9) |
| \qmlproperty string QtQuick.Controls::ComboBox::editText |
| |
| This property holds the text in the text field of an editable combo box. |
| |
| \sa editable, currentText, displayText |
| */ |
| QString QQuickComboBox::editText() const |
| { |
| Q_D(const QQuickComboBox); |
| return d->extra.isAllocated() ? d->extra->editText : QString(); |
| } |
| |
| void QQuickComboBox::setEditText(const QString &text) |
| { |
| Q_D(QQuickComboBox); |
| if (text == editText()) |
| return; |
| |
| d->extra.value().editText = text; |
| emit editTextChanged(); |
| } |
| |
| void QQuickComboBox::resetEditText() |
| { |
| setEditText(QString()); |
| } |
| |
| /*! |
| \since QtQuick.Controls 2.2 (Qt 5.9) |
| \qmlproperty Validator QtQuick.Controls::ComboBox::validator |
| |
| This property holds an input text validator for an editable combo box. |
| |
| When a validator is set, the text field will only accept input which |
| leaves the text property in an intermediate state. The \l accepted signal |
| will only be emitted if the text is in an acceptable state when the |
| \uicontrol Return or \uicontrol Enter key is pressed. |
| |
| The currently supported validators are \l[QtQuick]{IntValidator}, |
| \l[QtQuick]{DoubleValidator}, and \l[QtQuick]{RegExpValidator}. An |
| example of using validators is shown below, which allows input of |
| integers between \c 0 and \c 10 into the text field: |
| |
| \code |
| ComboBox { |
| model: 10 |
| editable: true |
| validator: IntValidator { |
| top: 9 |
| bottom: 0 |
| } |
| } |
| \endcode |
| |
| \sa acceptableInput, accepted, editable |
| */ |
| QValidator *QQuickComboBox::validator() const |
| { |
| Q_D(const QQuickComboBox); |
| return d->extra.isAllocated() ? d->extra->validator : nullptr; |
| } |
| |
| void QQuickComboBox::setValidator(QValidator *validator) |
| { |
| Q_D(QQuickComboBox); |
| if (validator == QQuickComboBox::validator()) |
| return; |
| |
| d->extra.value().validator = validator; |
| #if QT_CONFIG(validator) |
| if (validator) |
| validator->setLocale(d->locale); |
| #endif |
| emit validatorChanged(); |
| } |
| |
| /*! |
| \since QtQuick.Controls 2.2 (Qt 5.9) |
| \qmlproperty flags QtQuick.Controls::ComboBox::inputMethodHints |
| |
| Provides hints to the input method about the expected content of the combo box and how it |
| should operate. |
| |
| The default value is \c Qt.ImhNoPredictiveText. |
| |
| \include inputmethodhints.qdocinc |
| */ |
| Qt::InputMethodHints QQuickComboBox::inputMethodHints() const |
| { |
| Q_D(const QQuickComboBox); |
| return d->extra.isAllocated() ? d->extra->inputMethodHints : Qt::ImhNoPredictiveText; |
| } |
| |
| void QQuickComboBox::setInputMethodHints(Qt::InputMethodHints hints) |
| { |
| Q_D(QQuickComboBox); |
| if (hints == inputMethodHints()) |
| return; |
| |
| d->extra.value().inputMethodHints = hints; |
| emit inputMethodHintsChanged(); |
| } |
| |
| /*! |
| \since QtQuick.Controls 2.2 (Qt 5.9) |
| \qmlproperty bool QtQuick.Controls::ComboBox::inputMethodComposing |
| \readonly |
| |
| This property holds whether an editable combo box has partial text input from an input method. |
| |
| While it is composing, an input method may rely on mouse or key events from the combo box to |
| edit or commit the partial text. This property can be used to determine when to disable event |
| handlers that may interfere with the correct operation of an input method. |
| */ |
| bool QQuickComboBox::isInputMethodComposing() const |
| { |
| Q_D(const QQuickComboBox); |
| return d->contentItem && d->contentItem->property("inputMethodComposing").toBool(); |
| } |
| |
| /*! |
| \since QtQuick.Controls 2.2 (Qt 5.9) |
| \qmlproperty bool QtQuick.Controls::ComboBox::acceptableInput |
| \readonly |
| |
| This property holds whether the combo box contains acceptable text in the editable text field. |
| |
| If a validator has been set, the value is \c true only if the current text is acceptable |
| to the validator as a final string (not as an intermediate string). |
| |
| \sa validator, accepted |
| */ |
| bool QQuickComboBox::hasAcceptableInput() const |
| { |
| Q_D(const QQuickComboBox); |
| return d->contentItem && d->contentItem->property("acceptableInput").toBool(); |
| } |
| |
| /*! |
| \since QtQuick.Controls 2.5 (Qt 5.12) |
| \qmlproperty real QtQuick.Controls::ComboBox::implicitIndicatorWidth |
| \readonly |
| |
| This property holds the implicit indicator width. |
| |
| The value is equal to \c {indicator ? indicator.implicitWidth : 0}. |
| |
| This is typically used, together with \l {Control::}{implicitContentWidth} and |
| \l {Control::}{implicitBackgroundWidth}, to calculate the \l {Item::}{implicitWidth}. |
| |
| \sa implicitIndicatorHeight |
| */ |
| qreal QQuickComboBox::implicitIndicatorWidth() const |
| { |
| Q_D(const QQuickComboBox); |
| if (!d->indicator) |
| return 0; |
| return d->indicator->implicitWidth(); |
| } |
| |
| /*! |
| \since QtQuick.Controls 2.5 (Qt 5.12) |
| \qmlproperty real QtQuick.Controls::ComboBox::implicitIndicatorHeight |
| \readonly |
| |
| This property holds the implicit indicator height. |
| |
| The value is equal to \c {indicator ? indicator.implicitHeight : 0}. |
| |
| This is typically used, together with \l {Control::}{implicitContentHeight} and |
| \l {Control::}{implicitBackgroundHeight}, to calculate the \l {Item::}{implicitHeight}. |
| |
| \sa implicitIndicatorWidth |
| */ |
| qreal QQuickComboBox::implicitIndicatorHeight() const |
| { |
| Q_D(const QQuickComboBox); |
| if (!d->indicator) |
| return 0; |
| return d->indicator->implicitHeight(); |
| } |
| |
| /*! |
| \readonly |
| \since QtQuick.Controls 2.14 (Qt 5.14) |
| \qmlproperty string QtQuick.Controls::ComboBox::currentValue |
| |
| This property holds the value of the current item in the combo box. |
| |
| For an example of how to use this property, see \l {ComboBox Model Roles}. |
| |
| \sa currentIndex, currentText, valueRole |
| */ |
| QVariant QQuickComboBox::currentValue() const |
| { |
| Q_D(const QQuickComboBox); |
| return d->currentValue; |
| } |
| |
| QVariant QQuickComboBox::valueAt(int index) const |
| { |
| Q_D(const QQuickComboBox); |
| if (!d->isValidIndex(index)) |
| return QVariant(); |
| |
| const QString effectiveValueRole = d->valueRole.isEmpty() ? QStringLiteral("modelData") : d->valueRole; |
| return d->delegateModel->variantValue(index, effectiveValueRole); |
| } |
| |
| /*! |
| \since QtQuick.Controls 2.14 (Qt 5.14) |
| \qmlmethod int QtQuick.Controls::ComboBox::indexOfValue(object value) |
| |
| Returns the index of the specified \a value, or \c -1 if no match is found. |
| |
| For an example of how to use this method, see \l {ComboBox Model Roles}. |
| |
| \sa find(), currentValue, currentIndex, valueRole |
| */ |
| int QQuickComboBox::indexOfValue(const QVariant &value) const |
| { |
| for (int i = 0; i < count(); ++i) { |
| const QVariant ourValue = valueAt(i); |
| if (value == ourValue) |
| return i; |
| } |
| return -1; |
| } |
| |
| /*! |
| \since QtQuick.Controls 2.15 (Qt 5.15) |
| \qmlproperty bool QtQuick.Controls::ComboBox::selectTextByMouse |
| |
| This property holds whether the text field for an editable ComboBox |
| can be selected with the mouse. |
| |
| The default value is \c false. |
| */ |
| bool QQuickComboBox::selectTextByMouse() const |
| { |
| Q_D(const QQuickComboBox); |
| return d->extra.isAllocated() ? d->extra->selectTextByMouse : false; |
| } |
| |
| void QQuickComboBox::setSelectTextByMouse(bool canSelect) |
| { |
| Q_D(QQuickComboBox); |
| if (canSelect == selectTextByMouse()) |
| return; |
| |
| d->extra.value().selectTextByMouse = canSelect; |
| emit selectTextByMouseChanged(); |
| } |
| /*! |
| \qmlmethod string QtQuick.Controls::ComboBox::textAt(int index) |
| |
| Returns the text for the specified \a index, or an empty string |
| if the index is out of bounds. |
| |
| \sa textRole |
| */ |
| QString QQuickComboBox::textAt(int index) const |
| { |
| Q_D(const QQuickComboBox); |
| if (!d->isValidIndex(index)) |
| return QString(); |
| |
| const QString effectiveTextRole = d->textRole.isEmpty() ? QStringLiteral("modelData") : d->textRole; |
| return d->delegateModel->stringValue(index, effectiveTextRole); |
| } |
| |
| /*! |
| \qmlmethod int QtQuick.Controls::ComboBox::find(string text, enumeration flags) |
| |
| Returns the index of the specified \a text, or \c -1 if no match is found. |
| |
| The way the search is performed is defined by the specified match \a flags. By default, |
| combo box performs case sensitive exact matching (\c Qt.MatchExactly). All other match |
| types are case-insensitive unless the \c Qt.MatchCaseSensitive flag is also specified. |
| |
| \value Qt.MatchExactly The search term matches exactly (default). |
| \value Qt.MatchRegExp The search term matches as a regular expression. |
| \value Qt.MatchWildcard The search term matches using wildcards. |
| \value Qt.MatchFixedString The search term matches as a fixed string. |
| \value Qt.MatchStartsWith The search term matches the start of the item. |
| \value Qt.MatchEndsWidth The search term matches the end of the item. |
| \value Qt.MatchContains The search term is contained in the item. |
| \value Qt.MatchCaseSensitive The search is case sensitive. |
| |
| \sa textRole |
| */ |
| int QQuickComboBox::find(const QString &text, Qt::MatchFlags flags) const |
| { |
| Q_D(const QQuickComboBox); |
| return d->match(0, text, flags); |
| } |
| |
| /*! |
| \qmlmethod void QtQuick.Controls::ComboBox::incrementCurrentIndex() |
| |
| Increments the current index of the combo box, or the highlighted |
| index if the popup list is visible. |
| |
| \sa currentIndex, highlightedIndex |
| */ |
| void QQuickComboBox::incrementCurrentIndex() |
| { |
| Q_D(QQuickComboBox); |
| d->incrementCurrentIndex(); |
| } |
| |
| /*! |
| \qmlmethod void QtQuick.Controls::ComboBox::decrementCurrentIndex() |
| |
| Decrements the current index of the combo box, or the highlighted |
| index if the popup list is visible. |
| |
| \sa currentIndex, highlightedIndex |
| */ |
| void QQuickComboBox::decrementCurrentIndex() |
| { |
| Q_D(QQuickComboBox); |
| d->decrementCurrentIndex(); |
| } |
| |
| /*! |
| \since QtQuick.Controls 2.2 (Qt 5.9) |
| \qmlmethod void QtQuick.Controls::ComboBox::selectAll() |
| |
| Selects all the text in the editable text field of the combo box. |
| |
| \sa editText |
| */ |
| void QQuickComboBox::selectAll() |
| { |
| Q_D(QQuickComboBox); |
| QQuickTextInput *input = qobject_cast<QQuickTextInput *>(d->contentItem); |
| if (!input) |
| return; |
| input->selectAll(); |
| } |
| |
| bool QQuickComboBox::eventFilter(QObject *object, QEvent *event) |
| { |
| Q_D(QQuickComboBox); |
| switch (event->type()) { |
| case QEvent::MouseButtonRelease: |
| if (d->isPopupVisible()) |
| d->hidePopup(false); |
| break; |
| case QEvent::KeyPress: { |
| QKeyEvent *ke = static_cast<QKeyEvent *>(event); |
| if (d->filterKeyEvent(ke, false)) |
| return true; |
| event->accept(); |
| if (d->extra.isAllocated()) |
| d->extra->allowComplete = ke->key() != Qt::Key_Backspace && ke->key() != Qt::Key_Delete; |
| break; |
| } |
| case QEvent::FocusOut: |
| if (qGuiApp->focusObject() != this && (!d->popup || !d->popup->hasActiveFocus())) { |
| // Only close the popup if focus was transferred somewhere else |
| // than to the popup or the popup button (which normally means that |
| // the user clicked on the popup button to open it, not close it). |
| d->hidePopup(false); |
| setPressed(false); |
| |
| // The focus left the text field, so if the edit text matches an item in the model, |
| // change our currentIndex to that. This matches widgets' behavior. |
| const int indexForEditText = find(d->extra.value().editText, Qt::MatchFixedString); |
| if (indexForEditText > -1) |
| setCurrentIndex(indexForEditText); |
| } |
| break; |
| #if QT_CONFIG(im) |
| case QEvent::InputMethod: |
| if (d->extra.isAllocated()) |
| d->extra->allowComplete = !static_cast<QInputMethodEvent*>(event)->commitString().isEmpty(); |
| break; |
| #endif |
| default: |
| break; |
| } |
| return QQuickControl::eventFilter(object, event); |
| } |
| |
| void QQuickComboBox::focusInEvent(QFocusEvent *event) |
| { |
| Q_D(QQuickComboBox); |
| QQuickControl::focusInEvent(event); |
| if (d->contentItem && isEditable()) |
| d->contentItem->forceActiveFocus(event->reason()); |
| } |
| |
| void QQuickComboBox::focusOutEvent(QFocusEvent *event) |
| { |
| Q_D(QQuickComboBox); |
| QQuickControl::focusOutEvent(event); |
| |
| if (qGuiApp->focusObject() != d->contentItem && (!d->popup || !d->popup->hasActiveFocus())) { |
| // Only close the popup if focus was transferred |
| // somewhere else than to the popup or the inner line edit (which is |
| // normally done from QQuickComboBox::focusInEvent). |
| d->hidePopup(false); |
| setPressed(false); |
| } |
| } |
| |
| #if QT_CONFIG(im) |
| void QQuickComboBox::inputMethodEvent(QInputMethodEvent *event) |
| { |
| Q_D(QQuickComboBox); |
| QQuickControl::inputMethodEvent(event); |
| if (!isEditable() && !event->commitString().isEmpty()) |
| d->keySearch(event->commitString()); |
| else |
| event->ignore(); |
| } |
| #endif |
| |
| void QQuickComboBox::keyPressEvent(QKeyEvent *event) |
| { |
| Q_D(QQuickComboBox); |
| QQuickControl::keyPressEvent(event); |
| |
| switch (event->key()) { |
| case Qt::Key_Escape: |
| case Qt::Key_Back: |
| if (d->isPopupVisible()) |
| event->accept(); |
| break; |
| case Qt::Key_Space: |
| if (!event->isAutoRepeat()) |
| setPressed(true); |
| event->accept(); |
| break; |
| case Qt::Key_Enter: |
| case Qt::Key_Return: |
| if (d->isPopupVisible()) |
| setPressed(true); |
| event->accept(); |
| break; |
| case Qt::Key_Up: |
| d->keyNavigating = true; |
| d->decrementCurrentIndex(); |
| event->accept(); |
| break; |
| case Qt::Key_Down: |
| d->keyNavigating = true; |
| d->incrementCurrentIndex(); |
| event->accept(); |
| break; |
| case Qt::Key_Home: |
| d->keyNavigating = true; |
| if (d->isPopupVisible()) |
| d->setHighlightedIndex(0, Highlight); |
| else |
| d->setCurrentIndex(0, Activate); |
| event->accept(); |
| break; |
| case Qt::Key_End: |
| d->keyNavigating = true; |
| if (d->isPopupVisible()) |
| d->setHighlightedIndex(count() - 1, Highlight); |
| else |
| d->setCurrentIndex(count() - 1, Activate); |
| event->accept(); |
| break; |
| default: |
| if (!isEditable() && !event->text().isEmpty()) |
| d->keySearch(event->text()); |
| else |
| event->ignore(); |
| break; |
| } |
| } |
| |
| void QQuickComboBox::keyReleaseEvent(QKeyEvent *event) |
| { |
| Q_D(QQuickComboBox); |
| QQuickControl::keyReleaseEvent(event); |
| d->keyNavigating = false; |
| if (event->isAutoRepeat()) |
| return; |
| |
| switch (event->key()) { |
| case Qt::Key_Space: |
| if (!isEditable()) |
| d->togglePopup(true); |
| setPressed(false); |
| event->accept(); |
| break; |
| case Qt::Key_Enter: |
| case Qt::Key_Return: |
| if (!isEditable() || d->isPopupVisible()) |
| d->hidePopup(d->isPopupVisible()); |
| setPressed(false); |
| event->accept(); |
| break; |
| case Qt::Key_Escape: |
| case Qt::Key_Back: |
| if (d->isPopupVisible()) { |
| d->hidePopup(false); |
| setPressed(false); |
| event->accept(); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| #if QT_CONFIG(wheelevent) |
| void QQuickComboBox::wheelEvent(QWheelEvent *event) |
| { |
| Q_D(QQuickComboBox); |
| QQuickControl::wheelEvent(event); |
| if (d->wheelEnabled && !d->isPopupVisible()) { |
| if (event->angleDelta().y() > 0) |
| d->decrementCurrentIndex(); |
| else |
| d->incrementCurrentIndex(); |
| } |
| } |
| #endif |
| |
| bool QQuickComboBox::event(QEvent *e) |
| { |
| Q_D(QQuickComboBox); |
| if (e->type() == QEvent::LanguageChange) |
| d->updateCurrentTextAndValue(); |
| return QQuickControl::event(e); |
| } |
| |
| void QQuickComboBox::componentComplete() |
| { |
| Q_D(QQuickComboBox); |
| d->executeIndicator(true); |
| QQuickControl::componentComplete(); |
| if (d->popup) |
| d->executePopup(true); |
| |
| if (d->delegateModel && d->ownModel) |
| static_cast<QQmlDelegateModel *>(d->delegateModel)->componentComplete(); |
| |
| if (count() > 0) { |
| if (!d->hasCurrentIndex && d->currentIndex == -1) |
| setCurrentIndex(0); |
| else |
| d->updateCurrentTextAndValue(); |
| } |
| } |
| |
| void QQuickComboBox::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) |
| { |
| Q_D(QQuickComboBox); |
| QQuickControl::itemChange(change, value); |
| if (change == ItemVisibleHasChanged && !value.boolValue) { |
| d->hidePopup(false); |
| setPressed(false); |
| } |
| } |
| |
| void QQuickComboBox::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) |
| { |
| Q_D(QQuickComboBox); |
| if (oldItem) { |
| oldItem->removeEventFilter(this); |
| if (QQuickTextInput *oldInput = qobject_cast<QQuickTextInput *>(oldItem)) { |
| QObjectPrivate::disconnect(oldInput, &QQuickTextInput::accepted, d, &QQuickComboBoxPrivate::acceptInput); |
| QObjectPrivate::disconnect(oldInput, &QQuickTextInput::textChanged, d, &QQuickComboBoxPrivate::updateEditText); |
| disconnect(oldInput, &QQuickTextInput::inputMethodComposingChanged, this, &QQuickComboBox::inputMethodComposingChanged); |
| disconnect(oldInput, &QQuickTextInput::acceptableInputChanged, this, &QQuickComboBox::acceptableInputChanged); |
| } |
| } |
| if (newItem && isEditable()) { |
| newItem->installEventFilter(this); |
| if (QQuickTextInput *newInput = qobject_cast<QQuickTextInput *>(newItem)) { |
| QObjectPrivate::connect(newInput, &QQuickTextInput::accepted, d, &QQuickComboBoxPrivate::acceptInput); |
| QObjectPrivate::connect(newInput, &QQuickTextInput::textChanged, d, &QQuickComboBoxPrivate::updateEditText); |
| connect(newInput, &QQuickTextInput::inputMethodComposingChanged, this, &QQuickComboBox::inputMethodComposingChanged); |
| connect(newInput, &QQuickTextInput::acceptableInputChanged, this, &QQuickComboBox::acceptableInputChanged); |
| } |
| #if QT_CONFIG(cursor) |
| newItem->setCursor(Qt::IBeamCursor); |
| #endif |
| } |
| } |
| |
| void QQuickComboBox::localeChange(const QLocale &newLocale, const QLocale &oldLocale) |
| { |
| QQuickControl::localeChange(newLocale, oldLocale); |
| #if QT_CONFIG(validator) |
| if (QValidator *v = validator()) |
| v->setLocale(newLocale); |
| #endif |
| } |
| |
| QFont QQuickComboBox::defaultFont() const |
| { |
| return QQuickTheme::font(QQuickTheme::ComboBox); |
| } |
| |
| QPalette QQuickComboBox::defaultPalette() const |
| { |
| return QQuickTheme::palette(QQuickTheme::ComboBox); |
| } |
| |
| #if QT_CONFIG(accessibility) |
| QAccessible::Role QQuickComboBox::accessibleRole() const |
| { |
| return QAccessible::ComboBox; |
| } |
| |
| void QQuickComboBox::accessibilityActiveChanged(bool active) |
| { |
| Q_D(QQuickComboBox); |
| QQuickControl::accessibilityActiveChanged(active); |
| |
| if (active) { |
| maybeSetAccessibleName(d->hasDisplayText ? d->displayText : d->currentText); |
| setAccessibleProperty("editable", isEditable()); |
| } |
| } |
| #endif // |
| |
| QT_END_NAMESPACE |