diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/controls/ComboBox.qml | 296 | ||||
| -rw-r--r-- | src/controls/Styles/Base/ComboBoxStyle.qml | 50 | ||||
| -rw-r--r-- | src/controls/Styles/Desktop/ComboBoxStyle.qml | 5 |
3 files changed, 342 insertions, 9 deletions
diff --git a/src/controls/ComboBox.qml b/src/controls/ComboBox.qml index 4af49c145..74645db6d 100644 --- a/src/controls/ComboBox.qml +++ b/src/controls/ComboBox.qml @@ -58,10 +58,11 @@ import QtQuick.Controls.Private 1.0 } \endqml - Example 2: + 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" } @@ -73,6 +74,31 @@ import QtQuick.Controls.Private 1.0 } \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 (editableCombo.find(currentText) === -1) { + model.append({text: editText}) + currentIndex = editableCombo.find(editText) + } + } + } + \endqml + + You can create a custom appearance for a ComboBox by assigning a \l ComboBoxStyle. */ @@ -97,7 +123,19 @@ Control { \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.selectedText + 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 /*! This property specifies whether the combobox should gain active focus when pressed. The default value is \c false. */ @@ -114,6 +152,115 @@ Control { */ readonly property alias hovered: mouseArea.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 + + /*! 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; + } + + /*! 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{QtQuick2::IntValidator}, + \l{QtQuick2::DoubleValidator}, and \l{QtQuick2::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.1 + import QtQuick.Controls 1.1 + + ComboBox { + editable: true + model: 10 + validator: IntValidator {bottom: 0; top: 10;} + focus: true + } + \endqml + + \sa acceptableInput, accepted, editable + */ + property alias validator: input.validator + + /*! + \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 + + /*! + \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. + */ + signal accepted + + /*! + \qmlsignal ComboBox::activated(int index) + \since QtQuick.Controls 1.1 + + \a index is the triggered model index or -1 if a new string is accepted + + This signal is similar to currentIndex changed, but will only + be emitted if the combo box index was changed by the user and not + when set programatically. + */ + signal activated(int index) + + /*! + \qmlmethod ComboBox::selectAll() + \since QtQuick.Controls 1.1 + + Causes all \l editText to be selected. + */ + function selectAll() { + input.selectAll() + } + /*! \internal */ property var __popup: popup @@ -142,6 +289,118 @@ Control { 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); + } + } + } + + TextInput { + id: input + + visible: editable + enabled: editable + focus: true + clip: contentWidth > width + text: currentText + + anchors.fill: parent + anchors.leftMargin: 8 + anchors.rightMargin: 24 + verticalAlignment: Text.AlignVCenter + + renderType: Text.NativeRendering + selectByMouse: true + selectionColor: syspal.highlight + selectedTextColor: syspal.highlightedText + onAccepted: { + var idx = input.find(editText) + if (idx > -1) { + var string = textAt(idx); + if (string.length === editText.length) { + currentIndex = idx; + editText = string; + } + } else { + currentIndex = -1; + popup.currentText = editText; + } + comboBox.accepted(); + } + + SystemPalette { id: syspal } + + property bool blockUpdate: false + property string prevText: null + + 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 (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.onPressed: allowComplete = (event.key !== Qt.Key_Backspace && event.key !== Qt.Key_Delete); + + onTextChanged: { + if (editable && !blockUpdate && allowComplete) { + 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 + } + } + onTextRoleChanged: popup.resolveTextValue(textRole) Menu { @@ -150,6 +409,9 @@ Control { style: isPopup ? __style.__popupStyle : __style.__dropDownStyle + property string currentText: selectedText + onSelectedTextChanged: if (selectedText) popup.currentText = selectedText + readonly property string selectedText: items[__selectedIndex] ? items[__selectedIndex].text : "" property string textRole: "" @@ -170,8 +432,12 @@ Control { MenuItem { text: popup.textRole === '' ? modelData : - ((popup.__modelIsArray ? modelData[popup.textRole] : model[popup.textRole]) || '') - + ((popup.__modelIsArray ? modelData[popup.textRole] : model[popup.textRole]) || '') + onTriggered: { + if (index !== currentIndex) + activated(index) + comboBox.editText = text + } checkable: true exclusiveGroup: eg } @@ -236,6 +502,24 @@ Control { if (!popup.popupVisible) popup.show() } - Keys.onUpPressed: { if (currentIndex > 0) currentIndex-- } - Keys.onDownPressed: { if (currentIndex < popupItems.count - 1) currentIndex++ } + + Keys.onUpPressed: { + input.blockUpdate = true + if (currentIndex > 0) { + currentIndex--; + input.text = popup.currentText; + activated(currentIndex); + } + input.blockUpdate = false; + } + + Keys.onDownPressed: { + input.blockUpdate = true; + if (currentIndex < popupItems.count - 1) { + currentIndex++; + input.text = popup.currentText; + activated(currentIndex); + } + input.blockUpdate = false; + } } diff --git a/src/controls/Styles/Base/ComboBoxStyle.qml b/src/controls/Styles/Base/ComboBoxStyle.qml index 5a0ef9377..2011b219f 100644 --- a/src/controls/Styles/Base/ComboBoxStyle.qml +++ b/src/controls/Styles/Base/ComboBoxStyle.qml @@ -99,6 +99,44 @@ Style { } } + /*! \internal */ + property Component __editor: Item { + implicitWidth: 100 + implicitHeight: 25 + clip: true + BorderImage { + anchors.fill: parent + anchors.rightMargin: -2 + source: "images/editbox.png" + border.left: 4 + border.right: 4 + border.top: 4 + border.bottom: 4 + BorderImage { + anchors.fill: parent + anchors.margins: -1 + anchors.topMargin: -2 + anchors.rightMargin: 0 + anchors.bottomMargin: 1 + source: "images/focusframe.png" + visible: control.activeFocus + border.left: 4 + border.right: 4 + border.top: 4 + border.bottom: 4 + } + } + Rectangle { + color: "#aaa" + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.bottomMargin: 3 + anchors.topMargin: 1 + width: 1 + } + } + /*! This defines the label of the button. */ property Component label: Item { implicitWidth: textitem.implicitWidth + 20 @@ -118,18 +156,28 @@ Style { property bool popup: false anchors.centerIn: parent anchors.fill: parent - implicitWidth: Math.max(labelLoader.implicitWidth + padding.left + padding.right, backgroundLoader.implicitWidth) + implicitWidth: 125 implicitHeight: Math.max(labelLoader.implicitHeight + padding.top + padding.bottom, backgroundLoader.implicitHeight) Loader { id: backgroundLoader anchors.fill: parent sourceComponent: background + + } + + Loader { + id: editorLoader + anchors.fill: parent + anchors.rightMargin: 20 + anchors.bottomMargin: -1 + sourceComponent: control.editable ? __editor : null } Loader { id: labelLoader sourceComponent: label + visible: !control.editable anchors.fill: parent anchors.leftMargin: padding.left anchors.topMargin: padding.top diff --git a/src/controls/Styles/Desktop/ComboBoxStyle.qml b/src/controls/Styles/Desktop/ComboBoxStyle.qml index 91a8e82c6..e6495cebc 100644 --- a/src/controls/Styles/Desktop/ComboBoxStyle.qml +++ b/src/controls/Styles/Desktop/ComboBoxStyle.qml @@ -47,7 +47,7 @@ Style { property Component panel: Item { property bool popup: !!styleItem.styleHint("comboboxpopup") - implicitWidth: 115 + implicitWidth: 125 implicitHeight: styleItem.implicitHeight anchors.fill: parent StyleItem { @@ -70,7 +70,8 @@ Style { hints: control.styleHints properties: { - "popup": control.__popup + "popup": control.__popup, + "editable" : control.editable } } } |
