QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
MissionItemIndicatorGroup.qml
Go to the documentation of this file.
1pragma ComponentBehavior: Bound
2
3import QtQuick
4
5import QGroundControl
6import QGroundControl.Controls
7import QGroundControl.FlightMap
8
9/// Groups mission item indicators which are too close to select individually.
10Item {
11 id: root
12
13 required property FlightMap map
14 required property QmlObjectListModel missionItems
15
16 readonly property real groupingDistance: _mediumIndicatorRadius
17
18 readonly property real _smallIndicatorRadius: _oddCeil((ScreenTools.defaultFontPixelHeight * ScreenTools.smallFontPointRatio) / 2)
19 readonly property real _largeIndicatorRadius: _oddCeil(ScreenTools.defaultFontPixelHeight * 0.66)
20 readonly property real _mediumIndicatorRadius: _oddCeil(((_smallIndicatorRadius * 2) + _largeIndicatorRadius) / 3)
21 readonly property var _groupingState: {
22 const state = []
23 const count = missionItems ? missionItems.count : 0
24
25 for (let i = 0; i < count; i++) {
26 const item = missionItems.get(i)
27 if (!item || !item.isSimpleItem || (!item.specifiesCoordinate && !item.isTakeoffItem)) {
28 continue
29 }
30
31 const coordinate = _coordinateForItem(item)
32 state.push({
33 sequenceNumber: item.sequenceNumber,
34 coordinateValid: coordinate && coordinate.isValid,
35 latitude: coordinate && coordinate.isValid ? coordinate.latitude : NaN,
36 longitude: coordinate && coordinate.isValid ? coordinate.longitude : NaN,
37 current: item.isCurrentItem || item.hasCurrentChildItem
38 })
39 }
40
41 return state
42 }
43
44 property var _groupsBySequenceNumber: ({})
45 property var _selectionPanel
46
47 signal itemSelected(int sequenceNumber)
48
49 function groupForItem(item) {
50 return item ? _groupsBySequenceNumber[item.sequenceNumber] || null : null
51 }
52
53 function showGroup(item, clickRect) {
54 const group = groupForItem(item)
55 if (!group || group.items.length < 2) {
56 itemSelected(item.sequenceNumber)
57 return
58 }
59
60 _closeSelectionPanel()
61 const panel = selectionPanelComponent.createObject(mainWindow, {
62 clickRect: clickRect,
63 groupItems: group.items
64 })
65 if (!panel) {
66 return
67 }
68
69 _selectionPanel = panel
70 panel.open()
71 }
72
73 function _oddCeil(value) {
74 const rounded = Math.ceil(value)
75 return rounded + (rounded % 2 === 0 ? 1 : 0)
76 }
77
78 function _scheduleRegroup() {
79 regroupTimer.restart()
80 }
81
82 function _coordinateForItem(item) {
83 return item.isTakeoffItem && !item.specifiesCoordinate ? item.launchCoordinate : item.coordinate
84 }
85
86 function _closeSelectionPanel() {
87 if (_selectionPanel) {
88 _selectionPanel.close()
89 _selectionPanel = null
90 }
91 }
92
93 function _regroup() {
94 _closeSelectionPanel()
95
96 if (!map || !map.mapReady) {
97 _groupsBySequenceNumber = {}
98 return
99 }
100
101 const entries = []
102 const count = missionItems ? missionItems.count : 0
103 for (let i = 0; i < count; i++) {
104 const item = missionItems.get(i)
105 if (!item || !item.isSimpleItem || (!item.specifiesCoordinate && !item.isTakeoffItem)) {
106 continue
107 }
108
109 const coordinate = _coordinateForItem(item)
110 if (!coordinate || !coordinate.isValid) {
111 continue
112 }
113
114 const point = map.fromCoordinate(coordinate, false /* clipToViewPort */)
115 if (!Number.isFinite(point.x) || !Number.isFinite(point.y)) {
116 continue
117 }
118
119 entries.push({
120 item: item,
121 point: point,
122 current: item.isCurrentItem || item.hasCurrentChildItem
123 })
124 }
125
126 entries.sort((first, second) => {
127 if (first.current !== second.current) {
128 return first.current ? -1 : 1
129 }
130 return first.item.sequenceNumber - second.item.sequenceNumber
131 })
132
133 const representatives = MapMarkerGrouping.representatives(entries.map(entry => entry.point), groupingDistance)
134 const groups = []
135 const groupsByRepresentative = {}
136 for (let i = 0; i < entries.length; i++) {
137 const representative = representatives[i]
138 if (!groupsByRepresentative[representative]) {
139 groupsByRepresentative[representative] = { items: [], representative: entries[representative].item }
140 groups.push(groupsByRepresentative[representative])
141 }
142 groupsByRepresentative[representative].items.push(entries[i].item)
143 }
144
145 const groupsBySequenceNumber = {}
146 for (const group of groups) {
147 group.items.sort((first, second) => first.sequenceNumber - second.sequenceNumber)
148 for (const item of group.items) {
149 groupsBySequenceNumber[item.sequenceNumber] = group
150 }
151 }
152 _groupsBySequenceNumber = groupsBySequenceNumber
153 }
154
155 Timer {
156 id: regroupTimer
157
158 interval: 0
159 repeat: false
160 onTriggered: root._regroup()
161 }
162
163 Component {
164 id: selectionPanelComponent
165
166 MissionItemSelectionPanel {
167 id: selectionPanel
168
169 onItemSelected: (sequenceNumber) => root.itemSelected(sequenceNumber)
170
171 onClosed: {
172 if (root._selectionPanel === selectionPanel) {
173 root._selectionPanel = null
174 }
175 destroy()
176 }
177 }
178 }
179
180 Connections {
181 target: root.map
182
183 function onMapPanStop() { root._scheduleRegroup() }
184 function onMapReadyChanged() { root._scheduleRegroup() }
185 function onZoomLevelChanged() { root._scheduleRegroup() }
186 }
187
188 Connections {
189 target: root.missionItems
190
191 function onDataChanged() { root._scheduleRegroup() }
192 }
193
194 on_GroupingStateChanged: _scheduleRegroup()
195 onGroupingDistanceChanged: _scheduleRegroup()
196 onMapChanged: _scheduleRegroup()
197 onMissionItemsChanged: _scheduleRegroup()
198
199 Component.onCompleted: _scheduleRegroup()
200 Component.onDestruction: _closeSelectionPanel()
201}