6import QGroundControl.Controls
7import QGroundControl.FactControls
9/// Unified plan tree view showing Mission Items, GeoFence, and Rally Points
10/// as collapsible sections using a real TreeView with type-discriminating delegates.
14 required property var editorMap
15 required property var planMasterController
17 signal editingLayerChangeRequested(int layer)
19 readonly property int _layerMission: 1
20 readonly property int _layerFence: 2
21 readonly property int _layerRally: 3
23 property var _missionController: planMasterController.missionController
24 property var _geoFenceController: planMasterController.geoFenceController
25 property var _rallyPointController: planMasterController.rallyPointController
27 model: _missionController.visualItemsTree
29 boundsBehavior: Flickable.StopAtBounds
31 pointerNavigationEnabled: false
32 selectionBehavior: TableView.SelectionDisabled
35 // Helper: convert a persistent model index to the current visual row
36 function _rowFor(modelIndex) { return root.rowAtIndex(modelIndex) }
38 // QGCFlickableScrollIndicator expects parent to have indicatorColor (provided by QGCFlickable/QGCListView)
39 property color indicatorColor: qgcPal.text
41 QGCPalette { id: qgcPal; colorGroupEnabled: enabled }
43 QGCFlickableScrollIndicator { parent: root; orientation: QGCFlickableScrollIndicator.Horizontal }
44 QGCFlickableScrollIndicator { parent: root; orientation: QGCFlickableScrollIndicator.Vertical }
47 target: root._missionController
48 function onVisualItemsChanged() {
49 // Mission group always expanded after rebuild (clear / load)
50 root.collapseRecursively()
51 root.expand(_rowFor(_missionController.missionGroupIndex))
52 root.editingLayerChangeRequested(root._layerMission)
56 // Public API: select a layer and expand its group. Called by the layer tool buttons.
57 function selectLayer(nodeType) {
61 targetRow = _rowFor(_missionController.missionGroupIndex)
62 editingLayerChangeRequested(_layerMission)
65 targetRow = _rowFor(_missionController.fenceGroupIndex)
66 editingLayerChangeRequested(_layerFence)
69 targetRow = _rowFor(_missionController.rallyGroupIndex)
70 editingLayerChangeRequested(_layerRally)
75 if (!root.isExpanded(targetRow))
76 root.expand(targetRow)
78 root.positionViewAtRow(targetRow, TableView.AlignTop)
82 // Toggle expand/collapse for a group header. Does not affect the editing layer.
83 function _toggleGroup(row) {
84 if (root.isExpanded(row))
91 // Coalesces multiple delegate height changes into a single forceLayout() call
97 onTriggered: root.forceLayout()
103 required property TreeView treeView
104 required property bool isTreeNode
105 required property bool expanded
106 required property bool hasChildren
107 required property int depth
108 required property int row
109 required property var model
111 readonly property var nodeObject: model.object
112 readonly property string nodeType: model.nodeType
114 implicitWidth: root.width
115 implicitHeight: loader.item ? loader.item.height : 1
117 height: implicitHeight
119 onImplicitHeightChanged: layoutTimer.restart()
125 // Guard: non-group delegates need a valid object. During model
126 // row removal the role data goes null before the delegate is
127 // destroyed, which would cause "Cannot read property of null"
128 // warnings in every downstream editor binding.
129 switch (delegateRoot.nodeType) {
130 case "planFileGroup": return groupHeaderComponent
131 case "defaultsGroup": return groupHeaderComponent
132 case "missionGroup": return groupHeaderComponent
133 case "fenceGroup": return groupHeaderComponent
134 case "rallyGroup": return groupHeaderComponent
135 case "planFileInfo": return planFileInfoComponent
136 case "defaultsInfo": return defaultsEditorComponent
137 case "missionItem": return delegateRoot.nodeObject ? missionItemComponent : null
138 case "fenceEditor": return delegateRoot.nodeObject ? fenceEditorComponent : null
139 case "rallyHeader": return delegateRoot.nodeObject ? rallyHeaderComponent : null
140 case "rallyItem": return delegateRoot.nodeObject ? rallyItemComponent : null
146 // ── Group header (Mission Items / GeoFence / Rally Points) ──
148 id: groupHeaderComponent
151 width: delegateRoot.width
152 height: ScreenTools.implicitComboBoxHeight + ScreenTools.defaultFontPixelWidth
153 color: qgcPal.windowShade
157 spacing: ScreenTools.defaultFontPixelWidth * 0.5
158 anchors.verticalCenter: parent.verticalCenter
159 anchors.left: parent.left
160 anchors.leftMargin: ScreenTools.defaultFontPixelWidth * 0.5
163 width: ScreenTools.defaultFontPixelHeight * 0.75
165 source: "/InstrumentValueIcons/cheveron-right.svg"
167 anchors.verticalCenter: parent.verticalCenter
168 rotation: delegateRoot.expanded ? 90 : 0
172 text: delegateRoot.nodeObject ? delegateRoot.nodeObject.objectName : ""
174 anchors.verticalCenter: parent.verticalCenter
180 onClicked: root._toggleGroup(delegateRoot.row)
185 // ── Plan file info delegate ──
187 id: planFileInfoComponent
190 width: delegateRoot.width
191 height: planFileColumn.height + ScreenTools.defaultFontPixelHeight
192 color: qgcPal.windowShadeDark
196 anchors.left: parent.left
197 anchors.right: parent.right
198 anchors.verticalCenter: parent.verticalCenter
199 anchors.margins: ScreenTools.defaultFontPixelWidth
200 spacing: ScreenTools.defaultFontPixelHeight * 0.25
204 placeholderText: qsTr("Untitled")
207 Component.onCompleted: text = root.planMasterController.currentPlanFileName
210 target: root.planMasterController
211 function onCurrentPlanFileNameChanged() {
212 if (!planNameField.activeFocus) {
213 planNameField.text = root.planMasterController.currentPlanFileName
218 onEditingFinished: root.planMasterController.currentPlanFileName = text
224 // ── Defaults editor delegate ──
226 id: defaultsEditorComponent
230 width: delegateRoot.width
231 height: defaultsColumn.height + ScreenTools.defaultFontPixelHeight
232 color: qgcPal.windowShadeDark
234 property var _missionController: root._missionController
235 property var _controllerVehicle: root.planMasterController.controllerVehicle
236 property var _visualItems: root._missionController.visualItems
237 property bool _noMissionItemsAdded: _visualItems ? _visualItems.count <= 1 : true
238 property var _settingsItem: _visualItems && _visualItems.count > 0 ? _visualItems.get(0) : null
239 property bool _multipleFirmware: !QGroundControl.singleFirmwareSupport
240 property bool _multipleVehicleTypes: !QGroundControl.singleVehicleSupport
241 property bool _allowFWVehicleTypeSelection: _noMissionItemsAdded && !globals.activeVehicle
242 property bool _showCruiseSpeed: _controllerVehicle ? !_controllerVehicle.multiRotor : false
243 property bool _showHoverSpeed: _controllerVehicle ? (_controllerVehicle.multiRotor || _controllerVehicle.vtol) : false
244 property bool _vehicleHasHomePosition: _controllerVehicle ? _controllerVehicle.homePosition.isValid : false
245 property bool _waypointsOnlyMode: QGroundControl.corePlugin.options.missionWaypointsOnly
246 property real _fieldWidth: ScreenTools.defaultFontPixelWidth * 16
247 readonly property real _margin: ScreenTools.defaultFontPixelWidth / 2
250 target: defaultsRect._controllerVehicle
251 function onFirmwareTypeChanged() {
252 if (!defaultsRect._controllerVehicle.supports.terrainFrame
253 && defaultsRect._missionController.globalAltitudeMode === QGroundControl.AltitudeModeTerrainFrame) {
254 defaultsRect._missionController.globalAltitudeMode = QGroundControl.AltitudeModeCalcAboveTerrain
259 Component { id: altModeDialogComponent; AltModeDialog { } }
261 QGCPopupDialogFactory {
262 id: defaultsAltModeDialogFactory
263 dialogComponent: altModeDialogComponent
268 anchors.left: parent.left
269 anchors.right: parent.right
270 anchors.verticalCenter: parent.verticalCenter
271 anchors.margins: ScreenTools.defaultFontPixelWidth
272 spacing: ScreenTools.defaultFontPixelHeight * 0.5
275 Layout.fillWidth: true
276 label: qsTr("Altitude Mode")
277 buttonText: QGroundControl.altitudeModeShortDescription(defaultsRect._missionController.globalAltitudeMode)
281 let updateFunction = function(altMode) { defaultsRect._missionController.globalAltitudeMode = altMode }
282 if (!defaultsRect._controllerVehicle.supports.terrainFrame) {
283 removeModes.push(QGroundControl.AltitudeModeTerrainFrame)
285 if (!defaultsRect._noMissionItemsAdded) {
286 if (defaultsRect._missionController.globalAltitudeMode !== QGroundControl.AltitudeModeRelative) {
287 removeModes.push(QGroundControl.AltitudeModeRelative)
289 if (defaultsRect._missionController.globalAltitudeMode !== QGroundControl.AltitudeModeAbsolute) {
290 removeModes.push(QGroundControl.AltitudeModeAbsolute)
292 if (defaultsRect._missionController.globalAltitudeMode !== QGroundControl.AltitudeModeCalcAboveTerrain) {
293 removeModes.push(QGroundControl.AltitudeModeCalcAboveTerrain)
295 if (defaultsRect._missionController.globalAltitudeMode !== QGroundControl.AltitudeModeTerrainFrame) {
296 removeModes.push(QGroundControl.AltitudeModeTerrainFrame)
299 defaultsAltModeDialogFactory.open({ rgRemoveModes: removeModes, updateAltModeFn: updateFunction })
303 FactTextFieldSlider {
304 Layout.fillWidth: true
305 label: qsTr("Waypoints Altitude")
306 fact: QGroundControl.settingsManager.appSettings.defaultMissionItemAltitude
309 FactTextFieldSlider {
310 Layout.fillWidth: true
311 label: qsTr("Flight Speed")
312 fact: defaultsRect._settingsItem ? defaultsRect._settingsItem.speedSection.flightSpeed : null
313 showEnableCheckbox: true
314 enableCheckBoxChecked: defaultsRect._settingsItem ? defaultsRect._settingsItem.speedSection.specifyFlightSpeed : false
315 visible: defaultsRect._settingsItem ? defaultsRect._settingsItem.speedSection.available : false
317 onEnableCheckboxClicked: {
318 if (defaultsRect._settingsItem) {
319 defaultsRect._settingsItem.speedSection.specifyFlightSpeed = enableCheckBoxChecked
324 // ── Vehicle Info ──
326 id: vehicleInfoSectionHeader
327 Layout.fillWidth: true
328 text: qsTr("Vehicle Info")
329 visible: !defaultsRect._waypointsOnlyMode
334 Layout.fillWidth: true
335 columnSpacing: ScreenTools.defaultFontPixelWidth
336 rowSpacing: columnSpacing
338 visible: vehicleInfoSectionHeader.visible && vehicleInfoSectionHeader.checked
341 text: qsTr("Firmware")
342 Layout.fillWidth: true
343 visible: defaultsRect._multipleFirmware
346 fact: QGroundControl.settingsManager.appSettings.offlineEditingFirmwareClass
348 Layout.preferredWidth: defaultsRect._fieldWidth
349 visible: defaultsRect._multipleFirmware && defaultsRect._allowFWVehicleTypeSelection
352 text: defaultsRect._controllerVehicle ? defaultsRect._controllerVehicle.firmwareTypeString : ""
353 visible: defaultsRect._multipleFirmware && !defaultsRect._allowFWVehicleTypeSelection
357 text: qsTr("Vehicle")
358 Layout.fillWidth: true
359 visible: defaultsRect._multipleVehicleTypes
362 fact: QGroundControl.settingsManager.appSettings.offlineEditingVehicleClass
364 Layout.preferredWidth: defaultsRect._fieldWidth
365 visible: defaultsRect._multipleVehicleTypes && defaultsRect._allowFWVehicleTypeSelection
368 text: defaultsRect._controllerVehicle ? defaultsRect._controllerVehicle.vehicleTypeString : ""
369 visible: defaultsRect._multipleVehicleTypes && !defaultsRect._allowFWVehicleTypeSelection
374 Layout.alignment: Qt.AlignHCenter
375 Layout.fillWidth: true
376 wrapMode: Text.WordWrap
377 font.pointSize: ScreenTools.smallFontPointSize
378 text: qsTr("The following speed values are used to calculate total mission time. They do not affect the flight speed for the mission.")
379 visible: defaultsRect._showCruiseSpeed || defaultsRect._showHoverSpeed
383 text: qsTr("Cruise speed")
384 visible: defaultsRect._showCruiseSpeed
385 Layout.fillWidth: true
388 fact: QGroundControl.settingsManager.appSettings.offlineEditingCruiseSpeed
389 visible: defaultsRect._showCruiseSpeed
390 Layout.preferredWidth: defaultsRect._fieldWidth
394 text: qsTr("Hover speed")
395 visible: defaultsRect._showHoverSpeed
396 Layout.fillWidth: true
399 fact: QGroundControl.settingsManager.appSettings.offlineEditingHoverSpeed
400 visible: defaultsRect._showHoverSpeed
401 Layout.preferredWidth: defaultsRect._fieldWidth
405 // ── Launch Position ──
407 id: plannedHomePositionSection
408 Layout.fillWidth: true
409 text: qsTr("Launch Position")
410 visible: !defaultsRect._vehicleHasHomePosition
415 Layout.fillWidth: true
416 columnSpacing: ScreenTools.defaultFontPixelWidth
417 rowSpacing: columnSpacing
419 visible: plannedHomePositionSection.checked && !defaultsRect._vehicleHasHomePosition
422 text: qsTr("Altitude")
425 fact: defaultsRect._settingsItem ? defaultsRect._settingsItem.plannedHomePositionAltitude : null
426 Layout.fillWidth: true
431 Layout.fillWidth: true
432 wrapMode: Text.WordWrap
433 font.pointSize: ScreenTools.smallFontPointSize
434 text: qsTr("Actual position set by vehicle at flight time.")
435 horizontalAlignment: Text.AlignHCenter
436 visible: plannedHomePositionSection.checked && !defaultsRect._vehicleHasHomePosition
440 text: qsTr("Set To Map Center")
441 Layout.alignment: Qt.AlignHCenter
442 visible: plannedHomePositionSection.checked && !defaultsRect._vehicleHasHomePosition
444 if (defaultsRect._settingsItem) {
445 defaultsRect._settingsItem.coordinate = root.editorMap.center
453 // ── Mission item delegate ──
455 id: missionItemComponent
458 width: delegateRoot.width
460 masterController: root.planMasterController
461 missionItem: delegateRoot.nodeObject
464 onClicked: root._missionController.setCurrentPlanViewSeqNum(delegateRoot.nodeObject.sequenceNumber, false)
467 var viIndex = root._missionController.visualItemIndexForObject(delegateRoot.nodeObject)
469 root._missionController.removeVisualItem(viIndex)
473 onSelectNextNotReadyItem: {
474 for (var i = 0; i < root._missionController.visualItems.count; i++) {
475 var vmi = root._missionController.visualItems.get(i)
476 if (vmi.readyForSaveState === VisualMissionItem.NotReadyForSaveData) {
477 root._missionController.setCurrentPlanViewSeqNum(vmi.sequenceNumber, true)
485 // ── GeoFence editor (single child of fence group) ──
487 id: fenceEditorComponent
490 width: delegateRoot.width
491 myGeoFenceController: root._geoFenceController
492 flightMap: root.editorMap
496 // ── Rally header / instructions ──
498 id: rallyHeaderComponent
500 RallyPointEditorHeader {
501 width: delegateRoot.width
502 controller: root._rallyPointController
506 // ── Rally point item editor ──
508 id: rallyItemComponent
510 RallyPointItemEditor {
511 width: delegateRoot.width
512 rallyPoint: delegateRoot.nodeObject
513 controller: root._rallyPointController