123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710 |
- /****************************************************************************
- **
- ** Copyright (C) 2016 The Qt Company Ltd.
- ** Contact: https://www.qt.io/licensing/
- **
- ** This file is part of the Qt Quick Controls module of the Qt Toolkit.
- **
- ** $QT_BEGIN_LICENSE:LGPL$
- ** Commercial License Usage
- ** Licensees holding valid commercial Qt licenses may use this file in
- ** accordance with the commercial license agreement provided with the
- ** Software or, alternatively, in accordance with the terms contained in
- ** a written agreement between you and The Qt Company. For licensing terms
- ** and conditions see https://www.qt.io/terms-conditions. For further
- ** information use the contact form at https://www.qt.io/contact-us.
- **
- ** GNU 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.LGPL3 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-3.0.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 (at your option) the GNU General
- ** Public license version 3 or any later version approved by the KDE Free
- ** Qt Foundation. The licenses are as published by the Free Software
- ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
- ** included in the packaging of this file. Please review the following
- ** information to ensure the GNU General Public License requirements will
- ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
- ** https://www.gnu.org/licenses/gpl-3.0.html.
- **
- ** $QT_END_LICENSE$
- **
- ****************************************************************************/
- import QtQuick 2.2
- import QtQuick.Controls 1.2
- import QtQuick.Controls.Private 1.0
- /*!
- \qmltype ComboBox
- \inqmlmodule QtQuick.Controls
- \since 5.1
- \ingroup controls
- \brief Provides a drop-down list functionality.
- \image combobox.png
- Add items to the ComboBox by assigning it a ListModel, or a list of strings
- to the \l model property.
- \qml
- ComboBox {
- width: 200
- model: [ "Banana", "Apple", "Coconut" ]
- }
- \endqml
- In this example we are demonstrating how to use a ListModel with a combo box.
- \qml
- ComboBox {
- currentIndex: 2
- model: ListModel {
- id: cbItems
- ListElement { text: "Banana"; color: "Yellow" }
- ListElement { text: "Apple"; color: "Green" }
- ListElement { text: "Coconut"; color: "Brown" }
- }
- width: 200
- onCurrentIndexChanged: console.debug(cbItems.get(currentIndex).text + ", " + cbItems.get(currentIndex).color)
- }
- \endqml
- You can make a combo box editable by setting the \l editable property. An editable combo box will
- autocomplete its text based on what is available in the model.
- In the next example we demonstrate how you can append content to an editable combo box by
- reacting to the \l accepted signal. Note that you have to explicitly prevent duplicates.
- \qml
- ComboBox {
- editable: true
- model: ListModel {
- id: model
- ListElement { text: "Banana"; color: "Yellow" }
- ListElement { text: "Apple"; color: "Green" }
- ListElement { text: "Coconut"; color: "Brown" }
- }
- onAccepted: {
- if (find(currentText) === -1) {
- model.append({text: editText})
- currentIndex = find(editText)
- }
- }
- }
- \endqml
- You can create a custom appearance for a ComboBox by
- assigning a \l {ComboBoxStyle}.
- */
- Control {
- id: comboBox
- /*! \qmlproperty model ComboBox::model
- The model to populate the ComboBox from.
- Changing the model after initialization will reset \l currentIndex to \c 0.
- */
- property alias model: popupItems.model
- /*! The model role used for populating the ComboBox. */
- property string textRole: ""
- /*! \qmlproperty int ComboBox::currentIndex
- The index of the currently selected item in the ComboBox.
- Setting currentIndex to \c -1 will reset the selection and clear the text
- label. If \l editable is \c true, you may also need to manually clear \l editText.
- \sa model
- */
- property alias currentIndex: popup.__selectedIndex
- /*! \qmlproperty string ComboBox::currentText
- The text of the currently selected item in the ComboBox.
- \note Since \c currentText depends on \c currentIndex, there's no way to ensure \c currentText
- will be up to date whenever a \c onCurrentIndexChanged handler is called.
- */
- readonly property alias currentText: popup.currentText
- /*! This property holds whether the combo box can be edited by the user.
- The default value is \c false.
- \since QtQuick.Controls 1.1
- */
- property bool editable: false
- /*! \qmlproperty string ComboBox::editText
- \since QtQuick.Controls 1.1
- This property specifies text being manipulated by the user for an editable combo box.
- */
- property alias editText: input.text
- /*! \qmlproperty enumeration ComboBox::inputMethodHints
- \since QtQuick.Controls 1.5
- Provides hints to the input method about the expected content of the combo box and how it
- should operate.
- The value is a bit-wise combination of flags or \c Qt.ImhNone if no hints are set.
- Flags that alter behavior are:
- \list
- \li Qt.ImhHiddenText - Characters should be hidden, as is typically used when entering passwords.
- \li Qt.ImhSensitiveData - Typed text should not be stored by the active input method
- in any persistent storage like predictive user dictionary.
- \li Qt.ImhNoAutoUppercase - The input method should not try to automatically switch to upper case
- when a sentence ends.
- \li Qt.ImhPreferNumbers - Numbers are preferred (but not required).
- \li Qt.ImhPreferUppercase - Upper case letters are preferred (but not required).
- \li Qt.ImhPreferLowercase - Lower case letters are preferred (but not required).
- \li Qt.ImhNoPredictiveText - Do not use predictive text (i.e. dictionary lookup) while typing.
- \li Qt.ImhDate - The text editor functions as a date field.
- \li Qt.ImhTime - The text editor functions as a time field.
- \endlist
- Flags that restrict input (exclusive flags) are:
- \list
- \li Qt.ImhDigitsOnly - Only digits are allowed.
- \li Qt.ImhFormattedNumbersOnly - Only number input is allowed. This includes decimal point and minus sign.
- \li Qt.ImhUppercaseOnly - Only upper case letter input is allowed.
- \li Qt.ImhLowercaseOnly - Only lower case letter input is allowed.
- \li Qt.ImhDialableCharactersOnly - Only characters suitable for phone dialing are allowed.
- \li Qt.ImhEmailCharactersOnly - Only characters suitable for email addresses are allowed.
- \li Qt.ImhUrlCharactersOnly - Only characters suitable for URLs are allowed.
- \endlist
- Masks:
- \list
- \li Qt.ImhExclusiveInputMask - This mask yields nonzero if any of the exclusive flags are used.
- \endlist
- */
- property alias inputMethodHints: input.inputMethodHints
- /*! This property specifies whether the combobox should gain active focus when pressed.
- The default value is \c false. */
- property bool activeFocusOnPress: false
- /*! \qmlproperty bool ComboBox::pressed
- This property holds whether the button is being pressed. */
- readonly property bool pressed: mouseArea.effectivePressed || popup.__popupVisible
- /*! \qmlproperty bool ComboBox::hovered
- This property indicates whether the control is being hovered.
- */
- readonly property bool hovered: mouseArea.containsMouse || input.containsMouse
- /*! \qmlproperty int ComboBox::count
- \since QtQuick.Controls 1.1
- This property holds the number of items in the combo box.
- */
- readonly property alias count: popupItems.count
- /*! \qmlmethod string ComboBox::textAt(int index)
- Returns the text for a given \a index.
- If an invalid index is provided, \c null is returned
- \since QtQuick.Controls 1.1
- */
- function textAt (index) {
- if (index >= count || index < 0)
- return null;
- return popupItems.objectAt(index).text;
- }
- /*! \qmlmethod int ComboBox::find(string text)
- Finds and returns the index of a given \a text
- If no match is found, \c -1 is returned. The search is case sensitive.
- \since QtQuick.Controls 1.1
- */
- function find (text) {
- return input.find(text, Qt.MatchExactly)
- }
- /*!
- \qmlproperty Validator ComboBox::validator
- \since QtQuick.Controls 1.1
- Allows you to set a text validator for an editable ComboBox.
- When a validator is set,
- the text field will only accept input which leaves the text property in
- an intermediate state. The accepted signal will only be sent
- if the text is in an acceptable state when enter is pressed.
- Currently supported validators are \l[QtQuick]{IntValidator},
- \l[QtQuick]{DoubleValidator}, and \l[QtQuick]{RegExpValidator}. An
- example of using validators is shown below, which allows input of
- integers between 11 and 31 into the text field:
- \note This property is only applied when \l editable is \c true
- \qml
- import QtQuick 2.2
- import QtQuick.Controls 1.2
- ComboBox {
- editable: true
- model: 10
- validator: IntValidator {bottom: 0; top: 10;}
- focus: true
- }
- \endqml
- \sa acceptableInput, accepted, editable
- */
- property alias validator: input.validator
- /*!
- \since QtQuick.Controls 1.3
- This property contains the edit \l Menu for working
- with text selection. Set it to \c null if no menu
- is wanted.
- \note The menu is only in use when \l editable is \c true
- */
- property Component menu: input.editMenu.defaultMenu
- /*!
- \qmlproperty bool ComboBox::acceptableInput
- \since QtQuick.Controls 1.1
- Returns \c true if the combo box contains acceptable
- text in the editable text field.
- If a validator was set, this property will return \c
- true if the current text satisfies the validator or mask as
- a final string (not as an intermediate string).
- \sa validator, accepted
- */
- readonly property alias acceptableInput: input.acceptableInput
- /*!
- \qmlproperty bool ComboBox::selectByMouse
- \since QtQuick.Controls 1.3
- This property determines if the user can select the text in
- the editable text field with the mouse.
- The default value is \c true.
- */
- property bool selectByMouse: true
- /*!
- \qmlproperty bool ComboBox::inputMethodComposing
- \since QtQuick.Controls 1.3
- This property holds whether an editable ComboBox has partial text input from an input method.
- While it is composing an input method may rely on mouse or key events from the ComboBox
- to edit or commit the partial text. This property can be used to determine when to disable
- events handlers that may interfere with the correct operation of an input method.
- */
- readonly property bool inputMethodComposing: !!input.inputMethodComposing
- /*!
- \qmlsignal ComboBox::accepted()
- \since QtQuick.Controls 1.1
- This signal is emitted when the Return or Enter key is pressed on an
- \l editable combo box. If the confirmed string is not currently in the model,
- the currentIndex will be set to -1 and the \l currentText will be updated
- accordingly.
- \note If there is a \l validator set on the combobox,
- the signal will only be emitted if the input is in an acceptable state.
- The corresponding handler is \c onAccepted.
- */
- signal accepted
- /*!
- \qmlsignal ComboBox::activated(int index)
- \since QtQuick.Controls 1.1
- This signal is similar to currentIndex changed, but will only
- be emitted if the combo box index was changed by the user, not
- when set programmatically.
- \a index is the activated model index, or \c -1 if a new string is
- accepted.
- The corresponding handler is \c onActivated.
- */
- signal activated(int index)
- /*!
- \qmlmethod void ComboBox::selectAll()
- \since QtQuick.Controls 1.1
- Causes all \l editText to be selected.
- */
- function selectAll() {
- input.selectAll()
- }
- /*! \internal */
- function __selectPrevItem() {
- input.blockUpdate = true
- if (currentIndex > 0) {
- currentIndex--;
- input.text = popup.currentText;
- activated(currentIndex);
- }
- input.blockUpdate = false;
- }
- /*! \internal */
- function __selectNextItem() {
- input.blockUpdate = true;
- if (currentIndex < popupItems.count - 1) {
- currentIndex++;
- input.text = popup.currentText;
- activated(currentIndex);
- }
- input.blockUpdate = false;
- }
- /*! \internal */
- property var __popup: popup
- style: Settings.styleComponent(Settings.style, "ComboBoxStyle.qml", comboBox)
- activeFocusOnTab: true
- Accessible.name: editable ? editText : currentText
- Accessible.role: Accessible.ComboBox
- Accessible.editable: editable
- MouseArea {
- id: mouseArea
- property bool overridePressed: false
- readonly property bool effectivePressed: (pressed || overridePressed) && containsMouse
- anchors.fill: parent
- hoverEnabled: Settings.hoverEnabled
- onPressed: {
- if (comboBox.activeFocusOnPress)
- forceActiveFocus()
- if (!Settings.hasTouchScreen)
- popup.toggleShow()
- else
- overridePressed = true
- }
- onCanceled: overridePressed = false
- onClicked: {
- if (Settings.hasTouchScreen)
- popup.toggleShow()
- overridePressed = false
- }
- onWheel: {
- if (wheel.angleDelta.y > 0) {
- __selectPrevItem();
- } else if (wheel.angleDelta.y < 0){
- __selectNextItem();
- }
- }
- }
- Component.onCompleted: {
- if (currentIndex === -1)
- currentIndex = 0
- popup.ready = true
- popup.resolveTextValue(textRole)
- }
- Keys.onPressed: {
- // Perform one-character based lookup for non-editable combo box
- if (!editable && event.text.length > 0) {
- var index = input.find(event.text, Qt.MatchStartsWith);
- if (index >= 0 && index !== currentIndex) {
- currentIndex = index;
- activated(currentIndex);
- }
- }
- }
- TextInputWithHandles {
- id: input
- visible: editable
- enabled: editable
- focus: true
- clip: contentWidth > width
- control: comboBox
- cursorHandle: __style ? __style.__cursorHandle : undefined
- selectionHandle: __style ? __style.__selectionHandle : undefined
- anchors.fill: parent
- anchors.leftMargin: __style ? __style.padding.left : 0
- anchors.topMargin: __style ? __style.padding.top : 0
- anchors.rightMargin: __style ? __panel.dropDownButtonWidth + __style.padding.right : 0
- anchors.bottomMargin: __style ? __style.padding.bottom: 0
- verticalAlignment: Text.AlignVCenter
- font: __panel && __panel.font !== undefined ? __panel.font : TextSingleton.font
- renderType: __style ? __style.renderType : Text.NativeRendering
- color: __panel ? __panel.textColor : "black"
- selectionColor: __panel ? __panel.selectionColor : "blue"
- selectedTextColor: __panel ? __panel.selectedTextColor : "white"
- onAccepted: {
- var idx = input.find(editText, Qt.MatchFixedString)
- if (idx > -1) {
- editTextMatches = true;
- currentIndex = idx;
- editText = textAt(idx);
- } else {
- editTextMatches = false;
- currentIndex = -1;
- popup.currentText = editText;
- }
- comboBox.accepted();
- }
- property bool blockUpdate: false
- property string prevText
- property bool editTextMatches: true
- function find (text, searchType) {
- for (var i = 0 ; i < popupItems.count ; ++i) {
- var currentString = popupItems.objectAt(i).text
- if (searchType === Qt.MatchExactly) {
- if (text === currentString)
- return i;
- } else if (searchType === Qt.CaseSensitive) {
- if (currentString.indexOf(text) === 0)
- return i;
- } else if (searchType === Qt.MatchFixedString) {
- if (currentString.toLowerCase().indexOf(text.toLowerCase()) === 0
- && currentString.length === text.length)
- return i;
- } else if (currentString.toLowerCase().indexOf(text.toLowerCase()) === 0) {
- return i
- }
- }
- return -1;
- }
- // Finds first entry and shortest entry. Used by editable combo
- function tryComplete (inputText) {
- var candidate = "";
- var shortestString = "";
- for (var i = 0 ; i < popupItems.count ; ++i) {
- var currentString = popupItems.objectAt(i).text;
- if (currentString.toLowerCase().indexOf(inputText.toLowerCase()) === 0) {
- if (candidate.length) { // Find smallest possible match
- var cmp = 0;
- // We try to complete the shortest string that matches our search
- if (currentString.length < candidate.length)
- candidate = currentString
- while (cmp < Math.min(currentString.length, shortestString.length)
- && shortestString[cmp].toLowerCase() === currentString[cmp].toLowerCase())
- cmp++;
- shortestString = shortestString.substring(0, cmp);
- } else { // First match, select as current index and find other matches
- candidate = currentString;
- shortestString = currentString;
- }
- }
- }
- if (candidate.length)
- return inputText + candidate.substring(inputText.length, candidate.length);
- return inputText;
- }
- property bool allowComplete: false
- Keys.forwardTo: comboBox
- Keys.onPressed: allowComplete = (event.key !== Qt.Key_Backspace && event.key !== Qt.Key_Delete);
- onTextChanged: {
- if (editable && !blockUpdate && allowComplete && text.length > 0) {
- var completed = input.tryComplete(text)
- if (completed.length > text.length) {
- var oldtext = input.text;
- input.text = completed;
- input.select(text.length, oldtext.length);
- }
- }
- prevText = text
- }
- }
- Binding {
- target: input
- property: "text"
- value: popup.currentText
- when: input.editTextMatches
- }
- onTextRoleChanged: popup.resolveTextValue(textRole)
- ExclusiveGroup { id: eg }
- Menu {
- id: popup
- objectName: "popup"
- style: isPopup ? __style.__popupStyle : __style.__dropDownStyle
- property string currentText: selectedText
- onSelectedTextChanged: popup.currentText = selectedText
- property string selectedText
- on__SelectedIndexChanged: {
- if (__selectedIndex === -1)
- popup.currentText = ""
- else
- updateSelectedText()
- }
- property string textRole: ""
- property bool ready: false
- property bool isPopup: !editable && !!__panel && __panel.popup
- property int y: isPopup ? (comboBox.__panel.height - comboBox.__panel.implicitHeight) / 2.0 : comboBox.__panel.height
- __minimumWidth: comboBox.width
- __visualItem: comboBox
- property bool modelIsArray: false
- Instantiator {
- id: popupItems
- active: false
- property bool updatingModel: false
- onModelChanged: {
- popup.modelIsArray = !!model ? model.constructor === Array : false
- if (active) {
- if (updatingModel && popup.__selectedIndex === 0) {
- // We still want to update the currentText
- popup.updateSelectedText()
- } else {
- updatingModel = true
- popup.__selectedIndex = 0
- }
- }
- popup.resolveTextValue(comboBox.textRole)
- }
- MenuItem {
- text: popup.textRole === '' ?
- modelData :
- ((popup.modelIsArray ? modelData[popup.textRole] : model[popup.textRole]) || '')
- onTriggered: {
- if (index !== currentIndex)
- activated(index)
- comboBox.editText = text
- }
- checkable: true
- exclusiveGroup: eg
- }
- onObjectAdded: {
- popup.insertItem(index, object)
- if (!updatingModel && index === popup.__selectedIndex)
- popup.selectedText = object["text"]
- }
- onObjectRemoved: popup.removeItem(object)
- }
- function resolveTextValue(initialTextRole) {
- if (!ready || !model) {
- popupItems.active = false
- return;
- }
- var get = model['get'];
- if (!get && popup.modelIsArray && !!model[0]) {
- if (model[0].constructor !== String && model[0].constructor !== Number)
- get = function(i) { return model[i]; }
- }
- var modelMayHaveRoles = get !== undefined
- textRole = initialTextRole
- if (textRole === "" && modelMayHaveRoles && get(0)) {
- // No text role set, check whether model has a suitable role
- // If 'text' is found, or there's only one role, pick that.
- var listElement = get(0)
- var roleName = ""
- var roleCount = 0
- for (var role in listElement) {
- if (listElement[role].constructor === Function)
- continue;
- if (role === "text") {
- roleName = role
- break
- } else if (!roleName) {
- roleName = role
- }
- ++roleCount
- }
- if (roleCount > 1 && roleName !== "text") {
- console.warn("No suitable 'textRole' found for ComboBox.")
- } else {
- textRole = roleName
- }
- }
- if (!popupItems.active)
- popupItems.active = true
- else
- updateSelectedText()
- }
- function toggleShow() {
- if (popup.__popupVisible) {
- popup.__dismissAndDestroy()
- } else {
- if (items[__selectedIndex])
- items[__selectedIndex].checked = true
- __currentIndex = comboBox.currentIndex
- if (Qt.application.layoutDirection === Qt.RightToLeft)
- __popup(Qt.rect(comboBox.width, y, 0, 0), isPopup ? __selectedIndex : 0)
- else
- __popup(Qt.rect(0, y, 0, 0), isPopup ? __selectedIndex : 0)
- }
- }
- function updateSelectedText() {
- var selectedItem;
- if (__selectedIndex !== -1 && (selectedItem = items[__selectedIndex])) {
- input.editTextMatches = true
- selectedText = Qt.binding(function () { return selectedItem.text })
- if (currentText !== selectedText) // __selectedIndex went form -1 to 0
- selectedTextChanged()
- }
- }
- }
- // The key bindings below will only be in use when popup is
- // not visible. Otherwise, native popup key handling will take place:
- Keys.onSpacePressed: {
- if (!editable)
- popup.toggleShow()
- else
- event.accepted = false
- }
- Keys.onUpPressed: __selectPrevItem()
- Keys.onDownPressed: __selectNextItem()
- }
|