PlanView.qml 50.1 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
17
import QtQuick.Window   2.2
Don Gagne's avatar
Don Gagne committed
18

19 20 21 22 23 24 25 26 27 28 29
import QGroundControl                   1.0
import QGroundControl.FlightMap         1.0
import QGroundControl.ScreenTools       1.0
import QGroundControl.Controls          1.0
import QGroundControl.FactSystem        1.0
import QGroundControl.FactControls      1.0
import QGroundControl.Palette           1.0
import QGroundControl.Controllers       1.0
import QGroundControl.ShapeFileHelper   1.0
import QGroundControl.Airspace          1.0
import QGroundControl.Airmap            1.0
Don Gagne's avatar
Don Gagne committed
30 31

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

Don Gagne's avatar
Don Gagne committed
33
QGCView {
34
    id:         _qgcView
35
    viewPanel:  panel
36
    z:          QGroundControl.zOrderTopMost
37

Gus Grubba's avatar
Gus Grubba committed
38 39
    property bool planControlColapsed: false

40 41 42
    ///< This property is used to determine dirty state for prompting on QGC shutdown
    readonly property bool dirty: _planMasterController.dirty

43
    readonly property int   _decimalPlaces:             8
Gus Grubba's avatar
Gus Grubba committed
44
    readonly property real  _horizontalMargin:          ScreenTools.defaultFontPixelWidth  * 0.5
45
    readonly property real  _margin:                    ScreenTools.defaultFontPixelHeight * 0.5
Gus Grubba's avatar
Gus Grubba committed
46
    readonly property real  _radius:                    ScreenTools.defaultFontPixelWidth  * 0.5
47 48 49 50
    readonly property real  _rightPanelWidth:           Math.min(parent.width / 3, ScreenTools.defaultFontPixelWidth * 30)
    readonly property real  _toolButtonTopMargin:       parent.height - ScreenTools.availableHeight + (ScreenTools.defaultFontPixelHeight / 2)
    readonly property var   _defaultVehicleCoordinate:  QtPositioning.coordinate(37.803784, -122.462276)
    readonly property bool  _waypointsOnlyMode:         QGroundControl.corePlugin.options.missionWaypointsOnly
51

Gus Grubba's avatar
Gus Grubba committed
52
    property bool   _airspaceEnabled:                    QGroundControl.airmapSupported ? (QGroundControl.settingsManager.airMapSettings.enableAirMap.rawValue && QGroundControl.airspaceManager.connected): false
Gus Grubba's avatar
Gus Grubba committed
53 54 55 56 57 58 59 60 61 62 63 64
    property var    _planMasterController:              masterController
    property var    _missionController:                 _planMasterController.missionController
    property var    _geoFenceController:                _planMasterController.geoFenceController
    property var    _rallyPointController:              _planMasterController.rallyPointController
    property var    _visualItems:                       _missionController.visualItems
    property bool   _lightWidgetBorders:                editorMap.isSatelliteMap
    property bool   _addWaypointOnClick:                false
    property bool   _addROIOnClick:                     false
    property bool   _singleComplexItem:                 _missionController.complexMissionItemNames.length === 1
    property real   _toolbarHeight:                     _qgcView.height - ScreenTools.availableHeight
    property int    _editingLayer:                      _layerMission
    property int    _toolStripBottom:                   toolStrip.height + toolStrip.y
65
    property var    _appSettings:                       QGroundControl.settingsManager.appSettings
66

67 68 69 70
    readonly property int       _layerMission:              1
    readonly property int       _layerGeoFence:             2
    readonly property int       _layerRallyPoints:          3
    readonly property string    _armedVehicleUploadPrompt:  qsTr("Vehicle is currently armed. Do you want to upload the mission to the vehicle?")
71

72
    Component.onCompleted: {
73
        toolbar.planMasterController =  Qt.binding(function () { return _planMasterController })
74
        toolbar.currentMissionItem =    Qt.binding(function () { return _missionController.currentPlanViewItem })
75 76
    }

77 78
    function addComplexItem(complexItemName) {
        var coordinate = editorMap.center
79
        coordinate.latitude  = coordinate.latitude.toFixed(_decimalPlaces)
80
        coordinate.longitude = coordinate.longitude.toFixed(_decimalPlaces)
81
        coordinate.altitude  = coordinate.altitude.toFixed(_decimalPlaces)
82
        insertComplexMissionItem(complexItemName, coordinate, _missionController.visualItems.count)
83 84 85
    }

    function insertComplexMissionItem(complexItemName, coordinate, index) {
86
        var sequenceNumber = _missionController.insertComplexMissionItem(complexItemName, coordinate, index)
87
        _missionController.setCurrentPlanViewIndex(sequenceNumber, true)
88 89
    }

90 91
    function insertComplexMissionItemFromKMLOrSHP(complexItemName, file, index) {
        var sequenceNumber = _missionController.insertComplexMissionItemFromKMLOrSHP(complexItemName, file, index)
92 93 94
        _missionController.setCurrentPlanViewIndex(sequenceNumber, true)
    }

95
    function updateAirspace(reset) {
96 97 98 99
        if(_airspaceEnabled) {
            var coordinateNW = editorMap.toCoordinate(Qt.point(0,0), false /* clipToViewPort */)
            var coordinateSE = editorMap.toCoordinate(Qt.point(width,height), false /* clipToViewPort */)
            if(coordinateNW.isValid && coordinateSE.isValid) {
100
                QGroundControl.airspaceManager.setROI(coordinateNW, coordinateSE, true /*planView*/, reset)
101 102 103 104
            }
        }
    }

105 106 107 108 109
    property bool _firstMissionLoadComplete:    false
    property bool _firstFenceLoadComplete:      false
    property bool _firstRallyLoadComplete:      false
    property bool _firstLoadComplete:           false

110
    MapFitFunctions {
111
        id:                         mapFitFunctions  // The name for this id cannot be changed without breaking references outside of this code. Beware!
112 113
        map:                        editorMap
        usePlannedHomePosition:     true
114
        planMasterController:       _planMasterController
115 116
    }

117
    on_AirspaceEnabledChanged: {
118
        if(QGroundControl.airmapSupported) {
119 120
            if(_airspaceEnabled) {
                planControlColapsed = QGroundControl.airspaceManager.airspaceVisible
121
                updateAirspace(true)
122 123 124
            } else {
                planControlColapsed = false
            }
125
        } else {
Gus Grubba's avatar
Gus Grubba committed
126 127 128 129
            planControlColapsed = false
        }
    }

DonLakeFlyer's avatar
DonLakeFlyer committed
130
    Connections {
131
        target: _appSettings.defaultMissionItemAltitude
DonLakeFlyer's avatar
DonLakeFlyer committed
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147

        onRawValueChanged: {
            if (_visualItems.count > 1) {
                _qgcView.showDialog(applyNewAltitude, qsTr("Apply new alititude"), showDialogDefaultWidth, StandardButton.Yes | StandardButton.No)
            }
        }
    }

    Component {
        id: applyNewAltitude

        QGCViewMessage {
            message:    qsTr("You have changed the default altitude for mission items. Would you like to apply that altitude to all the items in the current mission?")

            function accept() {
                hideDialog()
148
                _missionController.applyDefaultMissionAltitude()
DonLakeFlyer's avatar
DonLakeFlyer committed
149 150 151 152
            }
        }
    }

153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
    Component {
        id: activeMissionUploadDialogComponent

        QGCViewDialog {

            Column {
                anchors.fill:   parent
                spacing:        ScreenTools.defaultFontPixelHeight

                QGCLabel {
                    width:      parent.width
                    wrapMode:   Text.WordWrap
                    text:       qsTr("Your vehicle is currently flying a mission. In order to upload a new or modified mission the current mission will be paused.")
                }

                QGCLabel {
                    width:      parent.width
                    wrapMode:   Text.WordWrap
                    text:       qsTr("After the mission is uploaded you can adjust the current waypoint and start the mission.")
                }

                QGCButton {
                    text:       qsTr("Pause and Upload")
                    onClicked: {
Gus Grubba's avatar
Gus Grubba committed
177
                        activeVehicle.flightMode = activeVehicle.pauseFlightMode
178
                        _planMasterController.sendToVehicle()
179
                        hideDialog()
180 181 182 183 184 185
                    }
                }
            }
        }
    }

Gus Grubba's avatar
Gus Grubba committed
186
    Connections {
187
        target: QGroundControl.airspaceManager
188
        onAirspaceVisibleChanged: {
189
            planControlColapsed = QGroundControl.airspaceManager.airspaceVisible
Gus Grubba's avatar
Gus Grubba committed
190 191 192
        }
    }

193 194 195 196 197 198 199
    Component {
        id: noItemForKML
        QGCViewMessage {
            message:    qsTr("You need at least one item to create a KML.")
        }
    }

200
    PlanMasterController {
201
        id: masterController
202

203
        Component.onCompleted: {
204
            start(false /* flyView */)
205
            _missionController.setCurrentPlanViewIndex(0, true)
206 207
        }

208 209 210 211
        function waitingOnDataMessage() {
            _qgcView.showMessage(qsTr("Unable to Save/Upload"), qsTr("Plan is waiting on terrain data from server for correct altitude values."), StandardButton.Ok)
        }

212
        function upload() {
213 214 215 216
            if (!readyForSaveSend()) {
                waitingOnDataMessage()
                return
            }
Gus Grubba's avatar
Gus Grubba committed
217
            if (activeVehicle && activeVehicle.armed && activeVehicle.flightMode === activeVehicle.missionFlightMode) {
218
                _qgcView.showDialog(activeMissionUploadDialogComponent, qsTr("Plan Upload"), _qgcView.showDialogDefaultWidth, StandardButton.Cancel)
219
            } else {
220 221
                sendToVehicle()
            }
DonLakeFlyer's avatar
DonLakeFlyer committed
222 223
        }

224
        function loadFromSelectedFile() {
225
            fileDialog.title =          qsTr("Select Plan File")
DonLakeFlyer's avatar
DonLakeFlyer committed
226
            fileDialog.planFiles =      true
227
            fileDialog.selectExisting = true
228
            fileDialog.nameFilters =    masterController.loadNameFilters
229 230
            fileDialog.fileExtension =  _appSettings.planFileExtension
            fileDialog.fileExtension2 = _appSettings.missionFileExtension
231
            fileDialog.openForLoad()
232 233 234
        }

        function saveToSelectedFile() {
235 236 237 238
            if (!readyForSaveSend()) {
                waitingOnDataMessage()
                return
            }
239
            fileDialog.title =          qsTr("Save Plan")
240
            fileDialog.planFiles =      true
241
            fileDialog.selectExisting = false
242
            fileDialog.nameFilters =    masterController.saveNameFilters
243 244
            fileDialog.fileExtension =  _appSettings.planFileExtension
            fileDialog.fileExtension2 = _appSettings.missionFileExtension
245
            fileDialog.openForSave()
246 247
        }

248
        function fitViewportToItems() {
249
            mapFitFunctions.fitMapViewportToMissionItems()
250
        }
251

252 253
        function loadShapeFromSelectedFile() {
            fileDialog.title =          qsTr("Load Shape")
254 255
            fileDialog.planFiles =      false
            fileDialog.selectExisting = true
256 257 258
            fileDialog.nameFilters =    ShapeFileHelper.fileDialogKMLOrSHPFilters
            fileDialog.fileExtension =  _appSettings.kmlFileExtension
            fileDialog.fileExtension2 = _appSettings.shpFileExtension
259 260 261
            fileDialog.openForLoad()
        }

262
        function saveKmlToSelectedFile() {
263 264 265 266
            if (!readyForSaveSend()) {
                waitingOnDataMessage()
                return
            }
267
            fileDialog.title =          qsTr("Save KML")
268
            fileDialog.planFiles =      false
269
            fileDialog.selectExisting = false
270 271
            fileDialog.nameFilters =    ShapeFileHelper.fileDialogKMLFilters
            fileDialog.fileExtension =  _appSettings.kmlFileExtension
272
            fileDialog.fileExtension2 = ""
273 274
            fileDialog.openForSave()
        }
275
    }
276

277 278
    Connections {
        target: _missionController
279

280
        onNewItemsFromVehicle: {
281 282 283
            if (_visualItems && _visualItems.count != 1) {
                mapFitFunctions.fitMapViewportToMissionItems()
            }
284
            _missionController.setCurrentPlanViewIndex(0, true)
285 286
        }
    }
287

288
    QGCPalette { id: qgcPal; colorGroupEnabled: enabled }
Don Gagne's avatar
Don Gagne committed
289

290 291 292 293
    ExclusiveGroup {
        id: _mapTypeButtonsExclusiveGroup
    }

294 295 296 297
    /// Inserts a new simple mission item
    ///     @param coordinate Location to insert item
    ///     @param index Insert item at this index
    function insertSimpleMissionItem(coordinate, index) {
298
        var sequenceNumber = _missionController.insertSimpleMissionItem(coordinate, index)
299
        _missionController.setCurrentPlanViewIndex(sequenceNumber, true)
300 301
    }

302 303 304 305 306 307 308 309 310 311
    /// Inserts a new ROI mission item
    ///     @param coordinate Location to insert item
    ///     @param index Insert item at this index
    function insertROIMissionItem(coordinate, index) {
        var sequenceNumber = _missionController.insertROIMissionItem(coordinate, index)
        _missionController.setCurrentPlanViewIndex(sequenceNumber, true)
        _addROIOnClick = false
        toolStrip.uncheckAll()
    }

312 313
    property int _moveDialogMissionItemIndex

314 315 316
    QGCFileDialog {
        id:             fileDialog
        qgcView:        _qgcView
317
        folder:         _appSettings.missionSavePath
318

319 320
        property bool planFiles: true    ///< true: working with plan files, false: working with kml file

321
        onAcceptedForSave: {
322 323 324 325 326
            if (planFiles) {
                masterController.saveToFile(file)
            } else {
                masterController.saveToKml(file)
            }
327
            close()
328 329
        }

330
        onAcceptedForLoad: {
331 332 333 334 335
            if (planFiles) {
                masterController.loadFromFile(file)
                masterController.fitViewportToItems()
                _missionController.setCurrentPlanViewIndex(0, true)
            } else {
336 337
                var retList = ShapeFileHelper.determineShapeType(file)
                if (retList[0] == ShapeFileHelper.Error) {
338
                    _qgcView.showMessage("Error", retList[1], StandardButton.Ok)
339
                } else if (retList[0] == ShapeFileHelper.Polygon) {
Gus Grubba's avatar
Gus Grubba committed
340
                     var editVehicle = activeVehicle ? activeVehicle : QGroundControl.multiVehicleManager.offlineEditingVehicle
341 342 343 344 345 346
                    if (editVehicle.fixedWing) {
                        insertComplexMissionItemFromKMLOrSHP(_missionController.surveyComplexItemName, file, -1)
                    } else {
                        polygonSelectPatternFile = file
                        _qgcView.showDialog(patternPolygonSelectDialog, fileDialog.title, _qgcView.showDialogDefaultWidth, StandardButton.Ok | StandardButton.Cancel)
                    }
347 348
                } else if (retList[0] == ShapeFileHelper.Polyline) {
                    insertComplexMissionItemFromKMLOrSHP(_missionController.corridorScanComplexItemName, file, -1)
349 350
                }
            }
351
            close()
352 353 354
        }
    }

355
    property string polygonSelectPatternFile
356
    Component {
357
        id: patternPolygonSelectDialog
358 359 360 361 362 363 364 365 366

        QGCViewDialog {
            function accept() {
                var complexItemName
                if (surveyRadio.checked) {
                    complexItemName = _missionController.surveyComplexItemName
                } else {
                    complexItemName = _missionController.structureScanComplexItemName
                }
367
                insertComplexMissionItemFromKMLOrSHP(complexItemName, polygonSelectPatternFile, -1)
368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383
                hideDialog()
            }

            ExclusiveGroup {
                id: radioGroup
            }

            Column {
                anchors.left:   parent.left
                anchors.right:  parent.right
                spacing:        ScreenTools.defaultFontPixelHeight

                QGCLabel {
                    anchors.left:   parent.left
                    anchors.right:  parent.right
                    wrapMode:       Text.WordWrap
384
                    text:           qsTr("Create which pattern type?")
385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
                }

                QGCRadioButton {
                    id:             surveyRadio
                    text:           qsTr("Survey")
                    checked:        true
                    exclusiveGroup: radioGroup
                }

                QGCRadioButton {
                    text:           qsTr("Structure Scan")
                    exclusiveGroup: radioGroup
                }
            }
        }
    }

402 403 404 405 406 407 408
    Component {
        id: moveDialog

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

Gus Grubba's avatar
Gus Grubba committed
409
                if (toIndex === 0) {
410 411
                    toIndex = 1
                }
412
                _missionController.moveMissionItem(_moveDialogMissionItemIndex, toIndex)
413 414 415 416 417 418 419 420 421 422 423 424
                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
425
                    text:           qsTr("Move the selected mission item to the be after following mission item:")
426 427 428 429
                }

                QGCComboBox {
                    id:             toCombo
430
                    model:          _visualItems.count
431 432 433 434 435 436
                    currentIndex:   _moveDialogMissionItemIndex
                }
            }
        }
    }

Don Gagne's avatar
Don Gagne committed
437 438
    QGCViewPanel {
        id:             panel
439
        anchors.fill:   parent
Don Gagne's avatar
Don Gagne committed
440

441
        FlightMap {
442 443 444 445 446
            id:                         editorMap
            anchors.fill:               parent
            mapName:                    "MissionEditor"
            allowGCSLocationCenter:     true
            allowVehicleLocationCenter: true
447
            planView:                   true
448
            qgcView:                    _qgcView
Don Gagne's avatar
Don Gagne committed
449

450
            // This is the center rectangle of the map which is not obscured by tools
Gus Grubba's avatar
Gus Grubba committed
451
            property rect centerViewport:   Qt.rect(_leftToolWidth, _toolbarHeight, editorMap.width - _leftToolWidth - _rightPanelWidth, editorMap.height - _statusHeight - _toolbarHeight)
452

453 454
            property real _leftToolWidth:   toolStrip.x + toolStrip.width
            property real _statusHeight:    waypointValuesDisplay.visible ? editorMap.height - waypointValuesDisplay.y : 0
455

456
            readonly property real animationDuration: 500
457

458 459
            // Initial map position duplicates Fly view position
            Component.onCompleted: editorMap.center = QGroundControl.flightMapPosition
460

461 462 463 464 465 466
            Behavior on zoomLevel {
                NumberAnimation {
                    duration:       editorMap.animationDuration
                    easing.type:    Easing.InOutQuad
                }
            }
467

468 469
            QGCMapPalette { id: mapPal; lightColors: editorMap.isSatelliteMap }

470 471
            onZoomLevelChanged: updateAirspace(false)
            onCenterChanged:    updateAirspace(false)
472

473 474 475 476 477
            MouseArea {
                //-- It's a whole lot faster to just fill parent and deal with top offset below
                //   than computing the coordinate offset.
                anchors.fill: parent
                onClicked: {
478 479 480
                    // Take focus to close any previous editing
                    editorMap.focus = true

481 482 483 484
                    //-- Don't pay attention to items beneath the toolbar.
                    var topLimit = parent.height - ScreenTools.availableHeight
                    if(mouse.y < topLimit) {
                        return
485 486
                    }

487 488 489 490
                    var coordinate = editorMap.toCoordinate(Qt.point(mouse.x, mouse.y), false /* clipToViewPort */)
                    coordinate.latitude = coordinate.latitude.toFixed(_decimalPlaces)
                    coordinate.longitude = coordinate.longitude.toFixed(_decimalPlaces)
                    coordinate.altitude = coordinate.altitude.toFixed(_decimalPlaces)
491

492 493 494
                    switch (_editingLayer) {
                    case _layerMission:
                        if (_addWaypointOnClick) {
495
                            insertSimpleMissionItem(coordinate, _missionController.visualItems.count)
496 497 498
                        } else if (_addROIOnClick) {
                            _addROIOnClick = false
                            insertROIMissionItem(coordinate, _missionController.visualItems.count)
499
                        }
500 501
                        break
                    case _layerRallyPoints:
502
                        if (_rallyPointController.supported) {
503
                            _rallyPointController.addPoint(coordinate)
504
                        }
505
                        break
Don Gagne's avatar
Don Gagne committed
506
                    }
Don Gagne's avatar
Don Gagne committed
507
                }
508
            }
Don Gagne's avatar
Don Gagne committed
509

510 511
            // Add the mission item visuals to the map
            Repeater {
512
                model: _editingLayer == _layerMission ? _missionController.visualItems : undefined
513

514 515
                delegate: MissionItemMapVisual {
                    map:        editorMap
DonLakeFlyer's avatar
DonLakeFlyer committed
516
                    qgcView:    _qgcView
517
                    onClicked:  _missionController.setCurrentPlanViewIndex(sequenceNumber, false)
518
                    visible:    _editingLayer == _layerMission
519
                }
520
            }
521

522 523
            // Add lines between waypoints
            MissionLineView {
524
                model: _editingLayer == _layerMission ? _missionController.waypointLines : undefined
525
            }
526

527 528 529 530 531 532 533
            // Add the vehicles to the map
            MapItemView {
                model: QGroundControl.multiVehicleManager.vehicles
                delegate:
                    VehicleMapItem {
                    vehicle:        object
                    coordinate:     object.coordinate
534
                    map:            editorMap
535 536
                    size:           ScreenTools.defaultFontPixelHeight * 3
                    z:              QGroundControl.zOrderMapItems - 1
537
                }
538
            }
539

540 541
            GeoFenceMapVisuals {
                map:                    editorMap
542
                myGeoFenceController:   _geoFenceController
543
                interactive:            _editingLayer == _layerGeoFence
544
                homePosition:           _missionController.plannedHomePosition
545 546
                planView:               true
            }
547

548 549
            RallyPointMapVisuals {
                map:                    editorMap
550
                myRallyPointController: _rallyPointController
551 552
                interactive:            _editingLayer == _layerRallyPoints
                planView:               true
553
            }
554

555 556
            // Airspace overlap support
            MapItemView {
557
                model:              _airspaceEnabled && QGroundControl.airspaceManager.airspaceVisible ? QGroundControl.airspaceManager.airspaces.circles : []
558 559 560
                delegate: MapCircle {
                    center:         object.center
                    radius:         object.radius
561
                    color:          object.color
Gus Grubba's avatar
Gus Grubba committed
562 563
                    border.color:   object.lineColor
                    border.width:   object.lineWidth
564 565 566 567
                }
            }

            MapItemView {
568
                model:              _airspaceEnabled && QGroundControl.airspaceManager.airspaceVisible ? QGroundControl.airspaceManager.airspaces.polygons : []
569 570
                delegate: MapPolygon {
                    path:           object.polygon
571
                    color:          object.color
Gus Grubba's avatar
Gus Grubba committed
572 573
                    border.color:   object.lineColor
                    border.width:   object.lineWidth
574 575 576
                }
            }

577 578 579 580 581 582 583 584 585
            ToolStrip {
                id:                 toolStrip
                anchors.leftMargin: ScreenTools.defaultFontPixelWidth
                anchors.left:       parent.left
                anchors.topMargin:  _toolButtonTopMargin
                anchors.top:        parent.top
                color:              qgcPal.window
                title:              qsTr("Plan")
                z:                  QGroundControl.zOrderWidgets
586 587 588 589
                showAlternateIcon:  [ masterController.dirty, false, false, false, false, false, false ]
                rotateImage:        [ masterController.syncInProgress, false, false, false, false, false, false ]
                animateImage:       [ masterController.dirty, false, false, false, false, false, false ]
                buttonEnabled:      [ !masterController.syncInProgress, true, true, true, true, true, true ]
Don Gagne's avatar
Don Gagne committed
590
                buttonVisible:      [ true, true, _waypointsOnlyMode, true, true, _showZoom, _showZoom ]
591 592 593 594 595 596
                maxHeight:          mapScale.y - toolStrip.y

                property bool _showZoom: !ScreenTools.isMobile

                model: [
                    {
597
                        name:                   qsTr("File"),
598 599 600
                        iconSource:             "/qmlimages/MapSync.svg",
                        alternateIconSource:    "/qmlimages/MapSyncChanged.svg",
                        dropPanelComponent:     syncDropPanel
601
                    },
602
                    {
603
                        name:                   qsTr("Waypoint"),
604 605
                        iconSource:             "/qmlimages/MapAddMission.svg",
                        toggle:                 true
606
                    },
607
                    {
608
                        name:                   qsTr("ROI"),
609 610
                        iconSource:             "/qmlimages/MapAddMission.svg",
                        toggle:                 true
611
                    },
612
                    {
613
                        name:               _singleComplexItem ? _missionController.complexMissionItemNames[0] : qsTr("Pattern"),
614 615
                        iconSource:         "/qmlimages/MapDrawShape.svg",
                        dropPanelComponent: _singleComplexItem ? undefined : patternDropPanel
616
                    },
617
                    {
618
                        name:               qsTr("Center"),
619 620 621 622
                        iconSource:         "/qmlimages/MapCenter.svg",
                        dropPanelComponent: centerMapDropPanel
                    },
                    {
623
                        name:               qsTr("In"),
624 625 626
                        iconSource:         "/qmlimages/ZoomPlus.svg"
                    },
                    {
627
                        name:               qsTr("Out"),
628 629 630 631 632 633
                        iconSource:         "/qmlimages/ZoomMinus.svg"
                    }
                ]

                onClicked: {
                    switch (index) {
634
                    case 1:
635
                        _addWaypointOnClick = checked
636
                        _addROIOnClick = false
637
                        break
638
                    case 2:
639 640 641
                        _addROIOnClick = checked
                        _addWaypointOnClick = false
                        break
642
                    case 3:
643
                        if (_singleComplexItem) {
644
                            addComplexItem(_missionController.complexMissionItemNames[0])
645
                        }
646
                        break
647
                    case 5:
648 649
                        editorMap.zoomLevel += 0.5
                        break
650
                    case 6:
651 652 653 654 655
                        editorMap.zoomLevel -= 0.5
                        break
                    }
                }
            }
Gus Grubba's avatar
Gus Grubba committed
656 657
        }
        //-----------------------------------------------------------
658 659 660
        // Right pane for mission editing controls
        Rectangle {
            id:                 rightPanel
661
            height:             ScreenTools.availableHeight
662 663
            width:              _rightPanelWidth
            color:              qgcPal.window
664
            opacity:            planExpanded.visible ? 0.2 : 0
Gus Grubba's avatar
Gus Grubba committed
665 666 667
            anchors.bottom:     parent.bottom
            anchors.right:      parent.right
            anchors.rightMargin: ScreenTools.defaultFontPixelWidth
668
        }
Gus Grubba's avatar
Gus Grubba committed
669 670
        //-------------------------------------------------------
        // Right Panel Controls
671
        Item {
Gus Grubba's avatar
Gus Grubba committed
672 673 674
            anchors.fill:           rightPanel
            Column {
                id:                 rightControls
Gus Grubba's avatar
Gus Grubba committed
675
                spacing:            ScreenTools.defaultFontPixelHeight * 0.5
676 677
                anchors.left:       parent.left
                anchors.right:      parent.right
Gus Grubba's avatar
Gus Grubba committed
678 679 680 681 682
                anchors.top:        parent.top
                anchors.topMargin:  ScreenTools.defaultFontPixelHeight * 0.25
                //-------------------------------------------------------
                // Airmap Airspace Control
                AirspaceControl {
Gus Grubba's avatar
Gus Grubba committed
683 684
                    id:             airspaceControl
                    width:          parent.width
685
                    visible:        _airspaceEnabled
686
                    planView:       true
687
                    showColapse:    true
688
                }
Gus Grubba's avatar
Gus Grubba committed
689 690 691 692
                //-------------------------------------------------------
                // Mission Controls (Colapsed)
                Rectangle {
                    width:      parent.width
Gus Grubba's avatar
Gus Grubba committed
693
                    height:     planControlColapsed ? colapsedRow.height + ScreenTools.defaultFontPixelHeight : 0
Gus Grubba's avatar
Gus Grubba committed
694 695
                    color:      qgcPal.missionItemEditor
                    radius:     _radius
696
                    visible:    planControlColapsed && _airspaceEnabled
Gus Grubba's avatar
Gus Grubba committed
697 698 699 700 701 702 703
                    Row {
                        id:                     colapsedRow
                        spacing:                ScreenTools.defaultFontPixelWidth
                        anchors.left:           parent.left
                        anchors.leftMargin:     ScreenTools.defaultFontPixelWidth
                        anchors.verticalCenter: parent.verticalCenter
                        QGCColoredImage {
704 705 706 707 708
                            width:              height
                            height:             ScreenTools.defaultFontPixelWidth * 2.5
                            sourceSize.height:  height
                            source:             "qrc:/res/waypoint.svg"
                            color:              qgcPal.text
Gus Grubba's avatar
Gus Grubba committed
709 710 711
                            anchors.verticalCenter: parent.verticalCenter
                        }
                        QGCLabel {
712 713
                            text:               qsTr("Plan")
                            color:              qgcPal.text
Gus Grubba's avatar
Gus Grubba committed
714
                            anchors.verticalCenter: parent.verticalCenter
715 716
                        }
                    }
Gus Grubba's avatar
Gus Grubba committed
717 718 719 720
                    QGCColoredImage {
                        width:                  height
                        height:                 ScreenTools.defaultFontPixelWidth * 2.5
                        sourceSize.height:      height
721
                        source:                 QGroundControl.airmapSupported ? "qrc:/airmap/expand.svg" : ""
722
                        color:                  "white"
723
                        visible:                QGroundControl.airmapSupported
Gus Grubba's avatar
Gus Grubba committed
724 725 726 727 728 729
                        anchors.right:          parent.right
                        anchors.rightMargin:    ScreenTools.defaultFontPixelWidth
                        anchors.verticalCenter: parent.verticalCenter
                    }
                    MouseArea {
                        anchors.fill:   parent
730
                        enabled:        QGroundControl.airmapSupported
Gus Grubba's avatar
Gus Grubba committed
731
                        onClicked: {
732
                            QGroundControl.airspaceManager.airspaceVisible = false
Gus Grubba's avatar
Gus Grubba committed
733 734
                        }
                    }
Gus Grubba's avatar
Gus Grubba committed
735
                }
Gus Grubba's avatar
Gus Grubba committed
736 737 738 739 740
                //-------------------------------------------------------
                // Mission Controls (Expanded)
                Rectangle {
                    id:         planExpanded
                    width:      parent.width
741
                    height:     (!planControlColapsed || !_airspaceEnabled) ? expandedCol.height + ScreenTools.defaultFontPixelHeight : 0
Gus Grubba's avatar
Gus Grubba committed
742 743
                    color:      qgcPal.missionItemEditor
                    radius:     _radius
744
                    visible:    !planControlColapsed || !_airspaceEnabled
Gus Grubba's avatar
Gus Grubba committed
745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824
                    Item {
                        height:             expandedCol.height
                        anchors.left:       parent.left
                        anchors.right:      parent.right
                        anchors.verticalCenter: parent.verticalCenter
                        Column {
                            id:                     expandedCol
                            spacing:                ScreenTools.defaultFontPixelHeight * 0.5
                            anchors.left:           parent.left
                            anchors.right:          parent.right
                            //-- Header
                            Row {
                                id:                     expandedRow
                                spacing:                ScreenTools.defaultFontPixelWidth
                                anchors.left:           parent.left
                                anchors.leftMargin:     ScreenTools.defaultFontPixelWidth
                                readonly property real _buttonRadius: ScreenTools.defaultFontPixelHeight * 0.75
                                QGCLabel {
                                    text:           qsTr("Plan")
                                    color:          qgcPal.text
                                    visible:        !QGroundControl.corePlugin.options.enablePlanViewSelector
                                    anchors.verticalCenter: parent.verticalCenter
                                }
                                ExclusiveGroup {
                                    id: planElementSelectorGroup
                                    onCurrentChanged: {
                                        switch (current) {
                                        case planElementMission:
                                            _editingLayer = _layerMission
                                            break
                                        case planElementGeoFence:
                                            _editingLayer = _layerGeoFence
                                            break
                                        case planElementRallyPoints:
                                            _editingLayer = _layerRallyPoints
                                            break
                                        }
                                    }
                                }
                                QGCRadioButton {
                                    id:             planElementMission
                                    exclusiveGroup: planElementSelectorGroup
                                    text:           qsTr("Mission")
                                    checked:        true
                                    visible:        QGroundControl.corePlugin.options.enablePlanViewSelector
                                    anchors.verticalCenter: parent.verticalCenter
                                }
                                QGCRadioButton {
                                    id:             planElementGeoFence
                                    exclusiveGroup: planElementSelectorGroup
                                    text:           qsTr("Fence")
                                    visible:        QGroundControl.corePlugin.options.enablePlanViewSelector
                                    anchors.verticalCenter: parent.verticalCenter
                                }
                                QGCRadioButton {
                                    id:             planElementRallyPoints
                                    exclusiveGroup: planElementSelectorGroup
                                    text:           qsTr("Rally")
                                    visible:        QGroundControl.corePlugin.options.enablePlanViewSelector
                                    anchors.verticalCenter: parent.verticalCenter
                                }
                            }
                        }
                    }
                }
            }
            //-------------------------------------------------------
            // Mission Item Editor
            Item {
                id:                     missionItemEditor
                anchors.left:           parent.left
                anchors.right:          parent.right
                anchors.top:            rightControls.bottom
                anchors.topMargin:      ScreenTools.defaultFontPixelHeight * 0.5
                anchors.bottom:         parent.bottom
                anchors.bottomMargin:   ScreenTools.defaultFontPixelHeight * 0.25
                visible:                _editingLayer == _layerMission && !planControlColapsed
                QGCListView {
                    id:             missionItemEditorListView
                    anchors.fill:   parent
Don Gagne's avatar
Don Gagne committed
825
                    spacing:        ScreenTools.defaultFontPixelHeight / 4
Gus Grubba's avatar
Gus Grubba committed
826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858
                    orientation:    ListView.Vertical
                    model:          _missionController.visualItems
                    cacheBuffer:    Math.max(height * 2, 0)
                    clip:           true
                    currentIndex:   _missionController.currentPlanViewIndex
                    highlightMoveDuration: 250
                    visible:        _editingLayer == _layerMission && !planControlColapsed
                    //-- List Elements
                    delegate: MissionItemEditor {
                        map:                editorMap
                        masterController:  _planMasterController
                        missionItem:        object
                        width:              parent.width
                        readOnly:           false
                        rootQgcView:        _qgcView
                        onClicked:  _missionController.setCurrentPlanViewIndex(object.sequenceNumber, false)
                        onRemove: {
                            var removeIndex = index
                            _missionController.removeMissionItem(removeIndex)
                            if (removeIndex >= _missionController.visualItems.count) {
                                removeIndex--
                            }
                            _missionController.setCurrentPlanViewIndex(removeIndex, true)
                        }
                        onInsertWaypoint:       insertSimpleMissionItem(editorMap.center, index)
                        onInsertComplexItem:    insertComplexMissionItem(complexItemName, editorMap.center, index)
                    }
                }
            }
            // GeoFence Editor
            GeoFenceEditor {
                anchors.top:            rightControls.bottom
                anchors.topMargin:      ScreenTools.defaultFontPixelHeight * 0.5
Don Gagne's avatar
Don Gagne committed
859
                anchors.bottom:         parent.bottom
Gus Grubba's avatar
Gus Grubba committed
860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884
                anchors.left:           parent.left
                anchors.right:          parent.right
                myGeoFenceController:   _geoFenceController
                flightMap:              editorMap
                visible:                _editingLayer == _layerGeoFence
            }
            // Rally Point Editor
            RallyPointEditorHeader {
                id:                     rallyPointHeader
                anchors.top:            rightControls.bottom
                anchors.topMargin:      ScreenTools.defaultFontPixelHeight * 0.5
                anchors.left:           parent.left
                anchors.right:          parent.right
                visible:                _editingLayer == _layerRallyPoints
                controller:             _rallyPointController
            }
            RallyPointItemEditor {
                id:                     rallyPointEditor
                anchors.top:            rallyPointHeader.bottom
                anchors.topMargin:      ScreenTools.defaultFontPixelHeight * 0.5
                anchors.left:           parent.left
                anchors.right:          parent.right
                visible:                _editingLayer == _layerRallyPoints && _rallyPointController.points.count
                rallyPoint:             _rallyPointController.currentRallyPoint
                controller:             _rallyPointController
885
            }
Gus Grubba's avatar
Gus Grubba committed
886
        }
887 888 889 890 891 892 893

        MapScale {
            id:                 mapScale
            anchors.margins:    ScreenTools.defaultFontPixelHeight * (0.66)
            anchors.bottom:     waypointValuesDisplay.visible ? waypointValuesDisplay.top : parent.bottom
            anchors.left:       parent.left
            mapControl:         editorMap
894
            visible:            _toolStripBottom < y
895 896 897 898 899 900
        }

        MissionItemStatus {
            id:                 waypointValuesDisplay
            anchors.margins:    ScreenTools.defaultFontPixelWidth
            anchors.left:       parent.left
901
            height:             ScreenTools.defaultFontPixelHeight * 7
902
            maxWidth:           parent.width - rightPanel.width - x
903
            anchors.bottom:     parent.bottom
904
            missionItems:       _missionController.visualItems
905
            visible:            _editingLayer === _layerMission && (_toolStripBottom + mapScale.height) < y && QGroundControl.corePlugin.options.showMissionStatus
906
        }
Gus Grubba's avatar
Gus Grubba committed
907
    }
908

909 910 911 912
    Component {
        id: syncLoadFromVehicleOverwrite
        QGCViewMessage {
            id:         syncLoadFromVehicleCheck
Don Gagne's avatar
Don Gagne committed
913
            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?")
914 915
            function accept() {
                hideDialog()
916
                masterController.loadFromVehicle()
917 918 919 920 921 922 923 924
            }
        }
    }

    Component {
        id: syncLoadFromFileOverwrite
        QGCViewMessage {
            id:         syncLoadFromVehicleCheck
DonLakeFlyer's avatar
DonLakeFlyer committed
925
            message:   qsTr("You have unsaved/unsent changes. Loading from a file will lose these changes. Are you sure you want to load from a file?")
926 927
            function accept() {
                hideDialog()
928
                masterController.loadFromSelectedFile()
929 930 931 932
            }
        }
    }

933 934 935
    Component {
        id: removeAllPromptDialog
        QGCViewMessage {
936
            message: qsTr("Are you sure you want to remove all items and create a new plan? ") +
937
                     (_planMasterController.offline ? "" : qsTr("This will also remove all items from the vehicle."))
938
            function accept() {
939 940 941 942 943
                if (_planMasterController.offline) {
                    masterController.removeAll()
                } else {
                    masterController.removeAllFromVehicle()
                }
944 945 946 947 948
                hideDialog()
            }
        }
    }

949 950 951 952 953 954 955 956 957 958 959
    Component {
        id: clearVehicleMissionDialog
        QGCViewMessage {
            message: qsTr("Are you sure you want to remove all mission items and clear the mission from the vehicle?")
            function accept() {
                masterController.removeAllFromVehicle()
                hideDialog()
            }
        }
    }

960 961 962 963 964 965 966 967 968 969 970
    //- ToolStrip DropPanel Components

    Component {
        id: centerMapDropPanel

        CenterMapDropPanel {
            map:            editorMap
            fitFunctions:   mapFitFunctions
        }
    }

971 972 973 974 975 976 977 978 979
    Component {
        id: patternDropPanel

        ColumnLayout {
            spacing:    ScreenTools.defaultFontPixelWidth * 0.5

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

            Repeater {
980
                model: _missionController.complexMissionItemNames
981 982 983 984 985 986

                QGCButton {
                    text:               modelData
                    Layout.fillWidth:   true

                    onClicked: {
987
                        addComplexItem(modelData)
988 989 990 991
                        dropPanel.hide()
                    }
                }
            }
992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010

            Rectangle {
                width:              parent.width * 0.8
                height:             1
                color:              qgcPal.text
                opacity:            0.5
                Layout.fillWidth:   true
                Layout.columnSpan:  2
            }

            QGCButton {
                text:               qsTr("Load KML/SHP...")
                Layout.fillWidth:   true
                enabled:            !masterController.syncInProgress
                onClicked: {
                    masterController.loadShapeFromSelectedFile()
                    dropPanel.hide()
                }
            }
1011 1012
        } // Column
    }
1013 1014

    Component {
1015
        id: syncDropPanel
1016

1017 1018 1019
        Column {
            id:         columnHolder
            spacing:    _margin
1020

1021
            property string _overwriteText: (_editingLayer == _layerMission) ? qsTr("Mission overwrite") : ((_editingLayer == _layerGeoFence) ? qsTr("GeoFence overwrite") : qsTr("Rally Points overwrite"))
1022

1023 1024 1025
            QGCLabel {
                width:      sendSaveGrid.width
                wrapMode:   Text.WordWrap
1026
                text:       masterController.dirty ?
Gus Grubba's avatar
Gus Grubba committed
1027
                                (activeVehicle ?
1028 1029 1030 1031
                                     qsTr("You have unsaved changes. You should upload to your vehicle, or save to a file:") :
                                     qsTr("You have unsaved changes.")
                                ) :
                                qsTr("Plan File:")
1032 1033
            }

1034 1035 1036 1037 1038 1039
            GridLayout {
                id:                 sendSaveGrid
                columns:            2
                anchors.margins:    _margin
                rowSpacing:         _margin
                columnSpacing:      ScreenTools.defaultFontPixelWidth
1040

1041
                QGCButton {
1042
                    text:               qsTr("New...")
1043
                    Layout.fillWidth:   true
1044 1045
                    enabled:            _visualItems.count > 1
                    onClicked:  {
1046
                        dropPanel.hide()
1047
                        _qgcView.showDialog(removeAllPromptDialog, qsTr("New Plan"), _qgcView.showDialogDefaultWidth, StandardButton.Yes | StandardButton.No)
1048 1049
                    }
                }
1050

1051
                QGCButton {
1052
                    text:               qsTr("Open...")
1053
                    Layout.fillWidth:   true
1054
                    enabled:            !masterController.syncInProgress
1055 1056
                    onClicked: {
                        dropPanel.hide()
1057
                        if (masterController.dirty) {
1058 1059
                            _qgcView.showDialog(syncLoadFromFileOverwrite, columnHolder._overwriteText, _qgcView.showDialogDefaultWidth, StandardButton.Yes | StandardButton.Cancel)
                        } else {
1060
                            masterController.loadFromSelectedFile()
1061 1062 1063
                        }
                    }
                }
1064

1065
                QGCButton {
1066
                    text:               qsTr("Save")
1067
                    Layout.fillWidth:   true
1068
                    enabled:            !masterController.syncInProgress && masterController.currentPlanFile !== ""
1069 1070
                    onClicked: {
                        dropPanel.hide()
1071 1072 1073 1074 1075
                        if(masterController.currentPlanFile !== "") {
                            masterController.saveToCurrent()
                        } else {
                            masterController.saveToSelectedFile()
                        }
1076 1077 1078 1079
                    }
                }

                QGCButton {
1080
                    text:               qsTr("Save As...")
1081
                    Layout.fillWidth:   true
1082
                    enabled:            !masterController.syncInProgress && _visualItems.count > 1
1083 1084
                    onClicked: {
                        dropPanel.hide()
1085
                        masterController.saveToSelectedFile()
1086 1087 1088 1089
                    }
                }

                QGCButton {
Gus Grubba's avatar
Gus Grubba committed
1090
                    text:               qsTr("Save Mission Waypoints As KML...")
1091
                    Layout.columnSpan:  2
1092
                    enabled:            !masterController.syncInProgress && _visualItems.count > 1
1093
                    onClicked: {
1094
                        // First point does not count
1095 1096 1097 1098
                        if (_visualItems.count < 2) {
                            _qgcView.showDialog(noItemForKML, qsTr("KML"), _qgcView.showDialogDefaultWidth, StandardButton.Cancel)
                            return
                        }
1099 1100 1101 1102
                        dropPanel.hide()
                        masterController.saveKmlToSelectedFile()
                    }
                }
1103

1104 1105 1106 1107 1108 1109 1110 1111 1112 1113
                Rectangle {
                    width:              parent.width * 0.8
                    height:             1
                    color:              qgcPal.text
                    opacity:            0.5
                    visible:            !QGroundControl.corePlugin.options.disableVehicleConnection
                    Layout.fillWidth:   true
                    Layout.columnSpan:  2
                }

1114
                QGCButton {
1115
                    text:               qsTr("Upload")
1116
                    Layout.fillWidth:   true
1117 1118 1119
                    enabled:            !masterController.offline && !masterController.syncInProgress && _visualItems.count > 1
                    visible:            !QGroundControl.corePlugin.options.disableVehicleConnection
                    onClicked: {
1120
                        dropPanel.hide()
1121
                        masterController.upload()
1122 1123
                    }
                }
1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147

                QGCButton {
                    text:               qsTr("Download")
                    Layout.fillWidth:   true
                    enabled:            !masterController.offline && !masterController.syncInProgress
                    visible:            !QGroundControl.corePlugin.options.disableVehicleConnection
                    onClicked: {
                        dropPanel.hide()
                        if (masterController.dirty) {
                            _qgcView.showDialog(syncLoadFromVehicleOverwrite, columnHolder._overwriteText, _qgcView.showDialogDefaultWidth, StandardButton.Yes | StandardButton.Cancel)
                        } else {
                            masterController.loadFromVehicle()
                        }
                    }
                }

                QGCButton {
                    text:               qsTr("Clear Vehicle Mission")
                    Layout.fillWidth:   true
                    Layout.columnSpan:  2
                    enabled:            !masterController.offline && !masterController.syncInProgress
                    visible:            !QGroundControl.corePlugin.options.disableVehicleConnection
                    onClicked: {
                        dropPanel.hide()
1148
                        _qgcView.showDialog(clearVehicleMissionDialog, text, _qgcView.showDialogDefaultWidth, StandardButton.Yes | StandardButton.Cancel)
1149 1150 1151
                    }
                }

1152
            }
1153 1154
        }
    }
Don Gagne's avatar
Don Gagne committed
1155
} // QGCVIew