From 7238b48e225f2f245ca22477e10739b12836cbcb Mon Sep 17 00:00:00 2001
From: srcejon <jon@beniston.com>
Date: Wed, 28 Feb 2024 12:34:57 +0000
Subject: [PATCH] ADS-B: Add QT 6 support for map.

---
 plugins/channelrx/demodadsb/adsbdemodgui.cpp  |   5 +-
 plugins/channelrx/demodadsb/map.qrc           |   2 +
 .../demodadsb/map/ModifiedMapView.qml         | 178 ++++++
 plugins/channelrx/demodadsb/map/map_6.qml     | 537 ++++++++++++++++++
 4 files changed, 721 insertions(+), 1 deletion(-)
 create mode 100644 plugins/channelrx/demodadsb/map/ModifiedMapView.qml
 create mode 100644 plugins/channelrx/demodadsb/map/map_6.qml

diff --git a/plugins/channelrx/demodadsb/adsbdemodgui.cpp b/plugins/channelrx/demodadsb/adsbdemodgui.cpp
index 0388c84f8..c77b38f5e 100644
--- a/plugins/channelrx/demodadsb/adsbdemodgui.cpp
+++ b/plugins/channelrx/demodadsb/adsbdemodgui.cpp
@@ -4875,8 +4875,11 @@ ADSBDemodGUI::ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb
     ui->map->rootContext()->setContextProperty("airportModel", &m_airportModel);
     ui->map->rootContext()->setContextProperty("airspaceModel", &m_airspaceModel);
     ui->map->rootContext()->setContextProperty("navAidModel", &m_navAidModel);
+#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
     ui->map->setSource(QUrl(QStringLiteral("qrc:/map/map.qml")));
-
+#else
+    ui->map->setSource(QUrl(QStringLiteral("qrc:/map/map_6.qml")));
+#endif
     connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
 
     m_adsbDemod = reinterpret_cast<ADSBDemod*>(rxChannel); //new ADSBDemod(m_deviceUISet->m_deviceSourceAPI);
diff --git a/plugins/channelrx/demodadsb/map.qrc b/plugins/channelrx/demodadsb/map.qrc
index ca4e46714..26efab54d 100644
--- a/plugins/channelrx/demodadsb/map.qrc
+++ b/plugins/channelrx/demodadsb/map.qrc
@@ -1,6 +1,8 @@
 <RCC>
   <qresource prefix="/">
     <file>map/map.qml</file>
+    <file>map/map_6.qml</file>
+    <file>map/ModifiedMapView.qml</file>
     <file>map/MapStation.qml</file>
     <file>map/aircraft_2engine.png</file>
     <file>map/aircraft_2enginesmall.png</file>
diff --git a/plugins/channelrx/demodadsb/map/ModifiedMapView.qml b/plugins/channelrx/demodadsb/map/ModifiedMapView.qml
new file mode 100644
index 000000000..2aa266940
--- /dev/null
+++ b/plugins/channelrx/demodadsb/map/ModifiedMapView.qml
@@ -0,0 +1,178 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+import QtQuick
+import QtLocation as QL
+import QtPositioning as QP
+import Qt.labs.animation
+/*!
+    \qmltype MapView
+    \inqmlmodule QtLocation
+    \brief An interactive map viewer component.
+
+    MapView wraps a Map and adds the typical interactive features:
+    changing the zoom level, panning and tilting the map.
+
+    The implementation is a QML assembly of smaller building blocks that are
+    available separately. In case you want to make changes in your own version
+    of this component, you can copy the QML, which is installed into the
+    \c qml/QtLocation module directory, and modify it as needed.
+
+    \sa Map
+*/
+Item {
+    /*!
+        \qmlproperty Map MapView::map
+
+        This property provides access to the underlying Map instance.
+    */
+    property alias map: map
+
+    /*!
+        \qmlproperty real minimumZoomLevel
+
+        The minimum zoom level according to the size of the view.
+
+        \sa Map::minimumZoomLevel
+    */
+    property real minimumZoomLevel: map.minimumZoomLevel
+
+    /*!
+        \qmlproperty real maximumZoomLevel
+
+        The maximum valid zoom level for the map.
+
+        \sa Map::maximumZoomLevel
+    */
+    property real maximumZoomLevel: map.maximumZoomLevel
+
+    // --------------------------------
+    // implementation
+    id: root
+    Component.onCompleted: map.resetPinchMinMax()
+
+    QL.Map {
+        id: map
+        width: parent.width
+        height: parent.height
+        tilt: tiltHandler.persistentTranslation.y / -5
+        property bool pinchAdjustingZoom: false
+
+        BoundaryRule on zoomLevel {
+            id: br
+            minimum: map.minimumZoomLevel
+            maximum: map.maximumZoomLevel
+        }
+
+        onZoomLevelChanged: {
+            br.returnToBounds();
+            if (!pinchAdjustingZoom) resetPinchMinMax()
+        }
+
+        function resetPinchMinMax() {
+            pinch.persistentScale = 1
+            pinch.scaleAxis.minimum = Math.pow(2, root.minimumZoomLevel - map.zoomLevel + 1)
+            pinch.scaleAxis.maximum = Math.pow(2, root.maximumZoomLevel - map.zoomLevel - 1)
+        }
+
+        PinchHandler {
+            id: pinch
+            target: null
+            property real rawBearing: 0
+            property QP.geoCoordinate startCentroid
+            onActiveChanged: if (active) {
+                flickAnimation.stop()
+                pinch.startCentroid = map.toCoordinate(pinch.centroid.position, false)
+            } else {
+                flickAnimation.restart(centroid.velocity)
+                map.resetPinchMinMax()
+            }
+            onScaleChanged: (delta) => {
+                map.pinchAdjustingZoom = true
+                map.zoomLevel += Math.log2(delta)
+                map.alignCoordinateToPoint(pinch.startCentroid, pinch.centroid.position)
+                map.pinchAdjustingZoom = false
+            }
+            onRotationChanged: (delta) => {
+                pinch.rawBearing -= delta
+                // snap to 0° if we're close enough
+                map.bearing = (Math.abs(pinch.rawBearing) < 5) ? 0 : pinch.rawBearing
+                map.alignCoordinateToPoint(pinch.startCentroid, pinch.centroid.position)
+            }
+            grabPermissions: PointerHandler.TakeOverForbidden
+        }
+        WheelHandler {
+            id: wheel
+            // workaround for QTBUG-87646 / QTBUG-112394 / QTBUG-112432:
+            // Magic Mouse pretends to be a trackpad but doesn't work with PinchHandler
+            // and we don't yet distinguish mice and trackpads on Wayland either
+            acceptedDevices: Qt.platform.pluginName === "cocoa" || Qt.platform.pluginName === "wayland"
+                             ? PointerDevice.Mouse | PointerDevice.TouchPad
+                             : PointerDevice.Mouse
+            onWheel: (event) => {
+                const loc = map.toCoordinate(wheel.point.position)
+                switch (event.modifiers) {
+                    case Qt.NoModifier:
+                        // jonb - Changed to make more like Qt5
+                        //map.zoomLevel += event.angleDelta.y / 120
+                        map.zoomLevel += event.angleDelta.y / 1000
+                        break
+                    case Qt.ShiftModifier:
+                        map.bearing += event.angleDelta.y / 15
+                        break
+                    case Qt.ControlModifier:
+                        map.tilt += event.angleDelta.y / 15
+                        break
+                }
+                map.alignCoordinateToPoint(loc, wheel.point.position)
+            }
+        }
+        DragHandler {
+            id: drag
+            signal flickStarted // for autotests only
+            signal flickEnded
+            target: null
+            onTranslationChanged: (delta) => map.pan(-delta.x, -delta.y)
+            onActiveChanged: if (active) {
+                flickAnimation.stop()
+            } else {
+                flickAnimation.restart(centroid.velocity)
+            }
+        }
+
+        property vector3d animDest
+        onAnimDestChanged: if (flickAnimation.running) {
+            const delta = Qt.vector2d(animDest.x - flickAnimation.animDestLast.x, animDest.y - flickAnimation.animDestLast.y)
+            map.pan(-delta.x, -delta.y)
+            flickAnimation.animDestLast = animDest
+        }
+
+        Vector3dAnimation on animDest {
+            id: flickAnimation
+            property vector3d animDestLast
+            from: Qt.vector3d(0, 0, 0)
+            duration: 500
+            easing.type: Easing.OutQuad
+            onStarted: drag.flickStarted()
+            onStopped: drag.flickEnded()
+
+            function restart(vel) {
+                stop()
+                map.animDest = Qt.vector3d(0, 0, 0)
+                animDestLast = Qt.vector3d(0, 0, 0)
+                to = Qt.vector3d(vel.x / duration * 100, vel.y / duration * 100, 0)
+                start()
+            }
+        }
+
+        DragHandler {
+            id: tiltHandler
+            minimumPointCount: 2
+            maximumPointCount: 2
+            target: null
+            xAxis.enabled: false
+            grabPermissions: PointerHandler.TakeOverForbidden
+            onActiveChanged: if (active) flickAnimation.stop()
+        }
+    }
+}
diff --git a/plugins/channelrx/demodadsb/map/map_6.qml b/plugins/channelrx/demodadsb/map/map_6.qml
new file mode 100644
index 000000000..259eeb05b
--- /dev/null
+++ b/plugins/channelrx/demodadsb/map/map_6.qml
@@ -0,0 +1,537 @@
+import QtQuick 2.14
+import QtQuick.Window 2.14
+import QtQuick.Controls 2.14
+import QtPositioning 6.5
+import QtLocation 6.5
+import Qt5Compat.GraphicalEffects
+
+Item {
+    id: qmlMap
+    property int aircraftZoomLevel: 11
+    property int aircraftMinZoomLevel: 11
+    property int airportZoomLevel: 11
+    property string mapProvider: "osm"
+    property variant mapPtr
+    property string requestedMapType
+    property bool lightIcons
+    property variant guiPtr
+    property bool smoothing
+
+    function createMap(pluginParameters, requestedMap, gui) {
+        requestedMapType = requestedMap
+        guiPtr = gui
+
+        var paramString = ""
+        for (var prop in pluginParameters) {
+            var parameter = 'PluginParameter { name: "' + prop + '"; value: "' + pluginParameters[prop] + '"}'
+            paramString = paramString + parameter
+        }
+        var pluginString = 'import QtLocation 6.5; Plugin{ name:"' + mapProvider + '"; '  + paramString + '}'
+        var plugin = Qt.createQmlObject (pluginString, qmlMap)
+
+        if (mapPtr) {
+            // Objects aren't destroyed immediately, so don't call findChild("map")
+            mapPtr.destroy()
+            mapPtr = null
+        }
+        mapPtr = actualMapComponent.createObject(page)
+        mapPtr.map.plugin = plugin
+        mapPtr.map.forceActiveFocus()
+        return mapPtr
+    }
+
+    Item {
+        id: page
+        anchors.fill: parent
+    }
+
+    Component {
+        id: actualMapComponent
+
+        ModifiedMapView {
+            id: mapView
+            objectName: "mapView"
+            anchors.fill: parent
+            map.center: QtPositioning.coordinate(51.5, 0.125) // London
+            map.zoomLevel: 10
+            map.objectName: "map"
+
+            // not in 6
+            //gesture.enabled: true
+            //gesture.acceptedGestures: MapGestureArea.PinchGesture | MapGestureArea.PanGesture
+
+            MouseArea {
+                anchors.fill: parent
+                propagateComposedEvents: true
+                onClicked: {
+                    // Unhighlight current aircraft
+                    guiPtr.clearHighlighted()
+                    mouse.accepted = false
+                }
+            }
+
+            MapStation {
+                id: station
+                objectName: "station"
+                stationName: "Home"
+            }
+
+            MapItemView {
+                model: airspaceModel
+                delegate: airspaceComponent
+                parent: mapView.map
+            }
+
+            MapItemView {
+                model: navAidModel
+                delegate: navAidComponent
+                parent: mapView.map
+            }
+
+            MapItemView {
+                model: airspaceModel
+                delegate: airspaceNameComponent
+                parent: mapView.map
+            }
+
+            MapItemView {
+                model: airportModel
+                delegate: airportComponent
+                parent: mapView.map
+            }
+
+            // This needs to be before aircraftComponent MapItemView, so it's drawn underneath
+            MapItemView {
+                model: aircraftModel
+                delegate: aircraftPathComponent
+                parent: mapView.map
+            }
+
+            MapItemView {
+                model: aircraftModel
+                delegate: aircraftComponent
+                parent: mapView.map
+            }
+
+            map.onZoomLevelChanged: {
+                if (map.zoomLevel > aircraftMinZoomLevel) {
+                    aircraftZoomLevel = map.zoomLevel
+                } else {
+                    aircraftZoomLevel = aircraftMinZoomLevel
+                }
+                if (map.zoomLevel > 11) {
+                    station.zoomLevel = map.zoomLevel
+                    airportZoomLevel = map.zoomLevel
+                } else {
+                    station.zoomLevel = 11
+                    airportZoomLevel = 11
+                }
+            }
+
+            map.onSupportedMapTypesChanged : {
+                for (var i = 0; i < map.supportedMapTypes.length; i++) {
+                    if (requestedMapType == map.supportedMapTypes[i].name) {
+                        map.activeMapType = map.supportedMapTypes[i]
+                    }
+                }
+                lightIcons = (requestedMapType == "Night Transit Map") || (requestedMapType == "mapbox://styles/mapbox/dark-v9")
+            }
+
+        }
+    }
+
+    Component {
+        id: navAidComponent
+        MapQuickItem {
+            id: navAid
+            anchorPoint.x: image.width/2
+            anchorPoint.y: image.height/2
+            coordinate: position
+            zoomLevel: airportZoomLevel
+
+            sourceItem: Grid {
+                columns: 1
+                Grid {
+                    horizontalItemAlignment: Grid.AlignHCenter
+                    columnSpacing: 5
+                    layer.enabled: smoothing
+                    layer.smooth: smoothing
+                    Image {
+                        id: image
+                        source: navAidImage
+                        visible: !lightIcons
+                        MouseArea {
+                            anchors.fill: parent
+                            onClicked: (mouse) => {
+                                selected = !selected
+                            }
+                        }
+                    }
+                    ColorOverlay {
+                        cached: true
+                        width: image.width
+                        height: image.height
+                        source: image
+                        color: "#c0ffffff"
+                        visible: lightIcons
+                    }
+                    Rectangle {
+                        id: bubble
+                        color: bubbleColour
+                        border.width: 1
+                        width: text.width + 5
+                        height: text.height + 5
+                        radius: 5
+                        Text {
+                            id: text
+                            anchors.centerIn: parent
+                            text: navAidData
+                        }
+                        MouseArea {
+                            anchors.fill: parent
+                            hoverEnabled: true
+                            onClicked: (mouse) => {
+                                selected = !selected
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    Component {
+        id: airspaceComponent
+        MapPolygon {
+            border.width: 1
+            border.color: airspaceBorderColor
+            color: airspaceFillColor
+            path: airspacePolygon
+        }
+    }
+
+    Component {
+        id: airspaceNameComponent
+        MapQuickItem {
+            coordinate: position
+            anchorPoint.x: airspaceText.width/2
+            anchorPoint.y: airspaceText.height/2
+            zoomLevel: airportZoomLevel
+            sourceItem: Grid {
+                columns: 1
+                Grid {
+                    layer.enabled: smoothing
+                    layer.smooth: smoothing
+                    horizontalItemAlignment: Grid.AlignHCenter
+                    Text {
+                        id: airspaceText
+                        text: details
+                    }
+                }
+            }
+        }
+    }
+
+    Component {
+        id: aircraftPathComponent
+        MapPolyline {
+            line.width: 2
+            line.color: 'gray'
+            path: aircraftPath
+        }
+    }
+
+    Component {
+        id: aircraftComponent
+        MapQuickItem {
+            id: aircraft
+            anchorPoint.x: image.width/2
+            anchorPoint.y: image.height/2
+            coordinate: position
+            zoomLevel: aircraftZoomLevel
+
+            sourceItem: Grid {
+                columns: 1
+                Grid {
+                    layer.enabled: smoothing
+                    layer.smooth: smoothing
+                    horizontalItemAlignment: Grid.AlignHCenter
+                    Image {
+                        id: image
+                        rotation: heading
+                        source: aircraftImage
+                        visible: !lightIcons
+                        MouseArea {
+                            anchors.fill: parent
+                            acceptedButtons: Qt.LeftButton | Qt.RightButton
+                            onClicked: {
+                                if (mouse.button === Qt.LeftButton) {
+                                    highlighted = true
+                                    console.log("z=" + aircraft.sourceItem.z)
+                                    aircraft.sourceItem.z = aircraft.sourceItem.z + 1
+                                } else if (mouse.button === Qt.RightButton) {
+                                    contextMenu.popup()
+                                }
+                            }
+                            onDoubleClicked: {
+                                target = true
+                            }
+                        }
+                    }
+                    ColorOverlay {
+                        cached: true
+                        width: image.width
+                        height: image.height
+                        rotation: heading
+                        source: image
+                        color: "#c0ffffff"
+                        visible: lightIcons
+                        MouseArea {
+                            anchors.fill: parent
+                            onClicked: {
+                                highlighted = true
+                            }
+                            onDoubleClicked: {
+                                target = true
+                            }
+                        }
+                    }
+                    Rectangle {
+                        id: bubble
+                        color: bubbleColour
+                        border.width: 1
+                        width: text.width * 1.1
+                        height: text.height * 1.1
+                        radius: 5
+                        Text {
+                            id: text
+                            anchors.centerIn: parent
+                            text: adsbData
+                            textFormat: TextEdit.RichText
+                        }
+                        MouseArea {
+                            anchors.fill: parent
+                            acceptedButtons: Qt.LeftButton | Qt.RightButton
+                            onClicked: {
+                                if (mouse.button === Qt.LeftButton) {
+                                    showAll = !showAll
+                                } else if (mouse.button === Qt.RightButton) {
+                                    contextMenu.popup()
+                                }
+                            }
+                            Menu {
+                                id: contextMenu
+                                MenuItem {
+                                    text: "Set as target"
+                                    onTriggered: target = true
+                                }
+                                MenuItem {
+                                    text: "Find on feature map"
+                                    onTriggered: aircraftModel.findOnMap(index)
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    Component {
+        id: airportComponent
+        MapItemGroup {
+            MapItemGroup {
+                property var groupVisible: false
+                id: rangeGroup
+                MapCircle {
+                    id: circle5nm
+                    center: position
+                    color: "transparent"
+                    border.color: "gray"
+                    radius: 9260 // 5nm in metres
+                    visible: rangeGroup.groupVisible
+                }
+                MapCircle {
+                    id: circle10nm
+                    center: position
+                    color: "transparent"
+                    border.color: "gray"
+                    radius: 18520
+                    visible: rangeGroup.groupVisible
+                }
+                MapCircle {
+                    id: circle15nm
+                    center: airport.coordinate
+                    color: "transparent"
+                    border.color: "gray"
+                    radius: 27780
+                    visible: rangeGroup.groupVisible
+                }
+                MapQuickItem {
+                    id: text5nm
+                    coordinate {
+                        latitude: position.latitude
+                        longitude: position.longitude + (5/60)/Math.cos(Math.abs(position.latitude)*Math.PI/180)
+                    }
+                    anchorPoint.x: 0
+                    anchorPoint.y: height/2
+                    sourceItem: Text {
+                        color: "grey"
+                        text: "5nm"
+                    }
+                    visible: rangeGroup.groupVisible
+                }
+                MapQuickItem {
+                    id: text10nm
+                    coordinate {
+                        latitude: position.latitude
+                        longitude: position.longitude + (10/60)/Math.cos(Math.abs(position.latitude)*Math.PI/180)
+                    }
+                    anchorPoint.x: 0
+                    anchorPoint.y: height/2
+                    sourceItem: Text {
+                        color: "grey"
+                        text: "10nm"
+                    }
+                    visible: rangeGroup.groupVisible
+                }
+                MapQuickItem {
+                    id: text15nm
+                    coordinate {
+                        latitude: position.latitude
+                        longitude: position.longitude + (15/60)/Math.cos(Math.abs(position.latitude)*Math.PI/180)
+                    }
+                    anchorPoint.x: 0
+                    anchorPoint.y: height/2
+                    sourceItem: Text {
+                        color: "grey"
+                        text: "15nm"
+                    }
+                    visible: rangeGroup.groupVisible
+                }
+            }
+
+            MapQuickItem {
+                id: airport
+                anchorPoint.x: image.width/2
+                anchorPoint.y: image.height/2
+                coordinate: position
+                zoomLevel: airportZoomLevel
+                sourceItem: Grid {
+                    columns: 1
+                    Grid {
+                        horizontalItemAlignment: Grid.AlignHCenter
+                        layer.enabled: smoothing
+                        layer.smooth: smoothing
+                        Image {
+                            id: image
+                            source: airportImage
+                            visible: !lightIcons
+                            MouseArea {
+                                anchors.fill: parent
+                                acceptedButtons: Qt.LeftButton | Qt.RightButton
+                                onClicked: (mouse) => {
+                                console.log("AIRPORT CLICKED ************************* ");
+                                    if (mouse.button === Qt.RightButton) {
+                                        showRangeItem.visible = !rangeGroup.groupVisible
+                                        hideRangeItem.visible = rangeGroup.groupVisible
+                                        menuItems.clear()
+                                        var scanners = airportModel.getFreqScanners()
+                                        for (var i = 0; i < scanners.length; i++) {
+                                            menuItems.append({
+                                                text: "Send to Frequency Scanner " + scanners[i],
+                                                airport: index,
+                                                scanner: scanners[i]
+                                            })
+                                        }
+                                        contextMenu.popup()
+                                    }
+                                }
+                                onDoubleClicked: (mouse) => {
+                                    rangeGroup.groupVisible = !rangeGroup.groupVisible
+                                }
+
+                                ListModel {
+                                    id: menuItems
+                                }
+
+                                Menu {
+                                    id: contextMenu
+                                    MenuItem {
+                                        id: showRangeItem
+                                        text: "Show range rings"
+                                        onTriggered: rangeGroup.groupVisible = true
+                                        height: visible ? implicitHeight : 0
+                                    }
+                                    MenuItem {
+                                        id: hideRangeItem
+                                        text: "Hide range rings"
+                                        onTriggered: rangeGroup.groupVisible = false
+                                        height: visible ? implicitHeight : 0
+                                    }
+                                    Instantiator {
+                                        model: menuItems
+                                        MenuItem {
+                                            text: model.text
+                                            onTriggered: airportModel.sendToFreqScanner(model.airport, model.scanner)
+                                        }
+                                        onObjectAdded: function(index, object) {
+                                            contextMenu.insertItem(index, object)
+                                        }
+                                        onObjectRemoved: function(index, object) {
+                                            contextMenu.removeItem(object)
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                        ColorOverlay {
+                            cached: true
+                            width: image.width
+                            height: image.height
+                            source: image
+                            color: "#c0ffffff"
+                            visible: lightIcons
+                        }
+                        Rectangle {
+                            id: bubble
+                            color: bubbleColour
+                            border.width: 1
+                            width: text.width + 5
+                            height: text.height + 5
+                            radius: 5
+                            Text {
+                                id: text
+                                anchors.centerIn: parent
+                                text: airportData
+                            }
+                            MouseArea {
+                                anchors.fill: parent
+                                onClicked: (mouse) => {
+                                console.log("AIRPORT 2 CLICKED ************************* ");
+                                    if (showFreq) {
+                                        var freqIdx = Math.floor((mouse.y-5)/((height-10)/airportDataRows))
+                                        if (freqIdx == 0) {
+                                            showFreq = false
+                                        }
+                                    } else {
+                                        showFreq = true
+                                    }
+                                }
+                                onDoubleClicked: (mouse) => {
+                                    if (showFreq) {
+                                        var freqIdx = Math.floor((mouse.y-5)/((height-10)/airportDataRows))
+                                        if (freqIdx != 0) {
+                                            selectedFreq = freqIdx - 1
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+}