Newer
Older
/*=====================================================================
QGroundControl Open Source Ground Control Station
(c) 2009, 2015 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
This file is part of the QGROUNDCONTROL project
QGROUNDCONTROL is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
QGROUNDCONTROL is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.
======================================================================*/
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
property bool syncNeeded: controller.missionItems.dirty // Unsaved changes, visible to parent container
viewPanel: panel
topDialogMargin: height - mainWindow.availableHeight
// zOrder comes from the Loader in MainWindow.qml
readonly property real _horizontalMargin: ScreenTools.defaultFontPixelWidth / 2
readonly property real _margin: ScreenTools.defaultFontPixelHeight / 2
readonly property var _activeVehicle: multiVehicleManager.activeVehicle
readonly property real _editFieldWidth: ScreenTools.defaultFontPixelWidth * 16
readonly property real _rightPanelWidth: ScreenTools.defaultFontPixelWidth * 30
readonly property real _rightPanelOpacity: 0.8
readonly property int _toolButtonCount: 6
readonly property string _autoSyncKey: "AutoSync"
readonly property int _addMissionItemsButtonAutoOffTimeout: 10000
readonly property var _defaultVehicleCoordinate: QtPositioning.coordinate(37.803784, -122.462276)
property var _missionItems: controller.missionItems
property var _currentMissionItem
property bool _firstVehiclePosition: true
property var activeVehiclePosition: _activeVehicle ? _activeVehicle.coordinate : QtPositioning.coordinate()
property bool _syncInProgress: _activeVehicle ? _activeVehicle.missionManager.inProgress : false
onActiveVehiclePositionChanged: updateMapToVehiclePosition()
Connections {
target: 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() {
if (ScreenTools.isMobile) {
_root.showDialog(mobileFilePicker, "Select Mission File", _root.showDialogDefaultWidth, StandardButton.Yes | StandardButton.Cancel)
} else {
controller.loadMissionFromFile()
fitViewportToMissionItems()
}
}
function saveToFile() {
if (ScreenTools.isMobile) {
_root.showDialog(mobileFileSaver, "Save Mission File", _root.showDialogDefaultWidth, StandardButton.Save | StandardButton.Cancel)
} else {
controller.saveToFile()
}
}
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 (_missionItems.count == 1) {
editorMap.center = _missionItems.get(0).coordinate
} else {
var missionItem = _missionItems.get(0)
var north = normalizeLat(missionItem.coordinate.latitude)
var south = north
var east = normalizeLon(missionItem.coordinate.longitude)
var west = east
for (var i=1; i<_missionItems.count; i++) {
missionItem = _missionItems.get(i)
if (missionItem.specifiesCoordinate && !missionItem.standaloneCoordinate) {
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)
onNewItemsFromVehicle: fitViewportToMissionItems()
QGCPalette { id: qgcPal; colorGroupEnabled: enabled }
ExclusiveGroup {
id: _mapTypeButtonsExclusiveGroup
}
ExclusiveGroup {
id: _dropButtonsExclusiveGroup
if (i == index) {
_currentMissionItem = _missionItems.get(i)
_currentMissionItem.isCurrentItem = true
} else {
_missionItems.get(i).isCurrentItem = false
}
property int _moveDialogMissionItemIndex
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
Component {
id: mobileFilePicker
QGCViewDialog {
ListView {
anchors.margins: _margin
anchors.fill: parent
spacing: _margin / 2
orientation: ListView.Vertical
model: controller.getMobileMissionFiles()
delegate: QGCButton {
text: modelData
onClicked: {
hideDialog()
controller.loadMobileMissionFromFile(modelData)
fitViewportToMissionItems()
}
}
}
}
}
Component {
id: mobileFileSaver
QGCViewDialog {
function accept() {
hideDialog()
controller.saveMobileMissionToFile(filenameTextField.text)
}
Column {
anchors.left: parent.left
anchors.right: parent.right
spacing: ScreenTools.defaultFontPixelHeight
QGCLabel {
text: "File name:"
}
QGCTextField {
id: filenameTextField
}
}
}
}
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
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: "Move the selected mission item to the be after following mission item:"
}
QGCComboBox {
id: toCombo
model: _missionItems.count
currentIndex: _moveDialogMissionItemIndex
}
}
}
}
QGCViewPanel {
id: panel
anchors.fill: parent
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
}
}
MouseArea {
anchors.fill: parent
onClicked: {
if (addMissionItemsButton.checked) {
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)
var index = controller.insertMissionItem(coordinate, controller.missionItems.count)
} else {
editorMap.zoomLevel = editorMap.maxZoomLevel - 2
// 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
model: controller.missionItems
MissionItemIndicator {
id: itemIndicator
coordinate: object.coordinate
onClicked: setCurrentItem(object.sequenceNumber)
if (object.isCurrentItem && itemIndicator.visible) {
if (object.specifiesCoordinate) {
// Setup our drag item
itemDragger.visible = true
itemDragger.missionItem = Qt.binding(function() { return object })
itemDragger.missionItemIndicator = Qt.binding(function() { return itemIndicator })
Connections {
target: object
onIsCurrentItemChanged: updateItemIndicator()
onCommandChanged: 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 {
label: object.sequenceNumber
isCurrentItem: object.isCurrentItem
z: 2
onClicked: setCurrentItem(object.sequenceNumber)
}
}
}
}
}
// Add lines between waypoints
MissionLineView {
model: controller.waypointLines
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.
preventStealing: true
onWheel: wheel.accepted = true
}
id: editorListView
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
height: Math.min(contentHeight, parent.height)
spacing: _margin / 2
orientation: ListView.Vertical
missionItem: object
width: parent.width
onClicked: setCurrentItem(object.sequenceNumber)
onRemove: {
controller.removeMissionItem(object.sequenceNumber)
onInsert: {
controller.insertMissionItem(editorMap.center, i)
setCurrentItem(i)
}
onMoveHomeToMapCenter: controller.missionItems.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
}
}
Item {
id: toolbarSpacer
height: mainWindow.tbHeight
width: 1
}
//-- Vertical Tool Buttons
Column {
id: toolColumn
anchors.margins: ScreenTools.defaultFontPixelHeight
anchors.left: parent.left
anchors.top: toolbarSpacer.bottom
spacing: ScreenTools.defaultFontPixelHeight
RoundButton {
id: addMissionItemsButton
buttonImage: "/qmlimages/MapAddMission.svg"
z: QGroundControl.zOrderWidgets
DropButton {
id: syncButton
dropDirection: dropRight
buttonImage: syncNeeded ? "/qmlimages/MapSyncChanged.svg" : "/qmlimages/MapSync.svg"
viewportMargins: ScreenTools.defaultFontPixelWidth / 2
exclusiveGroup: _dropButtonsExclusiveGroup
z: QGroundControl.zOrderWidgets
dropDownComponent: syncDropDownComponent
enabled: !_syncInProgress
}
DropButton {
id: centerMapButton
dropDirection: dropRight
buttonImage: "/qmlimages/MapCenter.svg"
viewportMargins: ScreenTools.defaultFontPixelWidth / 2
exclusiveGroup: _dropButtonsExclusiveGroup
z: QGroundControl.zOrderWidgets
dropDownComponent: Component {
Column {
QGCLabel { text: "Center map:" }
Row {
spacing: ScreenTools.defaultFontPixelWidth
onClicked: {
centerMapButton.hideDropDown()
editorMap.center = controller.missionItems.get(0).coordinate
QGCButton {
text: "Mission"
onClicked: {
centerMapButton.hideDropDown()
fitViewportToMissionItems()
}
QGCButton {
text: "Vehicle"
enabled: activeVehicle && activeVehicle.latitude != 0 && activeVehicle.longitude != 0
property var activeVehicle: multiVehicleManager.activeVehicle
onClicked: {
centerMapButton.hideDropDown()
editorMap.center = activeVehicle.coordinate
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
DropButton {
id: mapTypeButton
dropDirection: dropRight
buttonImage: "/qmlimages/MapType.svg"
viewportMargins: ScreenTools.defaultFontPixelWidth / 2
exclusiveGroup: _dropButtonsExclusiveGroup
z: QGroundControl.zOrderWidgets
dropDownComponent: Component {
Column {
QGCLabel { text: "Map type:" }
Row {
spacing: ScreenTools.defaultFontPixelWidth
Repeater {
model: QGroundControl.flightMapSettings.mapTypes
QGCButton {
checkable: true
checked: editorMap.mapType == text
text: modelData
exclusiveGroup: _mapTypeButtonsExclusiveGroup
onClicked: {
editorMap.mapType = text
checked = true
mapTypeButton.hideDropDown()
}
//-- Zoom Map In
RoundButton {
id: mapZoomPlus
visible: !ScreenTools.isTinyScreen && !ScreenTools.isShortScreen
buttonImage: "/qmlimages/ZoomPlus.svg"
z: QGroundControl.zOrderWidgets
onClicked: {
if(editorMap)
editorMap.zoomLevel += 0.5
checked = false
}
}
//-- Zoom Map Out
RoundButton {
id: mapZoomMinus
visible: !ScreenTools.isTinyScreen && !ScreenTools.isShortScreen
buttonImage: "/qmlimages/ZoomMinus.svg"
z: QGroundControl.zOrderWidgets
onClicked: {
if(editorMap)
editorMap.zoomLevel -= 0.5
checked = false
}
}
anchors.margins: ScreenTools.defaultFontPixelWidth
anchors.left: parent.left
anchors.bottom: parent.bottom
z: QGroundControl.zOrderTopMost
currentMissionItem: _currentMissionItem
missionItems: controller.missionItems
expandedWidth: missionItemEditor.x - (ScreenTools.defaultFontPixelWidth * 2)
} // Item - split view container
} // QGCViewPanel
Component {
id: syncLoadFromVehicleOverwrite
QGCViewMessage {
id: syncLoadFromVehicleCheck
message: "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: "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: "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 {
width: columnHolder.width
wrapMode: Text.WordWrap
text: syncNeeded && !controller.autoSync ?
"You have unsaved changed to you mission. You should send to your vehicle, or save to a file:" :
"Sync:"
}
Row {
visible: true //autoSyncCheckBox.enabled && autoSyncCheckBox.checked
spacing: ScreenTools.defaultFontPixelWidth
QGCButton {
text: "Send to vehicle"
enabled: _activeVehicle && !_activeVehicle.missionManager.inProgress
onClicked: {
syncButton.hideDropDown()
controller.sendMissionItems()
}
}
QGCButton {
text: "Load from vehicle"
enabled: _activeVehicle && !_activeVehicle.missionManager.inProgress
onClicked: {
syncButton.hideDropDown()
if (syncNeeded) {
_root.showDialog(syncLoadFromVehicleOverwrite, "Mission overwrite", _root.showDialogDefaultWidth, StandardButton.Yes | StandardButton.Cancel)
} else {
}
}
}
Row {
spacing: ScreenTools.defaultFontPixelWidth
QGCButton {
text: "Save to file..."
onClicked: {
syncButton.hideDropDown()
}
}
QGCButton {
text: "Load from file..."
onClicked: {
syncButton.hideDropDown()
if (syncNeeded) {
_root.showDialog(syncLoadFromFileOverwrite, "Mission overwrite", _root.showDialogDefaultWidth, StandardButton.Yes | StandardButton.Cancel)
} else {
QGCButton {
text: "Remove all"
onClicked: {
syncButton.hideDropDown()
_root.showDialog(removeAllPromptDialog, "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
}