MissionEditor.qml 41.4 KB
Newer Older
1 2 3 4 5 6 7 8
/****************************************************************************
 *
 *   (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.
 *
 ****************************************************************************/
Don Gagne's avatar
Don Gagne committed
9 10


11 12
import QtQuick          2.3
import QtQuick.Controls 1.2
Don Gagne's avatar
Don Gagne committed
13
import QtQuick.Dialogs  1.2
14 15 16
import QtLocation       5.3
import QtPositioning    5.3
import QtQuick.Layouts  1.2
Don Gagne's avatar
Don Gagne committed
17

18
import QGroundControl               1.0
Don Gagne's avatar
Don Gagne committed
19 20 21 22
import QGroundControl.FlightMap     1.0
import QGroundControl.ScreenTools   1.0
import QGroundControl.Controls      1.0
import QGroundControl.Palette       1.0
Don Gagne's avatar
Don Gagne committed
23
import QGroundControl.Mavlink       1.0
24
import QGroundControl.Controllers   1.0
Don Gagne's avatar
Don Gagne committed
25 26

/// Mission Editor
Don Gagne's avatar
Don Gagne committed
27

Don Gagne's avatar
Don Gagne committed
28
QGCView {
29 30
    id:         qgcView
    viewPanel:  panel
Don Gagne's avatar
Don Gagne committed
31

32
    // zOrder comes from the Loader in MainWindow.qml
Gus Grubba's avatar
Gus Grubba committed
33
    z: QGroundControl.zOrderTopMost
34

35 36 37 38 39
    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)
Gus Grubba's avatar
Gus Grubba committed
40
    readonly property real      _rightPanelOpacity:     1
41 42
    readonly property int       _toolButtonCount:       6
    readonly property real      _toolButtonTopMargin:   parent.height - ScreenTools.availableHeight + (ScreenTools.defaultFontPixelHeight / 2)
43
    readonly property var       _defaultVehicleCoordinate:   QtPositioning.coordinate(37.803784, -122.462276)
44

45
    property var    _visualItems:           missionController.visualItems
Don Gagne's avatar
Don Gagne committed
46
    property var    _currentMissionItem
47
    property int    _currentMissionIndex:   0
48 49
    property bool   _firstVehiclePosition:  true
    property var    activeVehiclePosition:  _activeVehicle ? _activeVehicle.coordinate : QtPositioning.coordinate()
50
    property bool   _lightWidgetBorders:    editorMap.isSatelliteMap
51
    property bool   _addWaypointOnClick:    false
52

53 54 55 56 57
    /// The controller which should be called for load/save, send to/from vehicle calls
    property var _syncDropDownController: missionController

    readonly property int _layerMission:        1
    readonly property int _layerGeoFence:       2
58
    readonly property int _layerRallyPoints:    3
59 60
    property int _editingLayer: _layerMission

61
    onActiveVehiclePositionChanged: updateMapToVehiclePosition()
62

63
    Connections {
64
        target: QGroundControl.multiVehicleManager
65 66 67 68 69 70

        onActiveVehicleChanged: {
            // When the active vehicle changes we need to allow the first vehicle position to move the map again
            _firstVehiclePosition = true
            updateMapToVehiclePosition()
        }
71
    }
72 73

    function updateMapToVehiclePosition() {
74
        if (_activeVehicle && _activeVehicle.coordinateValid && _activeVehicle.coordinate.isValid && _firstVehiclePosition) {
75 76
            _firstVehiclePosition = false
            editorMap.center = _activeVehicle.coordinate
77 78 79
        }
    }

80 81 82 83 84 85 86 87
    property bool _firstMissionLoadComplete:    false
    property bool _firstFenceLoadComplete:      false
    property bool _firstRallyLoadComplete:      false
    property bool _firstLoadComplete:           false

    function checkFirstLoadComplete() {
        if (!_firstLoadComplete && _firstMissionLoadComplete && _firstRallyLoadComplete && _firstFenceLoadComplete) {
            _firstLoadComplete = true
88
            mapFitFunctions.fitMapViewportToAllItems()
89 90 91
        }
    }

92 93 94
    MapFitFunctions {
        id:                         mapFitFunctions
        map:                        editorMap
95
        mapFitViewport:             editorMap.centerViewport
96 97 98 99 100 101
        usePlannedHomePosition:     true
        mapGeoFenceController:      geoFenceController
        mapMissionController:       missionController
        mapRallyPointController:    rallyPointController
    }

102
    MissionController {
103
        id: missionController
104

105 106
        Component.onCompleted: {
            start(true /* editMode */)
107
            setCurrentItem(0)
108 109
        }

110 111
        function loadFromSelectedFile() {
            if (ScreenTools.isMobile) {
Don Gagne's avatar
Don Gagne committed
112
                qgcView.showDialog(mobileFilePicker, qsTr("Select Mission File"), qgcView.showDialogDefaultWidth, StandardButton.Cancel)
113 114
            } else {
                missionController.loadFromFilePicker()
115
                mapFitFunctions.fitMapViewportToMissionItems()
116 117 118 119 120 121
                _currentMissionItem = _visualItems.get(0)
            }
        }

        function saveToSelectedFile() {
            if (ScreenTools.isMobile) {
122
                qgcView.showDialog(mobileFileSaver, qsTr("Save Mission File"), qgcView.showDialogDefaultWidth, StandardButton.Save | StandardButton.Cancel)
123 124 125 126 127
            } else {
                missionController.saveToFilePicker()
            }
        }

128
        function fitViewportToItems() {
129
            mapFitFunctions.fitMapViewportToMissionItems()
130 131
        }

132 133 134
        onVisualItemsChanged: {
            itemDragger.clearItem()
        }
135

136
        onNewItemsFromVehicle: {
137 138 139
            if (_visualItems && _visualItems.count != 1) {
                mapFitFunctions.fitMapViewportToMissionItems()
            }
140
            setCurrentItem(0)
141 142
            _firstMissionLoadComplete = true
            checkFirstLoadComplete()
143 144
        }
    }
145

146 147
    GeoFenceController {
        id: geoFenceController
148

149
        Component.onCompleted: start(true /* editMode */)
150

151 152 153 154 155 156 157 158 159 160 161 162 163
        function saveToSelectedFile() {
            if (ScreenTools.isMobile) {
                qgcView.showDialog(mobileFileSaver, qsTr("Save Fence File"), qgcView.showDialogDefaultWidth, StandardButton.Save | StandardButton.Cancel)
            } else {
                geoFenceController.saveToFilePicker()
            }
        }

        function loadFromSelectedFile() {
            if (ScreenTools.isMobile) {
                qgcView.showDialog(mobileFilePicker, qsTr("Select Fence File"), qgcView.showDialogDefaultWidth, StandardButton.Yes | StandardButton.Cancel)
            } else {
                geoFenceController.loadFromFilePicker()
164
                mapFitFunctions.fitMapViewportToFenceItems()
165 166 167
            }
        }

168 169 170 171 172 173 174 175
        function validateBreachReturn() {
            if (geoFenceController.polygon.path.length > 0) {
                if (!geoFenceController.polygon.containsCoordinate(geoFenceController.breachReturnPoint)) {
                    geoFenceController.breachReturnPoint = geoFenceController.polygon.center()
                }
                if (!geoFenceController.polygon.containsCoordinate(geoFenceController.breachReturnPoint)) {
                    geoFenceController.breachReturnPoint = geoFenceController.polygon.path[0]
                }
176 177
            }
        }
178 179

        function fitViewportToItems() {
180
            mapFitFunctions.fitMapViewportToFenceItems()
181 182 183 184 185 186
        }

        onLoadComplete: {
            _firstFenceLoadComplete = true
            switch (_syncDropDownController) {
            case geoFenceController:
187
                mapFitFunctions.fitMapViewportToFenceItems()
188 189 190 191 192 193
                break
            case missionController:
                checkFirstLoadComplete()
                break
            }
        }
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
    RallyPointController {
        id: rallyPointController

        onCurrentRallyPointChanged: {
            if (_editingLayer == _layerRallyPoints && !currentRallyPoint) {
                itemDragger.visible = false
                itemDragger.coordinateItem = undefined
                itemDragger.mapCoordinateIndicator = undefined
            }
        }

        Component.onCompleted: start(true /* editMode */)

        function saveToSelectedFile() {
            if (ScreenTools.isMobile) {
                qgcView.showDialog(mobileFileSaver, qsTr("Save Rally Point File"), qgcView.showDialogDefaultWidth, StandardButton.Save | StandardButton.Cancel)
            } else {
                rallyPointController.saveToFilePicker()
            }
        }

        function loadFromSelectedFile() {
            if (ScreenTools.isMobile) {
                qgcView.showDialog(mobileFilePicker, qsTr("Select Rally Point File"), qgcView.showDialogDefaultWidth, StandardButton.Yes | StandardButton.Cancel)
            } else {
                rallyPointController.loadFromFilePicker()
222
                mapFitFunctions.fitMapViewportToRallyItems()
223 224 225 226
            }
        }

        function fitViewportToItems() {
227
            mapFitFunctions.fitMapViewportToRallyItems()
228 229 230 231 232 233
        }

        onLoadComplete: {
            _firstRallyLoadComplete = true
            switch (_syncDropDownController) {
            case rallyPointController:
234
                mapFitFunctions.fitMapViewportToRallyItems()
235 236 237 238
                break
            case missionController:
                checkFirstLoadComplete()
                break
239 240 241 242
            }
        }
    }

243
    QGCPalette { id: qgcPal; colorGroupEnabled: enabled }
Don Gagne's avatar
Don Gagne committed
244

245 246 247 248
    ExclusiveGroup {
        id: _mapTypeButtonsExclusiveGroup
    }

249 250
    /// Sets a new current mission item
    ///     @param sequenceNumber - index for new item, -1 to clear current item
251
    function setCurrentItem(sequenceNumber) {
252 253 254 255 256 257 258 259 260 261 262 263
        if (sequenceNumber !== _currentMissionIndex) {
            _currentMissionItem = undefined
            _currentMissionIndex = -1
            for (var i=0; i<_visualItems.count; i++) {
                var visualItem = _visualItems.get(i)
                if (visualItem.sequenceNumber == sequenceNumber) {
                    _currentMissionItem = visualItem
                    _currentMissionItem.isCurrentItem = true
                    _currentMissionIndex = i
                } else {
                    visualItem.isCurrentItem = false
                }
Don Gagne's avatar
Don Gagne committed
264
            }
265 266 267
        }
    }

268 269 270 271 272 273 274 275 276
    /// Inserts a new simple mission item
    ///     @param coordinate Location to insert item
    ///     @param index Insert item at this index
    function insertSimpleMissionItem(coordinate, index) {
        setCurrentItem(-1)
        var sequenceNumber = missionController.insertSimpleMissionItem(coordinate, index)
        setCurrentItem(sequenceNumber)
    }

277 278
    property int _moveDialogMissionItemIndex

279 280 281
    Component {
        id: mobileFilePicker

Don Gagne's avatar
Don Gagne committed
282 283
        QGCMobileFileOpenDialog {
            fileExtension: _syncDropDownController.fileExtension
284 285 286 287
            onFilenameReturned: {
                _syncDropDownController.loadFromFile(filename)
                _syncDropDownController.fitViewportToItems()
            }
288 289 290 291 292 293
        }
    }

    Component {
        id: mobileFileSaver

Don Gagne's avatar
Don Gagne committed
294
        QGCMobileFileSaveDialog {
295
            fileExtension:      _syncDropDownController.fileExtension
296
            onFilenameReturned: _syncDropDownController.saveToFile(filename)
297 298 299
        }
    }

300 301 302 303 304 305 306 307 308 309
    Component {
        id: moveDialog

        QGCViewDialog {
            function accept() {
                var toIndex = toCombo.currentIndex

                if (toIndex == 0) {
                    toIndex = 1
                }
310
                missionController.moveMissionItem(_moveDialogMissionItemIndex, toIndex)
311 312 313 314 315 316 317 318 319 320 321 322
                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
323
                    text:           qsTr("Move the selected mission item to the be after following mission item:")
324 325 326 327
                }

                QGCComboBox {
                    id:             toCombo
328
                    model:          _visualItems.count
329 330 331 332 333 334
                    currentIndex:   _moveDialogMissionItemIndex
                }
            }
        }
    }

Don Gagne's avatar
Don Gagne committed
335 336
    QGCViewPanel {
        id:             panel
337 338 339 340
        height:         ScreenTools.availableHeight
        anchors.bottom: parent.bottom
        anchors.left:   parent.left
        anchors.right:  parent.right
Don Gagne's avatar
Don Gagne committed
341

Don Gagne's avatar
Don Gagne committed
342
        Item {
Don Gagne's avatar
Don Gagne committed
343 344
            anchors.fill: parent

Don Gagne's avatar
Don Gagne committed
345 346
            FlightMap {
                id:             editorMap
347
                height:         qgcView.height
348 349 350
                anchors.bottom: parent.bottom
                anchors.left:   parent.left
                anchors.right:  parent.right
Don Gagne's avatar
Don Gagne committed
351
                mapName:        "MissionEditor"
352

353 354 355 356 357 358 359
                // This is the center rectangle of the map which is not obscured by tools
                property rect centerViewport: Qt.rect(_leftToolWidth, _toolbarHeight, editorMap.width - _leftToolWidth - _rightPanelWidth, editorMap.height - _statusHeight - _toolbarHeight)

                property real _toolbarHeight:   qgcView.height - ScreenTools.availableHeight
                property real _leftToolWidth:   toolStrip.x + toolStrip.width
                property real _statusHeight:    waypointValuesDisplay.visible ? editorMap.height - waypointValuesDisplay.y : 0

360 361
                readonly property real animationDuration: 500

362 363 364
                // Initial map position duplicates Fly view position
                Component.onCompleted: editorMap.center = QGroundControl.flightMapPosition

365 366 367 368 369 370 371
                Behavior on zoomLevel {
                    NumberAnimation {
                        duration:       editorMap.animationDuration
                        easing.type:    Easing.InOutQuad
                    }
                }

372 373
                QGCMapPalette { id: mapPal; lightColors: editorMap.isSatelliteMap }

Don Gagne's avatar
Don Gagne committed
374
                MouseArea {
375 376
                    //-- It's a whole lot faster to just fill parent and deal with top offset below
                    //   than computing the coordinate offset.
Don Gagne's avatar
Don Gagne committed
377 378
                    anchors.fill: parent
                    onClicked: {
379 380
                        //-- Don't pay attention to items beneath the toolbar.
                        var topLimit = parent.height - ScreenTools.availableHeight
381 382 383 384
                        if(mouse.y < topLimit) {
                            return
                        }

DonLakeFlyer's avatar
DonLakeFlyer committed
385
                        var coordinate = editorMap.toCoordinate(Qt.point(mouse.x, mouse.y), false /* clipToViewPort */)
386 387 388 389 390 391
                        coordinate.latitude = coordinate.latitude.toFixed(_decimalPlaces)
                        coordinate.longitude = coordinate.longitude.toFixed(_decimalPlaces)
                        coordinate.altitude = coordinate.altitude.toFixed(_decimalPlaces)

                        switch (_editingLayer) {
                        case _layerMission:
392
                            if (_addWaypointOnClick) {
393
                                insertSimpleMissionItem(coordinate, missionController.visualItems.count)
394
                            }
395 396
                            break
                        case _layerGeoFence:
397
                            if (geoFenceController.breachReturnEnabled) {
398 399 400 401 402 403 404 405
                                geoFenceController.breachReturnPoint = coordinate
                                geoFenceController.validateBreachReturn()
                            }
                            break
                        case _layerRallyPoints:
                            if (rallyPointController.rallyPointsSupported) {
                                rallyPointController.addPoint(coordinate)
                            }
406
                            break
407
                        }
Don Gagne's avatar
Don Gagne committed
408
                    }
Don Gagne's avatar
Don Gagne committed
409
                }
Don Gagne's avatar
Don Gagne committed
410

411
                // We use this item to support dragging since dragging a MapQuickItem just doesn't seem to work
Don Gagne's avatar
Don Gagne committed
412 413
                Rectangle {
                    id:             itemDragger
414 415
                    x:              mapCoordinateIndicator ? (mapCoordinateIndicator.x + mapCoordinateIndicator.anchorPoint.x - (itemDragger.width / 2)) : 100
                    y:              mapCoordinateIndicator ? (mapCoordinateIndicator.y + mapCoordinateIndicator.anchorPoint.y - (itemDragger.height / 2)) : 100
416 417
                    width:          ScreenTools.defaultFontPixelHeight * 3
                    height:         ScreenTools.defaultFontPixelHeight * 3
Don Gagne's avatar
Don Gagne committed
418 419 420
                    color:          "transparent"
                    visible:        false
                    z:              QGroundControl.zOrderMapItems + 1    // Above item icons
421

422 423
                    property var    coordinateItem
                    property var    mapCoordinateIndicator
424 425 426 427 428 429 430 431
                    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))
DonLakeFlyer's avatar
DonLakeFlyer committed
432
                            var coordinate = editorMap.toCoordinate(point, false /* clipToViewPort */)
433
                            coordinate.altitude = itemDragger.coordinateItem.coordinate.altitude
434
                            itemDragger.preventCoordinateBindingLoop = true
435
                            itemDragger.coordinateItem.coordinate = coordinate
436 437 438
                            itemDragger.preventCoordinateBindingLoop = false
                        }
                    }
Don Gagne's avatar
Don Gagne committed
439

440
                    function clearItem() {
Don Gagne's avatar
Don Gagne committed
441
                        itemDragger.visible = false
442 443
                        itemDragger.coordinateItem = undefined
                        itemDragger.mapCoordinateIndicator = undefined
Don Gagne's avatar
Don Gagne committed
444 445
                    }

446 447 448 449 450 451 452 453
                    Drag.active:    itemDrag.drag.active
                    Drag.hotSpot.x: width  / 2
                    Drag.hotSpot.y: height / 2

                    MouseArea {
                        id:             itemDrag
                        anchors.fill:   parent
                        drag.target:    parent
Don Gagne's avatar
Don Gagne committed
454 455 456 457
                        drag.minimumX:  0
                        drag.minimumY:  0
                        drag.maximumX:  itemDragger.parent.width - parent.width
                        drag.maximumY:  itemDragger.parent.height - parent.height
Don Gagne's avatar
Don Gagne committed
458
                    }
459
                }
460

461
                // Add the mission item visuals to the map
462
                Repeater {
463
                    model: missionController.visualItems
464

465
                    delegate: MissionItemMapVisual {
466
                        map: editorMap
467 468 469
                    }
                }

470
                // Add lines between waypoints
471
                MissionLineView {
472
                    model:      _editingLayer == _layerMission ? missionController.waypointLines : undefined
Don Gagne's avatar
Don Gagne committed
473 474
                }

Don Gagne's avatar
Don Gagne committed
475 476
                // Add the vehicles to the map
                MapItemView {
477
                    model: QGroundControl.multiVehicleManager.vehicles
Don Gagne's avatar
Don Gagne committed
478 479
                    delegate:
                        VehicleMapItem {
480 481 482
                        vehicle:        object
                        coordinate:     object.coordinate
                        isSatellite:    editorMap.isSatelliteMap
Gus Grubba's avatar
Gus Grubba committed
483
                        size:           ScreenTools.defaultFontPixelHeight * 3
484 485
                        z:              QGroundControl.zOrderMapItems - 1
                    }
Don Gagne's avatar
Don Gagne committed
486 487
                }

488 489 490
                // Plan Element selector (Mission/Fence/Rally)
                Row {
                    id:                 planElementSelectorRow
Don Gagne's avatar
Don Gagne committed
491 492 493 494
                    anchors.topMargin:  parent.height - ScreenTools.availableHeight + _margin
                    anchors.top:        parent.top
                    anchors.leftMargin: parent.width - _rightPanelWidth
                    anchors.left:       parent.left
495
                    spacing:            _horizontalMargin
496
                    visible:            QGroundControl.corePlugin.options.enablePlanViewSelector
497 498

                    readonly property real _buttonRadius: ScreenTools.defaultFontPixelHeight * 0.75
Don Gagne's avatar
Don Gagne committed
499 500 501 502

                    ExclusiveGroup {
                        id: planElementSelectorGroup
                        onCurrentChanged: {
503 504 505 506 507 508 509 510 511 512 513 514 515 516
                            switch (current) {
                            case planElementMission:
                                _editingLayer = _layerMission
                                _syncDropDownController = missionController
                                break
                            case planElementGeoFence:
                                _editingLayer = _layerGeoFence
                                _syncDropDownController = geoFenceController
                                break
                            case planElementRallyPoints:
                                _editingLayer = _layerRallyPoints
                                _syncDropDownController = rallyPointController
                                break
                            }
517
                            _syncDropDownController.fitViewportToItems()
Don Gagne's avatar
Don Gagne committed
518 519 520
                        }
                    }

521
                    QGCRadioButton {
522 523
                        id:             planElementMission
                        exclusiveGroup: planElementSelectorGroup
524
                        text:           qsTr("Mission")
525
                        checked:        true
Don Gagne's avatar
Don Gagne committed
526
                        color:          mapPal.text
527 528
                        textStyle:      Text.Outline
                        textStyleColor: mapPal.textOutline
529 530 531 532
                    }

                    Item { height: 1; width: 1 }

533
                    QGCRadioButton {
534 535
                        id:             planElementGeoFence
                        exclusiveGroup: planElementSelectorGroup
536
                        text:           qsTr("Fence")
Don Gagne's avatar
Don Gagne committed
537
                        color:          mapPal.text
538 539
                        textStyle:      Text.Outline
                        textStyleColor: mapPal.textOutline
Don Gagne's avatar
Don Gagne committed
540
                    }
541 542 543

                    Item { height: 1; width: 1 }

544
                    QGCRadioButton {
545 546
                        id:             planElementRallyPoints
                        exclusiveGroup: planElementSelectorGroup
547
                        text:           qsTr("Rally")
Don Gagne's avatar
Don Gagne committed
548
                        color:          mapPal.text
549 550
                        textStyle:      Text.Outline
                        textStyleColor: mapPal.textOutline
551 552
                    }
                } // Row - Plan Element Selector
Don Gagne's avatar
Don Gagne committed
553

554
                // Mission Item Editor
Don Gagne's avatar
Don Gagne committed
555
                Item {
Don Gagne's avatar
Don Gagne committed
556 557
                    id:                 missionItemEditor
                    anchors.topMargin:  _margin
558
                    anchors.top:        planElementSelectorRow.visible ? planElementSelectorRow.bottom : planElementSelectorRow.top
Don Gagne's avatar
Don Gagne committed
559 560 561 562 563 564
                    anchors.bottom:     parent.bottom
                    anchors.right:      parent.right
                    width:              _rightPanelWidth
                    opacity:            _rightPanelOpacity
                    z:                  QGroundControl.zOrderTopMost
                    visible:            _editingLayer == _layerMission
565

Don Gagne's avatar
Don Gagne committed
566
                    MouseArea {
567 568 569 570 571
                        // 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:       missionItemEditorListView
                        onWheel:            wheel.accepted = true
                    }
Don Gagne's avatar
Don Gagne committed
572

Don Gagne's avatar
Don Gagne committed
573
                    QGCListView {
574
                        id:             missionItemEditorListView
575 576 577
                        anchors.left:   parent.left
                        anchors.right:  parent.right
                        anchors.top:    parent.top
578
                        height:         parent.height
579 580
                        spacing:        _margin / 2
                        orientation:    ListView.Vertical
581
                        model:          missionController.visualItems
582
                        cacheBuffer:    Math.max(height * 2, 0)
583
                        clip:           true
584
                        currentIndex:   _currentMissionIndex
585 586
                        highlightMoveDuration: 250

587
                        delegate: MissionItemEditor {
588
                            map:            editorMap
589 590
                            missionItem:    object
                            width:          parent.width
591
                            readOnly:       false
592 593 594 595

                            onClicked:  setCurrentItem(object.sequenceNumber)

                            onRemove: {
596
                                var removeIndex = index
597
                                itemDragger.clearItem()
598 599 600 601 602
                                missionController.removeMissionItem(removeIndex)
                                if (removeIndex >= missionController.visualItems.count) {
                                    removeIndex--
                                }
                                setCurrentItem(removeIndex)
603 604
                            }

605
                            onInsert: insertSimpleMissionItem(editorMap.center, index)
606
                        }
Don Gagne's avatar
Don Gagne committed
607
                    } // QGCListView
608 609
                } // Item - Mission Item editor

610 611
                // GeoFence Editor
                Loader {
Don Gagne's avatar
Don Gagne committed
612
                    anchors.topMargin:  _margin
613
                    anchors.top:        planElementSelectorRow.bottom
614 615 616 617 618 619 620 621 622 623 624 625 626
                    anchors.right:      parent.right
                    opacity:            _rightPanelOpacity
                    z:                  QGroundControl.zOrderTopMost
                    source:             _editingLayer == _layerGeoFence ? "qrc:/qml/GeoFenceEditor.qml" : ""

                    property real availableWidth:   _rightPanelWidth
                    property real availableHeight:  ScreenTools.availableHeight
                }

                // GeoFence polygon
                MapPolygon {
                    border.color:   "#80FF0000"
                    border.width:   3
627
                    path:           geoFenceController.polygon.path
628
                    z:              QGroundControl.zOrderMapItems
629
                    visible:        geoFenceController.polygonEnabled
630 631 632 633 634 635 636
                }

                // GeoFence circle
                MapCircle {
                    border.color:   "#80FF0000"
                    border.width:   3
                    center:         missionController.plannedHomePosition
637
                    radius:         geoFenceController.circleRadius
638
                    z:              QGroundControl.zOrderMapItems
639
                    visible:        geoFenceController.circleEnabled
640 641 642 643
                }

                // GeoFence breach return point
                MapQuickItem {
644 645
                    anchorPoint.x:  sourceItem.anchorPointX
                    anchorPoint.y:  sourceItem.anchorPointY
646
                    coordinate:     geoFenceController.breachReturnPoint
647
                    visible:        geoFenceController.breachReturnEnabled
648
                    sourceItem:     MissionItemIndexLabel { label: "F" }
649
                    z:              QGroundControl.zOrderMapItems
650 651
                }

652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685
                // Rally Point Editor

                RallyPointEditorHeader {
                    id:                 rallyPointHeader
                    anchors.topMargin:  _margin
                    anchors.top:        planElementSelectorRow.bottom
                    anchors.right:      parent.right
                    width:              _rightPanelWidth
                    opacity:            _rightPanelOpacity
                    z:                  QGroundControl.zOrderTopMost
                    visible:            _editingLayer == _layerRallyPoints
                    controller:         rallyPointController
                }

                RallyPointItemEditor {
                    id:                 rallyPointEditor
                    anchors.topMargin:  _margin
                    anchors.top:        rallyPointHeader.bottom
                    anchors.right:      parent.right
                    width:              _rightPanelWidth
                    opacity:            _rightPanelOpacity
                    z:                  QGroundControl.zOrderTopMost
                    visible:            _editingLayer == _layerRallyPoints && rallyPointController.points.count
                    rallyPoint:         rallyPointController.currentRallyPoint
                    controller:         rallyPointController
                }

                // Rally points on map

                MapItemView {
                    model: rallyPointController.points

                    delegate: MapQuickItem {
                        id:             itemIndicator
686 687
                        anchorPoint.x:  sourceItem.anchorPointX
                        anchorPoint.y:  sourceItem.anchorPointY
688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709
                        coordinate:     object.coordinate
                        z:              QGroundControl.zOrderMapItems

                        sourceItem: MissionItemIndexLabel {
                            id:         itemIndexLabel
                            label:      qsTr("R", "rally point map item label")
                            checked:    _editingLayer == _layerRallyPoints ? object == rallyPointController.currentRallyPoint : false

                            onClicked: rallyPointController.currentRallyPoint = object

                            onCheckedChanged: {
                                if (checked) {
                                    // Setup our drag item
                                    itemDragger.visible = true
                                    itemDragger.coordinateItem = Qt.binding(function() { return object })
                                    itemDragger.mapCoordinateIndicator = Qt.binding(function() { return itemIndicator })
                                }
                            }
                        }
                    }
                }

710
                ToolStrip {
Don Gagne's avatar
Don Gagne committed
711
                    id:                 toolStrip
712 713 714 715 716 717 718
                    anchors.leftMargin: ScreenTools.defaultFontPixelWidth
                    anchors.left:       parent.left
                    anchors.topMargin:  _toolButtonTopMargin
                    anchors.top:        parent.top
                    color:              qgcPal.window
                    title:              qsTr("Plan")
                    z:                  QGroundControl.zOrderWidgets
Don Gagne's avatar
Don Gagne committed
719 720 721 722
                    showAlternateIcon:  [ false, false, _syncDropDownController.dirty, false, false, false, false ]
                    rotateImage:        [ false, false, _syncDropDownController.syncInProgress, false, false, false, false ]
                    buttonEnabled:      [ true, true, !_syncDropDownController.syncInProgress, true, true, true, true ]
                    buttonVisible:      [ true, true, true, true, true, _showZoom, _showZoom ]
723
                    maxHeight:          mapScale.y - toolStrip.y
Don Gagne's avatar
Don Gagne committed
724

Don Gagne's avatar
Don Gagne committed
725
                    property bool _showZoom: !ScreenTools.isMobile
726 727 728 729 730 731 732 733

                    model: [
                        {
                            name:       "Waypoint",
                            iconSource: "/qmlimages/MapAddMission.svg",
                            toggle:     true
                        },
                        {
734 735 736
                            name:               "Pattern",
                            iconSource:         "/qmlimages/MapDrawShape.svg",
                            dropPanelComponent: patternDropPanel
737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752
                        },
                        {
                            name:                   "Sync",
                            iconSource:             "/qmlimages/MapSync.svg",
                            alternateIconSource:    "/qmlimages/MapSyncChanged.svg",
                            dropPanelComponent:     syncDropPanel
                        },
                        {
                            name:               "Center",
                            iconSource:         "/qmlimages/MapCenter.svg",
                            dropPanelComponent: centerMapDropPanel
                        },
                        {
                            name:               "Map",
                            iconSource:         "/qmlimages/MapType.svg",
                            dropPanelComponent: mapTypeDropPanel
Don Gagne's avatar
Don Gagne committed
753 754 755 756 757 758 759 760
                        },
                        {
                            name:               "In",
                            iconSource:         "/qmlimages/ZoomPlus.svg"
                        },
                        {
                            name:               "Out",
                            iconSource:         "/qmlimages/ZoomMinus.svg"
761 762 763 764
                        }
                    ]

                    onClicked: {
Don Gagne's avatar
Don Gagne committed
765
                        switch (index) {
Don Gagne's avatar
Don Gagne committed
766
                        case 0:
767
                            _addWaypointOnClick = checked
Don Gagne's avatar
Don Gagne committed
768 769 770 771 772 773 774 775 776 777 778 779 780
                            break
                        case 5:
                            editorMap.zoomLevel += 0.5
                            break
                        case 6:
                            editorMap.zoomLevel -= 0.5
                            break
                        case 5:
                            editorMap.zoomLevel += 0.5
                            break
                        case 6:
                            editorMap.zoomLevel -= 0.5
                            break
781 782 783 784
                        }
                    }
                }

785
                MapScale {
786
                    id:                 mapScale
787 788 789 790 791 792 793
                    anchors.margins:    ScreenTools.defaultFontPixelHeight * (0.66)
                    anchors.bottom:     waypointValuesDisplay.visible ? waypointValuesDisplay.top : parent.bottom
                    anchors.left:       parent.left
                    mapControl:         editorMap
                    visible:            !ScreenTools.isTinyScreen
                }

794
                MissionItemStatus {
795 796 797 798 799 800 801 802 803
                    id:                     waypointValuesDisplay
                    anchors.margins:        ScreenTools.defaultFontPixelWidth
                    anchors.left:           parent.left
                    anchors.bottom:         parent.bottom
                    z:                      QGroundControl.zOrderTopMost
                    currentMissionItem:     _currentMissionItem
                    missionItems:           missionController.visualItems
                    expandedWidth:          missionItemEditor.x - (ScreenTools.defaultFontPixelWidth * 2)
                    missionDistance:        missionController.missionDistance
804
                    missionTime:            missionController.missionTime
805 806
                    missionMaxTelemetry:    missionController.missionMaxTelemetry
                    visible:                _editingLayer == _layerMission && !ScreenTools.isShortScreen
807
                }
808
            } // FlightMap
Don Gagne's avatar
Don Gagne committed
809 810
        } // Item - split view container
    } // QGCViewPanel
811

812 813 814 815
    Component {
        id: syncLoadFromVehicleOverwrite
        QGCViewMessage {
            id:         syncLoadFromVehicleCheck
Don Gagne's avatar
Don Gagne committed
816
            message:   qsTr("You have unsaved/unsent changes. Loading from the Vehicle will lose these changes. Are you sure you want to load from the Vehicle?")
817 818
            function accept() {
                hideDialog()
Don Gagne's avatar
Don Gagne committed
819
                _syncDropDownController.loadFromVehicle()
820 821 822 823 824 825 826 827
            }
        }
    }

    Component {
        id: syncLoadFromFileOverwrite
        QGCViewMessage {
            id:         syncLoadFromVehicleCheck
Don Gagne's avatar
Don Gagne committed
828
            message:   qsTr("You have unsaved/unsent changes. Loading a from a file will lose these changes. Are you sure you want to load from a file?")
829 830
            function accept() {
                hideDialog()
Don Gagne's avatar
Don Gagne committed
831
                _syncDropDownController.loadFromSelectedFile()
832 833 834 835
            }
        }
    }

836 837 838
    Component {
        id: removeAllPromptDialog
        QGCViewMessage {
Don Gagne's avatar
Don Gagne committed
839
            message: qsTr("Are you sure you want to remove all items?")
840 841
            function accept() {
                itemDragger.clearItem()
Don Gagne's avatar
Don Gagne committed
842
                _syncDropDownController.removeAll()
843 844 845 846 847
                hideDialog()
            }
        }
    }

848 849
    //- ToolStrip DropPanel Components

850
    Component {
851
        id: syncDropPanel
852

853 854 855
        Column {
            id:         columnHolder
            spacing:    _margin
856

857
            property string _overwriteText: (_editingLayer == _layerMission) ? qsTr("Mission overwrite") : ((_editingLayer == _layerGeoFence) ? qsTr("GeoFence overwrite") : qsTr("Rally Points overwrite"))
Don Gagne's avatar
Don Gagne committed
858

859
            QGCLabel {
dogmaphobic's avatar
dogmaphobic committed
860
                width:      sendSaveGrid.width
861
                wrapMode:   Text.WordWrap
862
                text:       _syncDropDownController.dirty ?
Don Gagne's avatar
Don Gagne committed
863
                                qsTr("You have unsaved changes. You should send to your vehicle, or save to a file:") :
864
                                qsTr("Sync:")
865
            }
866

dogmaphobic's avatar
dogmaphobic committed
867 868 869 870 871 872
            GridLayout {
                id:                 sendSaveGrid
                columns:            2
                anchors.margins:    _margin
                rowSpacing:         _margin
                columnSpacing:      ScreenTools.defaultFontPixelWidth
873

874
                QGCButton {
dogmaphobic's avatar
dogmaphobic committed
875 876
                    text:               qsTr("Send To Vehicle")
                    Layout.fillWidth:   true
877
                    enabled:            _activeVehicle && !_syncDropDownController.syncInProgress
878
                    onClicked: {
879
                        dropPanel.hide()
880
                        _syncDropDownController.sendToVehicle()
881 882
                    }
                }
883

884
                QGCButton {
dogmaphobic's avatar
dogmaphobic committed
885 886
                    text:               qsTr("Load From Vehicle")
                    Layout.fillWidth:   true
887
                    enabled:            _activeVehicle && !_syncDropDownController.syncInProgress
888
                    onClicked: {
889
                        dropPanel.hide()
890
                        if (_syncDropDownController.dirty) {
891
                            qgcView.showDialog(syncLoadFromVehicleOverwrite, columnHolder._overwriteText, qgcView.showDialogDefaultWidth, StandardButton.Yes | StandardButton.Cancel)
892
                        } else {
893
                            _syncDropDownController.loadFromVehicle()
894
                        }
895 896
                    }
                }
897

898
                QGCButton {
dogmaphobic's avatar
dogmaphobic committed
899 900
                    text:               qsTr("Save To File...")
                    Layout.fillWidth:   true
901
                    enabled:            !_syncDropDownController.syncInProgress
902
                    onClicked: {
903
                        dropPanel.hide()
904
                        _syncDropDownController.saveToSelectedFile()
905 906
                    }
                }
907

908
                QGCButton {
dogmaphobic's avatar
dogmaphobic committed
909 910
                    text:               qsTr("Load From File...")
                    Layout.fillWidth:   true
911
                    enabled:            !_syncDropDownController.syncInProgress
912
                    onClicked: {
913
                        dropPanel.hide()
914
                        if (_syncDropDownController.dirty) {
915
                            qgcView.showDialog(syncLoadFromFileOverwrite, columnHolder._overwriteText, qgcView.showDialogDefaultWidth, StandardButton.Yes | StandardButton.Cancel)
916
                        } else {
917
                            _syncDropDownController.loadFromSelectedFile()
918
                        }
919 920
                    }
                }
921

dogmaphobic's avatar
dogmaphobic committed
922 923 924 925
                QGCButton {
                    text:               qsTr("Remove All")
                    Layout.fillWidth:   true
                    onClicked:  {
926
                        dropPanel.hide()
927
                        qgcView.showDialog(removeAllPromptDialog, qsTr("Remove all"), qgcView.showDialogDefaultWidth, StandardButton.Yes | StandardButton.No)
dogmaphobic's avatar
dogmaphobic committed
928
                    }
929 930
                }
            }
931 932
        }
    }
933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959

    Component {
        id: centerMapDropPanel

        CenterMapDropPanel {
            map:            editorMap
            fitFunctions:   mapFitFunctions
        }
    }

    Component {
        id: mapTypeDropPanel

        Column {
            spacing: _margin

            QGCLabel { text: qsTr("Map type:") }
            Row {
                spacing: ScreenTools.defaultFontPixelWidth
                Repeater {
                    model: QGroundControl.flightMapSettings.mapTypes

                    QGCButton {
                        checkable:      true
                        checked:        QGroundControl.flightMapSettings.mapType === text
                        text:           modelData
                        exclusiveGroup: _mapTypeButtonsExclusiveGroup
960

961 962 963 964 965 966 967 968 969
                        onClicked: {
                            QGroundControl.flightMapSettings.mapType = text
                            dropPanel.hide()
                        }
                    }
                }
            }
        }
    }
970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998

    Component {
        id: patternDropPanel

        ColumnLayout {
            spacing:    ScreenTools.defaultFontPixelWidth * 0.5

            QGCLabel { text: qsTr("Create complex pattern:") }

            Repeater {
                model: missionController.complexMissionItemNames

                QGCButton {
                    text:               modelData
                    Layout.fillWidth:   true

                    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 = missionController.insertComplexMissionItem(modelData, coordinate, missionController.visualItems.count)
                        setCurrentItem(sequenceNumber)
                        dropPanel.hide()
                    }
                }
            }
        } // Column
    }
Don Gagne's avatar
Don Gagne committed
999
} // QGCVIew