import QtQuick 2.3 import QtQuick.Controls 1.2 import QtQuick.Controls.Styles 1.4 import QGroundControl.Palette 1.0 import QGroundControl.ScreenTools 1.0 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 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 source: "/qmlimages/arrow-down.png" 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 } } } onClicked: { combo.focus = true 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 correctly 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) } } } } } }