From 5567c708e7bfaf16fda75b9692c045b704dccf1e Mon Sep 17 00:00:00 2001 From: Jon Beniston Date: Sun, 6 Aug 2023 09:08:53 +0100 Subject: [PATCH] Support Map plugin for Qt 6.5. Some Qt 6.6 fixes. --- CMakeLists.txt | 5 +- app/main.cpp | 11 + plugins/feature/map/CMakeLists.txt | 8 +- plugins/feature/map/map.qrc | 3 +- plugins/feature/map/map/ModifiedMapView.qml | 178 +++++++++ plugins/feature/map/map/map_6.qml | 413 ++++++++++++++++++++ plugins/feature/map/mapgui.cpp | 51 ++- plugins/feature/map/mapgui.h | 10 + plugins/feature/map/mapitem.cpp | 2 + plugins/feature/map/mapplugin.cpp | 2 +- plugins/feature/map/mapsettings.cpp | 4 +- plugins/feature/map/mapsettingsdialog.cpp | 8 + plugins/feature/map/osmtemplateserver.h | 3 +- plugins/feature/map/webserver.cpp | 3 +- sdrbase/util/osndb.cpp | 4 + 15 files changed, 690 insertions(+), 15 deletions(-) create mode 100644 plugins/feature/map/map/ModifiedMapView.qml create mode 100644 plugins/feature/map/map/map_6.qml diff --git a/CMakeLists.txt b/CMakeLists.txt index bc067f58d..1ed7dc6c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -593,7 +593,6 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) # Qt requirements # See: https://doc-snapshots.qt.io/qt6-dev/cmake-qt5-and-qt6-compatibility.html if(ENABLE_QT6) - # Qt6 doesn't currently support Location find_package(Qt6 COMPONENTS Core @@ -657,7 +656,8 @@ if (BUILD_GUI) WebEngineQuick WebEngineCore WebEngineWidgets - TextToSpeech) + TextToSpeech + Location) else() find_package(Qt5 REQUIRED COMPONENTS @@ -947,6 +947,7 @@ if (BUILD_GUI) ${OPENGL_LIBRARIES} Qt::Widgets Qt::Multimedia + Qt::Quick sdrbase sdrgui logging diff --git a/app/main.cpp b/app/main.cpp index 5c433ea3c..9e3d21ac5 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -29,6 +29,9 @@ #ifdef ANDROID #include "util/android.h" #endif +#if (QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)) +#include +#endif #include "loggerwithfile.h" #include "mainwindow.h" @@ -52,6 +55,14 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) && (QT_VERSION <= QT_VERSION_CHECK(6, 0, 0)) QApplication::setAttribute(Qt::AA_DisableWindowContextHelpButton); #endif +#if (QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)) + // Only use OpenGL, to easily combine QOpenGLWidget, QQuickWidget and QWebEngine + // in a single window + // See https://www.qt.io/blog/qt-quick-and-widgets-qt-6.4-edition + // This prevents Direct3D/Vulcan being used on Windows/Mac though for QQuickWidget + // and QWebEngine, so possibly should be reviewed in the future + QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL); +#endif #ifndef ANDROID QApplication::setAttribute(Qt::AA_DontUseNativeDialogs); // Don't use on Android, otherwise we can't access files on internal storage #endif diff --git a/plugins/feature/map/CMakeLists.txt b/plugins/feature/map/CMakeLists.txt index 6e610c9e5..a4ffd64c8 100644 --- a/plugins/feature/map/CMakeLists.txt +++ b/plugins/feature/map/CMakeLists.txt @@ -22,11 +22,13 @@ set(map_HEADERS webserver.h ) -if(Qt${QT_DEFAULT_MAJOR_VERSION}WebEngine_FOUND) +# WebEngine on Qt5, WebEngineCore on Qt6 +if(Qt${QT_DEFAULT_MAJOR_VERSION}WebEngine_FOUND OR Qt${QT_DEFAULT_MAJOR_VERSION}WebEngineCore_FOUND) add_compile_definitions(QT_WEBENGINE_FOUND) configure_file(mapguiwebengine.ui mapgui.ui) else() configure_file(mapguinowebengine.ui mapgui.ui) + message(STATUS "Not building 3D map (Qt${QT_DEFAULT_MAJOR_VERSION}WebEngine_FOUND=${Qt${QT_DEFAULT_MAJOR_VERSION}WebEngine_FOUND} Qt${QT_DEFAULT_MAJOR_VERSION}WebEngineCore_FOUND=${Qt${QT_DEFAULT_MAJOR_VERSION}WebEngineCore_FOUND})") endif() set(CMAKE_AUTOUIC_SEARCH_PATHS ${CMAKE_CURRENT_BINARY_DIR}) @@ -84,7 +86,9 @@ if(NOT SERVER_MODE) set(TARGET_LIB_GUI "sdrgui") set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR}) - if(Qt${QT_DEFAULT_MAJOR_VERSION}WebEngine_FOUND) + if(Qt${QT_DEFAULT_MAJOR_VERSION}WebEngineCore_FOUND) + set(TARGET_LIB "Qt::Widgets" Qt::Quick Qt::QuickWidgets Qt::Positioning Qt::Location Qt::WebEngineCore Qt::WebEngineWidgets) + elseif(Qt${QT_DEFAULT_MAJOR_VERSION}WebEngine_FOUND) set(TARGET_LIB "Qt::Widgets" Qt::Quick Qt::QuickWidgets Qt::Positioning Qt::Location Qt::WebEngine Qt::WebEngineCore Qt::WebEngineWidgets) else() set(TARGET_LIB "Qt::Widgets" Qt::Quick Qt::QuickWidgets Qt::Positioning Qt::Location) diff --git a/plugins/feature/map/map.qrc b/plugins/feature/map/map.qrc index b8a82c70f..ef95138e4 100644 --- a/plugins/feature/map/map.qrc +++ b/plugins/feature/map/map.qrc @@ -1,7 +1,8 @@ map/map.qml - map/map_5_12.qml + map/map_6.qml + map/ModifiedMapView.qml map/antenna.png map/antennatime.png map/antennadab.png diff --git a/plugins/feature/map/map/ModifiedMapView.qml b/plugins/feature/map/map/ModifiedMapView.qml new file mode 100644 index 000000000..2aa266940 --- /dev/null +++ b/plugins/feature/map/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/feature/map/map/map_6.qml b/plugins/feature/map/map/map_6.qml new file mode 100644 index 000000000..897b93657 --- /dev/null +++ b/plugins/feature/map/map/map_6.qml @@ -0,0 +1,413 @@ +import QtQuick 2.14 +import QtQuick.Window 2.14 +import QtQuick.Controls 2.14 +import QtPositioning 6.5 +import QtLocation 6.5 + +Item { + id: qmlMap + property int mapZoomLevel: 11 + property string mapProvider: "osm" + property variant mapPtr + property variant guiPtr + property bool smoothing + + function createMap(pluginParameters, gui) { + 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 + } + + function getMapTypes() { + var mapTypes = [] + if (mapPtr) { + for (var i = 0; i < mapPtr.map.supportedMapTypes.length; i++) { + mapTypes[i] = mapPtr.map.supportedMapTypes[i].name + } + } + return mapTypes + } + + function setMapType(mapTypeIndex) { + if (mapPtr && (mapTypeIndex < mapPtr.map.supportedMapTypes.length)) { + if (mapPtr.map.supportedMapTypes[mapTypeIndex] !== undefined) { + mapPtr.map.activeMapType = mapPtr.map.supportedMapTypes[mapTypeIndex] + } + } + } + + 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 + + MapItemView { + model: imageModelFiltered + delegate: imageComponent + parent: mapView.map + } + + MapItemView { + model: polygonModelFiltered + delegate: polygonComponent + parent: mapView.map + } + + MapItemView { + model: polygonModelFiltered + delegate: polygonNameComponent + parent: mapView.map + } + + MapItemView { + model: polylineModelFiltered + delegate: polylineComponent + parent: mapView.map + } + + MapItemView { + model: polylineModelFiltered + delegate: polylineNameComponent + parent: mapView.map + } + + // Tracks first, so drawn under other items + MapItemView { + model: mapModelFiltered + delegate: groundTrack1Component + parent: mapView.map + } + + MapItemView { + model: mapModelFiltered + delegate: groundTrack2Component + parent: mapView.map + } + + MapItemView { + model: mapModelFiltered + delegate: predictedGroundTrack1Component + parent: mapView.map + } + + MapItemView { + model: mapModelFiltered + delegate: predictedGroundTrack2Component + parent: mapView.map + } + + MapItemView { + model: mapModelFiltered + delegate: mapComponent + parent: mapView.map + } + + map.onZoomLevelChanged: { + mapZoomLevel = map.zoomLevel + mapModelFiltered.viewChanged(map.visibleRegion.boundingGeoRectangle().topLeft.longitude, map.visibleRegion.boundingGeoRectangle().topLeft.latitude, map.visibleRegion.boundingGeoRectangle().bottomRight.longitude, map.visibleRegion.boundingGeoRectangle().bottomRight.latitude, map.zoomLevel); + imageModelFiltered.viewChanged(map.visibleRegion.boundingGeoRectangle().topLeft.longitude, map.visibleRegion.boundingGeoRectangle().topLeft.latitude, map.visibleRegion.boundingGeoRectangle().bottomRight.longitude, map.visibleRegion.boundingGeoRectangle().bottomRight.latitude, map.zoomLevel); + polygonModelFiltered.viewChanged(map.visibleRegion.boundingGeoRectangle().topLeft.longitude, map.visibleRegion.boundingGeoRectangle().topLeft.latitude, map.visibleRegion.boundingGeoRectangle().bottomRight.longitude, map.visibleRegion.boundingGeoRectangle().bottomRight.latitude, map.zoomLevel); + polylineModelFiltered.viewChanged(map.visibleRegion.boundingGeoRectangle().topLeft.longitude, map.visibleRegion.boundingGeoRectangle().topLeft.latitude, map.visibleRegion.boundingGeoRectangle().bottomRight.longitude, map.visibleRegion.boundingGeoRectangle().bottomRight.latitude, map.zoomLevel); + } + + // The map displays MapPolyLines in the wrong place (+360 degrees) if + // they start to the left of the visible region, so we need to + // split them so they don't, each time the visible region is changed. meh. + map.onCenterChanged: { + polylineModelFiltered.viewChanged(map.visibleRegion.boundingGeoRectangle().topLeft.longitude, map.visibleRegion.boundingGeoRectangle().topLeft.latitude, map.visibleRegion.boundingGeoRectangle().bottomRight.longitude, map.visibleRegion.boundingGeoRectangle().bottomRight.latitude, map.zoomLevel); + polygonModelFiltered.viewChanged(map.visibleRegion.boundingGeoRectangle().topLeft.longitude, map.visibleRegion.boundingGeoRectangle().topLeft.latitude, map.visibleRegion.boundingGeoRectangle().bottomRight.longitude, map.visibleRegion.boundingGeoRectangle().bottomRight.latitude, map.zoomLevel); + imageModelFiltered.viewChanged(map.visibleRegion.boundingGeoRectangle().topLeft.longitude, map.visibleRegion.boundingGeoRectangle().topLeft.latitude, map.visibleRegion.boundingGeoRectangle().bottomRight.longitude, map.visibleRegion.boundingGeoRectangle().bottomRight.latitude, map.zoomLevel); + mapModelFiltered.viewChanged(map.visibleRegion.boundingGeoRectangle().topLeft.longitude, map.visibleRegion.boundingGeoRectangle().topLeft.latitude, map.visibleRegion.boundingGeoRectangle().bottomRight.longitude, map.visibleRegion.boundingGeoRectangle().bottomRight.latitude, map.zoomLevel); + mapModel.viewChanged(map.visibleRegion.boundingGeoRectangle().bottomLeft.longitude, map.visibleRegion.boundingGeoRectangle().bottomRight.longitude); + } + + map.onSupportedMapTypesChanged : { + guiPtr.supportedMapsChanged() + } + + } + } + + function mapRect() { + if (mapPtr) + return mapPtr.map.visibleRegion.boundingGeoRectangle(); + else + return null; + } + + Component { + id: imageComponent + MapQuickItem { + coordinate: position + anchorPoint.x: imageId.width/2 + anchorPoint.y: imageId.height/2 + zoomLevel: imageZoomLevel + sourceItem: Image { + id: imageId + source: imageData + } + autoFadeIn: false + } + } + + Component { + id: polygonComponent + MapPolygon { + border.width: 1 + border.color: borderColor + color: fillColor + path: polygon + autoFadeIn: false + } + } + + Component { + id: polygonNameComponent + MapQuickItem { + coordinate: position + anchorPoint.x: polygonText.width/2 + anchorPoint.y: polygonText.height/2 + zoomLevel: mapZoomLevel > 11 ? mapZoomLevel : 11 + sourceItem: Grid { + columns: 1 + Grid { + layer.enabled: smoothing + layer.smooth: smoothing + horizontalItemAlignment: Grid.AlignHCenter + Text { + id: polygonText + text: label + textFormat: TextEdit.RichText + } + } + } + } + } + + Component { + id: polylineComponent + MapPolyline { + line.width: 1 + line.color: lineColor + path: coordinates + autoFadeIn: false + } + } + + Component { + id: polylineNameComponent + MapQuickItem { + coordinate: position + anchorPoint.x: polylineText.width/2 + anchorPoint.y: polylineText.height/2 + zoomLevel: mapZoomLevel > 11 ? mapZoomLevel : 11 + sourceItem: Grid { + columns: 1 + Grid { + layer.enabled: smoothing + layer.smooth: smoothing + horizontalItemAlignment: Grid.AlignHCenter + Text { + id: polylineText + text: label + textFormat: TextEdit.RichText + } + } + } + } + } + + Component { + id: mapComponent + MapQuickItem { + id: mapElement + anchorPoint.x: image.width/2 + anchorPoint.y: image.height/2 + coordinate: position + // when zooming, mapImageMinZoom can be temporarily undefined. Not sure why + zoomLevel: (typeof mapImageMinZoom !== 'undefined') ? (mapZoomLevel > mapImageMinZoom ? mapZoomLevel : mapImageMinZoom) : zoomLevel + autoFadeIn: false // not in 5.12 + + sourceItem: Grid { + id: gridItem + columns: 1 + Grid { + horizontalItemAlignment: Grid.AlignHCenter + columnSpacing: 5 + layer.enabled: smoothing + layer.smooth: smoothing + Image { + id: image + rotation: mapImageRotation + source: mapImage + visible: mapImageVisible + MouseArea { + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: { + if (mouse.button === Qt.LeftButton) { + selected = !selected + if (selected) { + mapModel.moveToFront(mapModelFiltered.mapRowToSource(index)) + } + } else if (mouse.button === Qt.RightButton) { + if (frequency > 0) { + freqMenuItem.text = "Set frequency to " + frequencyString + freqMenuItem.enabled = true + } else { + freqMenuItem.text = "No frequency available" + freqMenuItem.enabled = false + } + var c = mapPtr.map.toCoordinate(Qt.point(mouse.x, mouse.y)) + coordsMenuItem.text = "Coords: " + c.latitude.toFixed(6) + ", " + c.longitude.toFixed(6) + contextMenu.popup() + } + } + } + } + Rectangle { + id: bubble + color: bubbleColour + border.width: 1 + width: text.width + 5 + height: text.height + 5 + radius: 5 + visible: mapTextVisible + Text { + id: text + anchors.centerIn: parent + text: mapText + textFormat: TextEdit.RichText + } + MouseArea { + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: { + if (mouse.button === Qt.LeftButton) { + selected = !selected + if (selected) { + mapModel.moveToFront(mapModelFiltered.mapRowToSource(index)) + } + } else if (mouse.button === Qt.RightButton) { + if (frequency > 0) { + freqMenuItem.text = "Set frequency to " + frequencyString + freqMenuItem.enabled = true + } else { + freqMenuItem.text = "No frequency available" + freqMenuItem.enabled = false + } + var c = mapPtr.map.toCoordinate(Qt.point(mouse.x, mouse.y)) + coordsMenuItem.text = "Coords: " + c.latitude.toFixed(6) + ", " + c.longitude.toFixed(6) + contextMenu.popup() + } + } + Menu { + id: contextMenu + MenuItem { + text: "Set as target" + onTriggered: target = true + } + MenuItem { + id: freqMenuItem + text: "Not set" + onTriggered: mapModel.setFrequency(frequency) + } + MenuItem { + text: "Move to front" + onTriggered: mapModel.moveToFront(mapModelFiltered.mapRowToSource(index)) + } + MenuItem { + text: "Move to back" + onTriggered: mapModel.moveToBack(mapModelFiltered.mapRowToSource(index)) + } + MenuItem { + text: "Track on 3D map" + onTriggered: mapModel.track3D(mapModelFiltered.mapRowToSource(index)) + } + MenuItem { + id: coordsMenuItem + text: "" + } + } + } + } + } + } + } + } + + Component { + id: predictedGroundTrack1Component + MapPolyline { + line.width: 2 + line.color: predictedGroundTrackColor + path: predictedGroundTrack1 + autoFadeIn: false + } + } + + // Part of the line that crosses edge of map + Component { + id: predictedGroundTrack2Component + MapPolyline { + line.width: 2 + line.color: predictedGroundTrackColor + path: predictedGroundTrack2 + autoFadeIn: false + } + } + + Component { + id: groundTrack1Component + MapPolyline { + line.width: 2 + line.color: groundTrackColor + path: groundTrack1 + autoFadeIn: false + } + } + + // Part of the line that crosses edge of map + Component { + id: groundTrack2Component + MapPolyline { + line.width: 2 + line.color: groundTrackColor + path: groundTrack2 + autoFadeIn: false + } + } + +} diff --git a/plugins/feature/map/mapgui.cpp b/plugins/feature/map/mapgui.cpp index 500bf3a81..06327423a 100644 --- a/plugins/feature/map/mapgui.cpp +++ b/plugins/feature/map/mapgui.cpp @@ -27,8 +27,8 @@ #ifdef QT_WEBENGINE_FOUND #include -#include -#include +#include +#include #endif #include "feature/featureuiset.h" @@ -235,7 +235,12 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur ui->map->rootContext()->setContextProperty("imageModelFiltered", &m_imageMapFilter); ui->map->rootContext()->setContextProperty("polygonModelFiltered", &m_polygonMapFilter); ui->map->rootContext()->setContextProperty("polylineModelFiltered", &m_polylineMapFilter); + connect(ui->map, &QQuickWidget::statusChanged, this, &MapGUI::statusChanged); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) ui->map->setSource(QUrl(QStringLiteral("qrc:/map/map/map.qml"))); +#else + ui->map->setSource(QUrl(QStringLiteral("qrc:/map/map/map_6.qml"))); +#endif m_settings.m_modelURL = QString("http://127.0.0.1:%1/3d/").arg(m_webPort); m_webServer->addPathSubstitution("3d", m_settings.m_modelDir); @@ -252,6 +257,10 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur QWebEngineSettings *settings = ui->web->settings(); settings->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true); connect(ui->web->page(), &QWebEnginePage::fullScreenRequested, this, &MapGUI::fullScreenRequested); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + connect(ui->web->page(), &QWebEnginePage::loadingChanged, this, &MapGUI::loadingChanged); + connect(ui->web, &QWebEngineView::renderProcessTerminated, this, &MapGUI::renderProcessTerminated); +#endif #endif // Get station position @@ -1103,7 +1112,7 @@ void MapGUI::clearOSMCache() { QFile file(dir.filePath(filename)); if (!file.remove()) { - qDebug() << "MapGUI::clearOSMCache: Failed to remove " << file; + qDebug() << "MapGUI::clearOSMCache: Failed to remove " << file.fileName(); } } } @@ -1126,7 +1135,7 @@ void MapGUI::clearWikiMediaOSMCache() { QFile file(dir.filePath(filename)); if (!file.remove()) { - qDebug() << "MapGUI::clearWikiMediaOSMCache: Failed to remove " << file; + qDebug() << "MapGUI::clearWikiMediaOSMCache: Failed to remove " << file.fileName(); } } } @@ -1213,7 +1222,6 @@ void MapGUI::applyMap2DSettings(bool reloadMap) qCritical() << "MapGUI::applyMap2DSettings - Failed to invoke createMap"; } QObject *newMap = retVal.value(); - // Restore position of map if (newMap != nullptr) { @@ -1399,6 +1407,39 @@ void MapGUI::applyMap3DSettings(bool reloadMap) #endif } +void MapGUI::statusChanged(QQuickWidget::Status status) +{ + // In Qt6, it seems a page can be loaded multiple times, and this slot is too + // This causes a problem in that the map created by the call to createMap can + // be lost, so we recreate it here each time + if (status == QQuickWidget::Ready) { + applyMap2DSettings(true); + } +} + +#ifdef QT_WEBENGINE_FOUND + +void MapGUI::renderProcessTerminated(QWebEnginePage::RenderProcessTerminationStatus terminationStatus, int exitCode) +{ + qDebug() << "MapGUI::renderProcessTerminated: " << terminationStatus << "exitCode" << exitCode; +} + +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + +void MapGUI::loadingChanged(const QWebEngineLoadingInfo &loadingInfo) +{ + if (loadingInfo.status() == QWebEngineLoadingInfo::LoadFailedStatus) + { + qDebug() << "MapGUI::loadingChanged: Failed to load " << loadingInfo.url().toString() + << "errorString: " << loadingInfo.errorString() << " " + << "errorDomain:" << loadingInfo.errorDomain() + << "errorCode:" << loadingInfo.errorCode() + ; + } +} +#endif +#endif + void MapGUI::init3DMap() { #ifdef QT_WEBENGINE_FOUND diff --git a/plugins/feature/map/mapgui.h b/plugins/feature/map/mapgui.h index 2834f4993..235f68b09 100644 --- a/plugins/feature/map/mapgui.h +++ b/plugins/feature/map/mapgui.h @@ -21,9 +21,14 @@ #include #include +#include #include #ifdef QT_WEBENGINE_FOUND #include +#include +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) +#include +#endif #endif #include @@ -255,7 +260,12 @@ private slots: virtual bool eventFilter(QObject *obj, QEvent *event) override; #ifdef QT_WEBENGINE_FOUND void fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest); + void renderProcessTerminated(QWebEnginePage::RenderProcessTerminationStatus terminationStatus, int exitCode); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + void loadingChanged(const QWebEngineLoadingInfo &loadingInfo); #endif +#endif + void statusChanged(QQuickWidget::Status status); void preferenceChanged(int elementType); void giroDataUpdated(const GIRO::GIROStationData& data); void mufUpdated(const QJsonDocument& document); diff --git a/plugins/feature/map/mapitem.cpp b/plugins/feature/map/mapitem.cpp index ba96454cb..10108f2e1 100644 --- a/plugins/feature/map/mapitem.cpp +++ b/plugins/feature/map/mapitem.cpp @@ -15,6 +15,8 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include + #include "mapitem.h" MapItem::MapItem(const QObject *sourcePipe, const QString &group, MapSettings::MapItemSettings *itemSettings, SWGSDRangel::SWGMapItem *mapItem) : diff --git a/plugins/feature/map/mapplugin.cpp b/plugins/feature/map/mapplugin.cpp index c4b2597ea..cc22a636a 100644 --- a/plugins/feature/map/mapplugin.cpp +++ b/plugins/feature/map/mapplugin.cpp @@ -30,7 +30,7 @@ const PluginDescriptor MapPlugin::m_pluginDescriptor = { Map::m_featureId, QStringLiteral("Map"), - QStringLiteral("7.12.0"), + QStringLiteral("7.15.3"), QStringLiteral("(c) Jon Beniston, M7RCE"), QStringLiteral("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/feature/map/mapsettings.cpp b/plugins/feature/map/mapsettings.cpp index 1848fdc7b..719bbdc09 100644 --- a/plugins/feature/map/mapsettings.cpp +++ b/plugins/feature/map/mapsettings.cpp @@ -152,7 +152,7 @@ MapSettings::~MapSettings() void MapSettings::resetToDefaults() { m_displayNames = true; -#ifdef LINUX +#if defined(LINUX) && (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) m_mapProvider = "mapboxgl"; // osm maps do not work in some versions of Linux https://github.com/f4exb/sdrangel/issues/1169 & 1369 #else m_mapProvider = "osm"; @@ -252,7 +252,7 @@ bool MapSettings::deserialize(const QByteArray& data) d.readBool(1, &m_displayNames, true); d.readString(2, &m_mapProvider, "osm"); -#ifdef LINUX +#if defined(LINUX) && (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) if (m_mapProvider == "osm") { m_mapProvider = "mapboxgl"; } diff --git a/plugins/feature/map/mapsettingsdialog.cpp b/plugins/feature/map/mapsettingsdialog.cpp index cec8ae03d..8a22a0a25 100644 --- a/plugins/feature/map/mapsettingsdialog.cpp +++ b/plugins/feature/map/mapsettingsdialog.cpp @@ -21,7 +21,11 @@ #include #include +#if (QT_VERSION < QT_VERSION_CHECK(6, 6, 0)) #include +#else +#include +#endif #include "util/units.h" @@ -113,6 +117,10 @@ MapSettingsDialog::MapSettingsDialog(MapSettings *settings, QWidget* parent) : ui(new Ui::MapSettingsDialog) { ui->setupUi(this); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + ui->mapProvider->clear(); + ui->mapProvider->addItem("OpenStreetMap"); +#endif ui->mapProvider->setCurrentIndex(MapSettings::m_mapProviders.indexOf(settings->m_mapProvider)); ui->thunderforestAPIKey->setText(settings->m_thunderforestAPIKey); ui->maptilerAPIKey->setText(settings->m_maptilerAPIKey); diff --git a/plugins/feature/map/osmtemplateserver.h b/plugins/feature/map/osmtemplateserver.h index 105aad1fe..211868bea 100644 --- a/plugins/feature/map/osmtemplateserver.h +++ b/plugins/feature/map/osmtemplateserver.h @@ -20,6 +20,7 @@ #include #include +#include #include class OSMTemplateServer : public QTcpServer @@ -61,7 +62,7 @@ private slots: { QString line = socket->readLine(); qDebug() << "HTTP Request: " << line; - QStringList tokens = QString(line).split(QRegExp("[ \r\n][ \r\n]*")); + QStringList tokens = QString(line).split(QRegularExpression("[ \r\n][ \r\n]*")); if (tokens[0] == "GET") { bool hires = tokens[1].contains("hires"); diff --git a/plugins/feature/map/webserver.cpp b/plugins/feature/map/webserver.cpp index c0e336b89..38cf71037 100644 --- a/plugins/feature/map/webserver.cpp +++ b/plugins/feature/map/webserver.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include "webserver.h" @@ -126,7 +127,7 @@ void WebServer::readClient() QString line = socket->readLine(); //qDebug() << "WebServer HTTP Request: " << line; - QStringList tokens = QString(line).split(QRegExp("[ \r\n][ \r\n]*")); + QStringList tokens = QString(line).split(QRegularExpression("[ \r\n][ \r\n]*")); if (tokens[0] == "GET") { // Get file type from extension diff --git a/sdrbase/util/osndb.cpp b/sdrbase/util/osndb.cpp index 0b556cda9..e1f03d913 100644 --- a/sdrbase/util/osndb.cpp +++ b/sdrbase/util/osndb.cpp @@ -18,7 +18,11 @@ #include #include +#if (QT_VERSION < QT_VERSION_CHECK(6, 6, 0)) #include +#else +#include +#endif #include "util/osndb.h"