From 4ecb85d4468782378c091e42d5733a976a3e0307 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 26 Nov 2015 18:02:15 +0100 Subject: Add ComboBox Change-Id: I4cfc2367db92786097a1ce66bd4b5a2f71322a2e Reviewed-by: Mitch Curtis --- src/imports/controls/ComboBox.qml | 133 ++++ src/imports/controls/controls.pri | 1 + .../images/qtlabscontrols-combobox-background.png | Bin 0 -> 2903 bytes .../images/qtlabscontrols-combobox-contentItem.png | Bin 0 -> 2913 bytes .../images/qtlabscontrols-combobox-delegate.png | Bin 0 -> 5012 bytes .../doc/images/qtlabscontrols-combobox-panel.png | Bin 0 -> 4969 bytes .../doc/images/qtlabscontrols-combobox.png | Bin 0 -> 4993 bytes .../qtlabscontrols-combobox-background.qml | 38 + .../qtlabscontrols-combobox-contentItem.qml | 38 + .../snippets/qtlabscontrols-combobox-delegate.qml | 54 ++ .../doc/snippets/qtlabscontrols-combobox-panel.qml | 47 ++ .../doc/snippets/qtlabscontrols-combobox.qml | 43 ++ .../controls/doc/src/qtlabscontrols-customize.qdoc | 32 + .../controls/doc/src/qtlabscontrols-index.qdoc | 3 + .../controls/doc/src/qtlabscontrols-input.qdoc | 6 + src/imports/controls/images/drop-indicator.png | Bin 0 -> 2873 bytes src/imports/controls/images/drop-indicator@2x.png | Bin 0 -> 2942 bytes src/imports/controls/images/drop-indicator@3x.png | Bin 0 -> 3013 bytes src/imports/controls/images/drop-indicator@4x.png | Bin 0 -> 3125 bytes src/imports/controls/material/ComboBox.qml | 154 ++++ .../controls/material/images/drop-indicator.png | Bin 0 -> 342 bytes .../controls/material/images/drop-indicator@2x.png | Bin 0 -> 273 bytes .../controls/material/images/drop-indicator@3x.png | Bin 0 -> 406 bytes .../controls/material/images/drop-indicator@4x.png | Bin 0 -> 520 bytes src/imports/controls/material/material.pri | 1 + .../controls/material/qquickmaterialstyle.cpp | 5 + .../controls/material/qquickmaterialstyle_p.h | 2 + .../material/qtlabsmaterialstyleplugin.qrc | 4 + src/imports/controls/qtlabscontrolsplugin.cpp | 1 + src/imports/controls/qtlabscontrolsplugin.qrc | 4 + src/imports/controls/universal/ComboBox.qml | 142 ++++ .../controls/universal/images/downarrow.png | Bin 0 -> 200 bytes .../controls/universal/images/downarrow@2x.png | Bin 0 -> 263 bytes .../controls/universal/images/downarrow@3x.png | Bin 0 -> 329 bytes .../controls/universal/images/downarrow@4x.png | Bin 0 -> 358 bytes .../universal/qtlabsuniversalstyleplugin.qrc | 4 + src/imports/controls/universal/universal.pri | 1 + src/imports/templates/qtlabstemplatesplugin.cpp | 2 + src/templates/qquickcombobox.cpp | 795 +++++++++++++++++++++ src/templates/qquickcombobox_p.h | 146 ++++ src/templates/templates.pri | 2 + tests/auto/controls/data/tst_combobox.qml | 553 ++++++++++++++ tests/auto/sanity/BLACKLIST | 6 + tests/manual/testbench/main.qml | 34 + 44 files changed, 2251 insertions(+) create mode 100644 src/imports/controls/ComboBox.qml create mode 100644 src/imports/controls/doc/images/qtlabscontrols-combobox-background.png create mode 100644 src/imports/controls/doc/images/qtlabscontrols-combobox-contentItem.png create mode 100644 src/imports/controls/doc/images/qtlabscontrols-combobox-delegate.png create mode 100644 src/imports/controls/doc/images/qtlabscontrols-combobox-panel.png create mode 100644 src/imports/controls/doc/images/qtlabscontrols-combobox.png create mode 100644 src/imports/controls/doc/snippets/qtlabscontrols-combobox-background.qml create mode 100644 src/imports/controls/doc/snippets/qtlabscontrols-combobox-contentItem.qml create mode 100644 src/imports/controls/doc/snippets/qtlabscontrols-combobox-delegate.qml create mode 100644 src/imports/controls/doc/snippets/qtlabscontrols-combobox-panel.qml create mode 100644 src/imports/controls/doc/snippets/qtlabscontrols-combobox.qml create mode 100644 src/imports/controls/images/drop-indicator.png create mode 100644 src/imports/controls/images/drop-indicator@2x.png create mode 100644 src/imports/controls/images/drop-indicator@3x.png create mode 100644 src/imports/controls/images/drop-indicator@4x.png create mode 100644 src/imports/controls/material/ComboBox.qml create mode 100644 src/imports/controls/material/images/drop-indicator.png create mode 100644 src/imports/controls/material/images/drop-indicator@2x.png create mode 100644 src/imports/controls/material/images/drop-indicator@3x.png create mode 100644 src/imports/controls/material/images/drop-indicator@4x.png create mode 100644 src/imports/controls/universal/ComboBox.qml create mode 100644 src/imports/controls/universal/images/downarrow.png create mode 100644 src/imports/controls/universal/images/downarrow@2x.png create mode 100644 src/imports/controls/universal/images/downarrow@3x.png create mode 100644 src/imports/controls/universal/images/downarrow@4x.png create mode 100644 src/templates/qquickcombobox.cpp create mode 100644 src/templates/qquickcombobox_p.h create mode 100644 tests/auto/controls/data/tst_combobox.qml diff --git a/src/imports/controls/ComboBox.qml b/src/imports/controls/ComboBox.qml new file mode 100644 index 00000000..7f2adcb0 --- /dev/null +++ b/src/imports/controls/ComboBox.qml @@ -0,0 +1,133 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Labs Controls 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$ +** +****************************************************************************/ + +import QtQuick 2.6 +import QtQuick.Window 2.2 +import Qt.labs.templates 1.0 as T + +T.ComboBox { + id: control + + implicitWidth: Math.max(background ? background.implicitWidth : 0, + contentItem.implicitWidth + leftPadding + rightPadding) + implicitHeight: Math.max(background ? background.implicitHeight : 0, + contentItem.implicitHeight + topPadding + bottomPadding) + baselineOffset: contentItem.y + contentItem.baselineOffset + + spacing: 8 + padding: 6 + leftPadding: 8 + rightPadding: 8 + + //! [delegate] + delegate: ItemDelegate { + width: control.width + text: control.textRole ? model[control.textRole] : modelData + checkable: true + autoExclusive: true + checked: control.currentIndex === index + highlighted: control.highlightedIndex === index + pressed: highlighted && control.pressed + } + //! [delegate] + + //! [contentItem] + contentItem: Text { + text: control.displayText + font: control.font + color: "#ffffff" + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + rightPadding: 18 + control.spacing + } + //! [contentItem] + + //! [background] + background: Item { + implicitWidth: 120 + implicitHeight: 40 + + Rectangle { + width: parent.width + height: parent.height + opacity: control.enabled ? 1.0 : 0.2 + color: control.pressed || panel.visible ? "#585A5C" : "#353637" + } + + Image { + x: parent.width - width - control.rightPadding + y: (parent.height - height) / 2 + source: "qrc:/images/drop-indicator.png" + } + } + //! [background] + + //! [panel] + panel: T.Panel { + id: popup + contentItem: Rectangle { + // TODO: Panel::anchors + readonly property var above: popup.visible ? control.mapToItem(null, 0, -height + 1) : Qt.point(0, 0) + readonly property var below: popup.visible ? control.mapToItem(null, 0, control.height - 1) : Qt.point(0, 0) + + x: below.x + y: above.y >= 0 && below.y + height > control.Window.height ? above.y : below.y + width: control.width + height: listview.height + + ListView { + id: listview + width: control.width + height: Math.min(200, contentHeight) + + clip: true + model: control.delegateModel + currentIndex: control.highlightedIndex + +// ScrollIndicator.vertical: ScrollIndicator { } + } + + Rectangle { + width: parent.width + height: parent.height + color: "transparent" + border.color: "#353637" + } + } + } + //! [panel] +} diff --git a/src/imports/controls/controls.pri b/src/imports/controls/controls.pri index 78c9a1d0..63e3373e 100644 --- a/src/imports/controls/controls.pri +++ b/src/imports/controls/controls.pri @@ -3,6 +3,7 @@ QML_FILES = \ BusyIndicator.qml \ Button.qml \ CheckBox.qml \ + ComboBox.qml \ Dial.qml \ Drawer.qml \ Frame.qml \ diff --git a/src/imports/controls/doc/images/qtlabscontrols-combobox-background.png b/src/imports/controls/doc/images/qtlabscontrols-combobox-background.png new file mode 100644 index 00000000..fa296270 Binary files /dev/null and b/src/imports/controls/doc/images/qtlabscontrols-combobox-background.png differ diff --git a/src/imports/controls/doc/images/qtlabscontrols-combobox-contentItem.png b/src/imports/controls/doc/images/qtlabscontrols-combobox-contentItem.png new file mode 100644 index 00000000..9b8e8175 Binary files /dev/null and b/src/imports/controls/doc/images/qtlabscontrols-combobox-contentItem.png differ diff --git a/src/imports/controls/doc/images/qtlabscontrols-combobox-delegate.png b/src/imports/controls/doc/images/qtlabscontrols-combobox-delegate.png new file mode 100644 index 00000000..88462185 Binary files /dev/null and b/src/imports/controls/doc/images/qtlabscontrols-combobox-delegate.png differ diff --git a/src/imports/controls/doc/images/qtlabscontrols-combobox-panel.png b/src/imports/controls/doc/images/qtlabscontrols-combobox-panel.png new file mode 100644 index 00000000..0e9eacbf Binary files /dev/null and b/src/imports/controls/doc/images/qtlabscontrols-combobox-panel.png differ diff --git a/src/imports/controls/doc/images/qtlabscontrols-combobox.png b/src/imports/controls/doc/images/qtlabscontrols-combobox.png new file mode 100644 index 00000000..69fc9c95 Binary files /dev/null and b/src/imports/controls/doc/images/qtlabscontrols-combobox.png differ diff --git a/src/imports/controls/doc/snippets/qtlabscontrols-combobox-background.qml b/src/imports/controls/doc/snippets/qtlabscontrols-combobox-background.qml new file mode 100644 index 00000000..d6715f07 --- /dev/null +++ b/src/imports/controls/doc/snippets/qtlabscontrols-combobox-background.qml @@ -0,0 +1,38 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import Qt.labs.controls 1.0 + +ComboBox { + model: ["ComboBox"] + Rectangle { + anchors.fill: background + color: 'transparent' + border.color: 'red' + } +} diff --git a/src/imports/controls/doc/snippets/qtlabscontrols-combobox-contentItem.qml b/src/imports/controls/doc/snippets/qtlabscontrols-combobox-contentItem.qml new file mode 100644 index 00000000..08523088 --- /dev/null +++ b/src/imports/controls/doc/snippets/qtlabscontrols-combobox-contentItem.qml @@ -0,0 +1,38 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import Qt.labs.controls 1.0 + +ComboBox { + model: ["ComboBox"] + Rectangle { + anchors.fill: contentItem + color: 'transparent' + border.color: 'red' + } +} diff --git a/src/imports/controls/doc/snippets/qtlabscontrols-combobox-delegate.qml b/src/imports/controls/doc/snippets/qtlabscontrols-combobox-delegate.qml new file mode 100644 index 00000000..47c4f606 --- /dev/null +++ b/src/imports/controls/doc/snippets/qtlabscontrols-combobox-delegate.qml @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import Qt.labs.controls 1.0 + +Item { + id: root + width: combo.width + height: combo.height + combo.panel.contentItem.height + ComboBox { + id: combo + model: ["First", "Second", "Third"] + panel.contentItem.parent: root + panel.contentItem.y: combo.height + delegate: ItemDelegate { + width: combo.width + text: modelData + checkable: true + autoExclusive: true + checked: combo.currentIndex === index + Rectangle { + visible: index === 0 + anchors.fill: parent + color: 'transparent' + border.color: 'red' + } + } + } +} diff --git a/src/imports/controls/doc/snippets/qtlabscontrols-combobox-panel.qml b/src/imports/controls/doc/snippets/qtlabscontrols-combobox-panel.qml new file mode 100644 index 00000000..d1788d55 --- /dev/null +++ b/src/imports/controls/doc/snippets/qtlabscontrols-combobox-panel.qml @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import Qt.labs.controls 1.0 + +Item { + id: root + width: combo.width + height: combo.height + combo.panel.contentItem.height + ComboBox { + id: combo + model: ["First", "Second", "Third"] + panel.contentItem.parent: root + panel.contentItem.y: combo.height + } + Rectangle { + parent: combo.panel.contentItem + anchors.fill: parent + color: 'transparent' + border.color: 'red' + } +} diff --git a/src/imports/controls/doc/snippets/qtlabscontrols-combobox.qml b/src/imports/controls/doc/snippets/qtlabscontrols-combobox.qml new file mode 100644 index 00000000..bac6136d --- /dev/null +++ b/src/imports/controls/doc/snippets/qtlabscontrols-combobox.qml @@ -0,0 +1,43 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import Qt.labs.controls 1.0 + +Item { + id: root + width: combo.width + height: combo.height + combo.panel.contentItem.height + //! [1] + ComboBox { + id: combo + model: ["First", "Second", "Third"] + panel.contentItem.parent: root + panel.contentItem.y: combo.height + } + //! [1] +} diff --git a/src/imports/controls/doc/src/qtlabscontrols-customize.qdoc b/src/imports/controls/doc/src/qtlabscontrols-customize.qdoc index a7bd7d8b..eb1dc71d 100644 --- a/src/imports/controls/doc/src/qtlabscontrols-customize.qdoc +++ b/src/imports/controls/doc/src/qtlabscontrols-customize.qdoc @@ -95,6 +95,38 @@ \snippet CheckBox.qml indicator + + \section1 Customizing ComboBox + + ComboBox consists of \l {Control::background}{background}, + \l {Control::contentItem}{content item}, \l {ComboBox::panel}{panel}, + and \l {ComboBox::delegate}{delegate}. + + \section3 Background + + \image qtlabscontrols-combobox-background.png + + \snippet ComboBox.qml background + + \section3 Content item + + \image qtlabscontrols-combobox-contentItem.png + + \snippet ComboBox.qml contentItem + + \section3 Panel + + \image qtlabscontrols-combobox-panel.png + + \snippet ComboBox.qml panel + + \section3 Delegate + + \image qtlabscontrols-combobox-delegate.png + + \snippet ComboBox.qml delegate + + \section1 Customizing Dial Dial consists of two visual items: \l {Control::background}{background} diff --git a/src/imports/controls/doc/src/qtlabscontrols-index.qdoc b/src/imports/controls/doc/src/qtlabscontrols-index.qdoc index 936e65e8..6e486ff0 100644 --- a/src/imports/controls/doc/src/qtlabscontrols-index.qdoc +++ b/src/imports/controls/doc/src/qtlabscontrols-index.qdoc @@ -218,6 +218,9 @@ \row \li \l [QtQuickControls] {CheckBox} \li \l [QtLabsControls] {CheckBox} + \row + \li \l [QtQuickControls] {ComboBox} + \li \l [QtLabsControls] {ComboBox} \row \li \l [QtQuickControls] {ExclusiveGroup} \li \l [QtLabsControls] {ButtonGroup} diff --git a/src/imports/controls/doc/src/qtlabscontrols-input.qdoc b/src/imports/controls/doc/src/qtlabscontrols-input.qdoc index 130ca83d..06c0b40d 100644 --- a/src/imports/controls/doc/src/qtlabscontrols-input.qdoc +++ b/src/imports/controls/doc/src/qtlabscontrols-input.qdoc @@ -38,6 +38,12 @@ following sections offer guidelines for choosing the appropriate type of input control, depending on the use case. + \section1 ComboBox Control + + \image qtlabscontrols-combobox.png + + \l ComboBox is used to select a value from a drop-down list. + \section1 Dial Control \image qtlabscontrols-dial.png diff --git a/src/imports/controls/images/drop-indicator.png b/src/imports/controls/images/drop-indicator.png new file mode 100644 index 00000000..77359311 Binary files /dev/null and b/src/imports/controls/images/drop-indicator.png differ diff --git a/src/imports/controls/images/drop-indicator@2x.png b/src/imports/controls/images/drop-indicator@2x.png new file mode 100644 index 00000000..54d23b4b Binary files /dev/null and b/src/imports/controls/images/drop-indicator@2x.png differ diff --git a/src/imports/controls/images/drop-indicator@3x.png b/src/imports/controls/images/drop-indicator@3x.png new file mode 100644 index 00000000..22c6d010 Binary files /dev/null and b/src/imports/controls/images/drop-indicator@3x.png differ diff --git a/src/imports/controls/images/drop-indicator@4x.png b/src/imports/controls/images/drop-indicator@4x.png new file mode 100644 index 00000000..7ab1eb86 Binary files /dev/null and b/src/imports/controls/images/drop-indicator@4x.png differ diff --git a/src/imports/controls/material/ComboBox.qml b/src/imports/controls/material/ComboBox.qml new file mode 100644 index 00000000..035863f6 --- /dev/null +++ b/src/imports/controls/material/ComboBox.qml @@ -0,0 +1,154 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Labs Controls 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$ +** +****************************************************************************/ + +import QtQuick 2.6 +import QtQuick.Window 2.2 +import Qt.labs.templates 1.0 as T +import Qt.labs.controls.material 1.0 +import QtGraphicalEffects 1.0 + +T.ComboBox { + id: control + + implicitWidth: Math.max(background ? background.implicitWidth : 0, + contentItem.implicitWidth + leftPadding + rightPadding) + implicitHeight: Math.max(background ? background.implicitHeight : 0, + contentItem.implicitHeight + topPadding + bottomPadding) + baselineOffset: contentItem.y + contentItem.baselineOffset + + spacing: 6 + padding: 12 + + //! [delegate] + delegate: ItemDelegate { + width: control.width + text: control.textRole ? model[control.textRole] : modelData + highlighted: control.highlightedIndex === index + pressed: highlighted && control.pressed + } + //! [delegate] + + //! [contentItem] + contentItem: Text { + text: control.displayText + font: control.font + color: control.enabled ? control.Material.primaryTextColor : control.Material.hintTextColor + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + rightPadding: 14 + control.spacing + } + //! [contentItem] + + //! [background] + background: Rectangle { + implicitWidth: 120 + implicitHeight: 32 + + radius: 2 + color: control.Material.comboBoxColor + + Behavior on color { + ColorAnimation { + duration: 400 + } + } + + layer.enabled: control.enabled + layer.effect: DropShadow { + verticalOffset: 1 + color: control.Material.dropShadowColor + samples: control.pressed ? 15 : 9 + spread: 0.5 + + // TODO: Doesn't work because of QTBUG-49072 + Behavior on radius { + NumberAnimation { duration: 1000 } + } + } + + Image { + x: parent.width - width - control.rightPadding + y: (parent.height - height) / 2 + opacity: !control.enabled ? 0.5 : 1.0 + source: "qrc:/qt-project.org/imports/Qt/labs/controls/material/images/drop-indicator.png" + } + } + //! [background] + + //! [panel] + panel: T.Panel { + id: popup + contentItem: Rectangle { + // TODO: Panel::anchors + readonly property var above: popup.visible ? control.mapToItem(null, 0, -height) : Qt.point(0, 0) + readonly property var below: popup.visible ? control.mapToItem(null, 0, control.height) : Qt.point(0, 0) + + x: below.x + y: above.y >= 0 && below.y + height > control.Window.height ? above.y : below.y + width: control.width + height: listview.height + + color: control.Material.comboBoxColor + + layer.enabled: control.enabled + layer.effect: DropShadow { + verticalOffset: 1 + color: control.Material.dropShadowColor + samples: 15 + spread: 0.5 + + // TODO: Doesn't work because of QTBUG-49072 + Behavior on radius { + NumberAnimation { duration: 1000 } + } + } + + ListView { + id: listview + width: control.width + height: Math.min(200, contentHeight) // TODO: 396 + + clip: true + model: control.delegateModel + currentIndex: control.highlightedIndex + +// ScrollIndicator.vertical: ScrollIndicator { } + } + } + } + //! [panel] +} diff --git a/src/imports/controls/material/images/drop-indicator.png b/src/imports/controls/material/images/drop-indicator.png new file mode 100644 index 00000000..b4396ec6 Binary files /dev/null and b/src/imports/controls/material/images/drop-indicator.png differ diff --git a/src/imports/controls/material/images/drop-indicator@2x.png b/src/imports/controls/material/images/drop-indicator@2x.png new file mode 100644 index 00000000..1c711bc2 Binary files /dev/null and b/src/imports/controls/material/images/drop-indicator@2x.png differ diff --git a/src/imports/controls/material/images/drop-indicator@3x.png b/src/imports/controls/material/images/drop-indicator@3x.png new file mode 100644 index 00000000..06dd8bd2 Binary files /dev/null and b/src/imports/controls/material/images/drop-indicator@3x.png differ diff --git a/src/imports/controls/material/images/drop-indicator@4x.png b/src/imports/controls/material/images/drop-indicator@4x.png new file mode 100644 index 00000000..b2157c30 Binary files /dev/null and b/src/imports/controls/material/images/drop-indicator@4x.png differ diff --git a/src/imports/controls/material/material.pri b/src/imports/controls/material/material.pri index a238dcfe..1c71b9fa 100644 --- a/src/imports/controls/material/material.pri +++ b/src/imports/controls/material/material.pri @@ -13,6 +13,7 @@ QML_FILES += \ $$PWD/BusyIndicator.qml \ $$PWD/Button.qml \ $$PWD/CheckBox.qml \ + $$PWD/ComboBox.qml \ $$PWD/Dial.qml \ $$PWD/Drawer.qml \ $$PWD/Frame.qml \ diff --git a/src/imports/controls/material/qquickmaterialstyle.cpp b/src/imports/controls/material/qquickmaterialstyle.cpp index a3d2329a..bce5db21 100644 --- a/src/imports/controls/material/qquickmaterialstyle.cpp +++ b/src/imports/controls/material/qquickmaterialstyle.cpp @@ -724,6 +724,11 @@ QColor QQuickMaterialStyle::drawerBackgroundColor() const return dividerTextColorLight; } +QColor QQuickMaterialStyle::comboBoxColor() const +{ + return m_theme == Light ? "#ffffff" : backgroundColorDark; +} + QColor QQuickMaterialStyle::color(QQuickMaterialStyle::Color color, QQuickMaterialStyle::Shade shade) const { int count = sizeof(colors) / sizeof(colors[0]); diff --git a/src/imports/controls/material/qquickmaterialstyle_p.h b/src/imports/controls/material/qquickmaterialstyle_p.h index 69891b9c..e657fd57 100644 --- a/src/imports/controls/material/qquickmaterialstyle_p.h +++ b/src/imports/controls/material/qquickmaterialstyle_p.h @@ -92,6 +92,7 @@ class QQuickMaterialStyle : public QQuickStyle Q_PROPERTY(QColor scrollBarColor READ scrollBarColor NOTIFY paletteChanged FINAL) Q_PROPERTY(QColor scrollBarPressedColor READ scrollBarPressedColor NOTIFY paletteChanged FINAL) Q_PROPERTY(QColor drawerBackgroundColor READ drawerBackgroundColor NOTIFY paletteChanged FINAL) + Q_PROPERTY(QColor comboBoxColor READ comboBoxColor NOTIFY paletteChanged FINAL) public: enum Theme { @@ -199,6 +200,7 @@ public: QColor scrollBarColor() const; QColor scrollBarPressedColor() const; QColor drawerBackgroundColor() const; + QColor comboBoxColor() const; Q_INVOKABLE QColor color(Color color, Shade shade) const; diff --git a/src/imports/controls/material/qtlabsmaterialstyleplugin.qrc b/src/imports/controls/material/qtlabsmaterialstyleplugin.qrc index 6c7b65f1..8398d505 100644 --- a/src/imports/controls/material/qtlabsmaterialstyleplugin.qrc +++ b/src/imports/controls/material/qtlabsmaterialstyleplugin.qrc @@ -4,5 +4,9 @@ images/check@2x.png images/check@3x.png images/check@4x.png + images/drop-indicator.png + images/drop-indicator@2x.png + images/drop-indicator@3x.png + images/drop-indicator@4x.png diff --git a/src/imports/controls/qtlabscontrolsplugin.cpp b/src/imports/controls/qtlabscontrolsplugin.cpp index 079d843d..2d71284e 100644 --- a/src/imports/controls/qtlabscontrolsplugin.cpp +++ b/src/imports/controls/qtlabscontrolsplugin.cpp @@ -71,6 +71,7 @@ void QtLabsControlsPlugin::registerTypes(const char *uri) qmlRegisterType(selector->select(QStringLiteral("/BusyIndicator.qml")), uri, 1, 0, "BusyIndicator"); qmlRegisterType(selector->select(QStringLiteral("/Button.qml")), uri, 1, 0, "Button"); qmlRegisterType(selector->select(QStringLiteral("/CheckBox.qml")), uri, 1, 0, "CheckBox"); + qmlRegisterType(selector->select(QStringLiteral("/ComboBox.qml")), uri, 1, 0, "ComboBox"); qmlRegisterType(selector->select(QStringLiteral("/Dial.qml")), uri, 1, 0, "Dial"); qmlRegisterType(selector->select(QStringLiteral("/Drawer.qml")), uri, 1, 0, "Drawer"); qmlRegisterType(selector->select(QStringLiteral("/Frame.qml")), uri, 1, 0, "Frame"); diff --git a/src/imports/controls/qtlabscontrolsplugin.qrc b/src/imports/controls/qtlabscontrolsplugin.qrc index c54083ed..bebbe570 100644 --- a/src/imports/controls/qtlabscontrolsplugin.qrc +++ b/src/imports/controls/qtlabscontrolsplugin.qrc @@ -11,5 +11,9 @@ images/dial-indicator@2x.png images/dial-indicator@3x.png images/dial-indicator@4x.png + images/drop-indicator.png + images/drop-indicator@2x.png + images/drop-indicator@3x.png + images/drop-indicator@4x.png diff --git a/src/imports/controls/universal/ComboBox.qml b/src/imports/controls/universal/ComboBox.qml new file mode 100644 index 00000000..0fdcb894 --- /dev/null +++ b/src/imports/controls/universal/ComboBox.qml @@ -0,0 +1,142 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Labs Controls 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$ +** +****************************************************************************/ + +import QtQuick 2.6 +import QtQuick.Window 2.2 +import Qt.labs.templates 1.0 as T +import Qt.labs.controls.universal 1.0 + +T.ComboBox { + id: control + + implicitWidth: Math.max(background ? background.implicitWidth : 0, + contentItem.implicitWidth + leftPadding + rightPadding) + implicitHeight: Math.max(background ? background.implicitHeight : 0, + contentItem.implicitHeight + topPadding + bottomPadding) + baselineOffset: contentItem.y + contentItem.baselineOffset + + spacing: 10 + topPadding: 5 + leftPadding: 12 + rightPadding: 10 + bottomPadding: 7 + + font.pixelSize: Universal.fontSize + font.family: Universal.fontFamily + + //! [delegate] + delegate: ItemDelegate { + width: control.width + text: control.textRole ? model[control.textRole] : modelData + highlighted: control.highlightedIndex === index + pressed: highlighted && control.pressed + } + //! [delegate] + + //! [contentItem] + contentItem: Text { + text: control.displayText + font: control.font + color: !control.enabled ? control.Universal.baseLowColor : control.Universal.baseHighColor + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + renderType: Text.NativeRendering + elide: Text.ElideRight + rightPadding: 12 + control.spacing + } + //! [contentItem] + + //! [background] + background: Rectangle { + implicitWidth: 120 + implicitHeight: 32 + + border.width: 2 // ComboBoxBorderThemeThickness + border.color: !control.enabled ? control.Universal.baseLowColor : + control.pressed || panel.visible ? control.Universal.baseMediumLowColor : control.Universal.baseMediumLowColor + color: !control.enabled ? control.Universal.baseLowColor : + control.pressed || panel.visible ? control.Universal.listMediumColor : control.Universal.altMediumLowColor + + Rectangle { + x: 2 + y: 2 + width: parent.width - 4 + height: parent.height - 4 + + visible: control.activeFocus + color: control.Universal.accentColor + opacity: control.Universal.theme === Universal.Light ? 0.4 : 0.6 + } + + Image { + id: checkmark + x: parent.width - width - control.rightPadding + y: (parent.height - height) / 2 + source: "image://universal/downarrow/" + (!control.enabled ? control.Universal.baseLowColor : control.Universal.baseMediumHighColor) + } + } + //! [background] + + //! [panel] + panel: T.Panel { + id: popup + contentItem: Rectangle { + // TODO: Panel::anchors + readonly property var above: popup.visible ? control.mapToItem(null, 0, control.height - height) : Qt.point(0, 0) + readonly property var below: popup.visible ? control.mapToItem(null, 0, 0) : Qt.point(0, 0) + + x: below.x + y: above.y >= 0 && below.y + height > control.Window.height ? above.y : below.y + width: control.width + height: listview.height + + color: control.Universal.chromeMediumLowColor + + ListView { + id: listview + width: control.width + height: Math.min(200, contentHeight) // TODO: 396 + + clip: true + model: control.delegateModel + currentIndex: control.highlightedIndex + +// ScrollIndicator.vertical: ScrollIndicator { } + } + } + } + //! [panel] +} diff --git a/src/imports/controls/universal/images/downarrow.png b/src/imports/controls/universal/images/downarrow.png new file mode 100644 index 00000000..fef7b0f2 Binary files /dev/null and b/src/imports/controls/universal/images/downarrow.png differ diff --git a/src/imports/controls/universal/images/downarrow@2x.png b/src/imports/controls/universal/images/downarrow@2x.png new file mode 100644 index 00000000..eabf658a Binary files /dev/null and b/src/imports/controls/universal/images/downarrow@2x.png differ diff --git a/src/imports/controls/universal/images/downarrow@3x.png b/src/imports/controls/universal/images/downarrow@3x.png new file mode 100644 index 00000000..f9d39a2c Binary files /dev/null and b/src/imports/controls/universal/images/downarrow@3x.png differ diff --git a/src/imports/controls/universal/images/downarrow@4x.png b/src/imports/controls/universal/images/downarrow@4x.png new file mode 100644 index 00000000..b252b588 Binary files /dev/null and b/src/imports/controls/universal/images/downarrow@4x.png differ diff --git a/src/imports/controls/universal/qtlabsuniversalstyleplugin.qrc b/src/imports/controls/universal/qtlabsuniversalstyleplugin.qrc index 6cd57f89..5edda119 100644 --- a/src/imports/controls/universal/qtlabsuniversalstyleplugin.qrc +++ b/src/imports/controls/universal/qtlabsuniversalstyleplugin.qrc @@ -4,6 +4,10 @@ images/checkmark@2x.png images/checkmark@3x.png images/checkmark@4x.png + images/downarrow.png + images/downarrow@2x.png + images/downarrow@3x.png + images/downarrow@4x.png images/leftarrow.png images/leftarrow@2x.png images/leftarrow@3x.png diff --git a/src/imports/controls/universal/universal.pri b/src/imports/controls/universal/universal.pri index 4865e499..7348ac84 100644 --- a/src/imports/controls/universal/universal.pri +++ b/src/imports/controls/universal/universal.pri @@ -3,6 +3,7 @@ QML_FILES += \ $$PWD/BusyIndicator.qml \ $$PWD/Button.qml \ $$PWD/CheckBox.qml \ + $$PWD/ComboBox.qml \ $$PWD/Dial.qml \ $$PWD/Frame.qml \ $$PWD/GroupBox.qml \ diff --git a/src/imports/templates/qtlabstemplatesplugin.cpp b/src/imports/templates/qtlabstemplatesplugin.cpp index 721e4cab..5e4877f8 100644 --- a/src/imports/templates/qtlabstemplatesplugin.cpp +++ b/src/imports/templates/qtlabstemplatesplugin.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -90,6 +91,7 @@ void QtLabsTemplatesPlugin::registerTypes(const char *uri) qmlRegisterType(uri, 1, 0, "BusyIndicator"); qmlRegisterType(uri, 1, 0, "Button"); qmlRegisterType(uri, 1, 0, "CheckBox"); + qmlRegisterType(uri, 1, 0, "ComboBox"); qmlRegisterType(uri, 1, 0, "Container"); qmlRegisterType(uri, 1, 0, "Control"); qmlRegisterType(uri, 1, 0, "Dial"); 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 +#include +#include +#include +#include + +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(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(); + + 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()) + model = model.value().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(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(d->delegateModel)->componentComplete(); + + if (count() > 0) { + if (d->currentIndex == -1) + setCurrentIndex(0); + else + d->updateCurrentText(); + } +} + +QT_END_NAMESPACE diff --git a/src/templates/qquickcombobox_p.h b/src/templates/qquickcombobox_p.h new file mode 100644 index 00000000..6c80edc8 --- /dev/null +++ b/src/templates/qquickcombobox_p.h @@ -0,0 +1,146 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QQUICKCOMBOBOX_P_H +#define QQUICKCOMBOBOX_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +QT_BEGIN_NAMESPACE + +class QQuickPanel; +class QQmlInstanceModel; +class QQuickComboBoxPrivate; + +class Q_LABSTEMPLATES_EXPORT QQuickComboBox : public QQuickControl +{ + Q_OBJECT + Q_PROPERTY(int count READ count NOTIFY countChanged FINAL) + Q_PROPERTY(QVariant model READ model WRITE setModel NOTIFY modelChanged FINAL) + Q_PROPERTY(QQmlInstanceModel *delegateModel READ delegateModel NOTIFY delegateModelChanged FINAL) + Q_PROPERTY(bool pressed READ isPressed WRITE setPressed NOTIFY pressedChanged FINAL) + Q_PROPERTY(int highlightedIndex READ highlightedIndex NOTIFY highlightedIndexChanged FINAL) + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged FINAL) + Q_PROPERTY(QString currentText READ currentText NOTIFY currentTextChanged FINAL) + Q_PROPERTY(QString displayText READ displayText WRITE setDisplayText RESET resetDisplayText NOTIFY displayTextChanged FINAL) + Q_PROPERTY(QString textRole READ textRole WRITE setTextRole NOTIFY textRoleChanged FINAL) + Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged FINAL) + Q_PROPERTY(QQuickPanel *panel READ panel WRITE setPanel NOTIFY panelChanged FINAL) + +public: + explicit QQuickComboBox(QQuickItem *parent = Q_NULLPTR); + + int count() const; + + QVariant model() const; + void setModel(const QVariant &model); + QQmlInstanceModel *delegateModel() const; + + bool isPressed() const; + void setPressed(bool pressed); + + int highlightedIndex() const; + + int currentIndex() const; + void setCurrentIndex(int index); + + QString currentText() const; + + QString displayText() const; + void setDisplayText(const QString &text); + void resetDisplayText(); + + QString textRole() const; + void setTextRole(const QString &role); + + QQmlComponent *delegate() const; + void setDelegate(QQmlComponent *delegate); + + QQuickPanel *panel() const; + void setPanel(QQuickPanel *panel); + + Q_INVOKABLE QString textAt(int index) const; + Q_INVOKABLE int find(const QString &text, Qt::MatchFlags flags = Qt::MatchExactly) const; + +Q_SIGNALS: + void countChanged(); + void modelChanged(); + void delegateModelChanged(); + void pressedChanged(); + void highlightedIndexChanged(); + void currentIndexChanged(); + void currentTextChanged(); + void displayTextChanged(); + void textRoleChanged(); + void delegateChanged(); + void panelChanged(); + + void activated(int index); + void highlighted(int index); + +protected: + void focusOutEvent(QFocusEvent *event) Q_DECL_OVERRIDE; + void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE; + void keyReleaseEvent(QKeyEvent *event) Q_DECL_OVERRIDE; + void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE; + void mouseMoveEvent(QMouseEvent *event) Q_DECL_OVERRIDE; + void mouseReleaseEvent(QMouseEvent *event) Q_DECL_OVERRIDE; + void mouseUngrabEvent() Q_DECL_OVERRIDE; + void timerEvent(QTimerEvent *event) Q_DECL_OVERRIDE; + + void componentComplete() Q_DECL_OVERRIDE; + +private: + Q_DISABLE_COPY(QQuickComboBox) + Q_DECLARE_PRIVATE(QQuickComboBox) +}; + +Q_DECLARE_TYPEINFO(QQuickComboBox, Q_COMPLEX_TYPE); + +QT_END_NAMESPACE + +#endif // QQUICKCOMBOBOX_P_H diff --git a/src/templates/templates.pri b/src/templates/templates.pri index 22fad255..82e1af9e 100644 --- a/src/templates/templates.pri +++ b/src/templates/templates.pri @@ -8,6 +8,7 @@ HEADERS += \ $$PWD/qquickbutton_p.h \ $$PWD/qquickbuttongroup_p.h \ $$PWD/qquickcheckbox_p.h \ + $$PWD/qquickcombobox_p.h \ $$PWD/qquickcontainer_p.h \ $$PWD/qquickcontainer_p_p.h \ $$PWD/qquickcontrol_p.h \ @@ -56,6 +57,7 @@ SOURCES += \ $$PWD/qquickbutton.cpp \ $$PWD/qquickbuttongroup.cpp \ $$PWD/qquickcheckbox.cpp \ + $$PWD/qquickcombobox.cpp \ $$PWD/qquickcontainer.cpp \ $$PWD/qquickcontrol.cpp \ $$PWD/qquickdial.cpp \ diff --git a/tests/auto/controls/data/tst_combobox.qml b/tests/auto/controls/data/tst_combobox.qml new file mode 100644 index 00000000..a0798842 --- /dev/null +++ b/tests/auto/controls/data/tst_combobox.qml @@ -0,0 +1,553 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtTest 1.0 +import Qt.labs.controls 1.0 + +TestCase { + id: testCase + name: "ComboBox" + + ApplicationWindow { + id: window + visible: true + width: 400 + height: 400 + } + + SignalSpy { + id: activatedSpy + signalName: "activated" + } + + SignalSpy { + id: highlightedSpy + signalName: "highlighted" + } + + Component { + id: comboBox + ComboBox { + delegate: ItemDelegate { + width: parent.width + } + } + } + + function initTestCase() { + window.requestActivate() + tryCompare(window, "active", true) + } + + function init() { + verify(!activatedSpy.target) + compare(activatedSpy.count, 0) + + verify(!highlightedSpy.target) + compare(highlightedSpy.count, 0) + } + + function cleanup() { + activatedSpy.target = null + activatedSpy.clear() + + highlightedSpy.target = null + highlightedSpy.clear() + } + + function test_defaults() { + var control = comboBox.createObject(window.contentItem) + verify(control) + + compare(control.count, 0) + compare(control.model, undefined) + compare(control.pressed, false) + compare(control.currentIndex, -1) + compare(control.highlightedIndex, -1) + compare(control.currentText, "") + verify(control.delegate) + verify(control.panel) + + control.destroy() + } + + function test_array() { + var control = comboBox.createObject(window.contentItem) + verify(control) + + var items = [ "Banana", "Apple", "Coconut" ] + + control.model = items + compare(control.model, items) + + compare(control.count, 3) + compare(control.currentIndex, 0) + compare(control.currentText, "Banana") + + control.currentIndex = 2 + compare(control.currentIndex, 2) + compare(control.currentText, "Coconut") + + control.model = null + compare(control.model, null) + compare(control.count, 0) + compare(control.currentIndex, -1) + compare(control.currentText, "") + + control.destroy() + } + + function test_number() { + var control = comboBox.createObject(window.contentItem) + verify(control) + + control.model = 10 + compare(control.model, 10) + + compare(control.count, 10) + compare(control.currentIndex, 0) + compare(control.currentText, "0") + + control.currentIndex = 9 + compare(control.currentIndex, 9) + compare(control.currentText, "9") + + control.model = 0 + compare(control.model, 0) + compare(control.count, 0) + compare(control.currentIndex, -1) + compare(control.currentText, "") + + control.destroy() + } + + ListModel { + id: listmodel + ListElement { text: "First" } + ListElement { text: "Second" } + ListElement { text: "Third" } + ListElement { text: "Fourth" } + ListElement { text: "Fifth" } + } + + function test_listModel() { + var control = comboBox.createObject(window.contentItem) + verify(control) + + control.model = listmodel + compare(control.model, listmodel) + + compare(control.count, 5) + compare(control.currentIndex, 0) + compare(control.currentText, "First") + + control.currentIndex = 2 + compare(control.currentIndex, 2) + compare(control.currentText, "Third") + + control.model = undefined + compare(control.model, undefined) + compare(control.count, 0) + compare(control.currentIndex, -1) + compare(control.currentText, "") + + control.destroy() + } + + ListModel { + id: fruitmodel + ListElement { name: "Apple"; color: "red" } + ListElement { name: "Orange"; color: "orange" } + ListElement { name: "Banana"; color: "yellow" } + } + + function test_textRole() { + var control = comboBox.createObject(window.contentItem) + verify(control) + + control.model = fruitmodel + compare(control.count, 3) + compare(control.currentIndex, 0) + compare(control.currentText, "") + + control.textRole = "name" + compare(control.currentText, "Apple") + + control.textRole = "color" + compare(control.currentText, "red") + + control.currentIndex = 1 + compare(control.currentIndex, 1) + compare(control.currentText, "orange") + + control.textRole = "name" + compare(control.currentText, "Orange") + + control.textRole = "" + compare(control.currentText, "") + + control.destroy() + } + + function test_textAt() { + var control = comboBox.createObject(window.contentItem) + verify(control) + + control.model = ["Apple", "Orange", "Banana"] + compare(control.textAt(0), "Apple") + compare(control.textAt(1), "Orange") + compare(control.textAt(2), "Banana") + compare(control.textAt(-1), "") // TODO: null? + compare(control.textAt(5), "") // TODO: null? + + control.destroy() + } + + function test_find_data() { + return [ + { tag: "Banana (MatchExactly)", term: "Banana", flags: Qt.MatchExactly, index: 0 }, + { tag: "banana (MatchExactly)", term: "banana", flags: Qt.MatchExactly, index: 1 }, + { tag: "bananas (MatchExactly)", term: "bananas", flags: Qt.MatchExactly, index: -1 }, + { tag: "Cocomuffin (MatchExactly)", term: "Cocomuffin", flags: Qt.MatchExactly, index: 4 }, + + { tag: "b(an)+a (MatchRegExp)", term: "B(an)+a", flags: Qt.MatchRegExp, index: 0 }, + { tag: "b(an)+a (MatchRegExp|MatchCaseSensitive)", term: "b(an)+a", flags: Qt.MatchRegExp | Qt.MatchCaseSensitive, index: 1 }, + { tag: "[coc]+\\w+ (MatchRegExp)", term: "[coc]+\\w+", flags: Qt.MatchRegExp, index: 2 }, + + { tag: "?pp* (MatchWildcard)", term: "?pp*", flags: Qt.MatchWildcard, index: 3 }, + { tag: "app* (MatchWildcard|MatchCaseSensitive)", term: "app*", flags: Qt.MatchWildcard | Qt.MatchCaseSensitive, index: -1 }, + + { tag: "Banana (MatchFixedString)", term: "Banana", flags: Qt.MatchFixedString, index: 0 }, + { tag: "banana (MatchFixedString|MatchCaseSensitive)", term: "banana", flags: Qt.MatchFixedString | Qt.MatchCaseSensitive, index: 1 }, + + { tag: "coco (MatchStartsWith)", term: "coco", flags: Qt.MatchStartsWith, index: 2 }, + { tag: "coco (MatchStartsWith|MatchCaseSensitive)", term: "coco", flags: Qt.StartsWith | Qt.MatchCaseSensitive, index: -1 }, + + { tag: "MUFFIN (MatchEndsWith)", term: "MUFFIN", flags: Qt.MatchEndsWith, index: 4 }, + { tag: "MUFFIN (MatchEndsWith|MatchCaseSensitive)", term: "MUFFIN", flags: Qt.MatchEndsWith | Qt.MatchCaseSensitive, index: -1 }, + + { tag: "Con (MatchContains)", term: "Con", flags: Qt.MatchContains, index: 2 }, + { tag: "Con (MatchContains|MatchCaseSensitive)", term: "Con", flags: Qt.MatchContains | Qt.MatchCaseSensitive, index: -1 }, + ] + } + + function test_find(data) { + var control = comboBox.createObject(window.contentItem) + verify(control) + + control.model = ["Banana", "banana", "Coconut", "Apple", "Cocomuffin"] + + compare(control.find(data.term, data.flags), data.index) + + control.destroy() + } + + function test_arrowKeys() { + var control = comboBox.createObject(window.contentItem, {model: 3}) + verify(control) + + activatedSpy.target = control + verify(activatedSpy.valid) + + highlightedSpy.target = control + verify(highlightedSpy.valid) + + waitForRendering(control) + + control.forceActiveFocus() + verify(control.activeFocus) + + compare(control.currentIndex, 0) + compare(control.highlightedIndex, -1) + + keyClick(Qt.Key_Down) + compare(control.currentIndex, 1) + compare(control.highlightedIndex, -1) + compare(highlightedSpy.count, 0) + compare(activatedSpy.count, 1) + compare(activatedSpy.signalArguments[0][0], 1) + activatedSpy.clear() + + keyClick(Qt.Key_Down) + compare(control.currentIndex, 2) + compare(control.highlightedIndex, -1) + compare(highlightedSpy.count, 0) + compare(activatedSpy.count, 1) + compare(activatedSpy.signalArguments[0][0], 2) + activatedSpy.clear() + + keyClick(Qt.Key_Down) + compare(control.currentIndex, 2) + compare(control.highlightedIndex, -1) + compare(highlightedSpy.count, 0) + compare(activatedSpy.count, 0) + + keyClick(Qt.Key_Up) + compare(control.currentIndex, 1) + compare(control.highlightedIndex, -1) + compare(highlightedSpy.count, 0) + compare(activatedSpy.count, 1) + compare(activatedSpy.signalArguments[0][0], 1) + activatedSpy.clear() + + keyClick(Qt.Key_Up) + compare(control.currentIndex, 0) + compare(control.highlightedIndex, -1) + compare(highlightedSpy.count, 0) + compare(activatedSpy.count, 1) + compare(activatedSpy.signalArguments[0][0], 0) + activatedSpy.clear() + + keyClick(Qt.Key_Up) + compare(control.currentIndex, 0) + compare(control.highlightedIndex, -1) + compare(highlightedSpy.count, 0) + compare(activatedSpy.count, 0) + + // show panel + keyClick(Qt.Key_Space) + + compare(control.currentIndex, 0) + compare(control.highlightedIndex, 0) + + keyClick(Qt.Key_Down) + compare(control.currentIndex, 0) + compare(control.highlightedIndex, 1) + compare(activatedSpy.count, 0) + compare(highlightedSpy.count, 1) + compare(highlightedSpy.signalArguments[0][0], 1) + highlightedSpy.clear() + + keyClick(Qt.Key_Down) + compare(control.currentIndex, 0) + compare(control.highlightedIndex, 2) + compare(activatedSpy.count, 0) + compare(highlightedSpy.count, 1) + compare(highlightedSpy.signalArguments[0][0], 2) + highlightedSpy.clear() + + keyClick(Qt.Key_Down) + compare(control.currentIndex, 0) + compare(control.highlightedIndex, 2) + compare(activatedSpy.count, 0) + compare(highlightedSpy.count, 0) + + keyClick(Qt.Key_Up) + compare(control.currentIndex, 0) + compare(control.highlightedIndex, 1) + compare(activatedSpy.count, 0) + compare(highlightedSpy.count, 1) + compare(highlightedSpy.signalArguments[0][0], 1) + highlightedSpy.clear() + + keyClick(Qt.Key_Up) + compare(control.currentIndex, 0) + compare(control.highlightedIndex, 0) + compare(activatedSpy.count, 0) + compare(highlightedSpy.count, 1) + compare(highlightedSpy.signalArguments[0][0], 0) + highlightedSpy.clear() + + keyClick(Qt.Key_Up) + compare(control.currentIndex, 0) + compare(control.highlightedIndex, 0) + compare(activatedSpy.count, 0) + compare(highlightedSpy.count, 0) + + keyClick(Qt.Key_Down) + compare(control.currentIndex, 0) + compare(control.highlightedIndex, 1) + compare(activatedSpy.count, 0) + compare(highlightedSpy.count, 1) + compare(highlightedSpy.signalArguments[0][0], 1) + highlightedSpy.clear() + + // hide panel + keyClick(Qt.Key_Space) + + compare(control.currentIndex, 1) + compare(control.highlightedIndex, -1) + + control.destroy() + } + + function test_keys_data() { + return [ + { tag: "space-space", key1: Qt.Key_Space, key2: Qt.Key_Space, showPopup: true, showPress: true, hidePopup: true, hidePress: true }, + { tag: "space-enter", key1: Qt.Key_Space, key2: Qt.Key_Enter, showPopup: true, showPress: true, hidePopup: true, hidePress: true }, + { tag: "space-return", key1: Qt.Key_Space, key2: Qt.Key_Return, showPopup: true, showPress: true, hidePopup: true, hidePress: true }, + { tag: "space-escape", key1: Qt.Key_Space, key2: Qt.Key_Escape, showPopup: true, showPress: true, hidePopup: true, hidePress: false }, + { tag: "space-0", key1: Qt.Key_Space, key2: Qt.Key_0, showPopup: true, showPress: true, hidePopup: false, hidePress: false }, + + { tag: "enter-enter", key1: Qt.Key_Enter, key2: Qt.Key_Enter, showPopup: false, showPress: false, hidePopup: true, hidePress: false }, + { tag: "return-return", key1: Qt.Key_Return, key2: Qt.Key_Return, showPopup: false, showPress: false, hidePopup: true, hidePress: false }, + { tag: "escape-escape", key1: Qt.Key_Escape, key2: Qt.Key_Escape, showPopup: false, showPress: false, hidePopup: true, hidePress: false }, + ] + } + + function test_keys(data) { + var control = comboBox.createObject(window.contentItem, {model: 3}) + verify(control) + + control.forceActiveFocus() + verify(control.activeFocus) + + compare(control.pressed, false) + compare(control.panel.visible, false) + + // show panel + keyPress(data.key1) + compare(control.pressed, data.showPress) + compare(control.panel.visible, false) + keyRelease(data.key1) + compare(control.pressed, false) + compare(control.panel.visible, data.showPopup) + + // hide panel + keyPress(data.key2) + compare(control.pressed, data.hidePress) + compare(control.panel.visible, data.showPopup) + keyRelease(data.key2) + compare(control.pressed, false) + compare(control.panel.visible, !data.hidePopup) + + control.destroy() + } + + function test_panel() { + var control = comboBox.createObject(window.contentItem, {model: 3}) + verify(control) + + // show below + mousePress(control) + compare(control.pressed, true) + compare(control.panel.visible, false) + mouseRelease(control) + compare(control.pressed, false) + compare(control.panel.visible, true) + verify(control.panel.contentItem.y >= control.y) + + // hide + mouseClick(control) + compare(control.pressed, false) + compare(control.panel.visible, false) + + // show above + control.y = window.height - control.height + mousePress(control) + compare(control.pressed, true) + compare(control.panel.visible, false) + mouseRelease(control) + compare(control.pressed, false) + compare(control.panel.visible, true) + verify(control.panel.contentItem.y < control.y) + + control.destroy() + } + + function test_mouse() { + var control = comboBox.createObject(window.contentItem, {model: 3}) + verify(control) + + activatedSpy.target = control + verify(activatedSpy.valid) + + highlightedSpy.target = control + verify(highlightedSpy.valid) + + mouseClick(control) + compare(control.panel.visible, true) + + var content = control.panel.contentItem + waitForRendering(content) + + // press - move - release outside - not activated - not closed + mousePress(content) + compare(activatedSpy.count, 0) + compare(highlightedSpy.count, 0) + mouseMove(content, content.width * 2) + compare(activatedSpy.count, 0) + compare(highlightedSpy.count, 0) + mouseRelease(content, content.width * 2) + compare(activatedSpy.count, 0) + compare(highlightedSpy.count, 0) + compare(control.panel.visible, true) + + // press - move - release inside - activated - closed + mousePress(content) + compare(activatedSpy.count, 0) + compare(highlightedSpy.count, 0) + mouseMove(content, content.width / 2 + 1, content.height / 2 + 1) + compare(activatedSpy.count, 0) + compare(highlightedSpy.count, 0) + mouseRelease(content) + compare(activatedSpy.count, 1) + compare(highlightedSpy.count, 1) + compare(control.panel.visible, false) + + control.destroy() + } + + function test_focus() { + var control = comboBox.createObject(window.contentItem, {model: 3}) + verify(control) + + // click - gain focus - show panel + mouseClick(control) + verify(control.activeFocus) + compare(control.panel.visible, true) + + // lose focus - hide panel + window.contentItem.forceActiveFocus() + verify(window.contentItem.activeFocus) + verify(!control.activeFocus) + compare(control.panel.visible, false) + + control.destroy() + } + + function test_baseline() { + var control = comboBox.createObject(testCase) + verify(control) + compare(control.baselineOffset, control.contentItem.y + control.contentItem.baselineOffset) + control.destroy() + } +} diff --git a/tests/auto/sanity/BLACKLIST b/tests/auto/sanity/BLACKLIST index 02e9a5f9..5a000c5c 100644 --- a/tests/auto/sanity/BLACKLIST +++ b/tests/auto/sanity/BLACKLIST @@ -2,3 +2,9 @@ * [signalHandlers:material/TextField.qml] * +[attachedObjects:controls/ComboBox.qml] +* +[attachedObjects:material/ComboBox.qml] +* +[attachedObjects:universal/ComboBox.qml] +* diff --git a/tests/manual/testbench/main.qml b/tests/manual/testbench/main.qml index 679c8a1c..54e0ae34 100644 --- a/tests/manual/testbench/main.qml +++ b/tests/manual/testbench/main.qml @@ -405,6 +405,40 @@ ApplicationWindow { } } + RowLayout { + Item { + implicitWidth: normalGroupBox.implicitWidth + implicitHeight: normalComboBox.implicitHeight + + ComboBox { + id: normalComboBox + model: 5 + } + } + + Item { + implicitWidth: normalGroupBox.implicitWidth + implicitHeight: normalComboBox.implicitHeight + + ComboBox { + pressed: true + model: ListModel { + ListElement { text: "Pressed" } + } + } + } + + Item { + implicitWidth: normalGroupBox.implicitWidth + implicitHeight: normalComboBox.implicitHeight + + ComboBox { + enabled: false + model: ["Disabled"] + } + } + } + RowLayout { GroupBox { id: normalGroupBox -- cgit v1.2.3