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: PlanEditLayers.layerMission
39 readonly property int _layerFence: PlanEditLayers.layerFence
40 readonly property int _layerRally: PlanEditLayers.layerRally
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 objectName: "planView_geoFenceMapVisuals"
393 map: editorMap
394 myGeoFenceController: _geoFenceController
395 interactive: _editingLayer == _layerFence
396 homePosition: _missionController.plannedHomePosition
397 planView: true
398 opacity: _editingLayer != _layerFence ? editorMap._nonInteractiveOpacity : 1
399 }
400
401 RallyPointMapVisuals {
402 objectName: "planView_rallyPointMapVisuals"
403 map: editorMap
404 myRallyPointController: _rallyPointController
405 interactive: _editingLayer == _layerRally
406 planView: true
407 opacity: _editingLayer != _layerRally ? editorMap._nonInteractiveOpacity : 1
408 }
409
410 }
411
412 //-----------------------------------------------------------
413 // Left tool strip
414 ToolStrip {
415 id: toolStrip
416 anchors.margins: _toolsMargin
417 anchors.left: parent.left
418 anchors.top: parent.top
419 z: QGroundControl.zOrderWidgets
420 maxHeight: parent.height - toolStrip.y
421 visible: _editingLayer == _layerMission
422
423 property bool _isMissionLayer: _editingLayer == _layerMission
424
425 Binding {
426 target: waypointButton
427 property: "checked"
428 value: _addWaypointOnClick
429 }
430
431 Binding {
432 target: roiButton
433 property: "checked"
434 value: _addROIOnClick
435 }
436
437 ToolStripActionList {
438 id: toolStripActionList
439 model: [
440 ToolStripAction {
441 objectName: "planToolStrip_takeoffButton"
442 text: qsTr("Takeoff")
443 iconSource: "/res/takeoff.svg"
444 enabled: _missionController.isInsertTakeoffValid
445 visible: toolStrip._isMissionLayer && !_planMasterController.controllerVehicle.rover
446 onTriggered: {
447 insertTakeoffItemAfterCurrent()
448 }
449 },
450 ToolStripAction {
451 objectName: "planToolStrip_patternButton"
452 text: _singleComplexItem ? _missionController.complexMissionItems[0].translatedName : qsTr("Pattern")
453 iconSource: "/qmlimages/MapDrawShape.svg"
454 enabled: _missionController.flyThroughCommandsAllowed
455 visible: toolStrip._isMissionLayer
456 dropPanelComponent: _singleComplexItem ? undefined : patternDropPanel
457 onTriggered: {
458 if (_singleComplexItem) {
459 insertComplexItemAfterCurrent(_missionController.complexMissionItems[0].canonicalName)
460 }
461 }
462 },
463 ToolStripAction {
464 id: waypointButton
465 objectName: "planToolStrip_waypointButton"
466 text: qsTr("Waypoint")
467 iconSource: "/res/waypoint.svg"
468 enabled: _missionController.flyThroughCommandsAllowed
469 visible: toolStrip._isMissionLayer
470 checkable: true
471 onTriggered: {
472 _addWaypointOnClick = !_addWaypointOnClick
473 if (_addWaypointOnClick) {
474 _addROIOnClick = false
475 // Arming an insert tool leaves template-creation mode, otherwise
476 // map clicks keep moving the home position instead of inserting
477 _planMasterController.userSelectedManualCreation = true
478 }
479 }
480 },
481 ToolStripAction {
482 id: roiButton
483 objectName: "planToolStrip_roiButton"
484 text: _missionController.isROIActive ? qsTr("Cancel ROI") : qsTr("ROI")
485 iconSource: "/qmlimages/roi.svg"
486 enabled: _missionController.isInsertROIValid
487 visible: toolStrip._isMissionLayer && _planMasterController.controllerVehicle.supports.roiMode
488 checkable: true
489 onTriggered: {
490 _addROIOnClick = !_addROIOnClick
491 if (_addROIOnClick) {
492 _addWaypointOnClick = false
493 _planMasterController.userSelectedManualCreation = true
494 }
495 }
496 },
497 ToolStripAction {
498 objectName: "planToolStrip_landButton"
499 text: _planMasterController.controllerVehicle.multiRotor
500 ? qsTr("Return")
501 : _missionController.isInsertLandValid && _missionController.hasLandItem
502 ? qsTr("Alt Land")
503 : qsTr("Land")
504 iconSource: "/res/rtl.svg"
505 enabled: _missionController.isInsertLandValid
506 visible: toolStrip._isMissionLayer
507 onTriggered: {
508 insertLandItemAfterCurrent()
509 }
510 },
511 ToolStripAction {
512 text: qsTr("Stats")
513 iconSource: "/res/chevron-double-right.svg"
514 visible: missionStatus.hidden && QGroundControl.corePlugin.options.showMissionStatus
515 onTriggered: missionStatus.showMissionStatus()
516 }
517 ]
518 }
519
520 model: toolStripActionList.model
521 }
522
523 MapScale {
524 anchors.margins: _toolsMargin
525 anchors.left: toolStrip.right
526 anchors.top: parent.top
527 mapControl: editorMap
528 autoHide: true
529 }
530
531 PlanViewRightPanel {
532 id: rightPanel
533 anchors.top: parent.top
534 anchors.bottom: parent.bottom
535 anchors.right: parent.right
536 width: _rightPanelWidth
537 planMasterController: _planMasterController
538 editorMap: editorMap
539 onEditingLayerChangeRequested: (layer) => _editingLayer = layer
540 }
541
542 // Layer switching icons — only active icon visible; click to expand choices leftward
543 Item {
544 id: layerSwitcher
545 objectName: "planView_layerSwitcher"
546 anchors.right: rightPanel.left
547 anchors.rightMargin: _toolsMargin
548 anchors.top: parent.top
549 anchors.topMargin: _toolsMargin
550 width: layerRow.width
551 height: _layerButtonSize
552 z: QGroundControl.zOrderWidgets
553 visible: !_planMasterController.showCreateFromTemplate
554
555 property bool expanded: false
556 property real _layerButtonSize: ScreenTools.defaultFontPixelHeight * 2.0
557 property real _spacing: ScreenTools.defaultFontPixelHeight * 0.25
558
559 Timer {
560 id: collapseTimer
561 interval: 5000
562 onTriggered: layerSwitcher.expanded = false
563 }
564
565 function toggle() {
566 expanded = !expanded
567 if (expanded) {
568 collapseTimer.restart()
569 } else {
570 collapseTimer.stop()
571 }
572 }
573
574 function choose(nodeType) {
575 expanded = false
576 collapseTimer.stop()
577 rightPanel.selectLayer(nodeType)
578 }
579
580 // Row laid out right-to-left: active icon on the right, choices expand left
581 Row {
582 id: layerRow
583 anchors.right: parent.right
584 spacing: layerSwitcher._spacing
585 layoutDirection: Qt.RightToLeft
586
587 // Active layer button (always visible)
588 Rectangle {
589 objectName: "layerSwitcher_activeButton"
590 width: layerSwitcher._layerButtonSize
591 height: width
592 radius: ScreenTools.defaultBorderRadius
593 color: QGroundControl.globalPalette.buttonHighlight
594
595 QGCColoredImage {
596 anchors.centerIn: parent
597 width: parent.width * 0.6
598 height: width
599 source: PlanEditLayers.layerInfos.find(l => l.layer === _editingLayer)?.icon ?? "/res/waypoint.svg"
600 color: QGroundControl.globalPalette.buttonHighlightText
601 }
602
603 QGCMouseArea {
604 anchors.fill: parent
605 onClicked: layerSwitcher.toggle()
606 }
607 }
608
609 // Choice buttons (only layers that are NOT the current one)
610 Repeater {
611 model: PlanEditLayers.layerInfos.filter(l => l.layer !== _editingLayer)
612
613 Rectangle {
614 required property var modelData
615 objectName: "layerSwitcher_choice_" + modelData.nodeType
616 width: choiceRow.implicitWidth + layerSwitcher._spacing * 2
617 height: layerSwitcher._layerButtonSize
618 radius: ScreenTools.defaultBorderRadius
619 color: QGroundControl.globalPalette.button
620 visible: opacity > 0
621 opacity: layerSwitcher.expanded ? 1 : 0
622
623 Behavior on opacity { NumberAnimation { duration: 150 } }
624
625 Row {
626 id: choiceRow
627 anchors.centerIn: parent
628 spacing: layerSwitcher._spacing
629
630 QGCColoredImage {
631 anchors.verticalCenter: parent.verticalCenter
632 width: layerSwitcher._layerButtonSize * 0.6
633 height: width
634 source: modelData.icon
635 color: QGroundControl.globalPalette.buttonText
636 }
637
638 QGCLabel {
639 anchors.verticalCenter: parent.verticalCenter
640 text: modelData.name
641 color: QGroundControl.globalPalette.buttonText
642 }
643 }
644
645 QGCMouseArea {
646 anchors.fill: parent
647 onClicked: layerSwitcher.choose(modelData.nodeType)
648 }
649 }
650 }
651 }
652 }
653
654 RowLayout {
655 id: missionStatus
656 anchors.margins: _toolsMargin
657 anchors.left: _calcLeftAnchor()
658 anchors.right: rightPanel.left
659 anchors.bottom: parent.bottom
660 spacing: 0
661 visible: !hidden && _editingLayer == _layerMission && QGroundControl.corePlugin.options.showMissionStatus
662
663 readonly property bool hidden: _planViewSettings.showMissionItemStatus.rawValue ? false : true
664
665 function showMissionStatus() {
666 _planViewSettings.showMissionItemStatus.rawValue = true
667 }
668
669 function _calcLeftAnchor() {
670 let bottomOfToolStrip = toolStrip.y + toolStrip.height
671 let largestStatsHeight = Math.max(terrainStatus.height, missionStats.height)
672 if (bottomOfToolStrip + largestStatsHeight > parent.height - missionStatus.anchors.margins) {
673 return toolStrip.right
674 }
675 return parent.left
676 }
677
678 function _toggleMissionStatusVisibility() {
679 _planViewSettings.showMissionItemStatus.rawValue = _planViewSettings.showMissionItemStatus.rawValue ? false : true
680 }
681
682 ColumnLayout {
683 id: missionStatsButtonLayout
684 Layout.alignment: Qt.AlignBottom
685 spacing: 0
686
687 property real _buttonImplicitWidth: ScreenTools.defaultFontPixelHeight * 1.5
688 property real _buttonImageMargins: _buttonImplicitWidth * 0.15
689
690 Rectangle {
691 id: terrainButton
692 implicitWidth: missionStatsButtonLayout._buttonImplicitWidth
693 implicitHeight: implicitWidth
694 color: checked ? QGroundControl.globalPalette.buttonHighlight : QGroundControl.globalPalette.button
695
696 property bool checked: true
697
698 QGCColoredImage {
699 anchors.margins: missionStatsButtonLayout._buttonImageMargins
700 anchors.fill: parent
701 source: "/res/terrain.svg"
702 color: parent.checked ? QGroundControl.globalPalette.buttonHighlightText : QGroundControl.globalPalette.buttonText
703 }
704
705 QGCMouseArea {
706 anchors.fill: parent
707 onClicked: {
708 terrainButton.checked = true
709 missionStatsButton.checked = false
710 }
711 }
712 }
713
714 Rectangle {
715 id: missionStatsButton
716 implicitWidth: missionStatsButtonLayout._buttonImplicitWidth
717 implicitHeight: implicitWidth
718 color: checked ? QGroundControl.globalPalette.buttonHighlight : QGroundControl.globalPalette.button
719
720 property bool checked: false
721
722 QGCColoredImage {
723 anchors.margins: missionStatsButtonLayout._buttonImageMargins
724 anchors.fill: parent
725 source: "/res/sliders.svg"
726 color: parent.checked ? QGroundControl.globalPalette.buttonHighlightText : QGroundControl.globalPalette.buttonText
727 }
728
729 QGCMouseArea {
730 anchors.fill: parent
731 onClicked: {
732 missionStatsButton.checked = true
733 terrainButton.checked = false
734 }
735 }
736 }
737
738 Rectangle {
739 id: bottomStatusOpenCloseButton
740 implicitWidth: missionStatsButtonLayout._buttonImplicitWidth
741 implicitHeight: implicitWidth
742 color: QGroundControl.globalPalette.button
743
744 QGCColoredImage {
745 anchors.margins: missionStatsButtonLayout._buttonImageMargins
746 anchors.fill: parent
747 source: "/res/chevron-double-left.svg"
748 color: QGroundControl.globalPalette.buttonText
749 }
750
751 QGCMouseArea {
752 anchors.fill: parent
753 onClicked: missionStatus._toggleMissionStatusVisibility()
754 }
755 }
756 }
757
758 TerrainStatus {
759 id: terrainStatus
760 Layout.alignment: Qt.AlignBottom
761 Layout.fillWidth: true
762 height: ScreenTools.defaultFontPixelHeight * 7
763 missionController: _missionController
764 visible: terrainButton.checked
765 onSetCurrentSeqNum: _missionController.setCurrentPlanViewSeqNum(seqNum, true)
766 }
767
768 MissionStats {
769 id: missionStats
770 Layout.alignment: Qt.AlignBottom
771 Layout.fillWidth: true
772 visible: missionStatsButton.checked
773 planMasterController: _root._planMasterController
774 }
775 }
776 }
777
778 //- ToolStrip ToolStripDropPanel Components
779
780 Component {
781 id: patternDropPanel
782
783 ColumnLayout {
784 spacing: ScreenTools.defaultFontPixelWidth * 0.5
785
786 QGCLabel { text: qsTr("Create complex pattern:") }
787
788 Repeater {
789 model: _missionController.complexMissionItems
790
791 QGCButton {
792 text: modelData.translatedName
793 Layout.fillWidth: true
794
795 onClicked: {
796 insertComplexItemAfterCurrent(modelData.canonicalName)
797 dropPanel.hide()
798 }
799 }
800 }
801 } // Column
802 }
803
804 QGCPopupDialogFactory {
805 id: promptForPlanUsageOnVehicleChangePopupFactory
806
807 dialogComponent: promptForPlanUsageOnVehicleChangePopupComponent
808 }
809
810 Component {
811 id: promptForPlanUsageOnVehicleChangePopupComponent
812 QGCPopupDialog {
813 title: _planMasterController.managerVehicle.isOfflineEditingVehicle ? qsTr("Plan View - Vehicle Disconnected") : qsTr("Plan View - Vehicle Changed")
814 buttons: Dialog.NoButton
815
816 ColumnLayout {
817 QGCLabel {
818 Layout.maximumWidth: parent.width
819 wrapMode: QGCLabel.WordWrap
820 text: _planMasterController.managerVehicle.isOfflineEditingVehicle ?
821 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?")
822 }
823
824 QGCButton {
825 Layout.fillWidth: true
826 text: (_planMasterController.dirtyForSave) ?
827 (_planMasterController.managerVehicle.isOfflineEditingVehicle ?
828 qsTr("Discard Unsaved Changes") : qsTr("Discard Unsaved Changes, Load New Plan From Vehicle")) : qsTr("Load New Plan From Vehicle")
829 onClicked: {
830 _planMasterController.showPlanFromManagerVehicle()
831 _promptForPlanUsageShowing = false
832 close();
833 }
834 }
835
836 QGCButton {
837 Layout.fillWidth: true
838 text: _planMasterController.managerVehicle.isOfflineEditingVehicle ?
839 qsTr("Keep Current Plan") : qsTr("Keep Current Plan, Don't Update From Vehicle")
840 onClicked: {
841 _promptForPlanUsageShowing = false
842 close()
843 }
844 }
845 }
846 }
847 }
848
849 Component {
850 id: insertOrCancelROIDropPanelComponent
851
852 DropPanel {
853 id: insertOrCancelROIDropPanel
854 onClosed: destroy()
855
856 property var mapClickCoord
857
858 sourceComponent: Component {
859 ColumnLayout {
860 spacing: ScreenTools.defaultFontPixelWidth / 2
861
862 QGCButton {
863 Layout.fillWidth: true
864 text: qsTr("Insert ROI")
865
866 onClicked: {
867 insertOrCancelROIDropPanel.close()
868 insertROIAfterCurrent(mapClickCoord)
869 }
870 }
871
872 QGCButton {
873 Layout.fillWidth: true
874 text: qsTr("Insert Cancel ROI")
875
876 onClicked: {
877 insertOrCancelROIDropPanel.close()
878 insertCancelROIAfterCurrent()
879 }
880 }
881 }
882 }
883 }
884 }
885}