diff --git a/src/QmlControls/QGCComboBox.qml b/src/QmlControls/QGCComboBox.qml index 473eff1abd7432691f0bfa84083f200203715b5a..1d30624cccecf45b1be2abd2fd417e13e8d78a1b 100644 --- a/src/QmlControls/QGCComboBox.qml +++ b/src/QmlControls/QGCComboBox.qml @@ -5,58 +5,246 @@ import QtQuick.Controls.Styles 1.4 import QGroundControl.Palette 1.0 import QGroundControl.ScreenTools 1.0 -ComboBox { - property var _qgcPal: QGCPalette { colorGroupEnabled: enabled } - property bool _showHighlight: pressed | hovered - property bool _showBorder: _qgcPal.globalTheme === QGCPalette.Light - - style: ComboBoxStyle { - font.pointSize: ScreenTools.defaultFontPointSize - textColor: _showHighlight ? - control._qgcPal.buttonHighlightText : - control._qgcPal.buttonText - - background: Item { +Button { + id: combo + + property real pointSize: ScreenTools.defaultFontPointSize ///< Point size for button text + property alias model: popupItems.model + property alias textRole: popup.textRole + property alias currentIndex: popup.__selectedIndex + + readonly property alias count: popupItems.count + readonly property alias currentText: popup.currentText + + property var _qgcPal: QGCPalette { colorGroupEnabled: enabled } + property int _horizontalPadding: ScreenTools.defaultFontPixelWidth + property int _verticalPadding: Math.round(ScreenTools.defaultFontPixelHeight / 2) + property var __popup: popup + + signal activated(int index) + + style: ButtonStyle { + /*! The padding between the background and the label components. */ + padding { + top: _verticalPadding + bottom: _verticalPadding + left: _horizontalPadding + right: _horizontalPadding + } + + /*! This defines the background of the button. */ + background: Rectangle { implicitWidth: ScreenTools.implicitComboBoxWidth implicitHeight: ScreenTools.implicitComboBoxHeight - - Rectangle { - anchors.fill: parent - color: _showHighlight ? control._qgcPal.buttonHighlight : control._qgcPal.button - border.width: _showBorder ? 1: 0 - border.color: control._qgcPal.buttonText - } + color: control._qgcPal.button QGCColoredImage { id: image width: ScreenTools.defaultFontPixelHeight / 2 height: width anchors.verticalCenter: parent.verticalCenter + anchors.rightMargin: width / 2 anchors.right: parent.right - anchors.rightMargin: dropDownButtonWidth / 2 source: "/qmlimages/arrow-down.png" - color: qgcPal.text + color: control._qgcPal.buttonText + } + } + + /*! This defines the label of the button. */ + label: Item { + implicitWidth: text.implicitWidth + implicitHeight: text.implicitHeight + baselineOffset: text.y + text.baselineOffset + + Text { + id: text + anchors.verticalCenter: parent.verticalCenter + antialiasing: true + text: control.currentText + font.pointSize: pointSize + font.family: ScreenTools.normalFontFamily + color: control._qgcPal.buttonText } } } - // Capture Wheel events to disable scrolling options in ComboBox. - // As a side effect, this also prevents scrolling the page when - // mouse is over a ComboBox, but this would also the case when - // scrolling items in the ComboBox is enabled. - MouseArea { - anchors.fill: parent - onWheel: { - // do nothing - wheel.accepted = true; - } - onPressed: { - // propogate to ComboBox - mouse.accepted = false; - } - onReleased: { - // propogate to ComboBox - mouse.accepted = false; + onClicked: popup.toggleShow() + + Component.onCompleted: { + if (currentIndex === -1) { + currentIndex = 0 + } + popup.resolveTextValue(textRole) + } + + function textAt (index) { + if (index >= count || index < 0) + return null; + return popupItems.objectAt(index).text; + } + + function find (text) { + for (var i = 0 ; i < popupItems.count ; ++i) { + var currentString = popupItems.objectAt(i).text + if (text === currentString) { + return i + } + } + return -1 + } + + Menu { + id: popup + __minimumWidth: combo.width + __visualItem: combo + + style: MenuStyle { + font: combo.font + __menuItemType: "comboboxitem" + __scrollerStyle: ScrollViewStyle { } + } + + property string textRole: "" + property bool showing: false + + property string currentText: selectedText + property string selectedText + + onSelectedTextChanged: popup.currentText = selectedText + + on__SelectedIndexChanged: { + if (__selectedIndex === -1) + popup.currentText = "" + else + updateSelectedText() + } + + property int _y: combo.height + property bool _modelIsArray: false + + onAboutToShow: showing = true + onAboutToHide: showing = false + + function toggleShow() { + if (popup._popupVisible) { + popup.__dismissAndDestroy() + } else { + __popup(Qt.rect(0, _y, 0, 0), 0) + } + } + + function resolveTextValue(initialTextRole) { + if (!model) { + 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 + } + } + updateSelectedText() + } + + function updateSelectedText() { + var selectedItem + if (__selectedIndex !== -1 && (selectedItem = items[__selectedIndex])) { + selectedText = Qt.binding(function () { return selectedItem.text }) + if (currentText !== selectedText) // __selectedIndex went form -1 to 0 + selectedTextChanged() + } + } + + Component { + id: menuItemComponent + + MenuItem { + property int index + + onTriggered: { + //console.log("onTriggered", index, currentIndex) + if (index !== currentIndex) { + //console.log("activated", index) + activated(index) + } + } + } + } + + Instantiator { + id: popupItems + + onModelChanged: { + popup._modelIsArray = !!model ? model.constructor === Array : false + popup.resolveTextValue(popup.textRole) + } + + onObjectAdded: { + // There is a bug in Instantiator which can cause objects to be added out of order from an index standpoint. + // If not handled correcty this will cause menu items to be added incorrectly due to the way Menu.insertItem works. + //console.log("menu add", index, object.text) + if (index === popup.__selectedIndex) { + popup.selectedText = object["text"] + } + + // Find the correct place for menu item. We can't just add at index, due to possible bad ordering + var insertIndex = -1 + for (var i=0; i index) { + insertIndex = i + break + } + } + if (insertIndex === -1) { + popup.insertItem(popup.items.length, object) + } else { + //console.log("out of order menu add", index, insertIndex) + popup.insertItem(insertIndex, object) + } + } + + onObjectRemoved: popup.removeItem(object) + + MenuItem { + text: popup.textRole === '' ? modelData : ((popup._modelIsArray ? modelData[popup.textRole] : model[popup.textRole]) || '') + + property int itemIndex: index + + onTriggered: { + //console.log("onTriggered", index, currentIndex) + if (index !== currentIndex) { + //console.log("activated", index) + activated(index) + } + } + } } } }