diff options
author | Mitch Curtis <mitch.curtis@qt.io> | 2019-03-04 11:12:05 +0100 |
---|---|---|
committer | Mitch Curtis <mitch.curtis@qt.io> | 2019-04-23 11:11:18 +0000 |
commit | f462312365d4955fc82b247b72f84e1c77d8104d (patch) | |
tree | 0b240fda0fe60d5110b91aecc6da413f5b086b81 | |
parent | 8b61881469901a793e9ac8eec7caefad547258a2 (diff) |
Add valueRole API to ComboBox
This allows the user to conveniently manage data for a role associated with
the text role. A common example of this is an enum stored in a backend with
nicely formatted text displayed to the user. Before this patch, developers
would have to write code like this:
ComboBox {
textRole: "text"
onActivated: backend.modifier = model[currentIndex].value
Component.onCompleted: currentIndex = findValue(backend.modifier)
model: [
{ value: Qt.NoModifier, text: qsTr("No modifier") },
{ value: Qt.ShiftModifier, text: qsTr("Shift") },
{ value: Qt.ControlModifier, text: qsTr("Control") }
]
function findValue(value) {
for (var i = 0; i < model.length; ++i) {
if (model[i].value === value)
return i
}
return -1
}
}
With this patch, the code becomes much simpler:
ComboBox {
textRole: "text"
valueRole: "value"
onActivated: backend.modifier = currentValue
Component.onCompleted: currentIndex = indexOfValue(backend.modifier)
model: [
{ value: Qt.NoModifier, text: qsTr("No modifier") },
{ value: Qt.ShiftModifier, text: qsTr("Shift") },
{ value: Qt.ControlModifier, text: qsTr("Control") }
]
}
[ChangeLog][Controls][ComboBox] Added valueRole, currentValue and
indexOfValue(). These allow convenient management of data for a role
associated with the text role.
Change-Id: I0ed19bd0ba9cf6b044a8113ff1a8782d43065449
Fixes: QTBUG-73491
Reviewed-by: Mitch Curtis <mitch.curtis@qt.io>
-rw-r--r-- | src/imports/controls/ComboBox.qml | 10 | ||||
-rw-r--r-- | src/imports/controls/doc/snippets/qtquickcontrols2-combobox-valuerole.qml | 58 | ||||
-rw-r--r-- | src/imports/controls/fusion/ComboBox.qml | 14 | ||||
-rw-r--r-- | src/imports/controls/imagine/ComboBox.qml | 12 | ||||
-rw-r--r-- | src/imports/controls/material/ComboBox.qml | 14 | ||||
-rw-r--r-- | src/imports/controls/universal/ComboBox.qml | 12 | ||||
-rw-r--r-- | src/imports/templates/qtquicktemplates2plugin.cpp | 3 | ||||
-rw-r--r-- | src/quicktemplates2/qquickcombobox.cpp | 139 | ||||
-rw-r--r-- | src/quicktemplates2/qquickcombobox_p.h | 14 | ||||
-rw-r--r-- | tests/auto/controls/data/tst_combobox.qml | 66 |
10 files changed, 289 insertions, 53 deletions
diff --git a/src/imports/controls/ComboBox.qml b/src/imports/controls/ComboBox.qml index 3bca9c02..8eefc686 100644 --- a/src/imports/controls/ComboBox.qml +++ b/src/imports/controls/ComboBox.qml @@ -34,11 +34,11 @@ ** ****************************************************************************/ -import QtQuick 2.12 -import QtQuick.Window 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Controls.impl 2.12 -import QtQuick.Templates 2.12 as T +import QtQuick 2.14 +import QtQuick.Window 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Controls.impl 2.14 +import QtQuick.Templates 2.14 as T T.ComboBox { id: control diff --git a/src/imports/controls/doc/snippets/qtquickcontrols2-combobox-valuerole.qml b/src/imports/controls/doc/snippets/qtquickcontrols2-combobox-valuerole.qml new file mode 100644 index 00000000..4d7ae3d3 --- /dev/null +++ b/src/imports/controls/doc/snippets/qtquickcontrols2-combobox-valuerole.qml @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.14 +import QtQuick.Controls 2.14 + +//! [file] +ApplicationWindow { + width: 640 + height: 480 + visible: true + + // Used as an example of a backend - this would usually be + // e.g. a C++ type exposed to QML. + QtObject { + id: backend + property int modifier + } + + ComboBox { + textRole: "text" + valueRole: "value" + // When an item is selected, update the backend. + onActivated: backend.modifier = currentValue + // Set the initial currentIndex to the value stored in the backend. + Component.onCompleted: currentIndex = indexOfValue(backend.modifier) + model: [ + { value: Qt.NoModifier, text: qsTr("No modifier") }, + { value: Qt.ShiftModifier, text: qsTr("Shift") }, + { value: Qt.ControlModifier, text: qsTr("Control") } + ] + } +} +//! [file] diff --git a/src/imports/controls/fusion/ComboBox.qml b/src/imports/controls/fusion/ComboBox.qml index 3ecb0cf2..58764809 100644 --- a/src/imports/controls/fusion/ComboBox.qml +++ b/src/imports/controls/fusion/ComboBox.qml @@ -34,13 +34,13 @@ ** ****************************************************************************/ -import QtQuick 2.12 -import QtQuick.Window 2.12 -import QtQuick.Templates 2.12 as T -import QtQuick.Controls 2.12 -import QtQuick.Controls.impl 2.12 -import QtQuick.Controls.Fusion 2.12 -import QtQuick.Controls.Fusion.impl 2.12 +import QtQuick 2.14 +import QtQuick.Window 2.14 +import QtQuick.Templates 2.14 as T +import QtQuick.Controls 2.14 +import QtQuick.Controls.impl 2.14 +import QtQuick.Controls.Fusion 2.14 +import QtQuick.Controls.Fusion.impl 2.14 T.ComboBox { id: control diff --git a/src/imports/controls/imagine/ComboBox.qml b/src/imports/controls/imagine/ComboBox.qml index 3a3ae682..2d582e98 100644 --- a/src/imports/controls/imagine/ComboBox.qml +++ b/src/imports/controls/imagine/ComboBox.qml @@ -34,12 +34,12 @@ ** ****************************************************************************/ -import QtQuick 2.12 -import QtQuick.Window 2.12 -import QtQuick.Templates 2.12 as T -import QtQuick.Controls 2.12 -import QtQuick.Controls.Imagine 2.12 -import QtQuick.Controls.Imagine.impl 2.12 +import QtQuick 2.14 +import QtQuick.Window 2.14 +import QtQuick.Templates 2.14 as T +import QtQuick.Controls 2.14 +import QtQuick.Controls.Imagine 2.14 +import QtQuick.Controls.Imagine.impl 2.14 T.ComboBox { id: control diff --git a/src/imports/controls/material/ComboBox.qml b/src/imports/controls/material/ComboBox.qml index 223f8fca..7d635902 100644 --- a/src/imports/controls/material/ComboBox.qml +++ b/src/imports/controls/material/ComboBox.qml @@ -34,13 +34,13 @@ ** ****************************************************************************/ -import QtQuick 2.12 -import QtQuick.Window 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Controls.impl 2.12 -import QtQuick.Templates 2.12 as T -import QtQuick.Controls.Material 2.12 -import QtQuick.Controls.Material.impl 2.12 +import QtQuick 2.14 +import QtQuick.Window 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Controls.impl 2.14 +import QtQuick.Templates 2.14 as T +import QtQuick.Controls.Material 2.14 +import QtQuick.Controls.Material.impl 2.14 T.ComboBox { id: control diff --git a/src/imports/controls/universal/ComboBox.qml b/src/imports/controls/universal/ComboBox.qml index 3ec7e98b..9a4e119b 100644 --- a/src/imports/controls/universal/ComboBox.qml +++ b/src/imports/controls/universal/ComboBox.qml @@ -34,12 +34,12 @@ ** ****************************************************************************/ -import QtQuick 2.12 -import QtQuick.Window 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Controls.impl 2.12 -import QtQuick.Templates 2.12 as T -import QtQuick.Controls.Universal 2.12 +import QtQuick 2.14 +import QtQuick.Window 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Controls.impl 2.14 +import QtQuick.Templates 2.14 as T +import QtQuick.Controls.Universal 2.14 T.ComboBox { id: control diff --git a/src/imports/templates/qtquicktemplates2plugin.cpp b/src/imports/templates/qtquicktemplates2plugin.cpp index 10f9b8dd..008293a2 100644 --- a/src/imports/templates/qtquicktemplates2plugin.cpp +++ b/src/imports/templates/qtquicktemplates2plugin.cpp @@ -355,6 +355,9 @@ void QtQuickTemplates2Plugin::registerTypes(const char *uri) qmlRegisterUncreatableType<QQuickSplitHandleAttached>(uri, 2, 13, "SplitHandle", QStringLiteral("SplitHandle is only available as an attached property.")); qmlRegisterType<QQuickSplitHandleAttached>(); + + // QtQuick.Templates 2.14 (new types and revisions in Qt 5.14) + qmlRegisterType<QQuickComboBox, 14>(uri, 2, 14, "ComboBox"); } QT_END_NAMESPACE diff --git a/src/quicktemplates2/qquickcombobox.cpp b/src/quicktemplates2/qquickcombobox.cpp index 0ffd76ab..9286da81 100644 --- a/src/quicktemplates2/qquickcombobox.cpp +++ b/src/quicktemplates2/qquickcombobox.cpp @@ -117,18 +117,14 @@ QT_BEGIN_NAMESPACE 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. + 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. - \code - ComboBox { - textRole: "key" - model: ListModel { - ListElement { key: "First"; value: 123 } - ListElement { key: "Second"; value: 456 } - ListElement { key: "Third"; value: 789 } - } - } - \endcode + 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 @@ -184,7 +180,7 @@ class QQuickComboBoxDelegateModel : public QQmlDelegateModel { public: explicit QQuickComboBoxDelegateModel(QQuickComboBox *combo); - QString stringValue(int index, const QString &role); + QVariant variantValue(int index, const QString &role) override; private: QQuickComboBox *combo = nullptr; @@ -196,23 +192,23 @@ QQuickComboBoxDelegateModel::QQuickComboBoxDelegateModel(QQuickComboBox *combo) { } -QString QQuickComboBoxDelegateModel::stringValue(int index, const QString &role) +QVariant QQuickComboBoxDelegateModel::variantValue(int index, const QString &role) { - QVariant model = combo->model(); + 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().toString(); - return data.value(role).toString(); + 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()).toString(); + return data->property(role.toUtf8()); } } - return QQmlDelegateModel::stringValue(index, role); + return QQmlDelegateModel::variantValue(index, role); } class QQuickComboBoxPrivate : public QQuickControlPrivate @@ -235,6 +231,7 @@ public: void updateEditText(); void updateCurrentText(); + void updateCurrentValue(); void acceptInput(); QString tryComplete(const QString &inputText); @@ -278,6 +275,8 @@ public: QString textRole; QString currentText; QString displayText; + QString valueRole; + QVariant currentValue; QQuickItem *pressedItem = nullptr; QQmlInstanceModel *delegateModel = nullptr; QQmlComponent *delegate = nullptr; @@ -453,6 +452,17 @@ void QQuickComboBoxPrivate::updateCurrentText() 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::acceptInput() { Q_Q(QQuickComboBox); @@ -499,8 +509,10 @@ void QQuickComboBoxPrivate::setCurrentIndex(int index, Activation activate) currentIndex = index; emit q->currentIndexChanged(); - if (componentComplete) + if (componentComplete) { updateCurrentText(); + updateCurrentValue(); + } if (activate) emit q->activated(index); @@ -1008,6 +1020,35 @@ void QQuickComboBox::setTextRole(const QString &role) } /*! + \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. @@ -1445,6 +1486,60 @@ qreal QQuickComboBox::implicitIndicatorHeight() const } /*! + \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->delegateModel || index < 0 || index >= d->delegateModel->count()) + return QVariant(); + + // We use QVariant because the model API uses QVariant. + QVariant value; + QObject *object = d->delegateModel->object(index); + if (object) { + const QString role = d->valueRole.isEmpty() ? QStringLiteral("modelData") : d->valueRole; + value = d->delegateModel->variantValue(index, role); + d->delegateModel->release(object); + } + return value; +} + +/*! + \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; +} + +/*! \qmlmethod string QtQuick.Controls::ComboBox::textAt(int index) Returns the text for the specified \a index, or an empty string @@ -1728,10 +1823,12 @@ void QQuickComboBox::componentComplete() static_cast<QQmlDelegateModel *>(d->delegateModel)->componentComplete(); if (count() > 0) { - if (!d->hasCurrentIndex && d->currentIndex == -1) + if (!d->hasCurrentIndex && d->currentIndex == -1) { setCurrentIndex(0); - else + } else { d->updateCurrentText(); + d->updateCurrentValue(); + } } } diff --git a/src/quicktemplates2/qquickcombobox_p.h b/src/quicktemplates2/qquickcombobox_p.h index 75e535a9..a55541d4 100644 --- a/src/quicktemplates2/qquickcombobox_p.h +++ b/src/quicktemplates2/qquickcombobox_p.h @@ -86,6 +86,9 @@ class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickComboBox : public QQuickControl Q_PROPERTY(qreal implicitIndicatorWidth READ implicitIndicatorWidth NOTIFY implicitIndicatorWidthChanged FINAL REVISION 5) Q_PROPERTY(qreal implicitIndicatorHeight READ implicitIndicatorHeight NOTIFY implicitIndicatorHeightChanged FINAL REVISION 5) Q_CLASSINFO("DeferredPropertyNames", "background,contentItem,indicator,popup") + // 2.14 (Qt 5.14) + Q_PROPERTY(QVariant currentValue READ currentValue NOTIFY currentValueChanged FINAL REVISION 14) + Q_PROPERTY(QString valueRole READ valueRole WRITE setValueRole NOTIFY valueRoleChanged FINAL REVISION 14) public: explicit QQuickComboBox(QQuickItem *parent = nullptr); @@ -114,6 +117,9 @@ public: QString textRole() const; void setTextRole(const QString &role); + QString valueRole() const; + void setValueRole(const QString &role); + QQmlComponent *delegate() const; void setDelegate(QQmlComponent *delegate); @@ -155,6 +161,11 @@ public: qreal implicitIndicatorWidth() const; qreal implicitIndicatorHeight() const; + // 2.14 (Qt 5.14) + QVariant currentValue() const; + Q_INVOKABLE QVariant valueAt(int index) const; + Q_INVOKABLE int indexOfValue(const QVariant &value) const; + public Q_SLOTS: void incrementCurrentIndex(); void decrementCurrentIndex(); @@ -189,6 +200,9 @@ Q_SIGNALS: // 2.5 (Qt 5.12) Q_REVISION(5) void implicitIndicatorWidthChanged(); Q_REVISION(5) void implicitIndicatorHeightChanged(); + // 2.14 (Qt 5.14) + Q_REVISION(14) void valueRoleChanged(); + Q_REVISION(14) void currentValueChanged(); protected: bool eventFilter(QObject *object, QEvent *event) override; diff --git a/tests/auto/controls/data/tst_combobox.qml b/tests/auto/controls/data/tst_combobox.qml index 7f061a2b..7d65b698 100644 --- a/tests/auto/controls/data/tst_combobox.qml +++ b/tests/auto/controls/data/tst_combobox.qml @@ -351,9 +351,73 @@ TestCase { compare(control.find(data.term, data.flags), data.index) } + function test_valueRole_data() { + return [ + { tag: "ListModel", model: fruitmodel }, + { tag: "ObjectArray", model: fruitarray } + ] + } + + function test_valueRole(data) { + var control = createTemporaryObject(emptyBox, testCase, + { model: data.model, valueRole: "color" }) + verify(control) + compare(control.count, 3) + compare(control.currentIndex, 0) + compare(control.currentValue, "red") + + control.valueRole = "name" + compare(control.currentValue, "Apple") + + control.currentIndex = 1 + compare(control.currentIndex, 1) + compare(control.currentValue, "Orange") + + control.valueRole = "color" + compare(control.currentValue, "orange") + + control.model = null + compare(control.currentIndex, -1) + // An invalid QVariant is represented as undefined. + compare(control.currentValue, undefined) + + control.valueRole = "" + compare(control.currentValue, undefined) + } + + function test_valueAt() { + var control = createTemporaryObject(comboBox, testCase, + { model: fruitmodel, textRole: "name", valueRole: "color" }) + verify(control) + + compare(control.valueAt(0), "red") + compare(control.valueAt(1), "orange") + compare(control.valueAt(2), "yellow") + compare(control.valueAt(-1), undefined) + compare(control.valueAt(5), undefined) + } + + function test_indexOfValue_data() { + return [ + { tag: "red", expectedIndex: 0 }, + { tag: "orange", expectedIndex: 1 }, + { tag: "yellow", expectedIndex: 2 }, + { tag: "brown", expectedIndex: -1 }, + ] + } + + function test_indexOfValue(data) { + var control = createTemporaryObject(comboBox, testCase, + { model: fruitmodel, textRole: "name", valueRole: "color" }) + verify(control) + + compare(control.indexOfValue(data.tag), data.expectedIndex) + } + function test_arrowKeys() { - var control = createTemporaryObject(comboBox, testCase, {model: 3}) + var control = createTemporaryObject(comboBox, testCase, + { model: fruitmodel, textRole: "name", valueRole: "color" }) verify(control) var activatedSpy = signalSpy.createObject(control, {target: control, signalName: "activated"}) |