MissionEditor.qml 40.2 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
        onVisualItemsChanged: itemDragger.clearItem()
133

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

144 145
    GeoFenceController {
        id: geoFenceController
146

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

149 150 151 152 153 154 155 156 157 158 159 160 161
        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()
162
                mapFitFunctions.fitMapViewportToFenceItems()
163 164 165
            }
        }

166
        function fitViewportToItems() {
167
            mapFitFunctions.fitMapViewportToFenceItems()
168 169 170 171 172 173
        }

        onLoadComplete: {
            _firstFenceLoadComplete = true
            switch (_syncDropDownController) {
            case geoFenceController:
174
                mapFitFunctions.fitMapViewportToFenceItems()
175 176 177 178 179 180
                break
            case missionController:
                checkFirstLoadComplete()
                break
            }
        }
181
    }
182

183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
    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()
209
                mapFitFunctions.fitMapViewportToRallyItems()
210 211 212 213
            }
        }

        function fitViewportToItems() {
214
            mapFitFunctions.fitMapViewportToRallyItems()
215 216 217 218 219 220
        }

        onLoadComplete: {
            _firstRallyLoadComplete = true
            switch (_syncDropDownController) {
            case rallyPointController:
221
                mapFitFunctions.fitMapViewportToRallyItems()
222 223 224 225
                break
            case missionController:
                checkFirstLoadComplete()
                break
226 227 228 229
            }
        }
    }

230
    QGCPalette { id: qgcPal; colorGroupEnabled: enabled }
Don Gagne's avatar
Don Gagne committed
231

232 233 234 235
    ExclusiveGroup {
        id: _mapTypeButtonsExclusiveGroup
    }

236 237
    /// Sets a new current mission item
    ///     @param sequenceNumber - index for new item, -1 to clear current item
238
    function setCurrentItem(sequenceNumber) {
239 240 241 242 243 244 245 246
        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
247
                    _currentMissionIndex = sequenceNumber
248 249 250
                } else {
                    visualItem.isCurrentItem = false
                }
Don Gagne's avatar
Don Gagne committed
251
            }
252 253 254
        }
    }

255 256 257 258 259 260 261 262 263
    /// 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)
    }

264 265
    property int _moveDialogMissionItemIndex

266 267 268
    Component {
        id: mobileFilePicker

Don Gagne's avatar
Don Gagne committed
269 270
        QGCMobileFileOpenDialog {
            fileExtension: _syncDropDownController.fileExtension
271 272 273 274
            onFilenameReturned: {
                _syncDropDownController.loadFromFile(filename)
                _syncDropDownController.fitViewportToItems()
            }
275 276 277 278 279 280
        }
    }

    Component {
        id: mobileFileSaver

Don Gagne's avatar
Don Gagne committed
281
        QGCMobileFileSaveDialog {
282
            fileExtension:      _syncDropDownController.fileExtension
283
            onFilenameReturned: _syncDropDownController.saveToFile(filename)
284 285 286
        }
    }

287 288 289 290 291 292 293 294 295 296
    Component {
        id: moveDialog

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

                if (toIndex == 0) {
                    toIndex = 1
                }
297
                missionController.moveMissionItem(_moveDialogMissionItemIndex, toIndex)
298 299 300 301 302 303 304 305 306 307 308 309
                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
310
                    text:           qsTr("Move the selected mission item to the be after following mission item:")
311 312 313 314
                }

                QGCComboBox {
                    id:             toCombo
315
                    model:          _visualItems.count
316 317 318 319 320 321
                    currentIndex:   _moveDialogMissionItemIndex
                }
            }
        }
    }

Don Gagne's avatar
Don Gagne committed
322 323
    QGCViewPanel {
        id:             panel
324 325 326 327
        height:         ScreenTools.availableHeight
        anchors.bottom: parent.bottom
        anchors.left:   parent.left
        anchors.right:  parent.right
Don Gagne's avatar
Don Gagne committed
328

Don Gagne's avatar
Don Gagne committed
329
        Item {
Don Gagne's avatar
Don Gagne committed
330 331
            anchors.fill: parent

Don Gagne's avatar
Don Gagne committed
332 333
            FlightMap {
                id:             editorMap
334
                height:         qgcView.height
335 336 337
                anchors.bottom: parent.bottom
                anchors.left:   parent.left
                anchors.right:  parent.right
Don Gagne's avatar
Don Gagne committed
338
                mapName:        "MissionEditor"
339

340 341 342 343 344 345 346
                // 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

347 348
                readonly property real animationDuration: 500

349 350 351
                // Initial map position duplicates Fly view position
                Component.onCompleted: editorMap.center = QGroundControl.flightMapPosition

352 353 354 355 356 357 358
                Behavior on zoomLevel {
                    NumberAnimation {
                        duration:       editorMap.animationDuration
                        easing.type:    Easing.InOutQuad
                    }
                }

359 360
                QGCMapPalette { id: mapPal; lightColors: editorMap.isSatelliteMap }

Don Gagne's avatar
Don Gagne committed
361
                MouseArea {
362 363
                    //-- 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
364 365
                    anchors.fill: parent
                    onClicked: {
366 367
                        //-- Don't pay attention to items beneath the toolbar.
                        var topLimit = parent.height - ScreenTools.availableHeight
368 369 370 371
                        if(mouse.y < topLimit) {
                            return
                        }

DonLakeFlyer's avatar
DonLakeFlyer committed
372
                        var coordinate = editorMap.toCoordinate(Qt.point(mouse.x, mouse.y), false /* clipToViewPort */)
373 374 375 376 377 378
                        coordinate.latitude = coordinate.latitude.toFixed(_decimalPlaces)
                        coordinate.longitude = coordinate.longitude.toFixed(_decimalPlaces)
                        coordinate.altitude = coordinate.altitude.toFixed(_decimalPlaces)

                        switch (_editingLayer) {
                        case _layerMission:
379
                            if (_addWaypointOnClick) {
380
                                insertSimpleMissionItem(coordinate, missionController.visualItems.count)
381
                            }
382
                            break
383 384 385 386
                        case _layerRallyPoints:
                            if (rallyPointController.rallyPointsSupported) {
                                rallyPointController.addPoint(coordinate)
                            }
387
                            break
388
                        }
Don Gagne's avatar
Don Gagne committed
389
                    }
Don Gagne's avatar
Don Gagne committed
390
                }
Don Gagne's avatar
Don Gagne committed
391

392
                // We use this item to support dragging since dragging a MapQuickItem just doesn't seem to work
Don Gagne's avatar
Don Gagne committed
393 394
                Rectangle {
                    id:             itemDragger
395 396
                    x:              mapCoordinateIndicator ? (mapCoordinateIndicator.x + mapCoordinateIndicator.anchorPoint.x - (itemDragger.width / 2)) : 100
                    y:              mapCoordinateIndicator ? (mapCoordinateIndicator.y + mapCoordinateIndicator.anchorPoint.y - (itemDragger.height / 2)) : 100
397 398
                    width:          ScreenTools.defaultFontPixelHeight * 3
                    height:         ScreenTools.defaultFontPixelHeight * 3
Don Gagne's avatar
Don Gagne committed
399 400 401
                    color:          "transparent"
                    visible:        false
                    z:              QGroundControl.zOrderMapItems + 1    // Above item icons
402

403 404
                    property var    coordinateItem
                    property var    mapCoordinateIndicator
405 406 407 408 409 410 411 412
                    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
413
                            var coordinate = editorMap.toCoordinate(point, false /* clipToViewPort */)
414
                            coordinate.altitude = itemDragger.coordinateItem.coordinate.altitude
415
                            itemDragger.preventCoordinateBindingLoop = true
416
                            itemDragger.coordinateItem.coordinate = coordinate
417 418 419
                            itemDragger.preventCoordinateBindingLoop = false
                        }
                    }
Don Gagne's avatar
Don Gagne committed
420

421
                    function clearItem() {
Don Gagne's avatar
Don Gagne committed
422
                        itemDragger.visible = false
423 424
                        itemDragger.coordinateItem = undefined
                        itemDragger.mapCoordinateIndicator = undefined
Don Gagne's avatar
Don Gagne committed
425 426
                    }

427 428 429 430 431 432 433 434
                    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
435 436 437 438
                        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
439
                    }
440
                }
441

442
                // Add the mission item visuals to the map
443
                Repeater {
444
                    model: missionController.visualItems
445

446
                    delegate: MissionItemMapVisual {
447
                        map: editorMap
448 449 450
                    }
                }

451
                // Add lines between waypoints
452
                MissionLineView {
453
                    model:      _editingLayer == _layerMission ? missionController.waypointLines : undefined
Don Gagne's avatar
Don Gagne committed
454 455
                }

Don Gagne's avatar
Don Gagne committed
456 457
                // Add the vehicles to the map
                MapItemView {
458
                    model: QGroundControl.multiVehicleManager.vehicles
Don Gagne's avatar
Don Gagne committed
459 460
                    delegate:
                        VehicleMapItem {
461 462 463
                        vehicle:        object
                        coordinate:     object.coordinate
                        isSatellite:    editorMap.isSatelliteMap
Gus Grubba's avatar
Gus Grubba committed
464
                        size:           ScreenTools.defaultFontPixelHeight * 3
465 466
                        z:              QGroundControl.zOrderMapItems - 1
                    }
Don Gagne's avatar
Don Gagne committed
467 468
                }

469 470 471
                // Plan Element selector (Mission/Fence/Rally)
                Row {
                    id:                 planElementSelectorRow
Don Gagne's avatar
Don Gagne committed
472 473 474 475
                    anchors.topMargin:  parent.height - ScreenTools.availableHeight + _margin
                    anchors.top:        parent.top
                    anchors.leftMargin: parent.width - _rightPanelWidth
                    anchors.left:       parent.left
476
                    z:                  QGroundControl.zOrderWidgets
477
                    spacing:            _horizontalMargin
478
                    visible:            QGroundControl.corePlugin.options.enablePlanViewSelector
479 480

                    readonly property real _buttonRadius: ScreenTools.defaultFontPixelHeight * 0.75
Don Gagne's avatar
Don Gagne committed
481 482 483 484

                    ExclusiveGroup {
                        id: planElementSelectorGroup
                        onCurrentChanged: {
485 486 487 488 489 490 491 492 493 494 495 496 497 498
                            switch (current) {
                            case planElementMission:
                                _editingLayer = _layerMission
                                _syncDropDownController = missionController
                                break
                            case planElementGeoFence:
                                _editingLayer = _layerGeoFence
                                _syncDropDownController = geoFenceController
                                break
                            case planElementRallyPoints:
                                _editingLayer = _layerRallyPoints
                                _syncDropDownController = rallyPointController
                                break
                            }
499
                            _syncDropDownController.fitViewportToItems()
Don Gagne's avatar
Don Gagne committed
500 501 502
                        }
                    }

503
                    QGCRadioButton {
504 505
                        id:             planElementMission
                        exclusiveGroup: planElementSelectorGroup
506
                        text:           qsTr("Mission")
507
                        checked:        true
Don Gagne's avatar
Don Gagne committed
508
                        color:          mapPal.text
509 510
                        textStyle:      Text.Outline
                        textStyleColor: mapPal.textOutline
511 512 513 514
                    }

                    Item { height: 1; width: 1 }

515
                    QGCRadioButton {
516 517
                        id:             planElementGeoFence
                        exclusiveGroup: planElementSelectorGroup
518
                        text:           qsTr("Fence")
Don Gagne's avatar
Don Gagne committed
519
                        color:          mapPal.text
520 521
                        textStyle:      Text.Outline
                        textStyleColor: mapPal.textOutline
Don Gagne's avatar
Don Gagne committed
522
                    }
523 524 525

                    Item { height: 1; width: 1 }

526
                    QGCRadioButton {
527 528
                        id:             planElementRallyPoints
                        exclusiveGroup: planElementSelectorGroup
529
                        text:           qsTr("Rally")
Don Gagne's avatar
Don Gagne committed
530
                        color:          mapPal.text
531 532
                        textStyle:      Text.Outline
                        textStyleColor: mapPal.textOutline
533 534
                    }
                } // Row - Plan Element Selector
Don Gagne's avatar
Don Gagne committed
535

536
                // Mission Item Editor
Don Gagne's avatar
Don Gagne committed
537
                Item {
Don Gagne's avatar
Don Gagne committed
538 539
                    id:                 missionItemEditor
                    anchors.topMargin:  _margin
540
                    anchors.top:        planElementSelectorRow.visible ? planElementSelectorRow.bottom : planElementSelectorRow.top
Don Gagne's avatar
Don Gagne committed
541 542 543 544 545 546
                    anchors.bottom:     parent.bottom
                    anchors.right:      parent.right
                    width:              _rightPanelWidth
                    opacity:            _rightPanelOpacity
                    z:                  QGroundControl.zOrderTopMost
                    visible:            _editingLayer == _layerMission
547

Don Gagne's avatar
Don Gagne committed
548
                    MouseArea {
549 550 551 552 553
                        // 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
554

Don Gagne's avatar
Don Gagne committed
555
                    QGCListView {
556
                        id:             missionItemEditorListView
557 558 559
                        anchors.left:   parent.left
                        anchors.right:  parent.right
                        anchors.top:    parent.top
560
                        height:         parent.height
561 562
                        spacing:        _margin / 2
                        orientation:    ListView.Vertical
563
                        model:          missionController.visualItems
564
                        cacheBuffer:    Math.max(height * 2, 0)
565
                        clip:           true
566
                        currentIndex:   _currentMissionIndex
567 568
                        highlightMoveDuration: 250

569
                        delegate: MissionItemEditor {
570
                            map:            editorMap
571 572
                            missionItem:    object
                            width:          parent.width
573
                            readOnly:       false
574 575 576 577

                            onClicked:  setCurrentItem(object.sequenceNumber)

                            onRemove: {
578
                                var removeIndex = index
579
                                itemDragger.clearItem()
580 581 582 583 584
                                missionController.removeMissionItem(removeIndex)
                                if (removeIndex >= missionController.visualItems.count) {
                                    removeIndex--
                                }
                                setCurrentItem(removeIndex)
585 586
                            }

587
                            onInsert: insertSimpleMissionItem(editorMap.center, index)
588
                        }
Don Gagne's avatar
Don Gagne committed
589
                    } // QGCListView
590 591
                } // Item - Mission Item editor

592 593
                // GeoFence Editor
                Loader {
Don Gagne's avatar
Don Gagne committed
594
                    anchors.topMargin:  _margin
595
                    anchors.top:        planElementSelectorRow.bottom
596 597
                    anchors.right:      parent.right
                    opacity:            _rightPanelOpacity
598 599
                    z:                  QGroundControl.zOrderWidgets
                    sourceComponent:    _editingLayer == _layerGeoFence ? geoFenceEditorComponent : undefined
600

601 602 603
                    property real   availableWidth:         _rightPanelWidth
                    property real   availableHeight:        ScreenTools.availableHeight
                    property var    myGeoFenceController:   geoFenceController
604 605
                }

606 607 608 609 610 611
                GeoFenceMapVisuals {
                    map:                    editorMap
                    myGeoFenceController:   geoFenceController
                    interactive:            _editingLayer == _layerGeoFence
                    homePosition:           missionController.plannedHomePosition
                    planView:               true
612 613
                }

614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647
                // 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
648 649
                        anchorPoint.x:  sourceItem.anchorPointX
                        anchorPoint.y:  sourceItem.anchorPointY
650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671
                        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 })
                                }
                            }
                        }
                    }
                }

672
                ToolStrip {
Don Gagne's avatar
Don Gagne committed
673
                    id:                 toolStrip
674 675 676 677 678 679 680
                    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
681 682
                    showAlternateIcon:  [ false, false, _syncDropDownController.dirty, false, false, false, false ]
                    rotateImage:        [ false, false, _syncDropDownController.syncInProgress, false, false, false, false ]
683
                    animateImage:       [ false, false, _syncDropDownController.dirty, false, false, false, false ]
Don Gagne's avatar
Don Gagne committed
684 685
                    buttonEnabled:      [ true, true, !_syncDropDownController.syncInProgress, true, true, true, true ]
                    buttonVisible:      [ true, true, true, true, true, _showZoom, _showZoom ]
686
                    maxHeight:          mapScale.y - toolStrip.y
Don Gagne's avatar
Don Gagne committed
687

Don Gagne's avatar
Don Gagne committed
688
                    property bool _showZoom: !ScreenTools.isMobile
689 690 691 692 693 694 695 696

                    model: [
                        {
                            name:       "Waypoint",
                            iconSource: "/qmlimages/MapAddMission.svg",
                            toggle:     true
                        },
                        {
697 698 699
                            name:               "Pattern",
                            iconSource:         "/qmlimages/MapDrawShape.svg",
                            dropPanelComponent: patternDropPanel
700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715
                        },
                        {
                            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
716 717 718 719 720 721 722 723
                        },
                        {
                            name:               "In",
                            iconSource:         "/qmlimages/ZoomPlus.svg"
                        },
                        {
                            name:               "Out",
                            iconSource:         "/qmlimages/ZoomMinus.svg"
724 725 726 727
                        }
                    ]

                    onClicked: {
Don Gagne's avatar
Don Gagne committed
728
                        switch (index) {
Don Gagne's avatar
Don Gagne committed
729
                        case 0:
730
                            _addWaypointOnClick = checked
Don Gagne's avatar
Don Gagne committed
731 732 733 734 735 736 737 738 739 740 741 742 743
                            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
744 745 746 747
                        }
                    }
                }

748
                MapScale {
749
                    id:                 mapScale
750 751 752 753 754 755 756
                    anchors.margins:    ScreenTools.defaultFontPixelHeight * (0.66)
                    anchors.bottom:     waypointValuesDisplay.visible ? waypointValuesDisplay.top : parent.bottom
                    anchors.left:       parent.left
                    mapControl:         editorMap
                    visible:            !ScreenTools.isTinyScreen
                }

757
                MissionItemStatus {
758 759 760 761 762 763 764 765 766
                    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
767
                    missionTime:            missionController.missionTime
768 769
                    missionMaxTelemetry:    missionController.missionMaxTelemetry
                    visible:                _editingLayer == _layerMission && !ScreenTools.isShortScreen
770
                }
771
            } // FlightMap
Don Gagne's avatar
Don Gagne committed
772 773
        } // Item - split view container
    } // QGCViewPanel
774

775 776 777 778
    Component {
        id: syncLoadFromVehicleOverwrite
        QGCViewMessage {
            id:         syncLoadFromVehicleCheck
Don Gagne's avatar
Don Gagne committed
779
            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?")
780 781
            function accept() {
                hideDialog()
Don Gagne's avatar
Don Gagne committed
782
                _syncDropDownController.loadFromVehicle()
783 784 785 786 787 788 789 790
            }
        }
    }

    Component {
        id: syncLoadFromFileOverwrite
        QGCViewMessage {
            id:         syncLoadFromVehicleCheck
Don Gagne's avatar
Don Gagne committed
791
            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?")
792 793
            function accept() {
                hideDialog()
Don Gagne's avatar
Don Gagne committed
794
                _syncDropDownController.loadFromSelectedFile()
795 796 797 798
            }
        }
    }

799 800 801
    Component {
        id: removeAllPromptDialog
        QGCViewMessage {
Don Gagne's avatar
Don Gagne committed
802
            message: qsTr("Are you sure you want to remove all items?")
803 804
            function accept() {
                itemDragger.clearItem()
Don Gagne's avatar
Don Gagne committed
805
                _syncDropDownController.removeAll()
806 807 808 809 810
                hideDialog()
            }
        }
    }

811 812
    //- ToolStrip DropPanel Components

813
    Component {
814
        id: syncDropPanel
815

816 817 818
        Column {
            id:         columnHolder
            spacing:    _margin
819

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

822
            QGCLabel {
dogmaphobic's avatar
dogmaphobic committed
823
                width:      sendSaveGrid.width
824
                wrapMode:   Text.WordWrap
825
                text:       _syncDropDownController.dirty ?
Don Gagne's avatar
Don Gagne committed
826
                                qsTr("You have unsaved changes. You should send to your vehicle, or save to a file:") :
827
                                qsTr("Sync:")
828
            }
829

dogmaphobic's avatar
dogmaphobic committed
830 831 832 833 834 835
            GridLayout {
                id:                 sendSaveGrid
                columns:            2
                anchors.margins:    _margin
                rowSpacing:         _margin
                columnSpacing:      ScreenTools.defaultFontPixelWidth
836

837
                QGCButton {
dogmaphobic's avatar
dogmaphobic committed
838 839
                    text:               qsTr("Send To Vehicle")
                    Layout.fillWidth:   true
840
                    enabled:            _activeVehicle && !_syncDropDownController.syncInProgress
841
                    onClicked: {
842
                        dropPanel.hide()
843
                        _syncDropDownController.sendToVehicle()
844 845
                    }
                }
846

847
                QGCButton {
dogmaphobic's avatar
dogmaphobic committed
848 849
                    text:               qsTr("Load From Vehicle")
                    Layout.fillWidth:   true
850
                    enabled:            _activeVehicle && !_syncDropDownController.syncInProgress
851
                    onClicked: {
852
                        dropPanel.hide()
853
                        if (_syncDropDownController.dirty) {
854
                            qgcView.showDialog(syncLoadFromVehicleOverwrite, columnHolder._overwriteText, qgcView.showDialogDefaultWidth, StandardButton.Yes | StandardButton.Cancel)
855
                        } else {
856
                            _syncDropDownController.loadFromVehicle()
857
                        }
858 859
                    }
                }
860

861
                QGCButton {
dogmaphobic's avatar
dogmaphobic committed
862 863
                    text:               qsTr("Save To File...")
                    Layout.fillWidth:   true
864
                    enabled:            !_syncDropDownController.syncInProgress
865
                    onClicked: {
866
                        dropPanel.hide()
867
                        _syncDropDownController.saveToSelectedFile()
868 869
                    }
                }
870

871
                QGCButton {
dogmaphobic's avatar
dogmaphobic committed
872 873
                    text:               qsTr("Load From File...")
                    Layout.fillWidth:   true
874
                    enabled:            !_syncDropDownController.syncInProgress
875
                    onClicked: {
876
                        dropPanel.hide()
877
                        if (_syncDropDownController.dirty) {
878
                            qgcView.showDialog(syncLoadFromFileOverwrite, columnHolder._overwriteText, qgcView.showDialogDefaultWidth, StandardButton.Yes | StandardButton.Cancel)
879
                        } else {
880
                            _syncDropDownController.loadFromSelectedFile()
881
                        }
882 883
                    }
                }
884

dogmaphobic's avatar
dogmaphobic committed
885 886 887 888
                QGCButton {
                    text:               qsTr("Remove All")
                    Layout.fillWidth:   true
                    onClicked:  {
889
                        dropPanel.hide()
890
                        qgcView.showDialog(removeAllPromptDialog, qsTr("Remove all"), qgcView.showDialogDefaultWidth, StandardButton.Yes | StandardButton.No)
dogmaphobic's avatar
dogmaphobic committed
891
                    }
892 893
                }
            }
894 895
        }
    }
896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922

    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
923

924 925 926 927 928 929 930 931 932
                        onClicked: {
                            QGroundControl.flightMapSettings.mapType = text
                            dropPanel.hide()
                        }
                    }
                }
            }
        }
    }
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 960 961

    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
    }
962 963 964 965 966 967 968 969 970 971 972

    Component {
        id: geoFenceEditorComponent

        GeoFenceEditor {
            availableWidth:         _rightPanelWidth
            availableHeight:        ScreenTools.availableHeight
            myGeoFenceController:   geoFenceController
            flightMap:              editorMap
        }
    }
Don Gagne's avatar
Don Gagne committed
973
} // QGCVIew