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 insertVTOLMulticopterTakeoffItemAfterCurrent() {
198 var nextIndex = _missionController.currentPlanViewVIIndex + 1
199 _missionController.insertVTOLMulticopterTakeoffItem(mapCenter(), nextIndex, true /* makeCurrentItem */)
200 }
201
202 function insertLandItemAfterCurrent() {
203 var nextIndex = _missionController.currentPlanViewVIIndex + 1
204 _missionController.insertLandItem(mapCenter(), nextIndex, true /* makeCurrentItem */)
205 }
206
207 function _landButtonText() {
208 // Must mirror MissionController::insertLandItem: only fixed-wing/VTOL get landing patterns
209 if (!_planMasterController.controllerVehicle.fixedWing && !_planMasterController.controllerVehicle.vtol) {
210 return qsTr("Return")
211 }
212 if (_missionController.isInsertLandValid && _missionController.hasLandItem) {
213 return qsTr("Alt Land")
214 }
215 return qsTr("Land")
216 }
217
218 QGCFileDialog {
219 id: fileDialog
220 folder: _appSettings ? _appSettings.missionSavePath : ""
221
222 property bool planFiles: true ///< true: working with plan files, false: working with kml file
223
224 onAcceptedForSave: (file) => {
225 if (planFiles) {
226 if (_planMasterController.saveToFile(file)) {
227 close()
228 }
229 } else {
230 _planMasterController.saveToKml(file)
231 close()
232 }
233 }
234
235 onAcceptedForLoad: (file) => {
236 _planMasterController.loadFromFile(file)
237 _planMasterController.fitViewportToItems()
238 _missionController.setCurrentPlanViewSeqNum(0, true)
239 close()
240 }
241 }
242
243 PlanViewToolBar {
244 id: planToolBar
245 planMasterController: _planMasterController
246 showRallyPointsHelp: _editingLayer === _layerRally
247 }
248
249 Item {
250 id: mainPlanViewArea
251 anchors.left: parent.left
252 anchors.right: parent.right
253 anchors.top: planToolBar.bottom
254 anchors.bottom: parent.bottom
255
256 FlightMap {
257 id: editorMap
258 objectName: "planView_map"
259 anchors.fill: parent
260 mapName: "MissionEditor"
261 allowGCSLocationCenter: true
262 allowVehicleLocationCenter: true
263 planView: true
264
265 zoomLevel: QGroundControl.flightMapZoom
266 center: QGroundControl.flightMapPosition
267
268 // This is the center rectangle of the map which is not obscured by tools
269 property rect centerViewport: Qt.rect(_leftToolWidth + _margin, _margin, editorMap.width - _leftToolWidth - _rightToolWidth - (_margin * 2), (missionStatus.visible ? missionStatus.y : height - _margin) - _margin)
270
271 property real _leftToolWidth: toolStrip.x + toolStrip.width
272 property real _rightToolWidth: rightPanel.width + rightPanel.anchors.rightMargin
273 property real _nonInteractiveOpacity: 0.5
274
275 // Initial map position duplicates Fly view position
276 Component.onCompleted: editorMap.center = QGroundControl.flightMapPosition
277
278 onZoomLevelChanged: {
279 QGroundControl.flightMapZoom = editorMap.zoomLevel
280 }
281 onCenterChanged: {
282 QGroundControl.flightMapPosition = editorMap.center
283 }
284
285 onMapClicked: (mouse) => {
286 // Take focus to close any previous editing
287 editorMap.focus = true
288
289 // Collapse layer switcher on any map click
290 layerSwitcher.expanded = false
291 collapseTimer.stop()
292
293 if (!mainWindow.allowViewSwitch()) {
294 return
295 }
296 var coordinate = editorMap.toCoordinate(Qt.point(mouse.x, mouse.y), false /* clipToViewPort */)
297 coordinate.latitude = coordinate.latitude.toFixed(_decimalPlaces)
298 coordinate.longitude = coordinate.longitude.toFixed(_decimalPlaces)
299 coordinate.altitude = coordinate.altitude.toFixed(_decimalPlaces)
300
301 switch (_editingLayer) {
302 case _layerMission:
303 if (_planMasterController.showCreateFromTemplate) {
304 _missionController.setHomePosition(coordinate)
305 } else if (_addROIOnClick) {
306 _addROIOnClick = false
307 if (_missionController.isROIActive) {
308 var pos = Qt.point(mouse.x, mouse.y)
309 // For some strange reason using mainWindow in mapToItem doesn't work, so we use globals.parent instead which also gets us mainWindow
310 pos = editorMap.mapToItem(globals.parent, pos)
311 var dropPanel = insertOrCancelROIDropPanelComponent.createObject(mainWindow, { mapClickCoord: coordinate, clickRect: Qt.rect(pos.x, pos.y, 0, 0) })
312 dropPanel.open()
313 } else {
314 insertROIAfterCurrent(coordinate)
315 }
316 } else if (_addWaypointOnClick) {
317 insertSimpleItemAfterCurrent(coordinate)
318 }
319 break
320 case _layerRally:
321 if (_rallyPointController.supported) {
322 _rallyPointController.addPoint(coordinate)
323 }
324 break
325 }
326 }
327
328 MissionItemIndicatorGroup {
329 id: _missionItemIndicatorGroup
330
331 map: editorMap
332 missionItems: _root._missionController.visualItems
333 onItemSelected: (sequenceNumber) => {
334 _root._missionController.setCurrentPlanViewSeqNum(sequenceNumber, false)
335 }
336 }
337
338 // Add the mission item visuals to the map
339 Repeater {
340 model: _missionController.visualItems
341 delegate: MissionItemMapVisual {
342 map: editorMap
343 opacity: _editingLayer == _layerMission ? 1 : editorMap._nonInteractiveOpacity
344 interactive: _editingLayer == _layerMission
345 vehicle: _planMasterController.controllerVehicle
346 indicatorGroup: _missionItemIndicatorGroup
347 onClicked: (sequenceNumber) => {
348 _root._missionController.setCurrentPlanViewSeqNum(sequenceNumber, false)
349 }
350 }
351 }
352
353 // Add lines between waypoints
354 MissionLineView {
355 showSpecialVisual: _missionController.isROIBeginCurrentItem
356 model: _missionController.simpleFlightPathSegments
357 opacity: _editingLayer == _layerMission ? 1 : editorMap._nonInteractiveOpacity
358 }
359
360 // Direction arrows in waypoint lines
361 MapItemView {
362 model: _editingLayer == _layerMission ? _missionController.directionArrows : undefined
363
364 delegate: MapLineArrow {
365 fromCoord: object ? object.coordinate1 : undefined
366 toCoord: object ? object.coordinate2 : undefined
367 arrowPosition: 3
368 mapControl: editorMap
369 z: QGroundControl.zOrderWaypointLines + 1
370 }
371 }
372
373 // UI for splitting the current segment
374 MapQuickItem {
375 id: splitSegmentItem
376
377 property real _screenLegLength: 0
378
379 anchorPoint.x: sourceItem.width / 2
380 anchorPoint.y: sourceItem.height / 2
381 z: QGroundControl.zOrderWaypointLines + 1
382 visible: _editingLayer == _layerMission
383 && _screenLegLength > _missionItemIndicatorGroup.groupingDistance * 2
384
385 sourceItem: SplitIndicator {
386 onClicked: _missionController.insertSimpleMissionItem(splitSegmentItem.coordinate,
387 _missionController.currentPlanViewVIIndex,
388 true /* makeCurrentItem */)
389 }
390
391 function _updateScreenLegLength() {
392 const segment = _root._missionController.splitSegment
393 if (segment && segment.coordinate1.isValid && segment.coordinate2.isValid) {
394 const fromPoint = editorMap.fromCoordinate(segment.coordinate1, false /* clipToViewPort */)
395 const toPoint = editorMap.fromCoordinate(segment.coordinate2, false /* clipToViewPort */)
396 _screenLegLength = Math.hypot(toPoint.x - fromPoint.x, toPoint.y - fromPoint.y)
397 } else {
398 _screenLegLength = 0
399 }
400 }
401
402 function _updateSplitCoord() {
403 if (_missionController.splitSegment) {
404 var distance = _missionController.splitSegment.coordinate1.distanceTo(_missionController.splitSegment.coordinate2)
405 var azimuth = _missionController.splitSegment.coordinate1.azimuthTo(_missionController.splitSegment.coordinate2)
406 splitSegmentItem.coordinate = _missionController.splitSegment.coordinate1.atDistanceAndAzimuth(distance / 2, azimuth)
407 } else {
408 coordinate = QtPositioning.coordinate()
409 }
410 _updateScreenLegLength()
411 }
412
413 Connections {
414 target: _missionController
415 function onSplitSegmentChanged() { splitSegmentItem._updateSplitCoord() }
416 }
417
418 Connections {
419 target: _missionController.splitSegment
420 function onCoordinate1Changed() { splitSegmentItem._updateSplitCoord() }
421 function onCoordinate2Changed() { splitSegmentItem._updateSplitCoord() }
422 }
423
424 Connections {
425 target: editorMap
426 function onCenterChanged() { splitSegmentItem._updateScreenLegLength() }
427 function onZoomLevelChanged() { splitSegmentItem._updateScreenLegLength() }
428 }
429 }
430
431 // Add the vehicles to the map
432 MapItemView {
433 model: QGroundControl.multiVehicleManager.vehicles
434 delegate: VehicleMapItem {
435 vehicle: object
436 coordinate: object.coordinate
437 map: editorMap
438 size: ScreenTools.defaultFontPixelHeight * 3
439 z: QGroundControl.zOrderMapItems - 1
440 }
441 }
442
443 GeoFenceMapVisuals {
444 objectName: "planView_geoFenceMapVisuals"
445 map: editorMap
446 myGeoFenceController: _geoFenceController
447 interactive: _editingLayer == _layerFence
448 homePosition: _missionController.plannedHomePosition
449 planView: true
450 opacity: _editingLayer != _layerFence ? editorMap._nonInteractiveOpacity : 1
451 }
452
453 RallyPointMapVisuals {
454 objectName: "planView_rallyPointMapVisuals"
455 map: editorMap
456 myRallyPointController: _rallyPointController
457 interactive: _editingLayer == _layerRally
458 planView: true
459 opacity: _editingLayer != _layerRally ? editorMap._nonInteractiveOpacity : 1
460 }
461
462 }
463
464 //-----------------------------------------------------------
465 // Left tool strip
466 ToolStrip {
467 id: toolStrip
468 anchors.margins: _toolsMargin
469 anchors.left: parent.left
470 anchors.top: parent.top
471 z: QGroundControl.zOrderWidgets
472 maxHeight: parent.height - toolStrip.y
473 visible: _editingLayer == _layerMission
474
475 property bool _isMissionLayer: _editingLayer == _layerMission
476
477 Binding {
478 target: waypointButton
479 property: "checked"
480 value: _addWaypointOnClick
481 }
482
483 Binding {
484 target: roiButton
485 property: "checked"
486 value: _addROIOnClick
487 }
488
489 ToolStripActionList {
490 id: toolStripActionList
491 model: [
492 ToolStripAction {
493 objectName: "planToolStrip_takeoffButton"
494 text: qsTr("Takeoff")
495 iconSource: "/res/takeoff.svg"
496 enabled: _missionController.isInsertTakeoffValid
497 visible: toolStrip._isMissionLayer && !_planMasterController.controllerVehicle.rover
498 dropPanelComponent: _planMasterController.controllerVehicle.supports.vtolMulticopterTakeoff
499 ? vtolTakeoffDropPanel
500 : null
501 onTriggered: {
502 insertTakeoffItemAfterCurrent()
503 }
504 },
505 ToolStripAction {
506 objectName: "planToolStrip_patternButton"
507 text: _singleComplexItem ? _missionController.complexMissionItems[0].translatedName : qsTr("Pattern")
508 iconSource: "/qmlimages/MapDrawShape.svg"
509 enabled: _missionController.flyThroughCommandsAllowed
510 visible: toolStrip._isMissionLayer
511 dropPanelComponent: _singleComplexItem ? undefined : patternDropPanel
512 onTriggered: {
513 if (_singleComplexItem) {
514 insertComplexItemAfterCurrent(_missionController.complexMissionItems[0].canonicalName)
515 }
516 }
517 },
518 ToolStripAction {
519 id: waypointButton
520 objectName: "planToolStrip_waypointButton"
521 text: qsTr("Waypoint")
522 iconSource: "/res/waypoint.svg"
523 enabled: _missionController.flyThroughCommandsAllowed
524 visible: toolStrip._isMissionLayer
525 checkable: true
526 onTriggered: {
527 _addWaypointOnClick = !_addWaypointOnClick
528 if (_addWaypointOnClick) {
529 _addROIOnClick = false
530 // Arming an insert tool leaves template-creation mode, otherwise
531 // map clicks keep moving the home position instead of inserting
532 _planMasterController.userSelectedManualCreation = true
533 }
534 }
535 },
536 ToolStripAction {
537 id: roiButton
538 objectName: "planToolStrip_roiButton"
539 text: _missionController.isROIActive ? qsTr("Cancel ROI") : qsTr("ROI")
540 iconSource: "/qmlimages/roi.svg"
541 enabled: _missionController.isInsertROIValid
542 visible: toolStrip._isMissionLayer && _planMasterController.controllerVehicle.supports.roiMode
543 checkable: true
544 onTriggered: {
545 _addROIOnClick = !_addROIOnClick
546 if (_addROIOnClick) {
547 _addWaypointOnClick = false
548 _planMasterController.userSelectedManualCreation = true
549 }
550 }
551 },
552 ToolStripAction {
553 objectName: "planToolStrip_landButton"
554 text: _landButtonText()
555 iconSource: "/res/rtl.svg"
556 enabled: _missionController.isInsertLandValid
557 visible: toolStrip._isMissionLayer
558 onTriggered: {
559 insertLandItemAfterCurrent()
560 }
561 },
562 ToolStripAction {
563 text: qsTr("Stats")
564 iconSource: "/res/chevron-double-right.svg"
565 visible: missionStatus.hidden && QGroundControl.corePlugin.options.showMissionStatus
566 onTriggered: missionStatus.showMissionStatus()
567 }
568 ]
569 }
570
571 model: toolStripActionList.model
572 }
573
574 MapScale {
575 anchors.margins: _toolsMargin
576 anchors.left: toolStrip.right
577 anchors.top: parent.top
578 mapControl: editorMap
579 autoHide: true
580 }
581
582 PlanViewRightPanel {
583 id: rightPanel
584 anchors.top: parent.top
585 anchors.bottom: parent.bottom
586 anchors.right: parent.right
587 width: _rightPanelWidth
588 planMasterController: _planMasterController
589 editorMap: editorMap
590 onEditingLayerChangeRequested: (layer) => _editingLayer = layer
591 }
592
593 // Layer switching icons — only active icon visible; click to expand choices leftward
594 Item {
595 id: layerSwitcher
596 objectName: "planView_layerSwitcher"
597 anchors.right: rightPanel.left
598 anchors.rightMargin: _toolsMargin
599 anchors.top: parent.top
600 anchors.topMargin: _toolsMargin
601 width: layerRow.width
602 height: _layerButtonSize
603 z: QGroundControl.zOrderWidgets
604 visible: !_planMasterController.showCreateFromTemplate
605
606 property bool expanded: false
607 property real _layerButtonSize: ScreenTools.defaultFontPixelHeight * 2.0
608 property real _spacing: ScreenTools.defaultFontPixelHeight * 0.25
609
610 Timer {
611 id: collapseTimer
612 interval: 5000
613 onTriggered: layerSwitcher.expanded = false
614 }
615
616 function toggle() {
617 expanded = !expanded
618 if (expanded) {
619 collapseTimer.restart()
620 } else {
621 collapseTimer.stop()
622 }
623 }
624
625 function choose(nodeType) {
626 expanded = false
627 collapseTimer.stop()
628 rightPanel.selectLayer(nodeType)
629 }
630
631 // Row laid out right-to-left: active icon on the right, choices expand left
632 Row {
633 id: layerRow
634 anchors.right: parent.right
635 spacing: layerSwitcher._spacing
636 layoutDirection: Qt.RightToLeft
637
638 // Active layer button (always visible)
639 Rectangle {
640 objectName: "layerSwitcher_activeButton"
641 width: layerSwitcher._layerButtonSize
642 height: width
643 radius: ScreenTools.defaultBorderRadius
644 color: QGroundControl.globalPalette.buttonHighlight
645
646 QGCColoredImage {
647 anchors.centerIn: parent
648 width: parent.width * 0.6
649 height: width
650 source: PlanEditLayers.layerInfos.find(l => l.layer === _editingLayer)?.icon ?? "/res/waypoint.svg"
651 color: QGroundControl.globalPalette.buttonHighlightText
652 }
653
654 QGCMouseArea {
655 anchors.fill: parent
656 onClicked: layerSwitcher.toggle()
657 }
658 }
659
660 // Choice buttons (only layers that are NOT the current one)
661 Repeater {
662 model: PlanEditLayers.layerInfos.filter(l => l.layer !== _editingLayer)
663
664 Rectangle {
665 required property var modelData
666 objectName: "layerSwitcher_choice_" + modelData.nodeType
667 width: choiceRow.implicitWidth + layerSwitcher._spacing * 2
668 height: layerSwitcher._layerButtonSize
669 radius: ScreenTools.defaultBorderRadius
670 color: QGroundControl.globalPalette.button
671 visible: opacity > 0
672 opacity: layerSwitcher.expanded ? 1 : 0
673
674 Behavior on opacity { NumberAnimation { duration: 150 } }
675
676 Row {
677 id: choiceRow
678 anchors.centerIn: parent
679 spacing: layerSwitcher._spacing
680
681 QGCColoredImage {
682 anchors.verticalCenter: parent.verticalCenter
683 width: layerSwitcher._layerButtonSize * 0.6
684 height: width
685 source: modelData.icon
686 color: QGroundControl.globalPalette.buttonText
687 }
688
689 QGCLabel {
690 anchors.verticalCenter: parent.verticalCenter
691 text: modelData.name
692 color: QGroundControl.globalPalette.buttonText
693 }
694 }
695
696 QGCMouseArea {
697 anchors.fill: parent
698 onClicked: layerSwitcher.choose(modelData.nodeType)
699 }
700 }
701 }
702 }
703 }
704
705 RowLayout {
706 id: missionStatus
707 anchors.margins: _toolsMargin
708 anchors.left: _calcLeftAnchor()
709 anchors.right: rightPanel.left
710 anchors.bottom: parent.bottom
711 spacing: 0
712 visible: !hidden && _editingLayer == _layerMission && QGroundControl.corePlugin.options.showMissionStatus
713
714 readonly property bool hidden: _planViewSettings.showMissionItemStatus.rawValue ? false : true
715
716 function showMissionStatus() {
717 _planViewSettings.showMissionItemStatus.rawValue = true
718 }
719
720 function _calcLeftAnchor() {
721 let bottomOfToolStrip = toolStrip.y + toolStrip.height
722 let largestStatsHeight = Math.max(terrainStatus.height, missionStats.height)
723 if (bottomOfToolStrip + largestStatsHeight > parent.height - missionStatus.anchors.margins) {
724 return toolStrip.right
725 }
726 return parent.left
727 }
728
729 function _toggleMissionStatusVisibility() {
730 _planViewSettings.showMissionItemStatus.rawValue = _planViewSettings.showMissionItemStatus.rawValue ? false : true
731 }
732
733 ColumnLayout {
734 id: missionStatsButtonLayout
735 Layout.alignment: Qt.AlignBottom
736 spacing: 0
737
738 property real _buttonImplicitWidth: ScreenTools.defaultFontPixelHeight * 1.5
739 property real _buttonImageMargins: _buttonImplicitWidth * 0.15
740
741 Rectangle {
742 id: terrainButton
743 implicitWidth: missionStatsButtonLayout._buttonImplicitWidth
744 implicitHeight: implicitWidth
745 color: checked ? QGroundControl.globalPalette.buttonHighlight : QGroundControl.globalPalette.button
746
747 property bool checked: true
748
749 QGCColoredImage {
750 anchors.margins: missionStatsButtonLayout._buttonImageMargins
751 anchors.fill: parent
752 source: "/res/terrain.svg"
753 color: parent.checked ? QGroundControl.globalPalette.buttonHighlightText : QGroundControl.globalPalette.buttonText
754 }
755
756 QGCMouseArea {
757 anchors.fill: parent
758 onClicked: {
759 terrainButton.checked = true
760 missionStatsButton.checked = false
761 }
762 }
763 }
764
765 Rectangle {
766 id: missionStatsButton
767 implicitWidth: missionStatsButtonLayout._buttonImplicitWidth
768 implicitHeight: implicitWidth
769 color: checked ? QGroundControl.globalPalette.buttonHighlight : QGroundControl.globalPalette.button
770
771 property bool checked: false
772
773 QGCColoredImage {
774 anchors.margins: missionStatsButtonLayout._buttonImageMargins
775 anchors.fill: parent
776 source: "/res/sliders.svg"
777 color: parent.checked ? QGroundControl.globalPalette.buttonHighlightText : QGroundControl.globalPalette.buttonText
778 }
779
780 QGCMouseArea {
781 anchors.fill: parent
782 onClicked: {
783 missionStatsButton.checked = true
784 terrainButton.checked = false
785 }
786 }
787 }
788
789 Rectangle {
790 id: bottomStatusOpenCloseButton
791 implicitWidth: missionStatsButtonLayout._buttonImplicitWidth
792 implicitHeight: implicitWidth
793 color: QGroundControl.globalPalette.button
794
795 QGCColoredImage {
796 anchors.margins: missionStatsButtonLayout._buttonImageMargins
797 anchors.fill: parent
798 source: "/res/chevron-double-left.svg"
799 color: QGroundControl.globalPalette.buttonText
800 }
801
802 QGCMouseArea {
803 anchors.fill: parent
804 onClicked: missionStatus._toggleMissionStatusVisibility()
805 }
806 }
807 }
808
809 TerrainStatus {
810 id: terrainStatus
811 Layout.alignment: Qt.AlignBottom
812 Layout.fillWidth: true
813 height: ScreenTools.defaultFontPixelHeight * 7
814 missionController: _missionController
815 visible: terrainButton.checked
816 onSetCurrentSeqNum: _missionController.setCurrentPlanViewSeqNum(seqNum, true)
817 }
818
819 MissionStats {
820 id: missionStats
821 Layout.alignment: Qt.AlignBottom
822 Layout.fillWidth: true
823 visible: missionStatsButton.checked
824 planMasterController: _root._planMasterController
825 }
826 }
827 }
828
829 //- ToolStrip ToolStripDropPanel Components
830
831 Component {
832 id: patternDropPanel
833
834 ColumnLayout {
835 spacing: ScreenTools.defaultFontPixelWidth * 0.5
836
837 QGCLabel { text: qsTr("Create complex pattern:") }
838
839 Repeater {
840 model: _missionController.complexMissionItems
841
842 QGCButton {
843 text: modelData.translatedName
844 Layout.fillWidth: true
845
846 onClicked: {
847 insertComplexItemAfterCurrent(modelData.canonicalName)
848 dropPanel.hide()
849 }
850 }
851 }
852 } // Column
853 }
854
855 Component {
856 id: vtolTakeoffDropPanel
857
858 ColumnLayout {
859 spacing: ScreenTools.defaultFontPixelWidth * 0.5
860
861 QGCLabel { text: qsTr("Choose takeoff mode:") }
862
863 QGCButton {
864 objectName: "planTakeoff_vtolButton"
865 text: qsTr("VTOL takeoff")
866 Layout.fillWidth: true
867 primary: true
868
869 onClicked: {
870 insertTakeoffItemAfterCurrent()
871 dropPanel.hide()
872 }
873 }
874
875 QGCButton {
876 objectName: "planTakeoff_mcButton"
877 text: qsTr("Multicopter takeoff")
878 Layout.fillWidth: true
879
880 onClicked: {
881 insertVTOLMulticopterTakeoffItemAfterCurrent()
882 dropPanel.hide()
883 }
884 }
885 }
886 }
887
888 QGCPopupDialogFactory {
889 id: promptForPlanUsageOnVehicleChangePopupFactory
890
891 dialogComponent: promptForPlanUsageOnVehicleChangePopupComponent
892 }
893
894 Component {
895 id: promptForPlanUsageOnVehicleChangePopupComponent
896 QGCPopupDialog {
897 title: _planMasterController.managerVehicle.isOfflineEditingVehicle ? qsTr("Plan View - Vehicle Disconnected") : qsTr("Plan View - Vehicle Changed")
898 buttons: Dialog.NoButton
899
900 ColumnLayout {
901 QGCLabel {
902 Layout.maximumWidth: parent.width
903 wrapMode: QGCLabel.WordWrap
904 text: _planMasterController.managerVehicle.isOfflineEditingVehicle ?
905 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?")
906 }
907
908 QGCButton {
909 Layout.fillWidth: true
910 text: (_planMasterController.dirtyForSave) ?
911 (_planMasterController.managerVehicle.isOfflineEditingVehicle ?
912 qsTr("Discard Unsaved Changes") : qsTr("Discard Unsaved Changes, Load New Plan From Vehicle")) : qsTr("Load New Plan From Vehicle")
913 onClicked: {
914 _planMasterController.showPlanFromManagerVehicle()
915 _promptForPlanUsageShowing = false
916 close();
917 }
918 }
919
920 QGCButton {
921 Layout.fillWidth: true
922 text: _planMasterController.managerVehicle.isOfflineEditingVehicle ?
923 qsTr("Keep Current Plan") : qsTr("Keep Current Plan, Don't Update From Vehicle")
924 onClicked: {
925 _promptForPlanUsageShowing = false
926 close()
927 }
928 }
929 }
930 }
931 }
932
933 Component {
934 id: insertOrCancelROIDropPanelComponent
935
936 DropPanel {
937 id: insertOrCancelROIDropPanel
938 onClosed: destroy()
939
940 property var mapClickCoord
941
942 sourceComponent: Component {
943 ColumnLayout {
944 spacing: ScreenTools.defaultFontPixelWidth / 2
945
946 QGCButton {
947 Layout.fillWidth: true
948 text: qsTr("Insert ROI")
949
950 onClicked: {
951 insertOrCancelROIDropPanel.close()
952 insertROIAfterCurrent(mapClickCoord)
953 }
954 }
955
956 QGCButton {
957 Layout.fillWidth: true
958 text: qsTr("Insert Cancel ROI")
959
960 onClicked: {
961 insertOrCancelROIDropPanel.close()
962 insertCancelROIAfterCurrent()
963 }
964 }
965 }
966 }
967 }
968 }
969}