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