1pragma ComponentBehavior: Bound
6import QGroundControl.Controls
7import QGroundControl.FlightMap
9/// Groups mission item indicators which are too close to select individually.
13 required property FlightMap map
14 required property QmlObjectListModel missionItems
16 readonly property real groupingDistance: _mediumIndicatorRadius
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: {
23 const count = missionItems ? missionItems.count : 0
25 for (let i = 0; i < count; i++) {
26 const item = missionItems.get(i)
27 if (!item || !item.isSimpleItem || (!item.specifiesCoordinate && !item.isTakeoffItem)) {
31 const coordinate = _coordinateForItem(item)
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
44 property var _groupsBySequenceNumber: ({})
45 property var _selectionPanel
47 signal itemSelected(int sequenceNumber)
49 function groupForItem(item) {
50 return item ? _groupsBySequenceNumber[item.sequenceNumber] || null : null
53 function showGroup(item, clickRect) {
54 const group = groupForItem(item)
55 if (!group || group.items.length < 2) {
56 itemSelected(item.sequenceNumber)
60 _closeSelectionPanel()
61 const panel = selectionPanelComponent.createObject(mainWindow, {
63 groupItems: group.items
69 _selectionPanel = panel
73 function _oddCeil(value) {
74 const rounded = Math.ceil(value)
75 return rounded + (rounded % 2 === 0 ? 1 : 0)
78 function _scheduleRegroup() {
79 regroupTimer.restart()
82 function _coordinateForItem(item) {
83 return item.isTakeoffItem && !item.specifiesCoordinate ? item.launchCoordinate : item.coordinate
86 function _closeSelectionPanel() {
87 if (_selectionPanel) {
88 _selectionPanel.close()
89 _selectionPanel = null
94 _closeSelectionPanel()
96 if (!map || !map.mapReady) {
97 _groupsBySequenceNumber = {}
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)) {
109 const coordinate = _coordinateForItem(item)
110 if (!coordinate || !coordinate.isValid) {
114 const point = map.fromCoordinate(coordinate, false /* clipToViewPort */)
115 if (!Number.isFinite(point.x) || !Number.isFinite(point.y)) {
122 current: item.isCurrentItem || item.hasCurrentChildItem
126 entries.sort((first, second) => {
127 if (first.current !== second.current) {
128 return first.current ? -1 : 1
130 return first.item.sequenceNumber - second.item.sequenceNumber
133 const representatives = MapMarkerGrouping.representatives(entries.map(entry => entry.point), groupingDistance)
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])
142 groupsByRepresentative[representative].items.push(entries[i].item)
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
152 _groupsBySequenceNumber = groupsBySequenceNumber
160 onTriggered: root._regroup()
164 id: selectionPanelComponent
166 MissionItemSelectionPanel {
169 onItemSelected: (sequenceNumber) => root.itemSelected(sequenceNumber)
172 if (root._selectionPanel === selectionPanel) {
173 root._selectionPanel = null
183 function onMapPanStop() { root._scheduleRegroup() }
184 function onMapReadyChanged() { root._scheduleRegroup() }
185 function onZoomLevelChanged() { root._scheduleRegroup() }
189 target: root.missionItems
191 function onDataChanged() { root._scheduleRegroup() }
194 on_GroupingStateChanged: _scheduleRegroup()
195 onGroupingDistanceChanged: _scheduleRegroup()
196 onMapChanged: _scheduleRegroup()
197 onMissionItemsChanged: _scheduleRegroup()
199 Component.onCompleted: _scheduleRegroup()
200 Component.onDestruction: _closeSelectionPanel()