aboutsummaryrefslogtreecommitdiffstats
path: root/src/templates/qquickcombobox.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/templates/qquickcombobox.cpp')
-rw-r--r--src/templates/qquickcombobox.cpp795
1 files changed, 795 insertions, 0 deletions
diff --git a/src/templates/qquickcombobox.cpp b/src/templates/qquickcombobox.cpp
new file mode 100644
index 00000000..0f56567e
--- /dev/null
+++ b/src/templates/qquickcombobox.cpp
@@ -0,0 +1,795 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Labs Templates 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 "qquickpanel_p.h"
+
+#include <QtCore/qregexp.h>
+#include <QtQml/qjsvalue.h>
+#include <QtQml/qqmlcontext.h>
+#include <QtQml/private/qqmldelegatemodel_p.h>
+#include <QtQuick/private/qquickevents_p_p.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \qmltype ComboBox
+ \inherits Control
+ \instantiates QQuickComboBox
+ \inqmlmodule Qt.labs.controls
+ \ingroup qtlabscontrols-input
+ \brief A combo box control.
+
+ \image qtlabscontrols-combobox.png
+
+ 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 also other types
+ of \l {qml-data-models}{data models} are supported.
+
+ \code
+ ComboBox {
+ model: ["First", "Second", "Third"]
+ }
+ \endcode
+
+ 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.
+
+ \code
+ ComboBox {
+ textRole: "key"
+ model: ListModel {
+ ListElement { key: "First"; value: 123 }
+ ListElement { key: "Second"; value: 456 }
+ ListElement { key: "Third"; value: 789 }
+ }
+ }
+ \endcode
+
+ \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}
+*/
+
+/*!
+ \qmlsignal void Qt.labs.controls::ComboBox::activated(int index)
+
+ This signal is emitted when the item at \a index is activated by the user.
+
+ \sa currentIndex
+*/
+
+/*!
+ \qmlsignal void Qt.labs.controls::ComboBox::highlighted(int index)
+
+ This signal is emitted when the item at \a index in the popup list is highlighted by the user.
+
+ \sa highlightedIndex
+*/
+
+class QQuickComboBoxPrivate : public QQuickControlPrivate
+{
+ Q_DECLARE_PUBLIC(QQuickComboBox)
+
+public:
+ QQuickComboBoxPrivate() : pressed(false), ownModel(false), hasDisplayText(false),
+ hideTimer(0), highlightedIndex(-1), currentIndex(-1), delegateModel(Q_NULLPTR),
+ delegate(Q_NULLPTR), panel(Q_NULLPTR) { }
+
+ bool isPanelVisible() const;
+ void showPanel();
+ void hidePanel(bool accept);
+ void togglePanel(bool accept);
+
+ void pressedOutside();
+ void itemClicked();
+
+ void initItem(int index, QObject *object);
+ void countChanged();
+ void updateCurrentText();
+ void increase();
+ void decrease();
+ void setHighlightedIndex(int index);
+
+ void createDelegateModel();
+
+ bool pressed;
+ bool ownModel;
+ bool hasDisplayText;
+ int hideTimer;
+ int highlightedIndex;
+ int currentIndex;
+ QVariant model;
+ QString textRole;
+ QString currentText;
+ QString displayText;
+ QQuickItem *pressedItem;
+ QQmlInstanceModel *delegateModel;
+ QQmlComponent *delegate;
+ QQuickPanel *panel;
+};
+
+bool QQuickComboBoxPrivate::isPanelVisible() const
+{
+ return panel && panel->isVisible();
+}
+
+void QQuickComboBoxPrivate::showPanel()
+{
+ if (panel && !panel->isVisible())
+ panel->show();
+ setHighlightedIndex(currentIndex);
+}
+
+void QQuickComboBoxPrivate::hidePanel(bool accept)
+{
+ Q_Q(QQuickComboBox);
+ if (panel && panel->isVisible())
+ panel->hide();
+ if (accept) {
+ q->setCurrentIndex(highlightedIndex);
+ emit q->activated(currentIndex);
+ }
+ setHighlightedIndex(-1);
+}
+
+void QQuickComboBoxPrivate::togglePanel(bool accept)
+{
+ if (!panel)
+ return;
+
+ if (panel->isVisible())
+ hidePanel(accept);
+ else
+ showPanel();
+}
+
+void QQuickComboBoxPrivate::pressedOutside()
+{
+ Q_Q(QQuickComboBox);
+ if (hideTimer <= 0)
+ hideTimer = q->startTimer(0);
+}
+
+void QQuickComboBoxPrivate::itemClicked()
+{
+ Q_Q(QQuickComboBox);
+ int index = delegateModel->indexOf(q->sender(), Q_NULLPTR);
+ if (index != -1) {
+ setHighlightedIndex(index);
+ emit q->highlighted(index);
+ hidePanel(true);
+ }
+}
+
+void QQuickComboBoxPrivate::initItem(int index, QObject *object)
+{
+ QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(object);
+ if (button)
+ connect(button, &QQuickAbstractButton::clicked, this, &QQuickComboBoxPrivate::itemClicked);
+
+ if (index == currentIndex)
+ updateCurrentText();
+}
+
+void QQuickComboBoxPrivate::countChanged()
+{
+ Q_Q(QQuickComboBox);
+ if (q->count() == 0)
+ q->setCurrentIndex(-1);
+ emit q->countChanged();
+}
+
+void QQuickComboBoxPrivate::updateCurrentText()
+{
+ Q_Q(QQuickComboBox);
+ QString text = q->textAt(currentIndex);
+ if (currentText != text) {
+ currentText = text;
+ emit q->currentTextChanged();
+ }
+ if (!hasDisplayText && displayText != text) {
+ displayText = text;
+ emit q->displayTextChanged();
+ }
+}
+
+void QQuickComboBoxPrivate::increase()
+{
+ Q_Q(QQuickComboBox);
+ if (isPanelVisible()) {
+ if (highlightedIndex < q->count() - 1) {
+ setHighlightedIndex(highlightedIndex + 1);
+ emit q->highlighted(highlightedIndex);
+ }
+ } else {
+ if (currentIndex < q->count() - 1) {
+ q->setCurrentIndex(currentIndex + 1);
+ emit q->activated(currentIndex);
+ }
+ }
+}
+
+void QQuickComboBoxPrivate::decrease()
+{
+ Q_Q(QQuickComboBox);
+ if (isPanelVisible()) {
+ if (highlightedIndex > 0) {
+ setHighlightedIndex(highlightedIndex - 1);
+ emit q->highlighted(highlightedIndex);
+ }
+ } else {
+ if (currentIndex > 0) {
+ q->setCurrentIndex(currentIndex - 1);
+ emit q->activated(currentIndex);
+ }
+ }
+}
+
+void QQuickComboBoxPrivate::setHighlightedIndex(int index)
+{
+ Q_Q(QQuickComboBox);
+ if (highlightedIndex != index) {
+ highlightedIndex = index;
+ emit q->highlightedIndexChanged();
+ }
+}
+
+void QQuickComboBoxPrivate::createDelegateModel()
+{
+ Q_Q(QQuickComboBox);
+ if (delegateModel) {
+ if (ownModel) {
+ delete delegateModel;
+ } else {
+ disconnect(delegateModel, &QQmlInstanceModel::countChanged, this, &QQuickComboBoxPrivate::countChanged);
+ disconnect(delegateModel, &QQmlInstanceModel::modelUpdated, this, &QQuickComboBoxPrivate::updateCurrentText);
+ disconnect(delegateModel, &QQmlInstanceModel::initItem, this, &QQuickComboBoxPrivate::initItem);
+ }
+ }
+
+ ownModel = false;
+ delegateModel = model.value<QQmlInstanceModel *>();
+
+ if (!delegateModel && model.isValid()) {
+ QQmlDelegateModel *dataModel = new QQmlDelegateModel(qmlContext(q), 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::updateCurrentText);
+ connect(delegateModel, &QQmlInstanceModel::initItem, this, &QQuickComboBoxPrivate::initItem);
+ }
+
+ emit q->delegateModelChanged();
+}
+
+QQuickComboBox::QQuickComboBox(QQuickItem *parent) :
+ QQuickControl(*(new QQuickComboBoxPrivate), parent)
+{
+ setActiveFocusOnTab(true);
+ setFlag(QQuickItem::ItemIsFocusScope);
+ setAcceptedMouseButtons(Qt::LeftButton);
+}
+
+/*!
+ \readonly
+ \qmlproperty int Qt.labs.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 Qt.labs.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) {
+ d->model = model;
+ d->createDelegateModel();
+ if (isComponentComplete()) {
+ setCurrentIndex(count() > 0 ? 0 : -1);
+ d->updateCurrentText();
+ }
+ emit modelChanged();
+ }
+}
+
+/*!
+ \internal
+ \qmlproperty model Qt.labs.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 Qt.labs.controls::ComboBox::pressed
+
+ This property holds whether the combo box button is pressed.
+*/
+bool QQuickComboBox::isPressed() const
+{
+ Q_D(const QQuickComboBox);
+ return d->pressed;
+}
+
+void QQuickComboBox::setPressed(bool pressed)
+{
+ Q_D(QQuickComboBox);
+ if (d->pressed != pressed) {
+ d->pressed = pressed;
+ emit pressedChanged();
+ }
+}
+
+/*!
+ \qmlproperty int Qt.labs.controls::ComboBox::highlightedIndex
+
+ This property holds the index of the highlighted item in the combo box popup list.
+
+ \sa highlighted(), currentIndex
+*/
+int QQuickComboBox::highlightedIndex() const
+{
+ Q_D(const QQuickComboBox);
+ return d->highlightedIndex;
+}
+
+/*!
+ \qmlproperty int Qt.labs.controls::ComboBox::currentIndex
+
+ This property holds the index of the current item in the combo box.
+
+ \sa activated(), currentText
+*/
+int QQuickComboBox::currentIndex() const
+{
+ Q_D(const QQuickComboBox);
+ return d->currentIndex;
+}
+
+void QQuickComboBox::setCurrentIndex(int index)
+{
+ Q_D(QQuickComboBox);
+ if (d->currentIndex != index) {
+ d->currentIndex = index;
+ emit currentIndexChanged();
+ if (isComponentComplete())
+ d->updateCurrentText();
+ }
+}
+
+/*!
+ \readonly
+ \qmlproperty string Qt.labs.controls::ComboBox::currentText
+
+ This property holds the text of the current item in the combo box.
+
+ \sa currentIndex, displayText, textRole
+*/
+QString QQuickComboBox::currentText() const
+{
+ Q_D(const QQuickComboBox);
+ return d->currentText;
+}
+
+/*!
+ \qmlproperty string Qt.labs.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) {
+ d->displayText = text;
+ emit displayTextChanged();
+ }
+}
+
+void QQuickComboBox::resetDisplayText()
+{
+ Q_D(QQuickComboBox);
+ if (d->hasDisplayText) {
+ d->hasDisplayText = false;
+ d->updateCurrentText();
+ }
+}
+
+/*!
+ \qmlproperty string Qt.labs.controls::ComboBox::textRole
+
+ This property holds the model role used for populating the combo box.
+
+ \sa model, currentText, displayText
+*/
+QString QQuickComboBox::textRole() const
+{
+ Q_D(const QQuickComboBox);
+ return d->textRole;
+}
+
+void QQuickComboBox::setTextRole(const QString &role)
+{
+ Q_D(QQuickComboBox);
+ if (d->textRole != role) {
+ d->textRole = role;
+ if (isComponentComplete())
+ d->updateCurrentText();
+ emit textRoleChanged();
+ }
+}
+
+/*!
+ \qmlproperty Component Qt.labs.controls::ComboBox::delegate
+
+ This property holds a delegate that presents an item in the combo box popup.
+
+ \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) {
+ delete d->delegate;
+ d->delegate = delegate;
+ QQmlDelegateModel *delegateModel = qobject_cast<QQmlDelegateModel*>(d->delegateModel);
+ if (delegateModel)
+ delegateModel->setDelegate(d->delegate);
+ emit delegateChanged();
+ }
+}
+
+/*!
+ \qmlproperty Panel Qt.labs.controls::ComboBox::panel
+
+ This property holds the popup panel.
+
+ \sa {Customizing ComboBox}
+*/
+QQuickPanel *QQuickComboBox::panel() const
+{
+ Q_D(const QQuickComboBox);
+ return d->panel;
+}
+
+void QQuickComboBox::setPanel(QQuickPanel *panel)
+{
+ Q_D(QQuickComboBox);
+ if (d->panel != panel) {
+ delete d->panel;
+ if (panel)
+ QObjectPrivate::connect(panel, &QQuickPanel::pressedOutside, d, &QQuickComboBoxPrivate::pressedOutside);
+ d->panel = panel;
+ emit panelChanged();
+ }
+}
+
+/*!
+ \qmlmethod string Qt.labs.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->delegateModel || index < 0 || index >= d->delegateModel->count())
+ return QString();
+ return d->delegateModel->stringValue(index, d->textRole.isEmpty() ? QStringLiteral("modelData") : d->textRole);
+}
+
+/*!
+ \qmlmethod int Qt.labs.controls::ComboBox::find(string text, flags = Qt.MatchExactly)
+
+ 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
+{
+ int itemCount = count();
+ uint matchType = flags & 0x0F;
+ Qt::CaseSensitivity cs = flags & Qt::MatchCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive;
+
+ for (int idx = 0; idx < itemCount; ++idx) {
+ QString t = textAt(idx);
+ switch (matchType) {
+ case Qt::MatchExactly:
+ if (t == text)
+ return idx;
+ break;
+ case Qt::MatchRegExp:
+ if (QRegExp(text, cs).exactMatch(t))
+ return idx;
+ break;
+ case Qt::MatchWildcard:
+ if (QRegExp(text, cs, QRegExp::Wildcard).exactMatch(t))
+ 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;
+ }
+ }
+ return -1;
+}
+
+void QQuickComboBox::focusOutEvent(QFocusEvent *event)
+{
+ Q_D(QQuickComboBox);
+ QQuickItem::focusOutEvent(event);
+ d->hidePanel(false);
+ setPressed(false);
+}
+
+void QQuickComboBox::keyPressEvent(QKeyEvent *event)
+{
+ Q_D(QQuickComboBox);
+ QQuickControl::keyPressEvent(event);
+ if (!d->panel)
+ return;
+
+ switch (event->key()) {
+ case Qt::Key_Space:
+ if (!event->isAutoRepeat())
+ setPressed(true);
+ event->accept();
+ break;
+ case Qt::Key_Enter:
+ case Qt::Key_Return:
+ if (d->isPanelVisible())
+ setPressed(true);
+ event->accept();
+ break;
+ case Qt::Key_Up:
+ d->decrease();
+ event->accept();
+ break;
+ case Qt::Key_Down:
+ d->increase();
+ event->accept();
+ break;
+ case Qt::Key_Escape:
+ event->accept();
+ default:
+ break;
+ }
+}
+
+void QQuickComboBox::keyReleaseEvent(QKeyEvent *event)
+{
+ Q_D(QQuickComboBox);
+ QQuickControl::keyReleaseEvent(event);
+ if (!d->panel || event->isAutoRepeat())
+ return;
+
+ switch (event->key()) {
+ case Qt::Key_Space:
+ d->togglePanel(true);
+ setPressed(false);
+ event->accept();
+ break;
+ case Qt::Key_Enter:
+ case Qt::Key_Return:
+ d->hidePanel(true);
+ setPressed(false);
+ event->accept();
+ break;
+ case Qt::Key_Escape:
+ d->hidePanel(false);
+ setPressed(false);
+ event->accept();
+ break;
+ default:
+ break;
+ }
+}
+
+void QQuickComboBox::mousePressEvent(QMouseEvent *event)
+{
+ QQuickControl::mousePressEvent(event);
+ setPressed(true);
+}
+
+void QQuickComboBox::mouseMoveEvent(QMouseEvent* event)
+{
+ QQuickControl::mouseMoveEvent(event);
+ setPressed(contains(event->pos()));
+}
+
+void QQuickComboBox::mouseReleaseEvent(QMouseEvent *event)
+{
+ Q_D(QQuickComboBox);
+ QQuickControl::mouseReleaseEvent(event);
+ if (d->pressed) {
+ setPressed(false);
+ if (!d->isPanelVisible())
+ forceActiveFocus(Qt::MouseFocusReason);
+ d->togglePanel(false);
+ }
+}
+
+void QQuickComboBox::mouseUngrabEvent()
+{
+ QQuickControl::mouseUngrabEvent();
+ setPressed(false);
+}
+
+void QQuickComboBox::timerEvent(QTimerEvent *event)
+{
+ Q_D(QQuickComboBox);
+ QQuickControl::timerEvent(event);
+ if (event->timerId() == d->hideTimer) {
+ killTimer(d->hideTimer);
+ d->hideTimer = 0;
+ if (!d->pressed)
+ d->hidePanel(false);
+ }
+}
+
+void QQuickComboBox::componentComplete()
+{
+ Q_D(QQuickComboBox);
+ QQuickControl::componentComplete();
+
+ if (d->delegateModel && d->ownModel)
+ static_cast<QQmlDelegateModel *>(d->delegateModel)->componentComplete();
+
+ if (count() > 0) {
+ if (d->currentIndex == -1)
+ setCurrentIndex(0);
+ else
+ d->updateCurrentText();
+ }
+}
+
+QT_END_NAMESPACE