From 698e6bb16d12b1e7e926f07fcc386a093f8e59f9 Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Thu, 13 Oct 2016 18:38:05 -0700 Subject: [PATCH] Switch ArduPIlot setup pages to new SetupPage usage Also added new FirmwarePlugin::brandImage support --- qgcresources.qrc | 2 + .../APM/APMAirframeComponent.cc | 4 +- .../APM/APMAirframeComponent.qml | 102 +- .../APM/APMCameraComponent.cc | 2 +- .../APM/APMCameraComponent.qml | 844 +++++++------ .../APM/APMFlightModesComponent.cc | 2 +- .../APM/APMFlightModesComponent.qml | 28 +- .../APM/APMPowerComponent.qml | 616 +++++----- .../APM/APMSafetyComponent.cc | 2 +- .../APM/APMSafetyComponentCopter.qml | 829 +++++++------ .../APM/APMSafetyComponentPlane.qml | 315 +++-- .../APM/APMSensorsComponent.cc | 3 +- .../APM/APMSensorsComponent.qml | 1084 +++++++++-------- .../APM/APMTuningComponent.cc | 2 +- .../APM/APMTuningComponentCopter.qml | 239 ++-- src/FirmwarePlugin/APM/APMBrandImage.png | Bin 0 -> 27565 bytes src/FirmwarePlugin/APM/APMFirmwarePlugin.h | 1 + src/FirmwarePlugin/FirmwarePlugin.h | 3 + src/FirmwarePlugin/PX4/PX4BrandImage.png | Bin 0 -> 4863 bytes src/FirmwarePlugin/PX4/PX4FirmwarePlugin.h | 1 + src/Vehicle/Vehicle.cc | 5 + src/Vehicle/Vehicle.h | 2 + src/ui/toolbar/MainToolBar.qml | 13 +- 23 files changed, 2000 insertions(+), 2099 deletions(-) create mode 100644 src/FirmwarePlugin/APM/APMBrandImage.png create mode 100644 src/FirmwarePlugin/PX4/PX4BrandImage.png diff --git a/qgcresources.qrc b/qgcresources.qrc index 418f773e97..da73e2a672 100644 --- a/qgcresources.qrc +++ b/qgcresources.qrc @@ -151,6 +151,8 @@ src/AutoPilotPlugins/Common/Images/ArrowDirection.svg src/AutoPilotPlugins/Common/Images/ArrowCW.svg src/AutoPilotPlugins/Common/Images/ArrowCCW.svg + src/FirmwarePlugin/APM/APMBrandImage.png + src/FirmwarePlugin/PX4/PX4BrandImage.png resources/Antenna_RC.svg diff --git a/src/AutoPilotPlugins/APM/APMAirframeComponent.cc b/src/AutoPilotPlugins/APM/APMAirframeComponent.cc index ccbb3ee0c2..d6aedc86db 100644 --- a/src/AutoPilotPlugins/APM/APMAirframeComponent.cc +++ b/src/AutoPilotPlugins/APM/APMAirframeComponent.cc @@ -32,8 +32,8 @@ QString APMAirframeComponent::name(void) const QString APMAirframeComponent::description(void) const { - return tr("The Airframe Component is used to select the airframe which matches your vehicle. " - "This will in turn set up the various tuning values for flight parameters."); + return tr("Airframe Setup is used to select the airframe which matches your vehicle. " + "You can also the load default parameter values associated with known vehicle types."); } QString APMAirframeComponent::iconResource(void) const diff --git a/src/AutoPilotPlugins/APM/APMAirframeComponent.qml b/src/AutoPilotPlugins/APM/APMAirframeComponent.qml index 62ae84b3e6..29ec60771b 100644 --- a/src/AutoPilotPlugins/APM/APMAirframeComponent.qml +++ b/src/AutoPilotPlugins/APM/APMAirframeComponent.qml @@ -11,26 +11,24 @@ import QtQuick 2.5 import QtQuick.Controls 1.2 import QtQuick.Dialogs 1.2 +import QtQuick.Layouts 1.2 import QGroundControl.FactSystem 1.0 -import QGroundControl.FactControls 1.0 import QGroundControl.Palette 1.0 import QGroundControl.Controls 1.0 import QGroundControl.Controllers 1.0 import QGroundControl.ScreenTools 1.0 -QGCView { - id: qgcView - viewPanel: panel - - QGCPalette { id: qgcPal; colorGroupEnabled: panel.enabled } +SetupPage { + id: airframePage + pageComponent: airframePageComponent property real _margins: ScreenTools.defaultFontPixelWidth property Fact _frame: controller.getParameterFact(-1, "FRAME") APMAirframeComponentController { id: controller - factPanel: panel + factPanel: airframePage.viewPanel } ExclusiveGroup { @@ -89,73 +87,47 @@ QGCView { } } - QGCViewPanel { - id: panel - anchors.fill: parent - - Item { - id: helpApplyRow - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - height: Math.max(helpText.contentHeight, applyButton.height) + Component { + id: airframePageComponent - QGCLabel { - id: helpText - anchors.rightMargin: _margins - anchors.left: parent.left - anchors.right: applyButton.right - text: qsTr("Please select your airframe type") - font.pointSize: ScreenTools.mediumFontPointSize - wrapMode: Text.WordWrap - } + Column { + width: availableWidth + height: 1000 + spacing: _margins - QGCButton { - id: applyButton + RowLayout { + anchors.left: parent.left anchors.right: parent.right - text: qsTr("Load common parameters") - onClicked: showDialog(applyRestartDialogComponent, qsTr("Load common parameters"), qgcView.showDialogDefaultWidth, StandardButton.Close) - } - } - - Item { - id: helpSpacer - anchors.top: helpApplyRow.bottom - height: parent.spacerHeight - width: 10 - } - - QGCFlickable { - id: scroll - anchors.top: helpSpacer.bottom - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - contentHeight: frameColumn.height - contentWidth: frameColumn.width - + spacing: _margins + QGCLabel { + font.pointSize: ScreenTools.mediumFontPointSize + wrapMode: Text.WordWrap + text: qsTr("Please select your airframe type") + Layout.fillWidth: true + } - Column { - id: frameColumn - spacing: _margins + QGCButton { + text: qsTr("Load common parameters") + onClicked: showDialog(applyRestartDialogComponent, qsTr("Load common parameters"), qgcView.showDialogDefaultWidth, StandardButton.Close) + } + } - Repeater { - model: controller.airframeTypesModel + Repeater { + model: controller.airframeTypesModel - QGCRadioButton { - text: object.name - checked: controller.currentAirframeType == object - exclusiveGroup: airframeTypeExclusive + QGCRadioButton { + text: object.name + checked: controller.currentAirframeType == object + exclusiveGroup: airframeTypeExclusive - onCheckedChanged: { - if (checked) { - controller.currentAirframeType = object - } + onCheckedChanged: { + if (checked) { + controller.currentAirframeType = object } } } } - } - } // QGCViewPanel -} // QGCView + } // Column + } // Component - pageComponent +} // SetupPage diff --git a/src/AutoPilotPlugins/APM/APMCameraComponent.cc b/src/AutoPilotPlugins/APM/APMCameraComponent.cc index 8a850bb3c9..5d597b5e8d 100644 --- a/src/AutoPilotPlugins/APM/APMCameraComponent.cc +++ b/src/AutoPilotPlugins/APM/APMCameraComponent.cc @@ -29,7 +29,7 @@ QString APMCameraComponent::name(void) const QString APMCameraComponent::description(void) const { - return tr("The Camera Component is used to setup camera and gimbal settings."); + return tr("Camera setup is used to adjust camera and gimbal settings."); } QString APMCameraComponent::iconResource(void) const diff --git a/src/AutoPilotPlugins/APM/APMCameraComponent.qml b/src/AutoPilotPlugins/APM/APMCameraComponent.qml index 1e7699f477..6ff44a5ceb 100644 --- a/src/AutoPilotPlugins/APM/APMCameraComponent.qml +++ b/src/AutoPilotPlugins/APM/APMCameraComponent.qml @@ -17,440 +17,436 @@ import QGroundControl.Palette 1.0 import QGroundControl.Controls 1.0 import QGroundControl.ScreenTools 1.0 -QGCView { - id: _cameraView - viewPanel: panel - anchors.fill: parent - - FactPanelController { id: controller; factPanel: panel } - - QGCPalette { id: palette; colorGroupEnabled: enabled } - - property Fact _mountRetractX: controller.getParameterFact(-1, "MNT_RETRACT_X") - property Fact _mountRetractY: controller.getParameterFact(-1, "MNT_RETRACT_Y") - property Fact _mountRetractZ: controller.getParameterFact(-1, "MNT_RETRACT_Z") - - property Fact _mountNeutralX: controller.getParameterFact(-1, "MNT_NEUTRAL_X") - property Fact _mountNeutralY: controller.getParameterFact(-1, "MNT_NEUTRAL_Y") - property Fact _mountNeutralZ: controller.getParameterFact(-1, "MNT_NEUTRAL_Z") - - property Fact _mountRCInTilt: controller.getParameterFact(-1, "MNT_RC_IN_TILT") - property Fact _mountStabTilt: controller.getParameterFact(-1, "MNT_STAB_TILT") - property Fact _mountAngMinTilt: controller.getParameterFact(-1, "MNT_ANGMIN_TIL") - property Fact _mountAngMaxTilt: controller.getParameterFact(-1, "MNT_ANGMAX_TIL") - - property Fact _mountRCInRoll: controller.getParameterFact(-1, "MNT_RC_IN_ROLL") - property Fact _mountStabRoll: controller.getParameterFact(-1, "MNT_STAB_ROLL") - property Fact _mountAngMinRoll: controller.getParameterFact(-1, "MNT_ANGMIN_ROL") - property Fact _mountAngMaxRoll: controller.getParameterFact(-1, "MNT_ANGMAX_ROL") - - property Fact _mountRCInPan: controller.getParameterFact(-1, "MNT_RC_IN_PAN") - property Fact _mountStabPan: controller.getParameterFact(-1, "MNT_STAB_PAN") - property Fact _mountAngMinPan: controller.getParameterFact(-1, "MNT_ANGMIN_PAN") - property Fact _mountAngMaxPan: controller.getParameterFact(-1, "MNT_ANGMAX_PAN") - - property Fact _rc5Function: controller.getParameterFact(-1, "RC5_FUNCTION") - property Fact _rc6Function: controller.getParameterFact(-1, "RC6_FUNCTION") - property Fact _rc7Function: controller.getParameterFact(-1, "RC7_FUNCTION") - property Fact _rc8Function: controller.getParameterFact(-1, "RC8_FUNCTION") - property Fact _rc9Function: controller.getParameterFact(-1, "RC9_FUNCTION") - property Fact _rc10Function: controller.getParameterFact(-1, "RC10_FUNCTION") - property Fact _rc11Function: controller.getParameterFact(-1, "RC11_FUNCTION") - property Fact _rc12Function: controller.getParameterFact(-1, "RC12_FUNCTION") - property Fact _rc13Function: controller.getParameterFact(-1, "RC13_FUNCTION") - property Fact _rc14Function: controller.getParameterFact(-1, "RC14_FUNCTION") - - property bool _tiltEnabled: false - property bool _panEnabled: false - property bool _rollEnabled: false - - // Gimbal Settings not available on older firmware - property bool _showGimbaLSettings: controller.parameterExists(-1, "MNT_DEFLT_MODE") - - readonly property real _margins: ScreenTools.defaultFontPixelHeight - readonly property int _rcFunctionDisabled: 0 - readonly property int _rcFunctionMountPan: 6 - readonly property int _rcFunctionMountTilt: 7 - readonly property int _rcFunctionMountRoll: 8 - readonly property int _firstGimbalOutChannel: 5 - readonly property int _lastGimbalOutChannel: 14 - readonly property int _mountDefaultModeRCTargetting: 3 - - Component.onCompleted: { - if (_showGimbaLSettings) { - gimbalSettingsLoader.sourceComponent = gimbalSettings - } - calcGimbalOutValues() - } - - function setGimbalSettingsServoInfo(loader, channel) { - var rcPrefix = "RC" + channel + "_" - - loader.gimbalOutIndex = channel - 4 - loader.servoPWMMinFact = controller.getParameterFact(-1, rcPrefix + "MIN") - loader.servoPWMMaxFact = controller.getParameterFact(-1, rcPrefix + "MAX") - loader.servoReverseFact = controller.getParameterFact(-1, rcPrefix + "REV") - } - - /// Gimbal output channels are stored in RC#_FUNCTION parameters. We need to loop through those - /// to find them and setup the ui accordindly. - function calcGimbalOutValues() { - gimbalDirectionTiltLoader.gimbalOutIndex = 0 - gimbalDirectionPanLoader.gimbalOutIndex = 0 - gimbalDirectionRollLoader.gimbalOutIndex = 0 - _tiltEnabled = false - _panEnabled = false - _rollEnabled = false - for (var channel=_firstGimbalOutChannel; channel<=_lastGimbalOutChannel; channel++) { - var functionFact = controller.getParameterFact(-1, "RC" + channel + "_FUNCTION") - if (functionFact.value == _rcFunctionMountTilt) { - _tiltEnabled = true - setGimbalSettingsServoInfo(gimbalDirectionTiltLoader, channel) - } else if (functionFact.value == _rcFunctionMountPan) { - _panEnabled = true - setGimbalSettingsServoInfo(gimbalDirectionPanLoader, channel) - } else if (functionFact.value == _rcFunctionMountRoll) { - _rollEnabled = true - setGimbalSettingsServoInfo(gimbalDirectionRollLoader, channel) - } - } - } - - function setRCFunction(channel, rcFunction) { - // First clear any previous settings for this function - for (var index=_firstGimbalOutChannel; index<=_lastGimbalOutChannel; index++) { - var functionFact = controller.getParameterFact(-1, "RC" + index + "_FUNCTION") - if (functionFact.value != _rcFunctionDisabled && functionFact.value == rcFunction) { - functionFact.value = _rcFunctionDisabled - } - } - - // Now set the function into the new channel - if (channel != 0) { - var functionFact = controller.getParameterFact(-1, "RC" + channel + "_FUNCTION") - functionFact.value = rcFunction - } - } - - // Whenever any RC#_FUNCTION parameters chagnes we need to go looking for gimbal output channels again - Connections { target: _rc5Function; onValueChanged: calcGimbalOutValues() } - Connections { target: _rc6Function; onValueChanged: calcGimbalOutValues() } - Connections { target: _rc7Function; onValueChanged: calcGimbalOutValues() } - Connections { target: _rc8Function; onValueChanged: calcGimbalOutValues() } - Connections { target: _rc9Function; onValueChanged: calcGimbalOutValues() } - Connections { target: _rc10Function; onValueChanged: calcGimbalOutValues() } - Connections { target: _rc11Function; onValueChanged: calcGimbalOutValues() } - Connections { target: _rc12Function; onValueChanged: calcGimbalOutValues() } - Connections { target: _rc13Function; onValueChanged: calcGimbalOutValues() } - Connections { target: _rc14Function; onValueChanged: calcGimbalOutValues() } - - // Whenever an MNT_RC_IN_* setting is changed make sure to turn on RC targeting - Connections { - target: _mountRCInPan - onValueChanged: _mountDefaultMode.value = _mountDefaultModeRCTargetting - } - - Connections { - target: _mountRCInRoll - onValueChanged: _mountDefaultMode.value = _mountDefaultModeRCTargetting - } - - Connections { - target: _mountRCInTilt - onValueChanged: _mountDefaultMode.value = _mountDefaultModeRCTargetting - } - - ListModel { - id: gimbalOutModel - ListElement { text: qsTr("Disabled"); value: 0 } - ListElement { text: qsTr("Channel 5"); value: 5 } - ListElement { text: qsTr("Channel 6"); value: 6 } - ListElement { text: qsTr("Channel 7"); value: 7 } - ListElement { text: qsTr("Channel 8"); value: 8 } - ListElement { text: qsTr("Channel 9"); value: 9 } - ListElement { text: qsTr("Channel 10"); value: 10 } - ListElement { text: qsTr("Channel 11"); value: 11 } - ListElement { text: qsTr("Channel 12"); value: 12 } - ListElement { text: qsTr("Channel 13"); value: 13 } - ListElement { text: qsTr("Channel 14"); value: 14 } - } +SetupPage { + id: cameraPage + pageComponent: cameraPageComponent Component { - id: gimbalDirectionSettings - - // The following properties must be set in the Loader - // property string directionTitle - // property bool directionEnabled - // property int gimbalOutIndex - // property Fact mountRcInFact - // property Fact mountStabFact - // property Fact mountAngMinFact - // property Fact mountAngMaxFact - // property Fact servoPWMMinFact - // property Fact servoPWMMaxFact - // property Fact servoReverseFact - // property int rcFunction - - Item { - width: rectangle.x + rectangle.width - height: rectangle.y + rectangle.height - - QGCLabel { - id: directionLabel - text: qsTr("Gimbal ") + directionTitle - font.family: ScreenTools.demiboldFontFamily - } - - Rectangle { - id: rectangle - anchors.topMargin: _margins / 2 - anchors.left: parent.left - anchors.top: directionLabel.bottom - width: mountAngMaxField.x + mountAngMaxField.width + _margins - height: servoPWMMaxField.y + servoPWMMaxField.height + _margins - color: palette.windowShade - - FactCheckBox { - id: mountStabCheckBox - anchors.topMargin: _margins - anchors.left: servoReverseCheckBox.left - anchors.top: parent.top - text: qsTr("Stabilize") - fact: mountStabFact - checkedValue: 1 - uncheckedValue: 0 - enabled: directionEnabled - } - - FactCheckBox { - id: servoReverseCheckBox - anchors.margins: _margins - anchors.top: mountStabCheckBox.bottom - anchors.right: parent.right - text: qsTr("Servo reverse") - checkedValue: 1 - uncheckedValue: 0 - fact: servoReverseFact - enabled: directionEnabled - } - - QGCLabel { - id: gimbalOutLabel - anchors.margins: _margins - anchors.left: parent.left - anchors.baseline: gimbalOutCombo.baseline - text: qsTr("Output channel:") - } - - QGCComboBox { - id: gimbalOutCombo - anchors.margins: _margins - anchors.top: parent.top - anchors.left: gimbalOutLabel.right - width: mountAngMinField.width - model: gimbalOutModel - currentIndex: gimbalOutIndex - - onActivated: setRCFunction(gimbalOutModel.get(index).value, rcFunction) - } - - QGCLabel { - id: mountRcInLabel - anchors.margins: _margins - anchors.left: parent.left - anchors.baseline: mountRcInCombo.baseline - text: qsTr("Input channel:") - enabled: directionEnabled - } - - FactComboBox { - id: mountRcInCombo - anchors.topMargin: _margins / 2 - anchors.top: gimbalOutCombo.bottom - anchors.left: gimbalOutCombo.left - width: mountAngMinField.width - fact: mountRcInFact - indexModel: false - enabled: directionEnabled - } - - QGCLabel { - id: mountAngLabel - anchors.margins: _margins - anchors.left: parent.left - anchors.baseline: mountAngMinField.baseline - text: qsTr("Gimbal angle limits:") - enabled: directionEnabled - } - - QGCLabel { - id: mountAngMinLabel - anchors.margins: _margins - anchors.left: mountAngLabel.right - anchors.baseline: mountAngMinField.baseline - text: qsTr("min") - enabled: directionEnabled - } - - FactTextField { - id: mountAngMinField - anchors.margins: _margins - anchors.top: mountRcInCombo.bottom - anchors.left: mountAngMinLabel.right - fact: mountAngMinFact - enabled: directionEnabled - } - - QGCLabel { - id: mountAngMaxLabel - anchors.margins: _margins - anchors.left: mountAngMinField.right - anchors.baseline: mountAngMinField.baseline - text: qsTr("max") - enabled: directionEnabled - } - - FactTextField { - id: mountAngMaxField - anchors.leftMargin: _margins - anchors.top: mountAngMinField.top - anchors.left: mountAngMaxLabel.right - fact: mountAngMaxFact - enabled: directionEnabled - } - - QGCLabel { - id: servoPWMLabel - anchors.margins: _margins - anchors.left: parent.left - anchors.baseline: servoPWMMinField.baseline - text: qsTr("Servo PWM limits:") - enabled: directionEnabled - } - - QGCLabel { - id: servoPWMMinLabel - anchors.left: mountAngMinLabel.left - anchors.baseline: servoPWMMinField.baseline - text: qsTr("min") - enabled: directionEnabled + id: cameraPageComponent + + Column { + spacing: _margins + width: availableWidth + + FactPanelController { id: controller; factPanel: cameraPage.viewPanel } + + QGCPalette { id: palette; colorGroupEnabled: true } + + property Fact _mountRetractX: controller.getParameterFact(-1, "MNT_RETRACT_X") + property Fact _mountRetractY: controller.getParameterFact(-1, "MNT_RETRACT_Y") + property Fact _mountRetractZ: controller.getParameterFact(-1, "MNT_RETRACT_Z") + + property Fact _mountNeutralX: controller.getParameterFact(-1, "MNT_NEUTRAL_X") + property Fact _mountNeutralY: controller.getParameterFact(-1, "MNT_NEUTRAL_Y") + property Fact _mountNeutralZ: controller.getParameterFact(-1, "MNT_NEUTRAL_Z") + + property Fact _mountRCInTilt: controller.getParameterFact(-1, "MNT_RC_IN_TILT") + property Fact _mountStabTilt: controller.getParameterFact(-1, "MNT_STAB_TILT") + property Fact _mountAngMinTilt: controller.getParameterFact(-1, "MNT_ANGMIN_TIL") + property Fact _mountAngMaxTilt: controller.getParameterFact(-1, "MNT_ANGMAX_TIL") + + property Fact _mountRCInRoll: controller.getParameterFact(-1, "MNT_RC_IN_ROLL") + property Fact _mountStabRoll: controller.getParameterFact(-1, "MNT_STAB_ROLL") + property Fact _mountAngMinRoll: controller.getParameterFact(-1, "MNT_ANGMIN_ROL") + property Fact _mountAngMaxRoll: controller.getParameterFact(-1, "MNT_ANGMAX_ROL") + + property Fact _mountRCInPan: controller.getParameterFact(-1, "MNT_RC_IN_PAN") + property Fact _mountStabPan: controller.getParameterFact(-1, "MNT_STAB_PAN") + property Fact _mountAngMinPan: controller.getParameterFact(-1, "MNT_ANGMIN_PAN") + property Fact _mountAngMaxPan: controller.getParameterFact(-1, "MNT_ANGMAX_PAN") + + property Fact _rc5Function: controller.getParameterFact(-1, "RC5_FUNCTION") + property Fact _rc6Function: controller.getParameterFact(-1, "RC6_FUNCTION") + property Fact _rc7Function: controller.getParameterFact(-1, "RC7_FUNCTION") + property Fact _rc8Function: controller.getParameterFact(-1, "RC8_FUNCTION") + property Fact _rc9Function: controller.getParameterFact(-1, "RC9_FUNCTION") + property Fact _rc10Function: controller.getParameterFact(-1, "RC10_FUNCTION") + property Fact _rc11Function: controller.getParameterFact(-1, "RC11_FUNCTION") + property Fact _rc12Function: controller.getParameterFact(-1, "RC12_FUNCTION") + property Fact _rc13Function: controller.getParameterFact(-1, "RC13_FUNCTION") + property Fact _rc14Function: controller.getParameterFact(-1, "RC14_FUNCTION") + + property bool _tiltEnabled: false + property bool _panEnabled: false + property bool _rollEnabled: false + + // Gimbal Settings not available on older firmware + property bool _showGimbaLSettings: controller.parameterExists(-1, "MNT_DEFLT_MODE") + + readonly property real _margins: ScreenTools.defaultFontPixelHeight + readonly property int _rcFunctionDisabled: 0 + readonly property int _rcFunctionMountPan: 6 + readonly property int _rcFunctionMountTilt: 7 + readonly property int _rcFunctionMountRoll: 8 + readonly property int _firstGimbalOutChannel: 5 + readonly property int _lastGimbalOutChannel: 14 + readonly property int _mountDefaultModeRCTargetting: 3 + + Component.onCompleted: { + if (_showGimbaLSettings) { + gimbalSettingsLoader.sourceComponent = gimbalSettings } + calcGimbalOutValues() + } - FactTextField { - id: servoPWMMinField - anchors.topMargin: _margins / 2 - anchors.leftMargin: _margins - anchors.top: mountAngMaxField.bottom - anchors.left: servoPWMMinLabel.right - fact: servoPWMMinFact - enabled: directionEnabled - } + function setGimbalSettingsServoInfo(loader, channel) { + var rcPrefix = "RC" + channel + "_" - QGCLabel { - id: servoPWMMaxLabel - anchors.margins: _margins - anchors.left: servoPWMMinField.right - anchors.baseline: servoPWMMinField.baseline - text: qsTr("max") - enabled: directionEnabled - } + loader.gimbalOutIndex = channel - 4 + loader.servoPWMMinFact = controller.getParameterFact(-1, rcPrefix + "MIN") + loader.servoPWMMaxFact = controller.getParameterFact(-1, rcPrefix + "MAX") + loader.servoReverseFact = controller.getParameterFact(-1, rcPrefix + "REV") + } - FactTextField { - id: servoPWMMaxField - anchors.leftMargin: _margins - anchors.top: servoPWMMinField.top - anchors.left: servoPWMMaxLabel.right - fact: servoPWMMaxFact - enabled: directionEnabled + /// Gimbal output channels are stored in RC#_FUNCTION parameters. We need to loop through those + /// to find them and setup the ui accordindly. + function calcGimbalOutValues() { + gimbalDirectionTiltLoader.gimbalOutIndex = 0 + gimbalDirectionPanLoader.gimbalOutIndex = 0 + gimbalDirectionRollLoader.gimbalOutIndex = 0 + _tiltEnabled = false + _panEnabled = false + _rollEnabled = false + for (var channel=_firstGimbalOutChannel; channel<=_lastGimbalOutChannel; channel++) { + var functionFact = controller.getParameterFact(-1, "RC" + channel + "_FUNCTION") + if (functionFact.value == _rcFunctionMountTilt) { + _tiltEnabled = true + setGimbalSettingsServoInfo(gimbalDirectionTiltLoader, channel) + } else if (functionFact.value == _rcFunctionMountPan) { + _panEnabled = true + setGimbalSettingsServoInfo(gimbalDirectionPanLoader, channel) + } else if (functionFact.value == _rcFunctionMountRoll) { + _rollEnabled = true + setGimbalSettingsServoInfo(gimbalDirectionRollLoader, channel) + } } - } // Rectangle - } // Item - } // Component - gimbalDirectionSettings - - Component { - id: gimbalSettings - - Item { - width: rectangle.x + rectangle.width - height: rectangle.y + rectangle.height - - property Fact _mountDefaultMode: controller.getParameterFact(-1, "MNT_DEFLT_MODE") - property Fact _mountType: controller.getParameterFact(-1, "MNT_TYPE") - - QGCLabel { - id: settingsLabel - text: qsTr("Gimbal Settings") - font.family: ScreenTools.demiboldFontFamily } - Rectangle { - id: rectangle - anchors.topMargin: _margins / 2 - anchors.top: settingsLabel.bottom - width: gimbalModeCombo.x + gimbalModeCombo.width + _margins - height: gimbalModeCombo.y + gimbalModeCombo.height + _margins - color: palette.windowShade - - QGCLabel { - id: gimbalTypeLabel - anchors.margins: _margins - anchors.left: parent.left - anchors.baseline: gimbalTypeCombo.baseline - text: qsTr("Type:") + function setRCFunction(channel, rcFunction) { + // First clear any previous settings for this function + for (var index=_firstGimbalOutChannel; index<=_lastGimbalOutChannel; index++) { + var functionFact = controller.getParameterFact(-1, "RC" + index + "_FUNCTION") + if (functionFact.value != _rcFunctionDisabled && functionFact.value == rcFunction) { + functionFact.value = _rcFunctionDisabled + } } - FactComboBox { - id: gimbalTypeCombo - anchors.topMargin: _margins - anchors.top: parent.top - anchors.left: gimbalModeCombo.left - width: gimbalModeCombo.width - fact: _mountType - indexModel: false + // Now set the function into the new channel + if (channel != 0) { + var functionFact = controller.getParameterFact(-1, "RC" + channel + "_FUNCTION") + functionFact.value = rcFunction } + } - QGCLabel { - id: rebootLabel - anchors.topMargin: _margins / 2 - anchors.leftMargin: _margins - anchors.rightMargin: _margins - anchors.left: parent.left - anchors.right: parent.right - anchors.top: gimbalTypeCombo.bottom - wrapMode: Text.WordWrap - text: qsTr("Gimbal Type changes takes affect next reboot of autopilot") - } + // Whenever any RC#_FUNCTION parameters chagnes we need to go looking for gimbal output channels again + Connections { target: _rc5Function; onValueChanged: calcGimbalOutValues() } + Connections { target: _rc6Function; onValueChanged: calcGimbalOutValues() } + Connections { target: _rc7Function; onValueChanged: calcGimbalOutValues() } + Connections { target: _rc8Function; onValueChanged: calcGimbalOutValues() } + Connections { target: _rc9Function; onValueChanged: calcGimbalOutValues() } + Connections { target: _rc10Function; onValueChanged: calcGimbalOutValues() } + Connections { target: _rc11Function; onValueChanged: calcGimbalOutValues() } + Connections { target: _rc12Function; onValueChanged: calcGimbalOutValues() } + Connections { target: _rc13Function; onValueChanged: calcGimbalOutValues() } + Connections { target: _rc14Function; onValueChanged: calcGimbalOutValues() } + + // Whenever an MNT_RC_IN_* setting is changed make sure to turn on RC targeting + Connections { + target: _mountRCInPan + onValueChanged: _mountDefaultMode.value = _mountDefaultModeRCTargetting + } - QGCLabel { - id: gimbalModeLabel - anchors.margins: _margins - anchors.left: parent.left - anchors.baseline: gimbalModeCombo.baseline - text: qsTr("Default Mode:") - } + Connections { + target: _mountRCInRoll + onValueChanged: _mountDefaultMode.value = _mountDefaultModeRCTargetting + } - FactComboBox { - id: gimbalModeCombo - anchors.margins: _margins - anchors.top: rebootLabel.bottom - anchors.left: gimbalModeLabel.right - width: ScreenTools.defaultFontPixelWidth * 15 - fact: _mountDefaultMode - indexModel: false - } - } // Rectangle - } // Item - } // Component - gimbalSettings + Connections { + target: _mountRCInTilt + onValueChanged: _mountDefaultMode.value = _mountDefaultModeRCTargetting + } - QGCViewPanel { - id: panel - anchors.fill: parent + ListModel { + id: gimbalOutModel + ListElement { text: qsTr("Disabled"); value: 0 } + ListElement { text: qsTr("Channel 5"); value: 5 } + ListElement { text: qsTr("Channel 6"); value: 6 } + ListElement { text: qsTr("Channel 7"); value: 7 } + ListElement { text: qsTr("Channel 8"); value: 8 } + ListElement { text: qsTr("Channel 9"); value: 9 } + ListElement { text: qsTr("Channel 10"); value: 10 } + ListElement { text: qsTr("Channel 11"); value: 11 } + ListElement { text: qsTr("Channel 12"); value: 12 } + ListElement { text: qsTr("Channel 13"); value: 13 } + ListElement { text: qsTr("Channel 14"); value: 14 } + } - QGCFlickable { - clip: true - anchors.fill: parent - contentWidth: gimbalDirectionTiltLoader.x + gimbalDirectionTiltLoader.width - contentHeight: _showGimbaLSettings ? gimbalSettingsLoader.y + gimbalSettingsLoader.height : gimbalDirectionPanLoader.y + gimbalDirectionPanLoader.height + Component { + id: gimbalDirectionSettings + + // The following properties must be set in the Loader + // property string directionTitle + // property bool directionEnabled + // property int gimbalOutIndex + // property Fact mountRcInFact + // property Fact mountStabFact + // property Fact mountAngMinFact + // property Fact mountAngMaxFact + // property Fact servoPWMMinFact + // property Fact servoPWMMaxFact + // property Fact servoReverseFact + // property int rcFunction + + Item { + width: rectangle.x + rectangle.width + height: rectangle.y + rectangle.height + + QGCLabel { + id: directionLabel + text: qsTr("Gimbal ") + directionTitle + font.family: ScreenTools.demiboldFontFamily + } + + Rectangle { + id: rectangle + anchors.topMargin: _margins / 2 + anchors.left: parent.left + anchors.top: directionLabel.bottom + width: mountAngMaxField.x + mountAngMaxField.width + _margins + height: servoPWMMaxField.y + servoPWMMaxField.height + _margins + color: palette.windowShade + + FactCheckBox { + id: mountStabCheckBox + anchors.topMargin: _margins + anchors.left: servoReverseCheckBox.left + anchors.top: parent.top + text: qsTr("Stabilize") + fact: mountStabFact + checkedValue: 1 + uncheckedValue: 0 + enabled: directionEnabled + } + + FactCheckBox { + id: servoReverseCheckBox + anchors.margins: _margins + anchors.top: mountStabCheckBox.bottom + anchors.right: parent.right + text: qsTr("Servo reverse") + checkedValue: 1 + uncheckedValue: 0 + fact: servoReverseFact + enabled: directionEnabled + } + + QGCLabel { + id: gimbalOutLabel + anchors.margins: _margins + anchors.left: parent.left + anchors.baseline: gimbalOutCombo.baseline + text: qsTr("Output channel:") + } + + QGCComboBox { + id: gimbalOutCombo + anchors.margins: _margins + anchors.top: parent.top + anchors.left: gimbalOutLabel.right + width: mountAngMinField.width + model: gimbalOutModel + currentIndex: gimbalOutIndex + + onActivated: setRCFunction(gimbalOutModel.get(index).value, rcFunction) + } + + QGCLabel { + id: mountRcInLabel + anchors.margins: _margins + anchors.left: parent.left + anchors.baseline: mountRcInCombo.baseline + text: qsTr("Input channel:") + enabled: directionEnabled + } + + FactComboBox { + id: mountRcInCombo + anchors.topMargin: _margins / 2 + anchors.top: gimbalOutCombo.bottom + anchors.left: gimbalOutCombo.left + width: mountAngMinField.width + fact: mountRcInFact + indexModel: false + enabled: directionEnabled + } + + QGCLabel { + id: mountAngLabel + anchors.margins: _margins + anchors.left: parent.left + anchors.baseline: mountAngMinField.baseline + text: qsTr("Gimbal angle limits:") + enabled: directionEnabled + } + + QGCLabel { + id: mountAngMinLabel + anchors.margins: _margins + anchors.left: mountAngLabel.right + anchors.baseline: mountAngMinField.baseline + text: qsTr("min") + enabled: directionEnabled + } + + FactTextField { + id: mountAngMinField + anchors.margins: _margins + anchors.top: mountRcInCombo.bottom + anchors.left: mountAngMinLabel.right + fact: mountAngMinFact + enabled: directionEnabled + } + + QGCLabel { + id: mountAngMaxLabel + anchors.margins: _margins + anchors.left: mountAngMinField.right + anchors.baseline: mountAngMinField.baseline + text: qsTr("max") + enabled: directionEnabled + } + + FactTextField { + id: mountAngMaxField + anchors.leftMargin: _margins + anchors.top: mountAngMinField.top + anchors.left: mountAngMaxLabel.right + fact: mountAngMaxFact + enabled: directionEnabled + } + + QGCLabel { + id: servoPWMLabel + anchors.margins: _margins + anchors.left: parent.left + anchors.baseline: servoPWMMinField.baseline + text: qsTr("Servo PWM limits:") + enabled: directionEnabled + } + + QGCLabel { + id: servoPWMMinLabel + anchors.left: mountAngMinLabel.left + anchors.baseline: servoPWMMinField.baseline + text: qsTr("min") + enabled: directionEnabled + } + + FactTextField { + id: servoPWMMinField + anchors.topMargin: _margins / 2 + anchors.leftMargin: _margins + anchors.top: mountAngMaxField.bottom + anchors.left: servoPWMMinLabel.right + fact: servoPWMMinFact + enabled: directionEnabled + } + + QGCLabel { + id: servoPWMMaxLabel + anchors.margins: _margins + anchors.left: servoPWMMinField.right + anchors.baseline: servoPWMMinField.baseline + text: qsTr("max") + enabled: directionEnabled + } + + FactTextField { + id: servoPWMMaxField + anchors.leftMargin: _margins + anchors.top: servoPWMMinField.top + anchors.left: servoPWMMaxLabel.right + fact: servoPWMMaxFact + enabled: directionEnabled + } + } // Rectangle + } // Item + } // Component - gimbalDirectionSettings + + Component { + id: gimbalSettings + + Item { + width: rectangle.x + rectangle.width + height: rectangle.y + rectangle.height + + property Fact _mountDefaultMode: controller.getParameterFact(-1, "MNT_DEFLT_MODE") + property Fact _mountType: controller.getParameterFact(-1, "MNT_TYPE") + + QGCLabel { + id: settingsLabel + text: qsTr("Gimbal Settings") + font.family: ScreenTools.demiboldFontFamily + } + + Rectangle { + id: rectangle + anchors.topMargin: _margins / 2 + anchors.top: settingsLabel.bottom + width: gimbalModeCombo.x + gimbalModeCombo.width + _margins + height: gimbalModeCombo.y + gimbalModeCombo.height + _margins + color: palette.windowShade + + QGCLabel { + id: gimbalTypeLabel + anchors.margins: _margins + anchors.left: parent.left + anchors.baseline: gimbalTypeCombo.baseline + text: qsTr("Type:") + } + + FactComboBox { + id: gimbalTypeCombo + anchors.topMargin: _margins + anchors.top: parent.top + anchors.left: gimbalModeCombo.left + width: gimbalModeCombo.width + fact: _mountType + indexModel: false + } + + QGCLabel { + id: rebootLabel + anchors.topMargin: _margins / 2 + anchors.leftMargin: _margins + anchors.rightMargin: _margins + anchors.left: parent.left + anchors.right: parent.right + anchors.top: gimbalTypeCombo.bottom + wrapMode: Text.WordWrap + text: qsTr("Gimbal Type changes takes affect next reboot of autopilot") + } + + QGCLabel { + id: gimbalModeLabel + anchors.margins: _margins + anchors.left: parent.left + anchors.baseline: gimbalModeCombo.baseline + text: qsTr("Default Mode:") + } + + FactComboBox { + id: gimbalModeCombo + anchors.margins: _margins + anchors.top: rebootLabel.bottom + anchors.left: gimbalModeLabel.right + width: ScreenTools.defaultFontPixelWidth * 15 + fact: _mountDefaultMode + indexModel: false + } + } // Rectangle + } // Item + } // Component - gimbalSettings Loader { id: gimbalDirectionTiltLoader @@ -471,8 +467,6 @@ QGCView { Loader { id: gimbalDirectionRollLoader - anchors.margins: _margins - anchors.top: gimbalDirectionTiltLoader.bottom sourceComponent: gimbalDirectionSettings property string directionTitle: qsTr("Roll") @@ -490,8 +484,6 @@ QGCView { Loader { id: gimbalDirectionPanLoader - anchors.margins: _margins - anchors.top: gimbalDirectionRollLoader.bottom sourceComponent: gimbalDirectionSettings property string directionTitle: qsTr("Pan") @@ -508,10 +500,8 @@ QGCView { } Loader { - id: gimbalSettingsLoader - anchors.margins: _margins - anchors.top: gimbalDirectionPanLoader.bottom + id: gimbalSettingsLoader } - } // Flickable - } // QGCViewPanel -} // QGCView + } // Column + } // Component +} // SetupPage diff --git a/src/AutoPilotPlugins/APM/APMFlightModesComponent.cc b/src/AutoPilotPlugins/APM/APMFlightModesComponent.cc index 7229a8ad9b..6a2babe081 100644 --- a/src/AutoPilotPlugins/APM/APMFlightModesComponent.cc +++ b/src/AutoPilotPlugins/APM/APMFlightModesComponent.cc @@ -26,7 +26,7 @@ QString APMFlightModesComponent::name(void) const QString APMFlightModesComponent::description(void) const { - return QStringLiteral("The Flight Modes Component is used to assign FLight Modes to Channel 5."); + return tr("Flight Modes Setup is used to configure the transmitter switches associated with Flight Modes."); } QString APMFlightModesComponent::iconResource(void) const diff --git a/src/AutoPilotPlugins/APM/APMFlightModesComponent.qml b/src/AutoPilotPlugins/APM/APMFlightModesComponent.qml index f958c3c231..623be09030 100644 --- a/src/AutoPilotPlugins/APM/APMFlightModesComponent.qml +++ b/src/AutoPilotPlugins/APM/APMFlightModesComponent.qml @@ -18,9 +18,9 @@ import QGroundControl.Controls 1.0 import QGroundControl.Controllers 1.0 import QGroundControl.ScreenTools 1.0 -QGCView { - id: rootQGCView - viewPanel: panel +SetupPage { + id: flightModePage + pageComponent: flightModePageComponent readonly property string _modeChannelParam: controller.modeChannelParam readonly property string _modeParamPrefix: controller.modeParamPrefix @@ -33,26 +33,19 @@ QGCView { property bool _fltmodeChExists: controller.parameterExists(-1, _modeChannelParam) property Fact _fltmodeCh: _fltmodeChExists ? controller.getParameterFact(-1, _modeChannelParam) : _nullFact - QGCPalette { id: qgcPal; colorGroupEnabled: panel.enabled } + QGCPalette { id: qgcPal; colorGroupEnabled: true } APMFlightModesComponentController { id: controller - factPanel: panel + factPanel: flightModePage.viewPanel } - QGCViewPanel { - id: panel - anchors.fill: parent - - QGCFlickable { - anchors.fill: parent - clip: true - contentHeight: flowLayout.height - contentWidth: flowLayout.width + Component { + id: flightModePageComponent Flow { id: flowLayout - width: panel.width // parent.width doesn't work here for some reason! + width: availableWidth spacing: _margins Column { @@ -181,6 +174,5 @@ QGCView { } // Rectangle - Channel options } // Column - Channel options } // Flow - } // QGCFlickable - } // QGCViewPanel -} // QGCView + } // Component - flightModePageComponent +} // SetupPage diff --git a/src/AutoPilotPlugins/APM/APMPowerComponent.qml b/src/AutoPilotPlugins/APM/APMPowerComponent.qml index 410945bf72..9a1c6b19e9 100644 --- a/src/AutoPilotPlugins/APM/APMPowerComponent.qml +++ b/src/AutoPilotPlugins/APM/APMPowerComponent.qml @@ -11,6 +11,7 @@ import QtQuick 2.5 import QtQuick.Controls 1.2 import QtQuick.Dialogs 1.2 +import QtQuick.Layouts 1.2 import QGroundControl.FactSystem 1.0 import QGroundControl.FactControls 1.0 @@ -18,384 +19,327 @@ import QGroundControl.Palette 1.0 import QGroundControl.Controls 1.0 import QGroundControl.ScreenTools 1.0 -QGCView { - id: rootQGCView - viewPanel: panel - - property Fact battAmpPerVolt: controller.getParameterFact(-1, "BATT_AMP_PERVOLT") - property Fact battCapacity: controller.getParameterFact(-1, "BATT_CAPACITY") - property Fact battCurrPin: controller.getParameterFact(-1, "BATT_CURR_PIN") - property Fact battMonitor: controller.getParameterFact(-1, "BATT_MONITOR") - property Fact battVoltMult: controller.getParameterFact(-1, "BATT_VOLT_MULT") - property Fact battVoltPin: controller.getParameterFact(-1, "BATT_VOLT_PIN") - - property real _margins: ScreenTools.defaultFontPixelHeight - property bool _showAdvanced: sensorCombo.currentIndex == sensorModel.count - 1 - - Component.onCompleted: calcSensor() - - function calcSensor() { - for (var i=0; i 0 - text: qsTr("Loiter above Home for:") + QGCRadioButton { + id: returnAltRadio + anchors.topMargin: _margins + anchors.left: returnAtCurrentRadio.left + anchors.top: returnAtCurrentRadio.bottom + text: qsTr("Return at specified altitude:") + exclusiveGroup: returnAltRadioGroup + checked: _rtlAltFact.value != 0 - onClicked: _rtlLoitTimeFact.value = (checked ? 60 : 0) - } + onClicked: _rtlAltFact.value = 1500 + } - FactTextField { - id: landDelayField - anchors.topMargin: _margins * 1.5 - anchors.left: rltAltField.left - anchors.top: rltAltField.bottom - fact: _rtlLoitTimeFact - showUnits: true - enabled: homeLoiterCheckbox.checked === true - } + FactTextField { + id: rltAltField + anchors.leftMargin: _margins + anchors.left: returnAltRadio.right + anchors.baseline: returnAltRadio.baseline + fact: _rtlAltFact + showUnits: true + enabled: returnAltRadio.checked + } - QGCRadioButton { - id: landRadio - anchors.left: returnAtCurrentRadio.left - anchors.baseline: landSpeedField.baseline - text: qsTr("Land with descent speed:") - checked: _rtlAltFinalFact.value == 0 - exclusiveGroup: landLoiterRadioGroup + QGCCheckBox { + id: homeLoiterCheckbox + anchors.left: returnAtCurrentRadio.left + anchors.baseline: landDelayField.baseline + checked: _rtlLoitTimeFact.value > 0 + text: qsTr("Loiter above Home for:") - onClicked: _rtlAltFinalFact.value = 0 - } + onClicked: _rtlLoitTimeFact.value = (checked ? 60 : 0) + } - FactTextField { - id: landSpeedField - anchors.topMargin: _margins * 1.5 - anchors.top: landDelayField.bottom - anchors.left: rltAltField.left - fact: _landSpeedFact - showUnits: true - enabled: landRadio.checked - } + FactTextField { + id: landDelayField + anchors.topMargin: _margins * 1.5 + anchors.left: rltAltField.left + anchors.top: rltAltField.bottom + fact: _rtlLoitTimeFact + showUnits: true + enabled: homeLoiterCheckbox.checked === true + } - QGCRadioButton { - id: finalLoiterRadio - anchors.left: returnAtCurrentRadio.left - anchors.baseline: rltAltFinalField.baseline - text: qsTr("Final loiter altitude:") - exclusiveGroup: landLoiterRadioGroup + QGCRadioButton { + id: landRadio + anchors.left: returnAtCurrentRadio.left + anchors.baseline: landSpeedField.baseline + text: qsTr("Land with descent speed:") + checked: _rtlAltFinalFact.value == 0 + exclusiveGroup: landLoiterRadioGroup - onClicked: _rtlAltFinalFact.value = _rtlAltFact.value - } + onClicked: _rtlAltFinalFact.value = 0 + } - FactTextField { - id: rltAltFinalField - anchors.topMargin: _margins / 2 - anchors.left: rltAltField.left - anchors.top: landSpeedField.bottom - fact: _rtlAltFinalFact - enabled: finalLoiterRadio.checked - showUnits: true - } - } // Rectangle - RTL Settings - } // Column - RTL Settings + FactTextField { + id: landSpeedField + anchors.topMargin: _margins * 1.5 + anchors.top: landDelayField.bottom + anchors.left: rltAltField.left + fact: _landSpeedFact + showUnits: true + enabled: landRadio.checked + } - Column { - spacing: _margins / 2 + QGCRadioButton { + id: finalLoiterRadio + anchors.left: returnAtCurrentRadio.left + anchors.baseline: rltAltFinalField.baseline + text: qsTr("Final loiter altitude:") + exclusiveGroup: landLoiterRadioGroup - QGCLabel { - text: qsTr("Arming Checks") - font.family: ScreenTools.demiboldFontFamily + onClicked: _rtlAltFinalFact.value = _rtlAltFact.value } - Rectangle { - width: flowLayout.width - height: armingCheckInnerColumn.height + (_margins * 2) - color: ggcPal.windowShade - - Column { - id: armingCheckInnerColumn - anchors.margins: _margins - anchors.top: parent.top + FactTextField { + id: rltAltFinalField + anchors.topMargin: _margins / 2 + anchors.left: rltAltField.left + anchors.top: landSpeedField.bottom + fact: _rtlAltFinalFact + enabled: finalLoiterRadio.checked + showUnits: true + } + } // Rectangle - RTL Settings + } // Column - RTL Settings + + Column { + spacing: _margins / 2 + + QGCLabel { + text: qsTr("Arming Checks") + font.family: ScreenTools.demiboldFontFamily + } + + Rectangle { + width: flowLayout.width + height: armingCheckInnerColumn.height + (_margins * 2) + color: ggcPal.windowShade + + Column { + id: armingCheckInnerColumn + anchors.margins: _margins + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + spacing: _margins + + FactBitmask { + id: armingCheckBitmask anchors.left: parent.left anchors.right: parent.right - spacing: _margins - - FactBitmask { - id: armingCheckBitmask - anchors.left: parent.left - anchors.right: parent.right - firstEntryIsAll: true - fact: _armingCheck - } + firstEntryIsAll: true + fact: _armingCheck + } - QGCLabel { - id: armingCheckWarning - anchors.left: parent.left - anchors.right: parent.right - wrapMode: Text.WordWrap - color: qgcPal.warningText - text: qsTr("Warning: Turning off arming checks can lead to loss of Vehicle control.") - visible: _armingCheck.value != 1 - } + QGCLabel { + id: armingCheckWarning + anchors.left: parent.left + anchors.right: parent.right + wrapMode: Text.WordWrap + color: qgcPal.warningText + text: qsTr("Warning: Turning off arming checks can lead to loss of Vehicle control.") + visible: _armingCheck.value != 1 } - } // Rectangle - Arming checks - } // Column - Arming Checks - } // Flow - } // QGCFlickable - } // QGCViewPanel -} // QGCView + } + } // Rectangle - Arming checks + } // Column - Arming Checks + } // Flow + } // Component - safetyPageComponent +} // SetupView diff --git a/src/AutoPilotPlugins/APM/APMSafetyComponentPlane.qml b/src/AutoPilotPlugins/APM/APMSafetyComponentPlane.qml index d1934d3d18..6b1515b38c 100644 --- a/src/AutoPilotPlugins/APM/APMSafetyComponentPlane.qml +++ b/src/AutoPilotPlugins/APM/APMSafetyComponentPlane.qml @@ -18,180 +18,171 @@ import QGroundControl.Palette 1.0 import QGroundControl.Controls 1.0 import QGroundControl.ScreenTools 1.0 -QGCView { - id: _safetyView - viewPanel: panel - anchors.fill: parent +SetupPage { + id: safetyPage + pageComponent: safetyPageComponent - FactPanelController { id: controller; factPanel: panel } + Component { + id: safetyPageComponent - QGCPalette { id: palette; colorGroupEnabled: enabled } + Flow { + id: flowLayout + width: availableWidth + spacing: _margins - property Fact _failsafeBattMah: controller.getParameterFact(-1, "FS_BATT_MAH") - property Fact _failsafeBattVoltage: controller.getParameterFact(-1, "FS_BATT_VOLTAGE") - property Fact _failsafeThrEnable: controller.getParameterFact(-1, "THR_FAILSAFE") - property Fact _failsafeThrValue: controller.getParameterFact(-1, "THR_FS_VALUE") - property Fact _failsafeGCSEnable: controller.getParameterFact(-1, "FS_GCS_ENABL") + FactPanelController { id: controller; factPanel: safetyPage.viewPanel } - property Fact _rtlAltFact: controller.getParameterFact(-1, "ALT_HOLD_RTL") + QGCPalette { id: palette; colorGroupEnabled: true } - property real _margins: ScreenTools.defaultFontPixelHeight + property Fact _failsafeBattMah: controller.getParameterFact(-1, "FS_BATT_MAH") + property Fact _failsafeBattVoltage: controller.getParameterFact(-1, "FS_BATT_VOLTAGE") + property Fact _failsafeThrEnable: controller.getParameterFact(-1, "THR_FAILSAFE") + property Fact _failsafeThrValue: controller.getParameterFact(-1, "THR_FS_VALUE") + property Fact _failsafeGCSEnable: controller.getParameterFact(-1, "FS_GCS_ENABL") - ExclusiveGroup { id: returnAltRadioGroup } + property Fact _rtlAltFact: controller.getParameterFact(-1, "ALT_HOLD_RTL") - QGCViewPanel { - id: panel - anchors.fill: parent + property real _margins: ScreenTools.defaultFontPixelHeight - QGCFlickable { - clip: true - anchors.fill: parent - contentWidth: flowLayout.width - contentHeight: flowLayout.height + ExclusiveGroup { id: returnAltRadioGroup } - Flow { - id: flowLayout - width: panel.width // parent.width doesn't work for some reason - spacing: _margins + Column { + spacing: _margins / 2 - Column { - spacing: _margins / 2 + QGCLabel { + text: qsTr("Failsafe Triggers") + font.family: ScreenTools.demiboldFontFamily + } - QGCLabel { - text: qsTr("Failsafe Triggers") - font.family: ScreenTools.demiboldFontFamily + Rectangle { + width: throttlePWMField.x + throttlePWMField.width + _margins + height: gcsCheckbox.y + gcsCheckbox.height + _margins + color: palette.windowShade + + QGCCheckBox { + id: throttleEnableCheckBox + anchors.margins: _margins + anchors.left: parent.left + anchors.baseline: throttlePWMField.baseline + text: qsTr("Throttle PWM threshold:") + checked: _failsafeThrEnable.value == 1 + + onClicked: _failsafeThrEnable.value = (checked ? 1 : 0) + } + + FactTextField { + id: throttlePWMField + anchors.margins: _margins + anchors.left: throttleEnableCheckBox.right + anchors.top: parent.top + fact: _failsafeThrValue + showUnits: true + enabled: throttleEnableCheckBox.checked + } + + QGCCheckBox { + id: voltageCheckBox + anchors.margins: _margins + anchors.left: parent.left + anchors.baseline: voltageField.baseline + text: qsTr("Voltage threshold:") + checked: _failsafeBattVoltage.value != 0 + + onClicked: _failsafeBattVoltage.value = checked ? 10.5 : 0 + } + + FactTextField { + id: voltageField + anchors.topMargin: _margins + anchors.left: throttlePWMField.left + anchors.top: throttlePWMField.bottom + fact: _failsafeBattVoltage + showUnits: true + enabled: voltageCheckBox.checked + } + + QGCCheckBox { + id: mahCheckBox + anchors.margins: _margins + anchors.left: parent.left + anchors.baseline: mahField.baseline + text: qsTr("MAH threshold:") + checked: _failsafeBattMah.value != 0 + + onClicked: _failsafeBattMah.value = checked ? 600 : 0 + } + + FactTextField { + id: mahField + anchors.topMargin: _margins / 2 + anchors.left: throttlePWMField.left + anchors.top: voltageField.bottom + fact: _failsafeBattMah + showUnits: true + enabled: mahCheckBox.checked + } + + QGCCheckBox { + id: gcsCheckbox + anchors.margins: _margins + anchors.left: parent.left + anchors.top: mahField.bottom + text: qsTr("GCS failsafe") + checked: _failsafeGCSEnable.value != 0 + + onClicked: _failsafeGCSEnable.value = checked ? 1 : 0 + } + } // Rectangle - Failsafe trigger settings + } // Column - Failsafe trigger settings + + Column { + spacing: _margins / 2 + + QGCLabel { + text: qsTr("Return to Launch") + font.family: ScreenTools.demiboldFontFamily + } + + Rectangle { + width: rltAltField.x + rltAltField.width + _margins + height: rltAltField.y + rltAltField.height + _margins + color: palette.windowShade + + QGCRadioButton { + id: returnAtCurrentRadio + anchors.margins: _margins + anchors.left: parent.left + anchors.top: parent.top + text: qsTr("Return at current altitude") + checked: _rtlAltFact.value < 0 + exclusiveGroup: returnAltRadioGroup + + onClicked: _rtlAltFact.value = -1 } - Rectangle { - width: throttlePWMField.x + throttlePWMField.width + _margins - height: gcsCheckbox.y + gcsCheckbox.height + _margins - color: palette.windowShade - - QGCCheckBox { - id: throttleEnableCheckBox - anchors.margins: _margins - anchors.left: parent.left - anchors.baseline: throttlePWMField.baseline - text: qsTr("Throttle PWM threshold:") - checked: _failsafeThrEnable.value == 1 - - onClicked: _failsafeThrEnable.value = (checked ? 1 : 0) - } - - FactTextField { - id: throttlePWMField - anchors.margins: _margins - anchors.left: throttleEnableCheckBox.right - anchors.top: parent.top - fact: _failsafeThrValue - showUnits: true - enabled: throttleEnableCheckBox.checked - } - - QGCCheckBox { - id: voltageCheckBox - anchors.margins: _margins - anchors.left: parent.left - anchors.baseline: voltageField.baseline - text: qsTr("Voltage threshold:") - checked: _failsafeBattVoltage.value != 0 - - onClicked: _failsafeBattVoltage.value = checked ? 10.5 : 0 - } - - FactTextField { - id: voltageField - anchors.topMargin: _margins - anchors.left: throttlePWMField.left - anchors.top: throttlePWMField.bottom - fact: _failsafeBattVoltage - showUnits: true - enabled: voltageCheckBox.checked - } - - QGCCheckBox { - id: mahCheckBox - anchors.margins: _margins - anchors.left: parent.left - anchors.baseline: mahField.baseline - text: qsTr("MAH threshold:") - checked: _failsafeBattMah.value != 0 - - onClicked: _failsafeBattMah.value = checked ? 600 : 0 - } - - FactTextField { - id: mahField - anchors.topMargin: _margins / 2 - anchors.left: throttlePWMField.left - anchors.top: voltageField.bottom - fact: _failsafeBattMah - showUnits: true - enabled: mahCheckBox.checked - } - - QGCCheckBox { - id: gcsCheckbox - anchors.margins: _margins - anchors.left: parent.left - anchors.top: mahField.bottom - text: qsTr("GCS failsafe") - checked: _failsafeGCSEnable.value != 0 - - onClicked: _failsafeGCSEnable.value = checked ? 1 : 0 - } - } // Rectangle - Failsafe trigger settings - } // Column - Failsafe trigger settings - - Column { - spacing: _margins / 2 - - QGCLabel { - text: qsTr("Return to Launch") - font.family: ScreenTools.demiboldFontFamily + QGCRadioButton { + id: returnAltRadio + anchors.topMargin: _margins / 2 + anchors.left: returnAtCurrentRadio.left + anchors.top: returnAtCurrentRadio.bottom + text: qsTr("Return at specified altitude:") + exclusiveGroup: returnAltRadioGroup + checked: _rtlAltFact.value >= 0 + + onClicked: _rtlAltFact.value = 10000 } - Rectangle { - width: rltAltField.x + rltAltField.width + _margins - height: rltAltField.y + rltAltField.height + _margins - color: palette.windowShade - - QGCRadioButton { - id: returnAtCurrentRadio - anchors.margins: _margins - anchors.left: parent.left - anchors.top: parent.top - text: qsTr("Return at current altitude") - checked: _rtlAltFact.value < 0 - exclusiveGroup: returnAltRadioGroup - - onClicked: _rtlAltFact.value = -1 - } - - QGCRadioButton { - id: returnAltRadio - anchors.topMargin: _margins / 2 - anchors.left: returnAtCurrentRadio.left - anchors.top: returnAtCurrentRadio.bottom - text: qsTr("Return at specified altitude:") - exclusiveGroup: returnAltRadioGroup - checked: _rtlAltFact.value >= 0 - - onClicked: _rtlAltFact.value = 10000 - } - - FactTextField { - id: rltAltField - anchors.leftMargin: _margins - anchors.left: returnAltRadio.right - anchors.baseline: returnAltRadio.baseline - fact: _rtlAltFact - showUnits: true - enabled: returnAltRadio.checked - } - } // Rectangle - RTL Settings - } // Column - RTL Settings - } // Flow - } // QGCFlickable - } // QGCViewPanel -} // QGCView + FactTextField { + id: rltAltField + anchors.leftMargin: _margins + anchors.left: returnAltRadio.right + anchors.baseline: returnAltRadio.baseline + fact: _rtlAltFact + showUnits: true + enabled: returnAltRadio.checked + } + } // Rectangle - RTL Settings + } // Column - RTL Settings + } // Flow + } // Component +} // SetupView diff --git a/src/AutoPilotPlugins/APM/APMSensorsComponent.cc b/src/AutoPilotPlugins/APM/APMSensorsComponent.cc index 3f45eeca45..e5933b5541 100644 --- a/src/AutoPilotPlugins/APM/APMSensorsComponent.cc +++ b/src/AutoPilotPlugins/APM/APMSensorsComponent.cc @@ -30,8 +30,7 @@ QString APMSensorsComponent::name(void) const QString APMSensorsComponent::description(void) const { - return tr("The Sensors Component allows you to calibrate the sensors within your vehicle. " - "Prior to flight you must calibrate the Magnetometer, Gyroscope and Accelerometer."); + return tr("Sensors Setup is used to calibrate the sensors within your vehicle."); } QString APMSensorsComponent::iconResource(void) const diff --git a/src/AutoPilotPlugins/APM/APMSensorsComponent.qml b/src/AutoPilotPlugins/APM/APMSensorsComponent.qml index 533e0b143a..bb87f6985f 100644 --- a/src/AutoPilotPlugins/APM/APMSensorsComponent.qml +++ b/src/AutoPilotPlugins/APM/APMSensorsComponent.qml @@ -8,624 +8,628 @@ ****************************************************************************/ -import QtQuick 2.2 -import QtQuick.Controls 1.2 -import QtQuick.Controls.Styles 1.2 -import QtQuick.Dialogs 1.2 - -import QGroundControl.FactSystem 1.0 -import QGroundControl.FactControls 1.0 -import QGroundControl.Palette 1.0 -import QGroundControl.Controls 1.0 -import QGroundControl.ScreenTools 1.0 -import QGroundControl.Controllers 1.0 - -QGCView { - id: qgcView - viewPanel: panel - - // Help text which is shown both in the status text area prior to pressing a cal button and in the - // pre-calibration dialog. - - readonly property string orientationHelpSet: "If the orientation is in the direction of flight, select None." - readonly property string orientationHelpCal: "Before calibrating make sure orientation settings are correct. " + orientationHelpSet - readonly property string compassRotationText: "If the compass or GPS module is mounted in flight direction, leave the default value (None)" - - readonly property string compassHelp: "For Compass calibration you will need to rotate your vehicle through a number of positions." - readonly property string gyroHelp: "For Gyroscope calibration you will need to place your vehicle on a surface and leave it still." - readonly property string accelHelp: "For Accelerometer calibration you will need to place your vehicle on all six sides on a perfectly level surface and hold it still in each orientation for a few seconds." - readonly property string levelHelp: "To level the horizon you need to place the vehicle in its level flight position and press OK." - readonly property string airspeedHelp: "For Airspeed calibration you will need to keep your airspeed sensor out of any wind and then blow across the sensor." - - readonly property string statusTextAreaDefaultText: "Start the individual calibration steps by clicking one of the buttons to the left." - - // Used to pass help text to the preCalibrationDialog dialog - property string preCalibrationDialogHelp - - property string _postCalibrationDialogText - property var _postCalibrationDialogParams - - readonly property string _badCompassCalText: "The calibration for Compass %1 appears to be poor. " + - "Check the compass position within your vehicle and re-do the calibration." - - readonly property int sideBarH1PointSize: ScreenTools.mediumFontPointSize - readonly property int mainTextH1PointSize: ScreenTools.mediumFontPointSize // Seems to be unused - - readonly property int rotationColumnWidth: 250 - - property Fact compass1Id: controller.getParameterFact(-1, "COMPASS_DEV_ID") - property Fact compass2Id: controller.getParameterFact(-1, "COMPASS_DEV_ID2") - property Fact compass3Id: controller.getParameterFact(-1, "COMPASS_DEV_ID3") - property Fact compass1ExternalFact: controller.getParameterFact(-1, "COMPASS_EXTERNAL") - property Fact compass1Rot: controller.getParameterFact(-1, "COMPASS_ORIENT") - - property Fact boardRot: controller.getParameterFact(-1, "AHRS_ORIENTATION") - - property bool accelCalNeeded: controller.accelSetupNeeded - property bool compassCalNeeded: controller.compassSetupNeeded - - - // The following parameters are not available in olders firmwares - - property bool compass2ExternalParamAvailable: controller.parameterExists(-1, "COMPASS_EXTERN2") - property bool compass3ExternalParamAvailable: controller.parameterExists(-1, "COMPASS_EXTERN3") - property bool compass2RotParamAvailable: controller.parameterExists(-1, "COMPASS_ORIENT2") - property bool compass3RotParamAvailable: controller.parameterExists(-1, "COMPASS_ORIENT3") - property bool compass1UseParamAvailable: controller.parameterExists(-1, "COMPASS_USE") - property bool compass2UseParamAvailable: controller.parameterExists(-1, "COMPASS_USE2") - property bool compass3UseParamAvailable: controller.parameterExists(-1, "COMPASS_USE3") - - property Fact noFact: Fact { } - property Fact compass2ExternalFact: compass2ExternalParamAvailable ? controller.getParameterFact(-1, "COMPASS_EXTERN2") : noFact - property Fact compass3ExternalFact: compass3ExternalParamAvailable ? controller.getParameterFact(-1, "COMPASS_EXTERN3") : noFact - property Fact compass2Rot: compass2RotParamAvailable ? controller.getParameterFact(-1, "COMPASS_ORIENT2") : noFact - property Fact compass3Rot: compass3RotParamAvailable ? controller.getParameterFact(-1, "COMPASS_ORIENT3") : noFact - property Fact compass1UseFact: compass1UseParamAvailable ? controller.getParameterFact(-1, "COMPASS_USE") : noFact - property Fact compass2UseFact: compass2UseParamAvailable ? controller.getParameterFact(-1, "COMPASS_USE2") : noFact - property Fact compass3UseFact: compass3UseParamAvailable ? controller.getParameterFact(-1, "COMPASS_USE3") : noFact - - // We track these values by binding through a separate property so we can handle missing params - property bool compass1External: compass1ExternalFact.value - property bool compass2External: compass2ExternalParamAvailable ? compass2ExternalFact.value : false // false: Simulate internal so we don't show rotation combos - property bool compass3External: compass3ExternalParamAvailable ? compass3ExternalFact.value : false // false: Simulate internal so we don't show rotation combos - property bool compass1Use: compass1UseParamAvailable ? compass1UseFact.value : true - property bool compass2Use: compass2UseParamAvailable ? compass2UseFact.value : true - property bool compass3Use: compass3UseParamAvailable ? compass3UseFact.value : true - - // Id > = signals compass available, rot < 0 signals internal compass - property bool showCompass1: compass1Id.value > 0 - property bool showCompass2: compass2Id.value > 0 - property bool showCompass3: compass3Id.value > 0 - - readonly property int _calTypeCompass: 1 ///< Calibrate compass - readonly property int _calTypeAccel: 2 ///< Calibrate accel - readonly property int _calTypeSet: 3 ///< Set orientations only - - property bool _orientationsDialogShowCompass: true - property string _orientationDialogHelp: orientationHelpSet - property int _orientationDialogCalType - - function validCompassOffsets(compassParamPrefix) { - var ofsX = controller.getParameterFact(-1, compassParamPrefix + "X") - var ofsY = controller.getParameterFact(-1, compassParamPrefix + "Y") - var ofsZ = controller.getParameterFact(-1, compassParamPrefix + "Z") - return Math.sqrt(ofsX.value^2 + ofsY.value^2 + ofsZ.value^2) < 600 - } - - function showOrientationsDialog(calType) { - var dialogTitle - var buttons = StandardButton.Ok - - _orientationDialogCalType = calType - switch (calType) { - case _calTypeCompass: - _orientationsDialogShowCompass = true - _orientationDialogHelp = orientationHelpCal - dialogTitle = qsTr("Calibrate Compass") - buttons |= StandardButton.Cancel - break - case _calTypeAccel: - _orientationsDialogShowCompass = false - _orientationDialogHelp = orientationHelpCal - dialogTitle = qsTr("Calibrate Accelerometer") - buttons |= StandardButton.Cancel - break - case _calTypeSet: - _orientationsDialogShowCompass = true - _orientationDialogHelp = orientationHelpSet - dialogTitle = qsTr("Sensor Settings") - break - } - - showDialog(orientationsDialogComponent, dialogTitle, qgcView.showDialogDefaultWidth, buttons) - } - - APMSensorsComponentController { - id: controller - factPanel: panel - statusLog: statusTextArea - progressBar: progressBar - compassButton: compassButton - accelButton: accelButton - compassMotButton: motorInterferenceButton - nextButton: nextButton - cancelButton: cancelButton - setOrientationsButton: setOrientationsButton - orientationCalAreaHelpText: orientationCalAreaHelpText - - onResetStatusTextArea: statusLog.text = statusTextAreaDefaultText - - onWaitingForCancelChanged: { - if (controller.waitingForCancel) { - showMessage(qsTr("Calibration Cancel"), qsTr("Waiting for Vehicle to response to Cancel. This may take a few seconds."), 0) - } else { - hideDialog() - } - } - - onCalibrationComplete: { - if (_orientationDialogCalType == _calTypeAccel) { - _postCalibrationDialogText = qsTr("Accelerometer calibration complete.") - _postCalibrationDialogParams = [ "INS_ACCSCAL_X", "INS_ACCSCAL_Y", "INS_ACCSCAL_Z", - "INS_ACC2SCAL_X", "INS_ACC2SCAL_Y", "INS_ACC2SCAL_Z", - "INS_ACC3SCAL_X", "INS_ACC3SCAL_Y", "INS_ACC3SCAL_Z", - "INS_GYROFFS_X", "INS_GYROFFS_Y", "INS_GYROFFS_Z", - "INS_GYR2OFFS_X", "INS_GYR2OFFS_Y", "INS_GYR2OFFS_Z", - "INS_GYR3OFFS_X", "INS_GYR3OFFS_Y", "INS_GYR3OFFS_Z" ] - showDialog(postCalibrationDialogComponent, qsTr("Calibration complete"), qgcView.showDialogDefaultWidth, StandardButton.Ok) - } else if (_orientationDialogCalType == _calTypeCompass) { - _postCalibrationDialogText = qsTr("Compass calibration complete. ") - _postCalibrationDialogParams = []; - if (compass1Id.value > 0) { - if (!validCompassOffsets("COMPASS_OFS_")) { - _postCalibrationDialogText += _badCompassCalText.replace("%1", 1) - } - _postCalibrationDialogParams.push("COMPASS_OFS_X") - _postCalibrationDialogParams.push("COMPASS_OFS_Y") - _postCalibrationDialogParams.push("COMPASS_OFS_Z") - } - if (compass2Id.value > 0) { - if (!validCompassOffsets("COMPASS_OFS_")) { - _postCalibrationDialogText += _badCompassCalText.replace("%1", 2) - } - _postCalibrationDialogParams.push("COMPASS_OFS2_X") - _postCalibrationDialogParams.push("COMPASS_OFS2_Y") - _postCalibrationDialogParams.push("COMPASS_OFS2_Z") - } - if (compass3Id.value > 0) { - if (!validCompassOffsets("COMPASS_OFS_")) { - _postCalibrationDialogText += _badCompassCalText.replace("%1", 3) - } - _postCalibrationDialogParams.push("COMPASS_OFS3_X") - _postCalibrationDialogParams.push("COMPASS_OFS3_Y") - _postCalibrationDialogParams.push("COMPASS_OFS3_Z") - } - showDialog(postCalibrationDialogComponent, qsTr("Calibration complete"), qgcView.showDialogDefaultWidth, StandardButton.Ok) - } - } - } - - Component.onCompleted: { - var usingUDP = controller.usingUDPLink() - if (usingUDP) { - console.log("onUsingUDPLink") - showMessage("Sensor Calibration", "Performing sensor calibration over a WiFi connection is known to be unreliable. You should disconnect and perform calibration using a direct USB connection instead.", StandardButton.Ok) - } - } - - QGCPalette { id: qgcPal; colorGroupEnabled: panel.enabled } +import QtQuick 2.2 +import QtQuick.Controls 1.2 +import QtQuick.Controls.Styles 1.2 +import QtQuick.Dialogs 1.2 +import QtQuick.Layouts 1.2 + +import QGroundControl.FactSystem 1.0 +import QGroundControl.FactControls 1.0 +import QGroundControl.Palette 1.0 +import QGroundControl.Controls 1.0 +import QGroundControl.ScreenTools 1.0 +import QGroundControl.Controllers 1.0 + +SetupPage { + id: sensorsPage + pageComponent: sensorsPageComponent Component { - id: postCalibrationDialogComponent - - QGCViewDialog { - QGCLabel { - id: textLabel - anchors.left: parent.left - anchors.right: parent.right - wrapMode: Text.WordWrap - text: _postCalibrationDialogText - } - - QGCCheckBox { - id: showValues - anchors.topMargin: ScreenTools.defaultFontPixelHeight - anchors.top: textLabel.bottom - text: qsTr("Show values") + id: sensorsPageComponent + + RowLayout { + width: 1000//availableWidth + height: 1000//availableHeight + spacing: ScreenTools.defaultFontPixelWidth / 2 + + // Help text which is shown both in the status text area prior to pressing a cal button and in the + // pre-calibration dialog. + + readonly property string orientationHelpSet: "If the orientation is in the direction of flight, select None." + readonly property string orientationHelpCal: "Before calibrating make sure orientation settings are correct. " + orientationHelpSet + readonly property string compassRotationText: "If the compass or GPS module is mounted in flight direction, leave the default value (None)" + + readonly property string compassHelp: "For Compass calibration you will need to rotate your vehicle through a number of positions." + readonly property string gyroHelp: "For Gyroscope calibration you will need to place your vehicle on a surface and leave it still." + readonly property string accelHelp: "For Accelerometer calibration you will need to place your vehicle on all six sides on a perfectly level surface and hold it still in each orientation for a few seconds." + readonly property string levelHelp: "To level the horizon you need to place the vehicle in its level flight position and press OK." + readonly property string airspeedHelp: "For Airspeed calibration you will need to keep your airspeed sensor out of any wind and then blow across the sensor." + + readonly property string statusTextAreaDefaultText: "Start the individual calibration steps by clicking one of the buttons to the left." + + // Used to pass help text to the preCalibrationDialog dialog + property string preCalibrationDialogHelp + + property string _postCalibrationDialogText + property var _postCalibrationDialogParams + + readonly property string _badCompassCalText: "The calibration for Compass %1 appears to be poor. " + + "Check the compass position within your vehicle and re-do the calibration." + + readonly property int sideBarH1PointSize: ScreenTools.mediumFontPointSize + readonly property int mainTextH1PointSize: ScreenTools.mediumFontPointSize // Seems to be unused + + readonly property int rotationColumnWidth: 250 + + property Fact compass1Id: controller.getParameterFact(-1, "COMPASS_DEV_ID") + property Fact compass2Id: controller.getParameterFact(-1, "COMPASS_DEV_ID2") + property Fact compass3Id: controller.getParameterFact(-1, "COMPASS_DEV_ID3") + property Fact compass1ExternalFact: controller.getParameterFact(-1, "COMPASS_EXTERNAL") + property Fact compass1Rot: controller.getParameterFact(-1, "COMPASS_ORIENT") + + property Fact boardRot: controller.getParameterFact(-1, "AHRS_ORIENTATION") + + property bool accelCalNeeded: controller.accelSetupNeeded + property bool compassCalNeeded: controller.compassSetupNeeded + + + // The following parameters are not available in olders firmwares + + property bool compass2ExternalParamAvailable: controller.parameterExists(-1, "COMPASS_EXTERN2") + property bool compass3ExternalParamAvailable: controller.parameterExists(-1, "COMPASS_EXTERN3") + property bool compass2RotParamAvailable: controller.parameterExists(-1, "COMPASS_ORIENT2") + property bool compass3RotParamAvailable: controller.parameterExists(-1, "COMPASS_ORIENT3") + property bool compass1UseParamAvailable: controller.parameterExists(-1, "COMPASS_USE") + property bool compass2UseParamAvailable: controller.parameterExists(-1, "COMPASS_USE2") + property bool compass3UseParamAvailable: controller.parameterExists(-1, "COMPASS_USE3") + + property Fact noFact: Fact { } + property Fact compass2ExternalFact: compass2ExternalParamAvailable ? controller.getParameterFact(-1, "COMPASS_EXTERN2") : noFact + property Fact compass3ExternalFact: compass3ExternalParamAvailable ? controller.getParameterFact(-1, "COMPASS_EXTERN3") : noFact + property Fact compass2Rot: compass2RotParamAvailable ? controller.getParameterFact(-1, "COMPASS_ORIENT2") : noFact + property Fact compass3Rot: compass3RotParamAvailable ? controller.getParameterFact(-1, "COMPASS_ORIENT3") : noFact + property Fact compass1UseFact: compass1UseParamAvailable ? controller.getParameterFact(-1, "COMPASS_USE") : noFact + property Fact compass2UseFact: compass2UseParamAvailable ? controller.getParameterFact(-1, "COMPASS_USE2") : noFact + property Fact compass3UseFact: compass3UseParamAvailable ? controller.getParameterFact(-1, "COMPASS_USE3") : noFact + + // We track these values by binding through a separate property so we can handle missing params + property bool compass1External: compass1ExternalFact.value + property bool compass2External: compass2ExternalParamAvailable ? compass2ExternalFact.value : false // false: Simulate internal so we don't show rotation combos + property bool compass3External: compass3ExternalParamAvailable ? compass3ExternalFact.value : false // false: Simulate internal so we don't show rotation combos + property bool compass1Use: compass1UseParamAvailable ? compass1UseFact.value : true + property bool compass2Use: compass2UseParamAvailable ? compass2UseFact.value : true + property bool compass3Use: compass3UseParamAvailable ? compass3UseFact.value : true + + // Id > = signals compass available, rot < 0 signals internal compass + property bool showCompass1: compass1Id.value > 0 + property bool showCompass2: compass2Id.value > 0 + property bool showCompass3: compass3Id.value > 0 + + readonly property int _calTypeCompass: 1 ///< Calibrate compass + readonly property int _calTypeAccel: 2 ///< Calibrate accel + readonly property int _calTypeSet: 3 ///< Set orientations only + + property bool _orientationsDialogShowCompass: true + property string _orientationDialogHelp: orientationHelpSet + property int _orientationDialogCalType + + function validCompassOffsets(compassParamPrefix) { + var ofsX = controller.getParameterFact(-1, compassParamPrefix + "X") + var ofsY = controller.getParameterFact(-1, compassParamPrefix + "Y") + var ofsZ = controller.getParameterFact(-1, compassParamPrefix + "Z") + return Math.sqrt(ofsX.value^2 + ofsY.value^2 + ofsZ.value^2) < 600 } - QGCFlickable { - anchors.topMargin: ScreenTools.defaultFontPixelHeight - anchors.top: showValues.bottom - anchors.bottom: parent.bottom - contentHeight: valueColumn.height - flickableDirection: Flickable.VerticalFlick - visible: showValues.checked - - Column { - id: valueColumn + function showOrientationsDialog(calType) { + var dialogTitle + var buttons = StandardButton.Ok + + _orientationDialogCalType = calType + switch (calType) { + case _calTypeCompass: + _orientationsDialogShowCompass = true + _orientationDialogHelp = orientationHelpCal + dialogTitle = qsTr("Calibrate Compass") + buttons |= StandardButton.Cancel + break + case _calTypeAccel: + _orientationsDialogShowCompass = false + _orientationDialogHelp = orientationHelpCal + dialogTitle = qsTr("Calibrate Accelerometer") + buttons |= StandardButton.Cancel + break + case _calTypeSet: + _orientationsDialogShowCompass = true + _orientationDialogHelp = orientationHelpSet + dialogTitle = qsTr("Sensor Settings") + break + } - Repeater { - model: _postCalibrationDialogParams + showDialog(orientationsDialogComponent, dialogTitle, qgcView.showDialogDefaultWidth, buttons) + } - QGCLabel { - text: fact.name +": " + fact.valueString + APMSensorsComponentController { + id: controller + factPanel: sensorsPage.viewPanel + statusLog: statusTextArea + progressBar: progressBar + compassButton: compassButton + accelButton: accelButton + compassMotButton: motorInterferenceButton + nextButton: nextButton + cancelButton: cancelButton + setOrientationsButton: setOrientationsButton + orientationCalAreaHelpText: orientationCalAreaHelpText + + onResetStatusTextArea: statusLog.text = statusTextAreaDefaultText + + onWaitingForCancelChanged: { + if (controller.waitingForCancel) { + showMessage(qsTr("Calibration Cancel"), qsTr("Waiting for Vehicle to response to Cancel. This may take a few seconds."), 0) + } else { + hideDialog() + } + } - property Fact fact: controller.getParameterFact(-1, modelData) + onCalibrationComplete: { + if (_orientationDialogCalType == _calTypeAccel) { + _postCalibrationDialogText = qsTr("Accelerometer calibration complete.") + _postCalibrationDialogParams = [ "INS_ACCSCAL_X", "INS_ACCSCAL_Y", "INS_ACCSCAL_Z", + "INS_ACC2SCAL_X", "INS_ACC2SCAL_Y", "INS_ACC2SCAL_Z", + "INS_ACC3SCAL_X", "INS_ACC3SCAL_Y", "INS_ACC3SCAL_Z", + "INS_GYROFFS_X", "INS_GYROFFS_Y", "INS_GYROFFS_Z", + "INS_GYR2OFFS_X", "INS_GYR2OFFS_Y", "INS_GYR2OFFS_Z", + "INS_GYR3OFFS_X", "INS_GYR3OFFS_Y", "INS_GYR3OFFS_Z" ] + showDialog(postCalibrationDialogComponent, qsTr("Calibration complete"), qgcView.showDialogDefaultWidth, StandardButton.Ok) + } else if (_orientationDialogCalType == _calTypeCompass) { + _postCalibrationDialogText = qsTr("Compass calibration complete. ") + _postCalibrationDialogParams = []; + if (compass1Id.value > 0) { + if (!validCompassOffsets("COMPASS_OFS_")) { + _postCalibrationDialogText += _badCompassCalText.replace("%1", 1) + } + _postCalibrationDialogParams.push("COMPASS_OFS_X") + _postCalibrationDialogParams.push("COMPASS_OFS_Y") + _postCalibrationDialogParams.push("COMPASS_OFS_Z") } + if (compass2Id.value > 0) { + if (!validCompassOffsets("COMPASS_OFS_")) { + _postCalibrationDialogText += _badCompassCalText.replace("%1", 2) + } + _postCalibrationDialogParams.push("COMPASS_OFS2_X") + _postCalibrationDialogParams.push("COMPASS_OFS2_Y") + _postCalibrationDialogParams.push("COMPASS_OFS2_Z") + } + if (compass3Id.value > 0) { + if (!validCompassOffsets("COMPASS_OFS_")) { + _postCalibrationDialogText += _badCompassCalText.replace("%1", 3) + } + _postCalibrationDialogParams.push("COMPASS_OFS3_X") + _postCalibrationDialogParams.push("COMPASS_OFS3_Y") + _postCalibrationDialogParams.push("COMPASS_OFS3_Z") + } + showDialog(postCalibrationDialogComponent, qsTr("Calibration complete"), qgcView.showDialogDefaultWidth, StandardButton.Ok) } } } - } - } - Component { - id: orientationsDialogComponent - - QGCViewDialog { - id: orientationsDialog - - function accept() { - if (_orientationDialogCalType == _calTypeAccel) { - controller.calibrateAccel() - } else if (_orientationDialogCalType == _calTypeCompass) { - controller.calibrateCompass() + Component.onCompleted: { + var usingUDP = controller.usingUDPLink() + if (usingUDP) { + console.log("onUsingUDPLink") + showMessage("Sensor Calibration", "Performing sensor calibration over a WiFi connection is known to be unreliable. You should disconnect and perform calibration using a direct USB connection instead.", StandardButton.Ok) } - orientationsDialog.hideDialog() } - QGCFlickable { - anchors.fill: parent - contentHeight: columnLayout.height - clip: true + QGCPalette { id: qgcPal; colorGroupEnabled: true } - Column { - id: columnLayout - anchors.margins: ScreenTools.defaultFontPixelWidth - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - spacing: ScreenTools.defaultFontPixelHeight + Component { + id: postCalibrationDialogComponent + QGCViewDialog { QGCLabel { - width: parent.width - wrapMode: Text.WordWrap - text: _orientationDialogHelp + id: textLabel + anchors.left: parent.left + anchors.right: parent.right + wrapMode: Text.WordWrap + text: _postCalibrationDialogText } - Column { - QGCLabel { - text: qsTr("Autopilot Orientation:") - } - - FactComboBox { - width: rotationColumnWidth - indexModel: false - fact: boardRot - } + QGCCheckBox { + id: showValues + anchors.topMargin: ScreenTools.defaultFontPixelHeight + anchors.top: textLabel.bottom + text: qsTr("Show values") } - Column { - visible: _orientationsDialogShowCompass && showCompass1 - - FactCheckBox { - text: "Use Compass 1" - fact: compass1UseFact - } + QGCFlickable { + anchors.topMargin: ScreenTools.defaultFontPixelHeight + anchors.top: showValues.bottom + anchors.bottom: parent.bottom + contentHeight: valueColumn.height + flickableDirection: Flickable.VerticalFlick + visible: showValues.checked Column { - visible: showCompass1Rot + id: valueColumn - QGCLabel { - text: qsTr("Compass 1 Orientation:") - } + Repeater { + model: _postCalibrationDialogParams + + QGCLabel { + text: fact.name +": " + fact.valueString - FactComboBox { - width: rotationColumnWidth - indexModel: false - fact: compass1Rot + property Fact fact: controller.getParameterFact(-1, modelData) + } } } } + } + } - Column { - visible: _orientationsDialogShowCompass && showCompass2 + Component { + id: orientationsDialogComponent - FactCheckBox { - text: "Use Compass 2" - fact: compass2UseFact + QGCViewDialog { + id: orientationsDialog + + function accept() { + if (_orientationDialogCalType == _calTypeAccel) { + controller.calibrateAccel() + } else if (_orientationDialogCalType == _calTypeCompass) { + controller.calibrateCompass() } + orientationsDialog.hideDialog() + } + + QGCFlickable { + anchors.fill: parent + contentHeight: columnLayout.height + clip: true Column { - visible: showCompass1Rot + id: columnLayout + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + spacing: ScreenTools.defaultFontPixelHeight QGCLabel { - text: qsTr("Compass 2 Orientation:") + width: parent.width + wrapMode: Text.WordWrap + text: _orientationDialogHelp } - FactComboBox { - width: rotationColumnWidth - indexModel: false - fact: compass2Rot + Column { + QGCLabel { + text: qsTr("Autopilot Orientation:") + } + + FactComboBox { + width: rotationColumnWidth + indexModel: false + fact: boardRot + } } - } - } - Column { - visible: _orientationsDialogShowCompass && showCompass3 + Column { + visible: _orientationsDialogShowCompass && showCompass1 - FactCheckBox { - text: "Use Compass 3" - fact: compass3UseFact - } + FactCheckBox { + text: "Use Compass 1" + fact: compass1UseFact + } - Column { - visible: showCompass3Rot + Column { + visible: showCompass1Rot - QGCLabel { - text: qsTr("Compass 3 Orientation:") + QGCLabel { + text: qsTr("Compass 1 Orientation:") + } + + FactComboBox { + width: rotationColumnWidth + indexModel: false + fact: compass1Rot + } + } } - FactComboBox { - width: rotationColumnWidth - indexModel: false - fact: compass3Rot + Column { + visible: _orientationsDialogShowCompass && showCompass2 + + FactCheckBox { + text: "Use Compass 2" + fact: compass2UseFact + } + + Column { + visible: showCompass1Rot + + QGCLabel { + text: qsTr("Compass 2 Orientation:") + } + + FactComboBox { + width: rotationColumnWidth + indexModel: false + fact: compass2Rot + } + } } - } - } - } // Column - } // QGCFlickable - } // QGCViewDialog - } // Component - orientationsDialogComponent - Component { - id: compassMotDialogComponent + Column { + visible: _orientationsDialogShowCompass && showCompass3 - QGCViewDialog { - id: compassMotDialog + FactCheckBox { + text: "Use Compass 3" + fact: compass3UseFact + } - function accept() { - controller.calibrateMotorInterference() - compassMotDialog.hideDialog() - } + Column { + visible: showCompass3Rot - QGCFlickable { - anchors.fill: parent - contentHeight: columnLayout.height - clip: true + QGCLabel { + text: qsTr("Compass 3 Orientation:") + } - Column { - id: columnLayout - anchors.margins: ScreenTools.defaultFontPixelWidth - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - spacing: ScreenTools.defaultFontPixelHeight + FactComboBox { + width: rotationColumnWidth + indexModel: false + fact: compass3Rot + } + } + } + } // Column + } // QGCFlickable + } // QGCViewDialog + } // Component - orientationsDialogComponent - QGCLabel { - anchors.left: parent.left - anchors.right: parent.right - wrapMode: Text.WordWrap - text: "This is recommended for vehicles that have only an internal compass and on vehicles where there is significant interference on the compass from the motors, power wires, etc. " + - "CompassMot only works well if you have a battery current monitor because the magnetic interference is linear with current drawn. " + - "It is technically possible to set-up CompassMot using throttle but this is not recommended." - } + Component { + id: compassMotDialogComponent - QGCLabel { - anchors.left: parent.left - anchors.right: parent.right - wrapMode: Text.WordWrap - text: "Disconnect your props, flip them over and rotate them one position around the frame. " + - "In this configuration they should push the copter down into the ground when the throttle is raised." - } + QGCViewDialog { + id: compassMotDialog - QGCLabel { - anchors.left: parent.left - anchors.right: parent.right - wrapMode: Text.WordWrap - text: "Secure the copter (perhaps with tape) so that it does not move." + function accept() { + controller.calibrateMotorInterference() + compassMotDialog.hideDialog() } - QGCLabel { - anchors.left: parent.left - anchors.right: parent.right - wrapMode: Text.WordWrap - text: "Turn on your transmitter and keep throttle at zero." - } + QGCFlickable { + anchors.fill: parent + contentHeight: columnLayout.height + clip: true - QGCLabel { - anchors.left: parent.left - anchors.right: parent.right - wrapMode: Text.WordWrap - text: "Click Ok to start CompassMot calibration." - } - } // Column - } // QGCFlickable - } // QGCViewDialog - } + Column { + id: columnLayout + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + spacing: ScreenTools.defaultFontPixelHeight - QGCViewPanel { - id: panel - anchors.fill: parent + QGCLabel { + anchors.left: parent.left + anchors.right: parent.right + wrapMode: Text.WordWrap + text: "This is recommended for vehicles that have only an internal compass and on vehicles where there is significant interference on the compass from the motors, power wires, etc. " + + "CompassMot only works well if you have a battery current monitor because the magnetic interference is linear with current drawn. " + + "It is technically possible to set-up CompassMot using throttle but this is not recommended." + } - Column { - id: buttonColumn - spacing: ScreenTools.defaultFontPixelHeight / 2 + QGCLabel { + anchors.left: parent.left + anchors.right: parent.right + wrapMode: Text.WordWrap + text: "Disconnect your props, flip them over and rotate them one position around the frame. " + + "In this configuration they should push the copter down into the ground when the throttle is raised." + } - readonly property int buttonWidth: ScreenTools.defaultFontPixelWidth * 15 + QGCLabel { + anchors.left: parent.left + anchors.right: parent.right + wrapMode: Text.WordWrap + text: "Secure the copter (perhaps with tape) so that it does not move." + } - IndicatorButton { - id: accelButton - width: parent.buttonWidth - text: qsTr("Accelerometer") - indicatorGreen: !accelCalNeeded + QGCLabel { + anchors.left: parent.left + anchors.right: parent.right + wrapMode: Text.WordWrap + text: "Turn on your transmitter and keep throttle at zero." + } - onClicked: showOrientationsDialog(_calTypeAccel) - } + QGCLabel { + anchors.left: parent.left + anchors.right: parent.right + wrapMode: Text.WordWrap + text: "Click Ok to start CompassMot calibration." + } + } // Column + } // QGCFlickable + } // QGCViewDialog + } // Component - compassMotDialogComponent - IndicatorButton { - id: compassButton - width: parent.buttonWidth - text: qsTr("Compass") - indicatorGreen: !compassCalNeeded + Column { + spacing: ScreenTools.defaultFontPixelHeight / 2 + Layout.alignment: Qt.AlignLeft | Qt.AlignTop - onClicked: { - if (controller.accelSetupNeeded) { - showMessage(qsTr("Calibrate Compass"), qsTr("Accelerometer must be calibrated prior to Compass."), StandardButton.Ok) - } else { - showOrientationsDialog(_calTypeCompass) + readonly property int buttonWidth: ScreenTools.defaultFontPixelWidth * 15 + + IndicatorButton { + id: accelButton + width: parent.buttonWidth + text: qsTr("Accelerometer") + indicatorGreen: !accelCalNeeded + + onClicked: showOrientationsDialog(_calTypeAccel) + } + + IndicatorButton { + id: compassButton + width: parent.buttonWidth + text: qsTr("Compass") + indicatorGreen: !compassCalNeeded + + onClicked: { + if (controller.accelSetupNeeded) { + showMessage(qsTr("Calibrate Compass"), qsTr("Accelerometer must be calibrated prior to Compass."), StandardButton.Ok) + } else { + showOrientationsDialog(_calTypeCompass) + } } } - } - QGCButton { - id: motorInterferenceButton - width: parent.buttonWidth - text: qsTr("CompassMot") - onClicked: showDialog(compassMotDialogComponent, qsTr("CompassMot - Compass Motor Interference Calibration"), qgcView.showDialogFullWidth, StandardButton.Cancel | StandardButton.Ok) - } + QGCButton { + id: motorInterferenceButton + width: parent.buttonWidth + text: qsTr("CompassMot") + onClicked: showDialog(compassMotDialogComponent, qsTr("CompassMot - Compass Motor Interference Calibration"), qgcView.showDialogFullWidth, StandardButton.Cancel | StandardButton.Ok) + } - QGCButton { - id: nextButton - width: parent.buttonWidth - text: qsTr("Next") - enabled: false - onClicked: controller.nextClicked() - } + QGCButton { + id: nextButton + width: parent.buttonWidth + text: qsTr("Next") + enabled: false + onClicked: controller.nextClicked() + } - QGCButton { - id: cancelButton - width: parent.buttonWidth - text: qsTr("Cancel") - enabled: false - onClicked: controller.cancelCalibration() - } + QGCButton { + id: cancelButton + width: parent.buttonWidth + text: qsTr("Cancel") + enabled: false + onClicked: controller.cancelCalibration() + } - QGCButton { - id: setOrientationsButton - width: parent.buttonWidth - text: qsTr("Sensor Settings") - onClicked: showOrientationsDialog(_calTypeSet) - } - } // Column - Buttons - - Column { - anchors.leftMargin: ScreenTools.defaultFontPixelWidth / 2 - anchors.left: buttonColumn.right - anchors.right: parent.right - anchors.top: parent.top - anchors.bottom: parent.bottom - - ProgressBar { - id: progressBar - anchors.left: parent.left - anchors.right: parent.right - } + QGCButton { + id: setOrientationsButton + width: parent.buttonWidth + text: qsTr("Sensor Settings") + onClicked: showOrientationsDialog(_calTypeSet) + } + } // Column - Buttons - Item { height: ScreenTools.defaultFontPixelHeight; width: 10 } // spacer + Column { + anchors.top: parent.top + anchors.bottom: parent.bottom + Layout.fillWidth: true - Item { - id: centerPanel - width: parent.width - height: parent.height - y + ProgressBar { + id: progressBar + anchors.left: parent.left + anchors.right: parent.right + } - TextArea { - id: statusTextArea - anchors.fill: parent - readOnly: true - frameVisible: false - text: statusTextAreaDefaultText + Item { height: ScreenTools.defaultFontPixelHeight; width: 10 } // spacer - style: TextAreaStyle { - textColor: qgcPal.text - backgroundColor: qgcPal.windowShade - } - } + Item { + id: centerPanel + width: parent.width + height: parent.height - y - Rectangle { - id: orientationCalArea - anchors.fill: parent - visible: controller.showOrientationCalArea - color: qgcPal.windowShade + TextArea { + id: statusTextArea + anchors.fill: parent + readOnly: true + frameVisible: false + text: statusTextAreaDefaultText - QGCLabel { - id: orientationCalAreaHelpText - anchors.margins: ScreenTools.defaultFontPixelWidth - anchors.top: orientationCalArea.top - anchors.left: orientationCalArea.left - width: parent.width - wrapMode: Text.WordWrap - font.pointSize: ScreenTools.mediumFontPointSize + style: TextAreaStyle { + textColor: qgcPal.text + backgroundColor: qgcPal.windowShade + } } - Flow { - anchors.topMargin: ScreenTools.defaultFontPixelWidth - anchors.top: orientationCalAreaHelpText.bottom - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - spacing: ScreenTools.defaultFontPixelWidth - - property real indicatorWidth: (width / 3) - (spacing * 2) - property real indicatorHeight: (height / 2) - spacing - - VehicleRotationCal { - width: parent.indicatorWidth - height: parent.indicatorHeight - visible: controller.orientationCalDownSideVisible - calValid: controller.orientationCalDownSideDone - calInProgress: controller.orientationCalDownSideInProgress - calInProgressText: controller.orientationCalDownSideRotate ? qsTr("Rotate") : qsTr("Hold Still") - imageSource: controller.orientationCalDownSideRotate ? "qrc:///qmlimages/VehicleDownRotate.png" : "qrc:///qmlimages/VehicleDown.png" - } - VehicleRotationCal { - width: parent.indicatorWidth - height: parent.indicatorHeight - visible: controller.orientationCalUpsideDownSideVisible - calValid: controller.orientationCalUpsideDownSideDone - calInProgress: controller.orientationCalUpsideDownSideInProgress - calInProgressText: controller.orientationCalUpsideDownSideRotate ? qsTr("Rotate") : qsTr("Hold Still") - imageSource: controller.orientationCalUpsideDownSideRotate ? "qrc:///qmlimages/VehicleUpsideDownRotate.png" : "qrc:///qmlimages/VehicleUpsideDown.png" - } - VehicleRotationCal { - width: parent.indicatorWidth - height: parent.indicatorHeight - visible: controller.orientationCalNoseDownSideVisible - calValid: controller.orientationCalNoseDownSideDone - calInProgress: controller.orientationCalNoseDownSideInProgress - calInProgressText: controller.orientationCalNoseDownSideRotate ? qsTr("Rotate") : qsTr("Hold Still") - imageSource: controller.orientationCalNoseDownSideRotate ? "qrc:///qmlimages/VehicleNoseDownRotate.png" : "qrc:///qmlimages/VehicleNoseDown.png" - } - VehicleRotationCal { - width: parent.indicatorWidth - height: parent.indicatorHeight - visible: controller.orientationCalTailDownSideVisible - calValid: controller.orientationCalTailDownSideDone - calInProgress: controller.orientationCalTailDownSideInProgress - calInProgressText: controller.orientationCalTailDownSideRotate ? qsTr("Rotate") : qsTr("Hold Still") - imageSource: controller.orientationCalTailDownSideRotate ? "qrc:///qmlimages/VehicleTailDownRotate.png" : "qrc:///qmlimages/VehicleTailDown.png" - } - VehicleRotationCal { - width: parent.indicatorWidth - height: parent.indicatorHeight - visible: controller.orientationCalLeftSideVisible - calValid: controller.orientationCalLeftSideDone - calInProgress: controller.orientationCalLeftSideInProgress - calInProgressText: controller.orientationCalLeftSideRotate ? qsTr("Rotate") : qsTr("Hold Still") - imageSource: controller.orientationCalLeftSideRotate ? "qrc:///qmlimages/VehicleLeftRotate.png" : "qrc:///qmlimages/VehicleLeft.png" + Rectangle { + id: orientationCalArea + anchors.fill: parent + visible: controller.showOrientationCalArea + color: qgcPal.windowShade + + QGCLabel { + id: orientationCalAreaHelpText + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.top: orientationCalArea.top + anchors.left: orientationCalArea.left + width: parent.width + wrapMode: Text.WordWrap + font.pointSize: ScreenTools.mediumFontPointSize } - VehicleRotationCal { - width: parent.indicatorWidth - height: parent.indicatorHeight - visible: controller.orientationCalRightSideVisible - calValid: controller.orientationCalRightSideDone - calInProgress: controller.orientationCalRightSideInProgress - calInProgressText: controller.orientationCalRightSideRotate ? qsTr("Rotate") : qsTr("Hold Still") - imageSource: controller.orientationCalRightSideRotate ? "qrc:///qmlimages/VehicleRightRotate.png" : "qrc:///qmlimages/VehicleRight.png" + + Flow { + anchors.topMargin: ScreenTools.defaultFontPixelWidth + anchors.top: orientationCalAreaHelpText.bottom + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + spacing: ScreenTools.defaultFontPixelWidth + + property real indicatorWidth: (width / 3) - (spacing * 2) + property real indicatorHeight: (height / 2) - spacing + + VehicleRotationCal { + width: parent.indicatorWidth + height: parent.indicatorHeight + visible: controller.orientationCalDownSideVisible + calValid: controller.orientationCalDownSideDone + calInProgress: controller.orientationCalDownSideInProgress + calInProgressText: controller.orientationCalDownSideRotate ? qsTr("Rotate") : qsTr("Hold Still") + imageSource: controller.orientationCalDownSideRotate ? "qrc:///qmlimages/VehicleDownRotate.png" : "qrc:///qmlimages/VehicleDown.png" + } + VehicleRotationCal { + width: parent.indicatorWidth + height: parent.indicatorHeight + visible: controller.orientationCalUpsideDownSideVisible + calValid: controller.orientationCalUpsideDownSideDone + calInProgress: controller.orientationCalUpsideDownSideInProgress + calInProgressText: controller.orientationCalUpsideDownSideRotate ? qsTr("Rotate") : qsTr("Hold Still") + imageSource: controller.orientationCalUpsideDownSideRotate ? "qrc:///qmlimages/VehicleUpsideDownRotate.png" : "qrc:///qmlimages/VehicleUpsideDown.png" + } + VehicleRotationCal { + width: parent.indicatorWidth + height: parent.indicatorHeight + visible: controller.orientationCalNoseDownSideVisible + calValid: controller.orientationCalNoseDownSideDone + calInProgress: controller.orientationCalNoseDownSideInProgress + calInProgressText: controller.orientationCalNoseDownSideRotate ? qsTr("Rotate") : qsTr("Hold Still") + imageSource: controller.orientationCalNoseDownSideRotate ? "qrc:///qmlimages/VehicleNoseDownRotate.png" : "qrc:///qmlimages/VehicleNoseDown.png" + } + VehicleRotationCal { + width: parent.indicatorWidth + height: parent.indicatorHeight + visible: controller.orientationCalTailDownSideVisible + calValid: controller.orientationCalTailDownSideDone + calInProgress: controller.orientationCalTailDownSideInProgress + calInProgressText: controller.orientationCalTailDownSideRotate ? qsTr("Rotate") : qsTr("Hold Still") + imageSource: controller.orientationCalTailDownSideRotate ? "qrc:///qmlimages/VehicleTailDownRotate.png" : "qrc:///qmlimages/VehicleTailDown.png" + } + VehicleRotationCal { + width: parent.indicatorWidth + height: parent.indicatorHeight + visible: controller.orientationCalLeftSideVisible + calValid: controller.orientationCalLeftSideDone + calInProgress: controller.orientationCalLeftSideInProgress + calInProgressText: controller.orientationCalLeftSideRotate ? qsTr("Rotate") : qsTr("Hold Still") + imageSource: controller.orientationCalLeftSideRotate ? "qrc:///qmlimages/VehicleLeftRotate.png" : "qrc:///qmlimages/VehicleLeft.png" + } + VehicleRotationCal { + width: parent.indicatorWidth + height: parent.indicatorHeight + visible: controller.orientationCalRightSideVisible + calValid: controller.orientationCalRightSideDone + calInProgress: controller.orientationCalRightSideInProgress + calInProgressText: controller.orientationCalRightSideRotate ? qsTr("Rotate") : qsTr("Hold Still") + imageSource: controller.orientationCalRightSideRotate ? "qrc:///qmlimages/VehicleRightRotate.png" : "qrc:///qmlimages/VehicleRight.png" + } } } - } - } // Item - Cal display area - } // Column - cal display - } // QGCViewPanel -} // QGCView + } // Item - Cal display area + } // Column - cal display + } // Row + } // Component - sensorsPageComponent +} // SetupPage diff --git a/src/AutoPilotPlugins/APM/APMTuningComponent.cc b/src/AutoPilotPlugins/APM/APMTuningComponent.cc index 32bd528b9f..980b576243 100644 --- a/src/AutoPilotPlugins/APM/APMTuningComponent.cc +++ b/src/AutoPilotPlugins/APM/APMTuningComponent.cc @@ -26,7 +26,7 @@ QString APMTuningComponent::name(void) const QString APMTuningComponent::description(void) const { - return tr("The Tuning Component is used to tune the flight characteristics of the Vehicle."); + return tr("Tuning Setup is used to tune the flight characteristics of the Vehicle."); } QString APMTuningComponent::iconResource(void) const diff --git a/src/AutoPilotPlugins/APM/APMTuningComponentCopter.qml b/src/AutoPilotPlugins/APM/APMTuningComponentCopter.qml index d1e70721e6..2bb09e6015 100644 --- a/src/AutoPilotPlugins/APM/APMTuningComponentCopter.qml +++ b/src/AutoPilotPlugins/APM/APMTuningComponentCopter.qml @@ -17,113 +17,109 @@ import QGroundControl.Palette 1.0 import QGroundControl.Controls 1.0 import QGroundControl.ScreenTools 1.0 -QGCView { - id: _safetyView - viewPanel: panel - anchors.fill: parent - - FactPanelController { id: controller; factPanel: panel } - - QGCPalette { id: palette; colorGroupEnabled: enabled } - - // Older firmwares use THR_MODE, newer use MOT_THST_HOVER - property bool _throttleMidExists: controller.parameterExists(-1, "THR_MID") - property Fact _hoverTuneParam: controller.getParameterFact(-1, _throttleMidExists ? "THR_MID" : "MOT_THST_HOVER") - property real _hoverTuneMin: _throttleMidExists ? 200 : 0 - property real _hoverTuneMax: _throttleMidExists ? 800 : 1 - property real _hoverTuneStep: _throttleMidExists ? 10 : 0.01 - - property Fact _rcFeel: controller.getParameterFact(-1, "RC_FEEL_RP") - property Fact _rateRollP: controller.getParameterFact(-1, "r.ATC_RAT_RLL_P") - property Fact _rateRollI: controller.getParameterFact(-1, "r.ATC_RAT_RLL_I") - property Fact _ratePitchP: controller.getParameterFact(-1, "r.ATC_RAT_PIT_P") - property Fact _ratePitchI: controller.getParameterFact(-1, "r.ATC_RAT_PIT_I") - property Fact _rateClimbP: controller.getParameterFact(-1, "ACCEL_Z_P") - property Fact _rateClimbI: controller.getParameterFact(-1, "ACCEL_Z_I") - - property Fact _ch7Opt: controller.getParameterFact(-1, "CH7_OPT") - property Fact _ch8Opt: controller.getParameterFact(-1, "CH8_OPT") - property Fact _ch9Opt: controller.getParameterFact(-1, "CH9_OPT") - property Fact _ch10Opt: controller.getParameterFact(-1, "CH10_OPT") - property Fact _ch11Opt: controller.getParameterFact(-1, "CH11_OPT") - property Fact _ch12Opt: controller.getParameterFact(-1, "CH12_OPT") - - readonly property int _firstOptionChannel: 7 - readonly property int _lastOptionChannel: 12 - - property Fact _autoTuneAxes: controller.getParameterFact(-1, "AUTOTUNE_AXES") - property int _autoTuneSwitchChannelIndex: 0 - readonly property int _autoTuneOption: 17 - - property real _margins: ScreenTools.defaultFontPixelHeight - - property bool _loadComplete: false - - ExclusiveGroup { id: fenceActionRadioGroup } - ExclusiveGroup { id: landLoiterRadioGroup } - ExclusiveGroup { id: returnAltRadioGroup } - - Component.onCompleted: { - // Qml Sliders have a strange behavior in which they first set Slider::value to some internal - // setting and then set Slider::value to the bound properties value. If you have an onValueChanged - // handler which updates your property with the new value, this first value change will trash - // your bound values. In order to work around this we don't set the values into the Sliders until - // after Qml load is done. We also don't track value changes until Qml load completes. - throttleHover.value = _hoverTuneParam.value - rollPitch.value = _rateRollP.value - climb.value = _rateClimbP.value - rcFeel.value = _rcFeel.value - _loadComplete = true - - calcAutoTuneChannel() - } - - /// The AutoTune switch is stored in one of the RC#_FUNCTION parameters. We need to loop through those - /// to find them and setup the ui accordindly. - function calcAutoTuneChannel() { - _autoTuneSwitchChannelIndex = 0 - for (var channel=_firstOptionChannel; channel<=_lastOptionChannel; channel++) { - var optionFact = controller.getParameterFact(-1, "CH" + channel + "_OPT") - if (optionFact.value == _autoTuneOption) { - _autoTuneSwitchChannelIndex = channel - _firstOptionChannel + 1 - break +SetupPage { + id: tuningPage + pageComponent: tuningPageComponent + + Component { + id: tuningPageComponent + + Column { + width: availableWidth + spacing: _margins + + FactPanelController { id: controller; factPanel: tuningPage.viewPanel } + + QGCPalette { id: palette; colorGroupEnabled: true } + + // Older firmwares use THR_MODE, newer use MOT_THST_HOVER + property bool _throttleMidExists: controller.parameterExists(-1, "THR_MID") + property Fact _hoverTuneParam: controller.getParameterFact(-1, _throttleMidExists ? "THR_MID" : "MOT_THST_HOVER") + property real _hoverTuneMin: _throttleMidExists ? 200 : 0 + property real _hoverTuneMax: _throttleMidExists ? 800 : 1 + property real _hoverTuneStep: _throttleMidExists ? 10 : 0.01 + + property Fact _rcFeel: controller.getParameterFact(-1, "RC_FEEL_RP") + property Fact _rateRollP: controller.getParameterFact(-1, "r.ATC_RAT_RLL_P") + property Fact _rateRollI: controller.getParameterFact(-1, "r.ATC_RAT_RLL_I") + property Fact _ratePitchP: controller.getParameterFact(-1, "r.ATC_RAT_PIT_P") + property Fact _ratePitchI: controller.getParameterFact(-1, "r.ATC_RAT_PIT_I") + property Fact _rateClimbP: controller.getParameterFact(-1, "ACCEL_Z_P") + property Fact _rateClimbI: controller.getParameterFact(-1, "ACCEL_Z_I") + + property Fact _ch7Opt: controller.getParameterFact(-1, "CH7_OPT") + property Fact _ch8Opt: controller.getParameterFact(-1, "CH8_OPT") + property Fact _ch9Opt: controller.getParameterFact(-1, "CH9_OPT") + property Fact _ch10Opt: controller.getParameterFact(-1, "CH10_OPT") + property Fact _ch11Opt: controller.getParameterFact(-1, "CH11_OPT") + property Fact _ch12Opt: controller.getParameterFact(-1, "CH12_OPT") + + readonly property int _firstOptionChannel: 7 + readonly property int _lastOptionChannel: 12 + + property Fact _autoTuneAxes: controller.getParameterFact(-1, "AUTOTUNE_AXES") + property int _autoTuneSwitchChannelIndex: 0 + readonly property int _autoTuneOption: 17 + + property real _margins: ScreenTools.defaultFontPixelHeight + + property bool _loadComplete: false + + ExclusiveGroup { id: fenceActionRadioGroup } + ExclusiveGroup { id: landLoiterRadioGroup } + ExclusiveGroup { id: returnAltRadioGroup } + + Component.onCompleted: { + // Qml Sliders have a strange behavior in which they first set Slider::value to some internal + // setting and then set Slider::value to the bound properties value. If you have an onValueChanged + // handler which updates your property with the new value, this first value change will trash + // your bound values. In order to work around this we don't set the values into the Sliders until + // after Qml load is done. We also don't track value changes until Qml load completes. + throttleHover.value = _hoverTuneParam.value + rollPitch.value = _rateRollP.value + climb.value = _rateClimbP.value + rcFeel.value = _rcFeel.value + _loadComplete = true + + calcAutoTuneChannel() } - } - } - - /// We need to clear AutoTune from any previous channel before setting it to a new one - function setChannelAutoTuneOption(channel) { - // First clear any previous settings for AutTune - for (var optionChannel=_firstOptionChannel; optionChannel<=_lastOptionChannel; optionChannel++) { - var optionFact = controller.getParameterFact(-1, "CH" + optionChannel + "_OPT") - if (optionFact.value == _autoTuneOption) { - optionFact.value = 0 + + /// The AutoTune switch is stored in one of the RC#_FUNCTION parameters. We need to loop through those + /// to find them and setup the ui accordindly. + function calcAutoTuneChannel() { + _autoTuneSwitchChannelIndex = 0 + for (var channel=_firstOptionChannel; channel<=_lastOptionChannel; channel++) { + var optionFact = controller.getParameterFact(-1, "CH" + channel + "_OPT") + if (optionFact.value == _autoTuneOption) { + _autoTuneSwitchChannelIndex = channel - _firstOptionChannel + 1 + break + } + } } - } - - // Now set the function into the new channel - if (channel != 0) { - var optionFact = controller.getParameterFact(-1, "CH" + channel + "_OPT") - optionFact.value = _autoTuneOption - } - } - - Connections { target: _ch7Opt; onValueChanged: calcAutoTuneChannel() } - Connections { target: _ch8Opt; onValueChanged: calcAutoTuneChannel() } - Connections { target: _ch9Opt; onValueChanged: calcAutoTuneChannel() } - Connections { target: _ch10Opt; onValueChanged: calcAutoTuneChannel() } - Connections { target: _ch11Opt; onValueChanged: calcAutoTuneChannel() } - Connections { target: _ch12Opt; onValueChanged: calcAutoTuneChannel() } - - QGCViewPanel { - id: panel - anchors.fill: parent - - QGCFlickable { - clip: true - anchors.fill: parent - contentHeight: autoTuneRect.y + autoTuneRect.height - flickableDirection: Flickable.VerticalFlick + + /// We need to clear AutoTune from any previous channel before setting it to a new one + function setChannelAutoTuneOption(channel) { + // First clear any previous settings for AutTune + for (var optionChannel=_firstOptionChannel; optionChannel<=_lastOptionChannel; optionChannel++) { + var optionFact = controller.getParameterFact(-1, "CH" + optionChannel + "_OPT") + if (optionFact.value == _autoTuneOption) { + optionFact.value = 0 + } + } + + // Now set the function into the new channel + if (channel != 0) { + var optionFact = controller.getParameterFact(-1, "CH" + channel + "_OPT") + optionFact.value = _autoTuneOption + } + } + + Connections { target: _ch7Opt; onValueChanged: calcAutoTuneChannel() } + Connections { target: _ch8Opt; onValueChanged: calcAutoTuneChannel() } + Connections { target: _ch9Opt; onValueChanged: calcAutoTuneChannel() } + Connections { target: _ch10Opt; onValueChanged: calcAutoTuneChannel() } + Connections { target: _ch11Opt; onValueChanged: calcAutoTuneChannel() } + Connections { target: _ch12Opt; onValueChanged: calcAutoTuneChannel() } QGCLabel { id: basicLabel @@ -133,10 +129,8 @@ QGCView { Rectangle { id: basicTuningRect - anchors.topMargin: _margins / 2 anchors.left: parent.left anchors.right: parent.right - anchors.top: basicLabel.bottom height: basicTuningColumn.y + basicTuningColumn.height + _margins color: palette.windowShade @@ -276,16 +270,15 @@ QGCView { } // Rectangle - Basic tuning Flow { - id: flowLayout - anchors.topMargin: _margins / 2 - width: panel.width // parent.width doesn't work here for some reason! - anchors.top: basicTuningRect.bottom - spacing: _margins + id: flowLayout + anchors.left: parent.left + anchors.right: parent.right + spacing: _margins Rectangle { - height: autoTuneLabel.height + autoTuneRect.height - width: autoTuneRect.width - color: palette.window + height: autoTuneLabel.height + autoTuneRect.height + width: autoTuneRect.width + color: palette.window QGCLabel { id: autoTuneLabel @@ -294,11 +287,11 @@ QGCView { } Rectangle { - id: autoTuneRect - width: autoTuneColumn.x + autoTuneColumn.width + _margins - height: autoTuneColumn.y + autoTuneColumn.height + _margins - anchors.top: autoTuneLabel.bottom - color: palette.windowShade + id: autoTuneRect + width: autoTuneColumn.x + autoTuneColumn.width + _margins + height: autoTuneColumn.y + autoTuneColumn.height + _margins + anchors.top: autoTuneLabel.bottom + color: palette.windowShade Column { id: autoTuneColumn @@ -417,6 +410,6 @@ QGCView { } // Rectangle - Channel 6 Tuning options } // Rectangle - Channel 6 Tuning options wrap } // Flow - Tune - } // QGCFlickable - } // QGCViewPanel -} // QGCView + } // Column + } // Component +} // SetupView diff --git a/src/FirmwarePlugin/APM/APMBrandImage.png b/src/FirmwarePlugin/APM/APMBrandImage.png new file mode 100644 index 0000000000000000000000000000000000000000..2c4c59f6498ae43764ec9397c1c9be1eb5825331 GIT binary patch literal 27565 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

(I?w7{r3LA^+ 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 literal 0 HcmV?d00001 diff --git a/src/FirmwarePlugin/APM/APMFirmwarePlugin.h b/src/FirmwarePlugin/APM/APMFirmwarePlugin.h index 2c5150d40d..f9ca107ca9 100644 --- a/src/FirmwarePlugin/APM/APMFirmwarePlugin.h +++ b/src/FirmwarePlugin/APM/APMFirmwarePlugin.h @@ -95,6 +95,7 @@ public: QObject* loadParameterMetaData (const QString& metaDataFile); GeoFenceManager* newGeoFenceManager (Vehicle* vehicle) { return new APMGeoFenceManager(vehicle); } RallyPointManager* newRallyPointManager (Vehicle* vehicle) { return new APMRallyPointManager(vehicle); } + QString brandImage (const Vehicle* vehicle) const { Q_UNUSED(vehicle); return QStringLiteral("/qmlimages/APM/BrandImage"); } QString getParameterMetaDataFile(Vehicle* vehicle); diff --git a/src/FirmwarePlugin/FirmwarePlugin.h b/src/FirmwarePlugin/FirmwarePlugin.h index 935cf1de8f..6038748b66 100644 --- a/src/FirmwarePlugin/FirmwarePlugin.h +++ b/src/FirmwarePlugin/FirmwarePlugin.h @@ -222,6 +222,9 @@ public: /// Return the resource file which contains the set of params loaded for offline editing. virtual QString offlineEditingParamFile(Vehicle* vehicle) { Q_UNUSED(vehicle); return QString(); } + + /// Return the resource file which contains the brand image for the vehicle. + virtual QString brandImage(const Vehicle* vehicle) const { Q_UNUSED(vehicle) return QString(); } }; #endif diff --git a/src/FirmwarePlugin/PX4/PX4BrandImage.png b/src/FirmwarePlugin/PX4/PX4BrandImage.png new file mode 100644 index 0000000000000000000000000000000000000000..ae49f7679dc5c2cbb1e8bb35c861f7b100480ba8 GIT binary patch literal 4863 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 literal 0 HcmV?d00001 diff --git a/src/FirmwarePlugin/PX4/PX4FirmwarePlugin.h b/src/FirmwarePlugin/PX4/PX4FirmwarePlugin.h index ec59a84079..47f963e6d5 100644 --- a/src/FirmwarePlugin/PX4/PX4FirmwarePlugin.h +++ b/src/FirmwarePlugin/PX4/PX4FirmwarePlugin.h @@ -58,6 +58,7 @@ public: bool adjustIncomingMavlinkMessage (Vehicle* vehicle, mavlink_message_t* message); GeoFenceManager* newGeoFenceManager (Vehicle* vehicle) { return new PX4GeoFenceManager(vehicle); } QString offlineEditingParamFile(Vehicle* vehicle) final { Q_UNUSED(vehicle); return QStringLiteral(":/FirmwarePlugin/PX4/PX4.OfflineEditing.params"); } + QString brandImage (const Vehicle* vehicle) const { Q_UNUSED(vehicle); return QStringLiteral("/qmlimages/PX4/BrandImage"); } // Use these constants to set flight modes using setFlightMode method. Don't use hardcoded string names since the // names may change. diff --git a/src/Vehicle/Vehicle.cc b/src/Vehicle/Vehicle.cc index 7afa35f0cb..631a93e86f 100644 --- a/src/Vehicle/Vehicle.cc +++ b/src/Vehicle/Vehicle.cc @@ -1922,6 +1922,11 @@ void Vehicle::_newGeoFenceAvailable(void) } } +QString Vehicle::brandImage(void) const +{ + return _firmwarePlugin->brandImage(this); +} + const char* VehicleGPSFactGroup::_hdopFactName = "hdop"; const char* VehicleGPSFactGroup::_vdopFactName = "vdop"; const char* VehicleGPSFactGroup::_courseOverGroundFactName = "courseOverGround"; diff --git a/src/Vehicle/Vehicle.h b/src/Vehicle/Vehicle.h index d88bd0e2ee..c2b3bfbed2 100644 --- a/src/Vehicle/Vehicle.h +++ b/src/Vehicle/Vehicle.h @@ -279,6 +279,7 @@ public: Q_PROPERTY(bool coaxialMotors READ coaxialMotors CONSTANT) Q_PROPERTY(bool xConfigMotors READ xConfigMotors CONSTANT) Q_PROPERTY(bool isOfflineEditingVehicle READ isOfflineEditingVehicle CONSTANT) + Q_PROPERTY(QString brandImage READ brandImage CONSTANT) /// true: Vehicle is flying, false: Vehicle is on ground Q_PROPERTY(bool flying READ flying WRITE setFlying NOTIFY flyingChanged) @@ -524,6 +525,7 @@ public: uint8_t baseMode () const { return _base_mode; } uint32_t customMode () const { return _custom_mode; } bool isOfflineEditingVehicle () const { return _offlineEditingVehicle; } + QString brandImage () const; Fact* roll (void) { return &_rollFact; } Fact* heading (void) { return &_headingFact; } diff --git a/src/ui/toolbar/MainToolBar.qml b/src/ui/toolbar/MainToolBar.qml index ad45e36ab4..f8b4170e94 100644 --- a/src/ui/toolbar/MainToolBar.qml +++ b/src/ui/toolbar/MainToolBar.qml @@ -54,7 +54,7 @@ Rectangle { MainToolBarController { id: _controller } function checkSettingsButton() { - settingsButton.checked = true + settingsButton.checked = true } function checkSetupButton() { @@ -421,6 +421,17 @@ Rectangle { primary: true onClicked: activeVehicle.disconnectInactiveVehicle() } + + Image { + anchors.rightMargin: ScreenTools.defaultFontPixelWidth / 2 + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + visible: parent.x < x && !disconnectButton.visible && source != "" + fillMode: Image.PreserveAspectFit + source: activeVehicle ? activeVehicle.brandImage : "" + } + } // Progress bar -- GitLab