Newer
Older
/****************************************************************************
*
* (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
*
* QGroundControl is licensed according to the terms in the file
* COPYING.md in the root of the source code directory.
*
****************************************************************************/
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Dialogs 1.2
import QtLocation 5.3
import QtPositioning 5.3
import QGroundControl 1.0
import QGroundControl.FlightMap 1.0
import QGroundControl.ScreenTools 1.0
import QGroundControl.Controls 1.0
import QGroundControl.Palette 1.0
import QGroundControl.Controllers 1.0
// zOrder comes from the Loader in MainWindow.qml
readonly property int _decimalPlaces: 8
readonly property real _horizontalMargin: ScreenTools.defaultFontPixelWidth / 2
readonly property real _margin: ScreenTools.defaultFontPixelHeight * 0.5
readonly property var _activeVehicle: QGroundControl.multiVehicleManager.activeVehicle
readonly property real _rightPanelWidth: Math.min(parent.width / 3, ScreenTools.defaultFontPixelWidth * 30)
readonly property real _rightPanelOpacity: 0.8
readonly property int _toolButtonCount: 6
readonly property string _autoSyncKey: "AutoSync"
readonly property real _toolButtonTopMargin: parent.height - ScreenTools.availableHeight + (ScreenTools.defaultFontPixelHeight / 2)
readonly property int _addMissionItemsButtonAutoOffTimeout: 10000
readonly property var _defaultVehicleCoordinate: QtPositioning.coordinate(37.803784, -122.462276)
property bool _syncNeeded: controller.visualItems.dirty // Unsaved changes, visible to parent container
Nate Weibley
committed
property var _visualItems: controller.visualItems
Nate Weibley
committed
property int _currentMissionIndex: 0
property bool _firstVehiclePosition: true
property var activeVehiclePosition: _activeVehicle ? _activeVehicle.coordinate : QtPositioning.coordinate()
property bool _lightWidgetBorders: editorMap.isSatelliteMap
onActiveVehiclePositionChanged: updateMapToVehiclePosition()
target: QGroundControl.multiVehicleManager
onActiveVehicleChanged: {
// When the active vehicle changes we need to allow the first vehicle position to move the map again
_firstVehiclePosition = true
updateMapToVehiclePosition()
}
function updateMapToVehiclePosition() {
if (_activeVehicle && _activeVehicle.coordinateValid && _activeVehicle.coordinate.isValid && _firstVehiclePosition) {
_firstVehiclePosition = false
editorMap.center = _activeVehicle.coordinate
function loadFromVehicle() {
controller.getMissionItems()
}
function loadFromFile() {
_root.showDialog(mobileFilePicker, qsTr("Select Mission File"), _root.showDialogDefaultWidth, StandardButton.Yes | StandardButton.Cancel)
Nate Weibley
committed
_currentMissionItem = _visualItems.get(0)
}
}
function saveToFile() {
if (ScreenTools.isMobile) {
_root.showDialog(mobileFileSaver, qsTr("Save Mission File"), _root.showDialogDefaultWidth, StandardButton.Save | StandardButton.Cancel)
}
function normalizeLat(lat) {
// Normalize latitude to range: 0 to 180, S to N
return lat + 90.0
}
function normalizeLon(lon) {
// Normalize longitude to range: 0 to 360, W to E
return lon + 180.0
}
/// Fix the map viewport to the current mission items.
if (_visualItems.count == 1) {
editorMap.center = _visualItems.get(0).coordinate
var north = normalizeLat(missionItem.coordinate.latitude)
var south = north
var east = normalizeLon(missionItem.coordinate.longitude)
var west = east
for (var i=1; i<_visualItems.count; i++) {
missionItem = _visualItems.get(i)
if (missionItem.specifiesCoordinate && !missionItem.isStandaloneCoordinate) {
var lat = normalizeLat(missionItem.coordinate.latitude)
var lon = normalizeLon(missionItem.coordinate.longitude)
north = Math.max(north, lat)
south = Math.min(south, lat)
east = Math.max(east, lon)
west = Math.min(west, lon)
}
editorMap.visibleRegion = QtPositioning.rectangle(QtPositioning.coordinate(north - 90.0, west - 180.0), QtPositioning.coordinate(south - 90.0, east - 180.0))
Component.onCompleted: {
start(true /* editMode */)
FIXME: autoSync is temporarily disconnected since it's still buggy
autoSync: QGroundControl.flightMapSettings.loadMapSetting(editorMap.mapName, _autoSyncKey, true)
onAutoSyncChanged: QGroundControl.flightMapSettings.saveMapSetting(editorMap.mapName, _autoSyncKey, autoSync)
onVisualItemsChanged: itemDragger.clearItem()
onNewItemsFromVehicle: fitViewportToMissionItems()
QGCPalette { id: qgcPal; colorGroupEnabled: enabled }
ExclusiveGroup {
id: _mapTypeButtonsExclusiveGroup
}
ExclusiveGroup {
id: _dropButtonsExclusiveGroup
for (var i=0; i<_visualItems.count; i++) {
var visualItem = _visualItems.get(i)
if (visualItem.sequenceNumber == sequenceNumber) {
_currentMissionItem = visualItem
Nate Weibley
committed
_currentMissionIndex = i
property int _moveDialogMissionItemIndex
Component {
id: mobileFilePicker
QGCMobileFileDialog {
openDialog: true
fileExtension: QGroundControl.missionFileExtension
onFilenameReturned: {
controller.loadMissionFromFile(filename)
fitViewportToMissionItems()
Nate Weibley
committed
_currentMissionItem = _visualItems.get(0)
}
}
}
Component {
id: mobileFileSaver
QGCMobileFileDialog {
openDialog: false
fileExtension: QGroundControl.missionFileExtension
onFilenameReturned: {
controller.saveMissionToFile(filename)
Component {
id: moveDialog
QGCViewDialog {
function accept() {
var toIndex = toCombo.currentIndex
if (toIndex == 0) {
toIndex = 1
}
controller.moveMissionItem(_moveDialogMissionItemIndex, toIndex)
hideDialog()
}
Column {
anchors.left: parent.left
anchors.right: parent.right
spacing: ScreenTools.defaultFontPixelHeight
QGCLabel {
anchors.left: parent.left
anchors.right: parent.right
wrapMode: Text.WordWrap
text: qsTr("Move the selected mission item to the be after following mission item:")
}
QGCComboBox {
id: toCombo
currentIndex: _moveDialogMissionItemIndex
}
}
}
}
dogmaphobic
committed
height: ScreenTools.availableHeight
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
dogmaphobic
committed
height: _root.height
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
readonly property real animationDuration: 500
// Initial map position duplicates Fly view position
Component.onCompleted: editorMap.center = QGroundControl.flightMapPosition
Behavior on zoomLevel {
NumberAnimation {
duration: editorMap.animationDuration
easing.type: Easing.InOutQuad
}
}
QGCMapPalette { id: mapPal; lightColors: editorMap.isSatelliteMap }
dogmaphobic
committed
//-- It's a whole lot faster to just fill parent and deal with top offset below
// than computing the coordinate offset.
dogmaphobic
committed
//-- Don't pay attention to items beneath the toolbar.
var topLimit = parent.height - ScreenTools.availableHeight
if(mouse.y >= topLimit) {
var coordinate = editorMap.toCoordinate(Qt.point(mouse.x, mouse.y))
coordinate.latitude = coordinate.latitude.toFixed(_decimalPlaces)
coordinate.longitude = coordinate.longitude.toFixed(_decimalPlaces)
coordinate.altitude = coordinate.altitude.toFixed(_decimalPlaces)
if (addMissionItemsButton.checked) {
var sequenceNumber = controller.insertSimpleMissionItem(coordinate, controller.visualItems.count)
setCurrentItem(sequenceNumber)
} else {
editorMap.mapClicked(coordinate)
}
// We use this item to support dragging since dragging a MapQuickItem just doesn't seem to work
Rectangle {
id: itemDragger
x: missionItemIndicator ? (missionItemIndicator.x + missionItemIndicator.anchorPoint.x - (itemDragger.width / 2)) : 100
y: missionItemIndicator ? (missionItemIndicator.y + missionItemIndicator.anchorPoint.y - (itemDragger.height / 2)) : 100
width: ScreenTools.defaultFontPixelHeight * 2
height: ScreenTools.defaultFontPixelHeight * 2
color: "transparent"
visible: false
z: QGroundControl.zOrderMapItems + 1 // Above item icons
property var missionItem
property var missionItemIndicator
property bool preventCoordinateBindingLoop: false
onXChanged: liveDrag()
onYChanged: liveDrag()
function liveDrag() {
if (!itemDragger.preventCoordinateBindingLoop && Drag.active) {
var point = Qt.point(itemDragger.x + (itemDragger.width / 2), itemDragger.y + (itemDragger.height / 2))
var coordinate = editorMap.toCoordinate(point)
coordinate.altitude = itemDragger.missionItem.coordinate.altitude
itemDragger.preventCoordinateBindingLoop = true
itemDragger.missionItem.coordinate = coordinate
itemDragger.preventCoordinateBindingLoop = false
}
}
itemDragger.visible = false
itemDragger.missionItem = undefined
itemDragger.missionItemIndicator = undefined
}
Drag.active: itemDrag.drag.active
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
MouseArea {
id: itemDrag
anchors.fill: parent
drag.target: parent
drag.minimumX: 0
drag.minimumY: 0
drag.maximumX: itemDragger.parent.width - parent.width
drag.maximumY: itemDragger.parent.height - parent.height
// Add the complex mission item polygon to the map
model: controller.complexVisualItems
delegate: MapPolygon {
color: 'green'
path: object.polygonPath
opacity: 0.5
}
}
// Add the complex mission item grid to the map
MapItemView {
model: controller.complexVisualItems
delegate: MapPolyline {
line.color: "white"
// Add the complex mission item exit coordinates
MapItemView {
model: controller.complexVisualItems
delegate: exitCoordinateComponent
}
Component {
id: exitCoordinateComponent
MissionItemIndicator {
coordinate: object.exitCoordinate
z: QGroundControl.zOrderMapItems
missionItem: object
sequenceNumber: object.lastSequenceNumber
visible: object.specifiesCoordinate
}
// Add the simple mission items to the map
MapItemView {
model: controller.visualItems
delegate: missionItemComponent
}
MissionItemIndicator {
id: itemIndicator
coordinate: object.coordinate
sequenceNumber: object.sequenceNumber
dogmaphobic
committed
//-- If you don't want to allow selecting items beneath the
// toolbar, the code below has to check and see if mouse.y
// is greater than (map.height - ScreenTools.availableHeight)
onClicked: setCurrentItem(object.sequenceNumber)
function updateItemIndicator() {
if (object.isCurrentItem && itemIndicator.visible && object.specifiesCoordinate && object.isSimpleItem) {
// Setup our drag item
itemDragger.visible = true
itemDragger.missionItem = Qt.binding(function() { return object })
itemDragger.missionItemIndicator = Qt.binding(function() { return itemIndicator })
onIsCurrentItemChanged: updateItemIndicator()
onSpecifiesCoordinateChanged: updateItemIndicator()
// These are the non-coordinate child mission items attached to this item
Row {
anchors.top: parent.top
anchors.left: parent.right
Repeater {
model: object.childItems
delegate: MissionItemIndexLabel {
isCurrentItem: object.isCurrentItem
z: 2
onClicked: setCurrentItem(object.sequenceNumber)
}
}
}
}
}
// Add lines between waypoints
MissionLineView {
model: controller.waypointLines
model: QGroundControl.multiVehicleManager.vehicles
delegate:
VehicleMapItem {
vehicle: object
coordinate: object.coordinate
isSatellite: editorMap.isSatelliteMap
size: ScreenTools.defaultFontPixelHeight * 5
z: QGroundControl.zOrderMapItems - 1
}
}
dogmaphobic
committed
height: ScreenTools.availableHeight
anchors.bottom: parent.bottom
anchors.right: parent.right
width: _rightPanelWidth
opacity: _rightPanelOpacity
MouseArea {
// This MouseArea prevents the Map below it from getting Mouse events. Without this
// things like mousewheel will scroll the Flickable and then scroll the map as well.
anchors.fill: editorListView
onWheel: wheel.accepted = true
}
id: editorListView
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
spacing: _margin / 2
orientation: ListView.Vertical
Nate Weibley
committed
currentIndex: _currentMissionIndex
highlightMoveDuration: 250
missionItem: object
width: parent.width
onClicked: setCurrentItem(object.sequenceNumber)
onRemove: {
var sequenceNumber = controller.insertSimpleMissionItem(editorMap.center, insertAfterIndex)
setCurrentItem(sequenceNumber)
}
onMoveHomeToMapCenter: controller.visualItems.get(0).coordinate = editorMap.center
} // ListView
} // Item - Mission Item editor
//-- Dismiss Drop Down (if any)
MouseArea {
anchors.fill: parent
enabled: _dropButtonsExclusiveGroup.current != null
onClicked: {
if(_dropButtonsExclusiveGroup.current)
_dropButtonsExclusiveGroup.current.checked = false
_dropButtonsExclusiveGroup.current = null
}
}
QGCLabel {
id: planLabel
text: qsTr("Plan")
color: mapPal.text
visible: !ScreenTools.isShortScreen
anchors.topMargin: _toolButtonTopMargin
anchors.horizontalCenter: toolColumn.horizontalCenter
anchors.top: parent.top
}
anchors.topMargin: ScreenTools.isShortScreen ? _toolButtonTopMargin : ScreenTools.defaultFontPixelHeight / 2
anchors.leftMargin: ScreenTools.defaultFontPixelHeight
anchors.top: ScreenTools.isShortScreen ? parent.top : planLabel.bottom
spacing: ScreenTools.defaultFontPixelHeight
id: addMissionItemsButton
buttonImage: "/qmlimages/MapAddMission.svg"
lightBorders: _lightWidgetBorders
id: addShapeButton
buttonImage: "/qmlimages/MapDrawShape.svg"
lightBorders: _lightWidgetBorders
onClicked: {
var coordinate = editorMap.center
coordinate.latitude = coordinate.latitude.toFixed(_decimalPlaces)
coordinate.longitude = coordinate.longitude.toFixed(_decimalPlaces)
coordinate.altitude = coordinate.altitude.toFixed(_decimalPlaces)
var sequenceNumber = controller.insertComplexMissionItem(coordinate, controller.visualItems.count)
setCurrentItem(sequenceNumber)
DropButton {
id: syncButton
dropDirection: dropRight
buttonImage: _syncNeeded ? "/qmlimages/MapSyncChanged.svg" : "/qmlimages/MapSync.svg"
viewportMargins: ScreenTools.defaultFontPixelWidth / 2
exclusiveGroup: _dropButtonsExclusiveGroup
dropDownComponent: syncDropDownComponent
enabled: !controller.syncInProgress
rotateImage: controller.syncInProgress
DropButton {
id: centerMapButton
dropDirection: dropRight
buttonImage: "/qmlimages/MapCenter.svg"
viewportMargins: ScreenTools.defaultFontPixelWidth / 2
exclusiveGroup: _dropButtonsExclusiveGroup
dropDownComponent: Component {
Column {
QGCLabel { text: qsTr("Center map:") }
Row {
spacing: ScreenTools.defaultFontPixelWidth
QGCButton {
onClicked: {
centerMapButton.hideDropDown()
editorMap.center = controller.visualItems.get(0).coordinate
onClicked: {
centerMapButton.hideDropDown()
fitViewportToMissionItems()
}
enabled: activeVehicle && activeVehicle.latitude != 0 && activeVehicle.longitude != 0
property var activeVehicle: _activeVehicle
onClicked: {
centerMapButton.hideDropDown()
editorMap.center = activeVehicle.coordinate
DropButton {
id: mapTypeButton
dropDirection: dropRight
buttonImage: "/qmlimages/MapType.svg"
viewportMargins: ScreenTools.defaultFontPixelWidth / 2
exclusiveGroup: _dropButtonsExclusiveGroup
dropDownComponent: Component {
Column {
QGCLabel { text: qsTr("Map type:") }
Row {
spacing: ScreenTools.defaultFontPixelWidth
Repeater {
model: QGroundControl.flightMapSettings.mapTypes
checked: QGroundControl.flightMapSettings.mapType === text
text: modelData
exclusiveGroup: _mapTypeButtonsExclusiveGroup
onClicked: {
QGroundControl.flightMapSettings.mapType = text
checked = true
mapTypeButton.hideDropDown()
}
id: mapZoomPlus
visible: !ScreenTools.isTinyScreen && !ScreenTools.isShortScreen
buttonImage: "/qmlimages/ZoomPlus.svg"
lightBorders: _lightWidgetBorders
onClicked: {
if(editorMap)
editorMap.zoomLevel += 0.5
checked = false
}
}
//-- Zoom Map Out
RoundButton {
id: mapZoomMinus
visible: !ScreenTools.isTinyScreen && !ScreenTools.isShortScreen
buttonImage: "/qmlimages/ZoomMinus.svg"
lightBorders: _lightWidgetBorders
onClicked: {
if(editorMap)
editorMap.zoomLevel -= 0.5
checked = false
}
}
MapScale {
anchors.margins: ScreenTools.defaultFontPixelHeight * (0.66)
anchors.bottom: waypointValuesDisplay.visible ? waypointValuesDisplay.top : parent.bottom
anchors.left: parent.left
z: QGroundControl.zOrderWidgets
mapControl: editorMap
visible: !ScreenTools.isTinyScreen
}
anchors.margins: ScreenTools.defaultFontPixelWidth
anchors.left: parent.left
anchors.bottom: parent.bottom
z: QGroundControl.zOrderTopMost
currentMissionItem: _currentMissionItem
expandedWidth: missionItemEditor.x - (ScreenTools.defaultFontPixelWidth * 2)
missionDistance: controller.missionDistance
missionMaxTelemetry: controller.missionMaxTelemetry
cruiseDistance: controller.cruiseDistance
hoverDistance: controller.hoverDistance
} // Item - split view container
} // QGCViewPanel
Component {
id: syncLoadFromVehicleOverwrite
QGCViewMessage {
id: syncLoadFromVehicleCheck
message: qsTr("You have unsaved/unsent mission changes. Loading the mission from the Vehicle will lose these changes. Are you sure you want to load the mission from the Vehicle?")
function accept() {
hideDialog()
}
}
}
Component {
id: syncLoadFromFileOverwrite
QGCViewMessage {
id: syncLoadFromVehicleCheck
message: qsTr("You have unsaved/unsent mission changes. Loading a mission from a file will lose these changes. Are you sure you want to load a mission from a file?")
function accept() {
hideDialog()
Component {
id: removeAllPromptDialog
QGCViewMessage {
message: qsTr("Are you sure you want to delete all mission items?")
function accept() {
itemDragger.clearItem()
controller.removeAllMissionItems()
hideDialog()
}
}
}
Component {
id: syncDropDownComponent
Column {
id: columnHolder
spacing: _margin
QGCLabel {
qsTr("You have unsaved changes to your mission. You should send to your vehicle, or save to a file:") :
GridLayout {
id: sendSaveGrid
columns: 2
anchors.margins: _margin
rowSpacing: _margin
columnSpacing: ScreenTools.defaultFontPixelWidth
visible: true //autoSyncCheckBox.enabled && autoSyncCheckBox.checked
text: qsTr("Send To Vehicle")
Layout.fillWidth: true
enabled: _activeVehicle && !controller.syncInProgress
onClicked: {
syncButton.hideDropDown()
controller.sendMissionItems()
}
}
QGCButton {
text: qsTr("Load From Vehicle")
Layout.fillWidth: true
enabled: _activeVehicle && !controller.syncInProgress
onClicked: {
syncButton.hideDropDown()
_root.showDialog(syncLoadFromVehicleOverwrite, qsTr("Mission overwrite"), _root.showDialogDefaultWidth, StandardButton.Yes | StandardButton.Cancel)
text: qsTr("Save To File...")
Layout.fillWidth: true
enabled: !controller.syncInProgress
onClicked: {
syncButton.hideDropDown()
text: qsTr("Load From File...")
Layout.fillWidth: true
enabled: !controller.syncInProgress
onClicked: {
syncButton.hideDropDown()
_root.showDialog(syncLoadFromFileOverwrite, qsTr("Mission overwrite"), _root.showDialogDefaultWidth, StandardButton.Yes | StandardButton.Cancel)
QGCButton {
text: qsTr("Remove All")
Layout.fillWidth: true
onClicked: {
syncButton.hideDropDown()
_root.showDialog(removeAllPromptDialog, qsTr("Delete all"), _root.showDialogDefaultWidth, StandardButton.Yes | StandardButton.No)
}
/*
FIXME: autoSync is temporarily disconnected since it's still buggy
QGCLabel {
id: autoSyncDisallowedLabel
visible: _activeVehicle && _activeVehicle.armed
text: "AutoSync is not allowed whie vehicle is armed"
}
QGCCheckBox {
id: autoSyncCheckBox
checked: controller.autoSync
text: "Automatically sync changes with vehicle"
enabled: _activeVehicle ? !_activeVehicle.armed : false
onClicked: controller.autoSync = checked
}