QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
PlanView.qml
Go to the documentation of this file.
1import QtQuick
2import QtQuick.Controls
3import QtQuick.Dialogs
4import QtLocation
5import QtPositioning
6import QtQuick.Layouts
7import QtQuick.Window
8
9import QGroundControl
10import QGroundControl.FlightMap
11import QGroundControl.Controls
12import QGroundControl.FactControls
13import QGroundControl.FlyView
14
15Item {
16 id: _root
17
18 readonly property int _decimalPlaces: 8
19 readonly property real _margin: ScreenTools.defaultFontPixelHeight * 0.5
20 readonly property real _toolsMargin: ScreenTools.defaultFontPixelWidth * 0.75
21 readonly property real _rightPanelWidth: Math.min(width / 3, ScreenTools.defaultFontPixelWidth * 30)
22
23 property var _planMasterController: planMasterController
24 property var _missionController: _planMasterController.missionController
25 property var _geoFenceController: _planMasterController.geoFenceController
26 property var _rallyPointController: _planMasterController.rallyPointController
27 property var _visualItems: _missionController.visualItems
28 property bool _singleComplexItem: _missionController.complexMissionItemNames.length === 1
29 property int _editingLayer: _layerMission
30 property var _appSettings: QGroundControl.settingsManager.appSettings
31 property var _planViewSettings: QGroundControl.settingsManager.planViewSettings
32 property bool _promptForPlanUsageShowing: false
33 property bool _addROIOnClick: false
34 property bool _addWaypointOnClick: false
35
36 readonly property int _layerMission: 1
37 readonly property int _layerFence: 2
38 readonly property int _layerRally: 3
39
40 onVisibleChanged: {
41 if(visible) {
42 editorMap.zoomLevel = QGroundControl.flightMapZoom
43 editorMap.center = QGroundControl.flightMapPosition
44 }
45 }
46
47 function mapCenter() {
48 var coordinate = editorMap.center
49 coordinate.latitude = coordinate.latitude.toFixed(_decimalPlaces)
50 coordinate.longitude = coordinate.longitude.toFixed(_decimalPlaces)
51 coordinate.altitude = coordinate.altitude.toFixed(_decimalPlaces)
52 return coordinate
53 }
54
55 MapFitFunctions {
56 id: mapFitFunctions // The name for this id cannot be changed without breaking references outside of this code. Beware!
57 map: editorMap
58 usePlannedHomePosition: true
59 planMasterController: _planMasterController
60 }
61
62 PlanMasterController {
63 id: planMasterController
64 flyView: false
65
66 Component.onCompleted: {
67 _planMasterController.start()
68 _missionController.setCurrentPlanViewSeqNum(0, true)
69 }
70
71 onPromptForPlanUsageOnVehicleChange: {
72 if (!_promptForPlanUsageShowing) {
73 _promptForPlanUsageShowing = true
74 promptForPlanUsageOnVehicleChangePopupFactory.open()
75 }
76 }
77
78 function waitingOnIncompleteDataMessage(save) {
79 var saveOrUpload = save ? qsTr("Save") : qsTr("Upload")
80 QGroundControl.showMessageDialog(_root, qsTr("Unable to %1").arg(saveOrUpload), qsTr("Plan has incomplete items. Complete all items and %1 again.").arg(saveOrUpload))
81 }
82
83 function waitingOnTerrainDataMessage(save) {
84 var saveOrUpload = save ? qsTr("Save") : qsTr("Upload")
85 QGroundControl.showMessageDialog(_root, qsTr("Unable to %1").arg(saveOrUpload), qsTr("Plan is waiting on terrain data from server for correct altitude values."))
86 }
87
88 function checkReadyForSaveUpload(save) {
89 if (readyForSaveState() == VisualMissionItem.NotReadyForSaveData) {
90 waitingOnIncompleteDataMessage(save)
91 return false
92 } else if (readyForSaveState() == VisualMissionItem.NotReadyForSaveTerrain) {
93 waitingOnTerrainDataMessage(save)
94 return false
95 }
96 return true
97 }
98
99 function upload() {
100 if (!checkReadyForSaveUpload(false /* save */)) {
101 return
102 }
103 switch (_missionController.sendToVehiclePreCheck()) {
104 case MissionController.SendToVehiclePreCheckStateOk: sendToVehicle()
105 break
106 case MissionController.SendToVehiclePreCheckStateActiveMission: QGroundControl.showMessageDialog(_root, qsTr("Send To Vehicle"), qsTr("Current mission must be paused prior to uploading a new Plan"))
107 break
108 case MissionController.SendToVehiclePreCheckStateFirwmareVehicleMismatch: QGroundControl.showMessageDialog(_root, qsTr("Plan Upload"),
109 qsTr("This Plan was created for a different firmware or vehicle type than the firmware/vehicle type of vehicle you are uploading to. " +
110 "This can lead to errors or incorrect behavior. " +
111 "It is recommended to recreate the Plan for the correct firmware/vehicle type.\n\n" +
112 "Click 'Ok' to upload the Plan anyway."),
113 Dialog.Ok | Dialog.Cancel,
114 function() { _planMasterController.sendToVehicle() })
115 break
116 }
117 }
118
119 function loadFromSelectedFile() {
120 fileDialog.title = qsTr("Select Plan File")
121 fileDialog.planFiles = true
122 fileDialog.nameFilters = _planMasterController.loadNameFilters
123 fileDialog.openForLoad()
124 }
125
126 function saveToSelectedFile() {
127 if (!checkReadyForSaveUpload(true /* save */)) {
128 return
129 }
130 fileDialog.title = qsTr("Save Plan")
131 fileDialog.planFiles = true
132 fileDialog.nameFilters = _planMasterController.saveNameFilters
133 fileDialog.openForSave()
134 }
135
136 function fitViewportToItems() {
137 mapFitFunctions.fitMapViewportToMissionItems()
138 }
139
140 function saveKmlToSelectedFile() {
141 if (!checkReadyForSaveUpload(true /* save */)) {
142 return
143 }
144 fileDialog.title = qsTr("Save KML")
145 fileDialog.planFiles = false
146 fileDialog.nameFilters = ShapeFileHelper.fileDialogKMLFilters
147 fileDialog.openForSave()
148 }
149 }
150
151 Connections {
152 target: _missionController
153
154 function onNewItemsFromVehicle() {
155 if (_visualItems && _visualItems.count !== 1) {
156 mapFitFunctions.fitMapViewportToMissionItems()
157 }
158 _missionController.setCurrentPlanViewSeqNum(0, true)
159 }
160 }
161
162 function insertSimpleItemAfterCurrent(coordinate) {
163 var nextIndex = _missionController.currentPlanViewVIIndex + 1
164 _missionController.insertSimpleMissionItem(coordinate, nextIndex, true /* makeCurrentItem */)
165 }
166
167 function insertROIAfterCurrent(coordinate) {
168 var nextIndex = _missionController.currentPlanViewVIIndex + 1
169 _missionController.insertROIMissionItem(coordinate, nextIndex, true /* makeCurrentItem */)
170 }
171
172 function insertCancelROIAfterCurrent() {
173 var nextIndex = _missionController.currentPlanViewVIIndex + 1
174 _missionController.insertCancelROIMissionItem(nextIndex, true /* makeCurrentItem */)
175 }
176
177 function insertComplexItemAfterCurrent(complexItemName) {
178 var nextIndex = _missionController.currentPlanViewVIIndex + 1
179 _missionController.insertComplexMissionItem(complexItemName, mapCenter(), nextIndex, true /* makeCurrentItem */)
180 }
181
182 function insertTakeoffItemAfterCurrent() {
183 var nextIndex = _missionController.currentPlanViewVIIndex + 1
184 _missionController.insertTakeoffItem(mapCenter(), nextIndex, true /* makeCurrentItem */)
185 }
186
187 function insertLandItemAfterCurrent() {
188 var nextIndex = _missionController.currentPlanViewVIIndex + 1
189 _missionController.insertLandItem(mapCenter(), nextIndex, true /* makeCurrentItem */)
190 }
191
192 QGCFileDialog {
193 id: fileDialog
194 folder: _appSettings ? _appSettings.missionSavePath : ""
195
196 property bool planFiles: true ///< true: working with plan files, false: working with kml file
197
198 onAcceptedForSave: (file) => {
199 if (planFiles) {
200 if (_planMasterController.saveToFile(file)) {
201 close()
202 }
203 } else {
204 _planMasterController.saveToKml(file)
205 close()
206 }
207 }
208
209 onAcceptedForLoad: (file) => {
210 _planMasterController.loadFromFile(file)
211 _planMasterController.fitViewportToItems()
212 _missionController.setCurrentPlanViewSeqNum(0, true)
213 close()
214 }
215 }
216
217 PlanViewToolBar {
218 id: planToolBar
219 planMasterController: _planMasterController
220 showRallyPointsHelp: _editingLayer === _layerRally
221 }
222
223 Item {
224 id: mainPlanViewArea
225 anchors.left: parent.left
226 anchors.right: parent.right
227 anchors.top: planToolBar.bottom
228 anchors.bottom: parent.bottom
229
230 FlightMap {
231 id: editorMap
232 anchors.fill: parent
233 mapName: "MissionEditor"
234 allowGCSLocationCenter: true
235 allowVehicleLocationCenter: true
236 planView: true
237
238 zoomLevel: QGroundControl.flightMapZoom
239 center: QGroundControl.flightMapPosition
240
241 // This is the center rectangle of the map which is not obscured by tools
242 property rect centerViewport: Qt.rect(_leftToolWidth + _margin, _margin, editorMap.width - _leftToolWidth - _rightToolWidth - (_margin * 2), (missionStatus.visible ? missionStatus.y : height - _margin) - _margin)
243
244 property real _leftToolWidth: toolStrip.x + toolStrip.width
245 property real _rightToolWidth: rightPanel.width + rightPanel.anchors.rightMargin
246 property real _nonInteractiveOpacity: 0.5
247
248 // Initial map position duplicates Fly view position
249 Component.onCompleted: editorMap.center = QGroundControl.flightMapPosition
250
251 onZoomLevelChanged: {
252 QGroundControl.flightMapZoom = editorMap.zoomLevel
253 }
254 onCenterChanged: {
255 QGroundControl.flightMapPosition = editorMap.center
256 }
257
258 onMapClicked: (mouse) => {
259 // Take focus to close any previous editing
260 editorMap.focus = true
261
262 // Collapse layer switcher on any map click
263 layerSwitcher.expanded = false
264 collapseTimer.stop()
265
266 if (!mainWindow.allowViewSwitch()) {
267 return
268 }
269 var coordinate = editorMap.toCoordinate(Qt.point(mouse.x, mouse.y), false /* clipToViewPort */)
270 coordinate.latitude = coordinate.latitude.toFixed(_decimalPlaces)
271 coordinate.longitude = coordinate.longitude.toFixed(_decimalPlaces)
272 coordinate.altitude = coordinate.altitude.toFixed(_decimalPlaces)
273
274 switch (_editingLayer) {
275 case _layerMission:
276 if (_addROIOnClick) {
277 _addROIOnClick = false
278 if (_missionController.isROIActive) {
279 var pos = Qt.point(mouse.x, mouse.y)
280 // For some strange reason using mainWindow in mapToItem doesn't work, so we use globals.parent instead which also gets us mainWindow
281 pos = editorMap.mapToItem(globals.parent, pos)
282 var dropPanel = insertOrCancelROIDropPanelComponent.createObject(mainWindow, { mapClickCoord: coordinate, clickRect: Qt.rect(pos.x, pos.y, 0, 0) })
283 dropPanel.open()
284 } else {
285 insertROIAfterCurrent(coordinate)
286 }
287 } else if (_addWaypointOnClick) {
288 insertSimpleItemAfterCurrent(coordinate)
289 }
290 break
291 case _layerRally:
292 if (_rallyPointController.supported) {
293 _rallyPointController.addPoint(coordinate)
294 }
295 break
296 }
297 }
298
299 // Add the mission item visuals to the map
300 Repeater {
301 model: _missionController.visualItems
302 delegate: MissionItemMapVisual {
303 map: editorMap
304 opacity: _editingLayer == _layerMission ? 1 : editorMap._nonInteractiveOpacity
305 interactive: _editingLayer == _layerMission
306 vehicle: _planMasterController.controllerVehicle
307 onClicked: (sequenceNumber) => { _missionController.setCurrentPlanViewSeqNum(sequenceNumber, false) }
308 }
309 }
310
311 // Add lines between waypoints
312 MissionLineView {
313 showSpecialVisual: _missionController.isROIBeginCurrentItem
314 model: _missionController.simpleFlightPathSegments
315 opacity: _editingLayer == _layerMission ? 1 : editorMap._nonInteractiveOpacity
316 }
317
318 // Direction arrows in waypoint lines
319 MapItemView {
320 model: _editingLayer == _layerMission ? _missionController.directionArrows : undefined
321
322 delegate: MapLineArrow {
323 fromCoord: object ? object.coordinate1 : undefined
324 toCoord: object ? object.coordinate2 : undefined
325 arrowPosition: 3
326 z: QGroundControl.zOrderWaypointLines + 1
327 }
328 }
329
330 // UI for splitting the current segment
331 MapQuickItem {
332 id: splitSegmentItem
333 anchorPoint.x: sourceItem.width / 2
334 anchorPoint.y: sourceItem.height / 2
335 z: QGroundControl.zOrderWaypointLines + 1
336 visible: _editingLayer == _layerMission
337
338 sourceItem: SplitIndicator {
339 onClicked: _missionController.insertSimpleMissionItem(splitSegmentItem.coordinate,
340 _missionController.currentPlanViewVIIndex,
341 true /* makeCurrentItem */)
342 }
343
344 function _updateSplitCoord() {
345 if (_missionController.splitSegment) {
346 var distance = _missionController.splitSegment.coordinate1.distanceTo(_missionController.splitSegment.coordinate2)
347 var azimuth = _missionController.splitSegment.coordinate1.azimuthTo(_missionController.splitSegment.coordinate2)
348 splitSegmentItem.coordinate = _missionController.splitSegment.coordinate1.atDistanceAndAzimuth(distance / 2, azimuth)
349 } else {
350 coordinate = QtPositioning.coordinate()
351 }
352 }
353
354 Connections {
355 target: _missionController
356 function onSplitSegmentChanged() { splitSegmentItem._updateSplitCoord() }
357 }
358
359 Connections {
360 target: _missionController.splitSegment
361 function onCoordinate1Changed() { splitSegmentItem._updateSplitCoord() }
362 function onCoordinate2Changed() { splitSegmentItem._updateSplitCoord() }
363 }
364 }
365
366 // Add the vehicles to the map
367 MapItemView {
368 model: QGroundControl.multiVehicleManager.vehicles
369 delegate: VehicleMapItem {
370 vehicle: object
371 coordinate: object.coordinate
372 map: editorMap
373 size: ScreenTools.defaultFontPixelHeight * 3
374 z: QGroundControl.zOrderMapItems - 1
375 }
376 }
377
378 GeoFenceMapVisuals {
379 map: editorMap
380 myGeoFenceController: _geoFenceController
381 interactive: _editingLayer == _layerFence
382 homePosition: _missionController.plannedHomePosition
383 planView: true
384 opacity: _editingLayer != _layerFence ? editorMap._nonInteractiveOpacity : 1
385 }
386
387 RallyPointMapVisuals {
388 map: editorMap
389 myRallyPointController: _rallyPointController
390 interactive: _editingLayer == _layerRally
391 planView: true
392 opacity: _editingLayer != _layerRally ? editorMap._nonInteractiveOpacity : 1
393 }
394
395 }
396
397 //-----------------------------------------------------------
398 // Left tool strip
399 ToolStrip {
400 id: toolStrip
401 anchors.margins: _toolsMargin
402 anchors.left: parent.left
403 anchors.top: parent.top
404 z: QGroundControl.zOrderWidgets
405 maxHeight: parent.height - toolStrip.y
406 visible: _editingLayer == _layerMission
407
408 property bool _isMissionLayer: _editingLayer == _layerMission
409
410 Binding {
411 target: waypointButton
412 property: "checked"
413 value: _addWaypointOnClick
414 }
415
416 Binding {
417 target: roiButton
418 property: "checked"
419 value: _addROIOnClick
420 }
421
422 ToolStripActionList {
423 id: toolStripActionList
424 model: [
425 ToolStripAction {
426 text: qsTr("Takeoff")
427 iconSource: "/res/takeoff.svg"
428 enabled: _missionController.isInsertTakeoffValid
429 visible: toolStrip._isMissionLayer && !_planMasterController.controllerVehicle.rover
430 onTriggered: {
431 insertTakeoffItemAfterCurrent()
432 }
433 },
434 ToolStripAction {
435 text: _singleComplexItem ? _missionController.complexMissionItemNames[0] : qsTr("Pattern")
436 iconSource: "/qmlimages/MapDrawShape.svg"
437 enabled: _missionController.flyThroughCommandsAllowed
438 visible: toolStrip._isMissionLayer
439 dropPanelComponent: _singleComplexItem ? undefined : patternDropPanel
440 onTriggered: {
441 if (_singleComplexItem) {
442 insertComplexItemAfterCurrent(_missionController.complexMissionItemNames[0])
443 }
444 }
445 },
446 ToolStripAction {
447 id: waypointButton
448 text: qsTr("Waypoint")
449 iconSource: "/res/waypoint.svg"
450 visible: toolStrip._isMissionLayer
451 checkable: true
452 onTriggered: { _addWaypointOnClick = !_addWaypointOnClick; if (_addWaypointOnClick) _addROIOnClick = false }
453 },
454 ToolStripAction {
455 id: roiButton
456 text: qsTr("ROI")
457 iconSource: "/qmlimages/roi.svg"
458 visible: toolStrip._isMissionLayer && _planMasterController.controllerVehicle.supports.roiMode
459 checkable: true
460 onTriggered: { _addROIOnClick = !_addROIOnClick; if (_addROIOnClick) _addWaypointOnClick = false }
461 },
462 ToolStripAction {
463 text: _planMasterController.controllerVehicle.multiRotor
464 ? qsTr("Return")
465 : _missionController.isInsertLandValid && _missionController.hasLandItem
466 ? qsTr("Alt Land")
467 : qsTr("Land")
468 iconSource: "/res/rtl.svg"
469 enabled: _missionController.isInsertLandValid
470 visible: toolStrip._isMissionLayer
471 onTriggered: {
472 insertLandItemAfterCurrent()
473 }
474 },
475 ToolStripAction {
476 text: qsTr("Stats")
477 iconSource: "/res/chevron-double-right.svg"
478 visible: missionStatus.hidden && QGroundControl.corePlugin.options.showMissionStatus
479 onTriggered: missionStatus.showMissionStatus()
480 }
481 ]
482 }
483
484 model: toolStripActionList.model
485 }
486
487 MapScale {
488 anchors.margins: _toolsMargin
489 anchors.left: toolStrip.right
490 anchors.top: parent.top
491 mapControl: editorMap
492 autoHide: true
493 }
494
495 PlanViewRightPanel {
496 id: rightPanel
497 anchors.top: parent.top
498 anchors.bottom: parent.bottom
499 anchors.right: parent.right
500 width: _rightPanelWidth
501 planMasterController: _planMasterController
502 editorMap: editorMap
503 onEditingLayerChangeRequested: (layer) => _editingLayer = layer
504 }
505
506 // Layer switching icons — only active icon visible; click to expand choices leftward
507 Item {
508 id: layerSwitcher
509 anchors.right: rightPanel.left
510 anchors.rightMargin: _toolsMargin
511 anchors.top: parent.top
512 anchors.topMargin: _toolsMargin
513 width: layerRow.width
514 height: _layerButtonSize
515 z: QGroundControl.zOrderWidgets
516
517 property bool expanded: false
518 property real _layerButtonSize: ScreenTools.defaultFontPixelHeight * 2.0
519 property real _spacing: ScreenTools.defaultFontPixelHeight * 0.25
520
521 readonly property var _layers: [
522 { layer: _layerMission, icon: "/res/waypoint.svg", nodeType: "missionGroup" },
523 { layer: _layerFence, icon: "/res/GeoFence.svg", nodeType: "fenceGroup" },
524 { layer: _layerRally, icon: "/res/RallyPoint.svg", nodeType: "rallyGroup" }
525 ]
526
527 Timer {
528 id: collapseTimer
529 interval: 5000
530 onTriggered: layerSwitcher.expanded = false
531 }
532
533 function toggle() {
534 expanded = !expanded
535 if (expanded) {
536 collapseTimer.restart()
537 } else {
538 collapseTimer.stop()
539 }
540 }
541
542 function choose(nodeType) {
543 expanded = false
544 collapseTimer.stop()
545 rightPanel.selectLayer(nodeType)
546 }
547
548 // Row laid out right-to-left: active icon on the right, choices expand left
549 Row {
550 id: layerRow
551 anchors.right: parent.right
552 spacing: layerSwitcher._spacing
553 layoutDirection: Qt.RightToLeft
554
555 // Active layer button (always visible)
556 Rectangle {
557 width: layerSwitcher._layerButtonSize
558 height: width
559 radius: ScreenTools.defaultBorderRadius
560 color: QGroundControl.globalPalette.buttonHighlight
561
562 QGCColoredImage {
563 anchors.centerIn: parent
564 width: parent.width * 0.6
565 height: width
566 source: layerSwitcher._layers.find(l => l.layer === _editingLayer)?.icon ?? "/res/waypoint.svg"
567 color: QGroundControl.globalPalette.buttonHighlightText
568 }
569
570 QGCMouseArea {
571 anchors.fill: parent
572 onClicked: layerSwitcher.toggle()
573 }
574 }
575
576 // Choice buttons (only layers that are NOT the current one)
577 Repeater {
578 model: layerSwitcher._layers.filter(l => l.layer !== _editingLayer)
579
580 Rectangle {
581 required property var modelData
582 width: layerSwitcher._layerButtonSize
583 height: width
584 radius: ScreenTools.defaultBorderRadius
585 color: QGroundControl.globalPalette.button
586 visible: opacity > 0
587 opacity: layerSwitcher.expanded ? 1 : 0
588
589 Behavior on opacity { NumberAnimation { duration: 150 } }
590
591 QGCColoredImage {
592 anchors.centerIn: parent
593 width: parent.width * 0.6
594 height: width
595 source: modelData.icon
596 color: QGroundControl.globalPalette.buttonText
597 }
598
599 QGCMouseArea {
600 anchors.fill: parent
601 onClicked: layerSwitcher.choose(modelData.nodeType)
602 }
603 }
604 }
605 }
606 }
607
608 RowLayout {
609 id: missionStatus
610 anchors.margins: _toolsMargin
611 anchors.left: _calcLeftAnchor()
612 anchors.right: rightPanel.left
613 anchors.bottom: parent.bottom
614 spacing: 0
615 visible: !hidden && _editingLayer == _layerMission && QGroundControl.corePlugin.options.showMissionStatus
616
617 readonly property bool hidden: _planViewSettings.showMissionItemStatus.rawValue ? false : true
618
619 function showMissionStatus() {
620 _planViewSettings.showMissionItemStatus.rawValue = true
621 }
622
623 function _calcLeftAnchor() {
624 let bottomOfToolStrip = toolStrip.y + toolStrip.height
625 let largestStatsHeight = Math.max(terrainStatus.height, missionStats.height)
626 if (bottomOfToolStrip + largestStatsHeight > parent.height - missionStatus.anchors.margins) {
627 return toolStrip.right
628 }
629 return parent.left
630 }
631
632 function _toggleMissionStatusVisibility() {
633 _planViewSettings.showMissionItemStatus.rawValue = _planViewSettings.showMissionItemStatus.rawValue ? false : true
634 }
635
636 ColumnLayout {
637 id: missionStatsButtonLayout
638 Layout.alignment: Qt.AlignBottom
639 spacing: 0
640
641 property real _buttonImplicitWidth: ScreenTools.defaultFontPixelHeight * 1.5
642 property real _buttonImageMargins: _buttonImplicitWidth * 0.15
643
644 Rectangle {
645 id: terrainButton
646 implicitWidth: missionStatsButtonLayout._buttonImplicitWidth
647 implicitHeight: implicitWidth
648 color: checked ? QGroundControl.globalPalette.buttonHighlight : QGroundControl.globalPalette.button
649
650 property bool checked: true
651
652 QGCColoredImage {
653 anchors.margins: missionStatsButtonLayout._buttonImageMargins
654 anchors.fill: parent
655 source: "/res/terrain.svg"
656 color: parent.checked ? QGroundControl.globalPalette.buttonHighlightText : QGroundControl.globalPalette.buttonText
657 }
658
659 QGCMouseArea {
660 anchors.fill: parent
661 onClicked: {
662 terrainButton.checked = true
663 missionStatsButton.checked = false
664 }
665 }
666 }
667
668 Rectangle {
669 id: missionStatsButton
670 implicitWidth: missionStatsButtonLayout._buttonImplicitWidth
671 implicitHeight: implicitWidth
672 color: checked ? QGroundControl.globalPalette.buttonHighlight : QGroundControl.globalPalette.button
673
674 property bool checked: false
675
676 QGCColoredImage {
677 anchors.margins: missionStatsButtonLayout._buttonImageMargins
678 anchors.fill: parent
679 source: "/res/sliders.svg"
680 color: parent.checked ? QGroundControl.globalPalette.buttonHighlightText : QGroundControl.globalPalette.buttonText
681 }
682
683 QGCMouseArea {
684 anchors.fill: parent
685 onClicked: {
686 missionStatsButton.checked = true
687 terrainButton.checked = false
688 }
689 }
690 }
691
692 Rectangle {
693 id: bottomStatusOpenCloseButton
694 implicitWidth: missionStatsButtonLayout._buttonImplicitWidth
695 implicitHeight: implicitWidth
696 color: QGroundControl.globalPalette.button
697
698 QGCColoredImage {
699 anchors.margins: missionStatsButtonLayout._buttonImageMargins
700 anchors.fill: parent
701 source: "/res/chevron-double-left.svg"
702 color: QGroundControl.globalPalette.buttonText
703 }
704
705 QGCMouseArea {
706 anchors.fill: parent
707 onClicked: missionStatus._toggleMissionStatusVisibility()
708 }
709 }
710 }
711
712 TerrainStatus {
713 id: terrainStatus
714 Layout.alignment: Qt.AlignBottom
715 Layout.fillWidth: true
716 height: ScreenTools.defaultFontPixelHeight * 7
717 missionController: _missionController
718 visible: terrainButton.checked
719 onSetCurrentSeqNum: _missionController.setCurrentPlanViewSeqNum(seqNum, true)
720 }
721
722 MissionStats {
723 id: missionStats
724 Layout.alignment: Qt.AlignBottom
725 Layout.fillWidth: true
726 visible: missionStatsButton.checked
727 planMasterController: _root._planMasterController
728 }
729 }
730 }
731
732 //- ToolStrip ToolStripDropPanel Components
733
734 Component {
735 id: patternDropPanel
736
737 ColumnLayout {
738 spacing: ScreenTools.defaultFontPixelWidth * 0.5
739
740 QGCLabel { text: qsTr("Create complex pattern:") }
741
742 Repeater {
743 model: _missionController.complexMissionItemNames
744
745 QGCButton {
746 text: modelData
747 Layout.fillWidth: true
748
749 onClicked: {
750 insertComplexItemAfterCurrent(modelData)
751 dropPanel.hide()
752 }
753 }
754 }
755 } // Column
756 }
757
758 QGCPopupDialogFactory {
759 id: promptForPlanUsageOnVehicleChangePopupFactory
760
761 dialogComponent: promptForPlanUsageOnVehicleChangePopupComponent
762 }
763
764 Component {
765 id: promptForPlanUsageOnVehicleChangePopupComponent
766 QGCPopupDialog {
767 title: _planMasterController.managerVehicle.isOfflineEditingVehicle ? qsTr("Plan View - Vehicle Disconnected") : qsTr("Plan View - Vehicle Changed")
768 buttons: Dialog.NoButton
769
770 ColumnLayout {
771 QGCLabel {
772 Layout.maximumWidth: parent.width
773 wrapMode: QGCLabel.WordWrap
774 text: _planMasterController.managerVehicle.isOfflineEditingVehicle ?
775 qsTr("The vehicle associated with the plan in the Plan View is no longer available. What would you like to do with that plan?") : qsTr("The plan being worked on in the Plan View is not from the current vehicle. What would you like to do with that plan?")
776 }
777
778 QGCButton {
779 Layout.fillWidth: true
780 text: (_planMasterController.dirtyForSave) ?
781 (_planMasterController.managerVehicle.isOfflineEditingVehicle ?
782 qsTr("Discard Unsaved Changes") : qsTr("Discard Unsaved Changes, Load New Plan From Vehicle")) : qsTr("Load New Plan From Vehicle")
783 onClicked: {
784 _planMasterController.showPlanFromManagerVehicle()
785 _promptForPlanUsageShowing = false
786 close();
787 }
788 }
789
790 QGCButton {
791 Layout.fillWidth: true
792 text: _planMasterController.managerVehicle.isOfflineEditingVehicle ?
793 qsTr("Keep Current Plan") : qsTr("Keep Current Plan, Don't Update From Vehicle")
794 onClicked: {
795 _promptForPlanUsageShowing = false
796 close()
797 }
798 }
799 }
800 }
801 }
802
803 Component {
804 id: insertOrCancelROIDropPanelComponent
805
806 DropPanel {
807 id: insertOrCancelROIDropPanel
808 onClosed: destroy()
809
810 property var mapClickCoord
811
812 sourceComponent: Component {
813 ColumnLayout {
814 spacing: ScreenTools.defaultFontPixelWidth / 2
815
816 QGCButton {
817 Layout.fillWidth: true
818 text: qsTr("Insert ROI")
819
820 onClicked: {
821 insertOrCancelROIDropPanel.close()
822 insertROIAfterCurrent(mapClickCoord)
823 }
824 }
825
826 QGCButton {
827 Layout.fillWidth: true
828 text: qsTr("Insert Cancel ROI")
829
830 onClicked: {
831 insertOrCancelROIDropPanel.close()
832 insertCancelROIAfterCurrent()
833 }
834 }
835 }
836 }
837 }
838 }
839}