diff --git a/.gitignore b/.gitignore index 99ce770b8..1392cb6bd 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ build-android-release/ build-android-debug/ build-linux/ build-macos/ +.project # Doxygen generated documentation doc/APIdoc diff --git a/3rdParty/enrouteText b/3rdParty/enrouteText index 60dbc81a7..36f84448d 160000 --- a/3rdParty/enrouteText +++ b/3rdParty/enrouteText @@ -1 +1 @@ -Subproject commit 60dbc81a7115485c09f885312e6456dffb213ccd +Subproject commit 36f84448d9737f67333a94d6e67abb7e41cb4070 diff --git a/CMakeLists.txt b/CMakeLists.txt index 08baf7945..a18eac47d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,7 @@ option(QTDEPLOY "Generate and run Qt deployment scripts" OFF) # Project data # -project(enroute VERSION 2.31.10) +project(enroute VERSION 2.31.11) set(APP_ID de.akaflieg_freiburg.enroute) set(DISPLAY_NAME "Enroute") math(EXPR PROJECT_VERSION_CODE 10000*${PROJECT_VERSION_MAJOR}+100*${PROJECT_VERSION_MINOR}+${PROJECT_VERSION_PATCH}) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c96dc10cd..e531d7dae 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -69,9 +69,9 @@ set(SOURCES navigation/Leg.h navigation/Navigator.h navigation/RemainingRouteInfo.h - notam/Notam.h - notam/NotamList.h - notam/NotamProvider.h + notam/NOTAM.h + notam/NOTAMList.h + notam/NOTAMProvider.h notification/Notification.h notification/Notification_DataUpdateAvailable.h notification/NotificationManager.h @@ -163,9 +163,9 @@ set(SOURCES navigation/Leg.cpp navigation/Navigator.cpp navigation/RemainingRouteInfo.cpp - notam/Notam.cpp - notam/NotamList.cpp - notam/NotamProvider.cpp + notam/NOTAM.cpp + notam/NOTAMList.cpp + notam/NOTAMProvider.cpp notification/Notification.cpp notification/Notification_DataUpdateAvailable.cpp notification/NotificationManager.cpp @@ -538,7 +538,6 @@ qt_add_qml_module(${PROJECT_NAME} qml/dialogs/AircraftSaveDialog.qml qml/dialogs/ErrorDialog.qml qml/dialogs/FirstRunDialog.qml - qml/dialogs/FlightRouteAddWPDialog.qml qml/dialogs/FlightRouteSaveDialog.qml qml/dialogs/LongTextDialogMD.qml qml/dialogs/NotamListDialog.qml diff --git a/src/DemoRunner.cpp b/src/DemoRunner.cpp index bbac4823d..30cc286f9 100644 --- a/src/DemoRunner.cpp +++ b/src/DemoRunner.cpp @@ -89,7 +89,7 @@ void DemoRunner::generateGooglePlayScreenshots() generateScreenshotsForDevices({"phone", "sevenInch", "tenInch"}, false); } -void DemoRunner::generateScreenshotsForDevices(QStringList devices, bool manual) +void DemoRunner::generateScreenshotsForDevices(const QStringList &devices, bool manual) { #if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) || defined(Q_OS_IOS) Q_ASSERT(m_engine != nullptr); diff --git a/src/DemoRunner.h b/src/DemoRunner.h index 4148b0a4d..d61723cb5 100644 --- a/src/DemoRunner.h +++ b/src/DemoRunner.h @@ -111,7 +111,7 @@ public slots: QPointer m_engine; - void generateScreenshotsForDevices(QStringList, bool); + void generateScreenshotsForDevices(const QStringList &, bool); static void saveScreenshot(bool, QQuickWindow *, const QString&); }; diff --git a/src/GlobalObject.cpp b/src/GlobalObject.cpp index b918950fa..29211d41b 100644 --- a/src/GlobalObject.cpp +++ b/src/GlobalObject.cpp @@ -33,7 +33,7 @@ #include "geomaps/WaypointLibrary.h" #include "navigation/Clock.h" #include "navigation/Navigator.h" -#include "notam/NotamProvider.h" +#include "notam/NOTAMProvider.h" #include "notification/NotificationManager.h" #include "platform/FileExchange.h" #include "platform/PlatformAdaptor.h" @@ -56,7 +56,7 @@ QPointer g_geoMapProvider {}; QPointer g_librarian {}; QPointer g_platformAdaptor {}; QPointer g_navigator {}; -QPointer g_notamProvider {}; +QPointer g_notamProvider {}; QPointer g_networkAccessManager {}; QPointer g_notificationManager {}; QPointer g_passwordDB {}; @@ -203,9 +203,9 @@ auto GlobalObject::networkAccessManager() -> QNetworkAccessManager* } -auto GlobalObject::notamProvider() -> NOTAM::NotamProvider* +auto GlobalObject::notamProvider() -> NOTAM::NOTAMProvider* { - return allocateInternal(g_notamProvider); + return allocateInternal(g_notamProvider); } diff --git a/src/GlobalObject.h b/src/GlobalObject.h index eb5e24932..5407f4a4b 100644 --- a/src/GlobalObject.h +++ b/src/GlobalObject.h @@ -48,7 +48,7 @@ class Navigator; namespace NOTAM { -class NotamProvider; +class NOTAMProvider; } // namespace NOTAM namespace Notifications @@ -221,7 +221,7 @@ class GlobalObject : public QObject * * @returns Pointer to appplication-wide static instance. */ - Q_INVOKABLE static NOTAM::NotamProvider* notamProvider(); + Q_INVOKABLE static NOTAM::NOTAMProvider* notamProvider(); /*! \brief Pointer to appplication-wide static notification manager instance * diff --git a/src/dataManagement/DataManager.cpp b/src/dataManagement/DataManager.cpp index a60a5d403..31a637631 100644 --- a/src/dataManagement/DataManager.cpp +++ b/src/dataManagement/DataManager.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -61,13 +62,16 @@ void DataManagement::DataManager::deferredInitialization() // If there is a downloaded maps.json file, we read it. updateDataItemListAndWhatsNew(); - // If the last update is more than one day ago, automatically initiate an - // update, so that maps stay at least roughly current. - auto lastUpdate = QSettings().value(QStringLiteral("DataManager/MapListTimeStamp"), QDateTime()).toDateTime(); - if (!lastUpdate.isValid() || (qAbs(lastUpdate.daysTo(QDateTime::currentDateTime()) > 0))) - { - updateRemoteDataItemList(); - } + // Update maps.json file if that is too old. Check that whenever the app comes forward. + updateRemoteDataItemListIfOutdated(); + connect(qGuiApp, &QGuiApplication::applicationStateChanged, this, + [this](Qt::ApplicationState state) + { + if (state == Qt::ApplicationActive) + { + updateRemoteDataItemListIfOutdated(); + } + }); } @@ -401,8 +405,6 @@ void DataManagement::DataManager::updateDataItemListAndWhatsNew() } } - qWarning() << minVersionString << currentVersionString; - if (minVersionString > currentVersionString) { if (!m_appUpdateRequired) @@ -489,3 +491,15 @@ void DataManagement::DataManager::updateDataItemListAndWhatsNew() emit whatsNewChanged(); } } + + +void DataManagement::DataManager::updateRemoteDataItemListIfOutdated() +{ + // If the last update is more than one day ago, automatically initiate an + // update, so that maps stay at least roughly current. + auto lastUpdate = QSettings().value(QStringLiteral("DataManager/MapListTimeStamp"), QDateTime()).toDateTime(); + if (!lastUpdate.isValid() || (qAbs(lastUpdate.daysTo(QDateTime::currentDateTime()) > 0))) + { + m_mapList.startDownload(); + } +} diff --git a/src/dataManagement/DataManager.h b/src/dataManagement/DataManager.h index f8d79dfa7..1bc368e39 100644 --- a/src/dataManagement/DataManager.h +++ b/src/dataManagement/DataManager.h @@ -301,12 +301,10 @@ class DataManager : public GlobalObject public slots: /*! \brief Triggers an update of the list of remotely available data items * - * This will trigger a download the file maps.json from the remote server. + * This will trigger a download the file maps.json from the remote server if the last update + * is more than one day ago. */ - void updateRemoteDataItemList() - { - m_mapList.startDownload(); - } + void updateRemoteDataItemListIfOutdated(); signals: /*! Notifier signal */ @@ -360,7 +358,7 @@ public slots: QString m_dataDirectory {QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/aviation_maps"}; // The current whats new string from _aviationMaps. - QString m_whatsNew {}; + QString m_whatsNew; // This Downloadable object manages the central text file that describes the // remotely available aviation maps. diff --git a/src/dataManagement/Downloadable_SingleFile.h b/src/dataManagement/Downloadable_SingleFile.h index b65463ed0..5fd6ad8dd 100644 --- a/src/dataManagement/Downloadable_SingleFile.h +++ b/src/dataManagement/Downloadable_SingleFile.h @@ -382,13 +382,13 @@ class Downloadable_SingleFile : public Downloadable_Abstract // Temporary file for storing partiall data when downloading the remote // file. Set to nullptr when no download is in progress. - QPointer m_saveFile{}; + QPointer m_saveFile; // URL of the remote file, as set in the constructor QUrl m_url; // Name of the local file, as set in the constructor - QString m_fileName{}; + QString m_fileName; // Modification date of the remote file, set directly via a setter method or // by calling downloadRemoteFileInfo(). diff --git a/src/fileFormats/GeoTIFF.h b/src/fileFormats/GeoTIFF.h index 7fc9259a6..016cb5e98 100644 --- a/src/fileFormats/GeoTIFF.h +++ b/src/fileFormats/GeoTIFF.h @@ -170,10 +170,10 @@ class GeoTIFF : public TIFF void interpretGeoData(); // Geographic coordinates for corner of raster image - QGeoCoordinate m_topLeft {}; - QGeoCoordinate m_topRight {}; - QGeoCoordinate m_bottomLeft {}; - QGeoCoordinate m_bottomRight {}; + QGeoCoordinate m_topLeft; + QGeoCoordinate m_topRight; + QGeoCoordinate m_bottomLeft; + QGeoCoordinate m_bottomRight; // Name QString m_name; diff --git a/src/fileFormats/TIFF.h b/src/fileFormats/TIFF.h index ae61fb0d1..c4f10e14a 100644 --- a/src/fileFormats/TIFF.h +++ b/src/fileFormats/TIFF.h @@ -119,7 +119,7 @@ class TIFF : public DataFileAbstract void readRasterSize(); // Size of the raster imags - QSize m_rasterSize {}; + QSize m_rasterSize; // TIFF tags and associated data QMap m_TIFFFields; diff --git a/src/fileFormats/TripKit.h b/src/fileFormats/TripKit.h index 070467087..6388bd945 100644 --- a/src/fileFormats/TripKit.h +++ b/src/fileFormats/TripKit.h @@ -136,7 +136,7 @@ class TripKit : public DataFileAbstract // Some trip kits contain the content top-level, other trip kits hide the content ina top-level // directory. This string is either empty or of the form "topLevelDirName/". - QString m_prefix {}; + QString m_prefix; }; } // namespace FileFormats diff --git a/src/geomaps/Airspace.h b/src/geomaps/Airspace.h index 33665ad65..fe8691f88 100644 --- a/src/geomaps/Airspace.h +++ b/src/geomaps/Airspace.h @@ -156,11 +156,11 @@ class Airspace { // in meters. If the height string cannot be parsed, returns the original string [[nodiscard]] static auto makeMetric(const QString& standard) -> QString; - QString m_name{}; - QString m_CAT{}; - QString m_upperBound{}; - QString m_lowerBound{}; - QGeoPolygon m_polygon{}; + QString m_name; + QString m_CAT; + QString m_upperBound; + QString m_lowerBound; + QGeoPolygon m_polygon; }; /*! \brief Comparison */ diff --git a/src/geomaps/VAC.h b/src/geomaps/VAC.h index 2d7bfa859..f65f4d43e 100644 --- a/src/geomaps/VAC.h +++ b/src/geomaps/VAC.h @@ -197,22 +197,22 @@ class VAC // /*! \brief Member variable for property of the same name */ - QGeoCoordinate bottomLeft {}; + QGeoCoordinate bottomLeft; /*! \brief Member variable for property of the same name */ - QGeoCoordinate bottomRight {}; + QGeoCoordinate bottomRight; /*! \brief Member variable for property of the same name */ - QString fileName {}; + QString fileName; /*! \brief Member variable for property of the same name */ - QString name {}; + QString name; /*! \brief Member variable for property of the same name */ - QGeoCoordinate topLeft {}; + QGeoCoordinate topLeft; /*! \brief Member variable for property of the same name */ - QGeoCoordinate topRight {}; + QGeoCoordinate topRight; private: // Obtain values for topLeft etc by looking at the file name diff --git a/src/icons.qrc.in b/src/icons.qrc.in index 75f08b4b9..d645352be 100644 --- a/src/icons.qrc.in +++ b/src/icons.qrc.in @@ -55,7 +55,8 @@ ${material-design-icons_SOURCE_DIR}/content/svg/production/ic_remove_24px.svg ${material-design-icons_SOURCE_DIR}/content/svg/production/ic_remove_circle_24px.svg ${material-design-icons_SOURCE_DIR}/maps/svg/production/ic_satellite_24px.svg - ${material-design-icons_SOURCE_DIR}/content/svg/design/ic_send_24px.svg + ${material-design-icons_SOURCE_DIR}/action/svg/production/ic_search_24px.svg + ${material-design-icons_SOURCE_DIR}/content/svg/production/ic_send_24px.svg ${material-design-icons_SOURCE_DIR}/action/svg/production/ic_settings_24px.svg ${material-design-icons_SOURCE_DIR}/action/svg/production/ic_settings_ethernet_24px.svg ${material-design-icons_SOURCE_DIR}/communication/svg/production/ic_speaker_phone_24px.svg diff --git a/src/main.cpp b/src/main.cpp index d9a7f5b14..bb58d550c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -186,12 +186,9 @@ auto main(int argc, char *argv[]) -> int KDSingleApplication kdsingleapp; if (!kdsingleapp.isPrimaryInstance()) { - if (positionalArguments.length() > 0) - { + if (!positionalArguments.empty()) { kdsingleapp.sendMessage(positionalArguments[0].toUtf8()); - } - else - { + } else { kdsingleapp.sendMessage(QByteArray()); } return 0; diff --git a/src/navigation/Aircraft.h b/src/navigation/Aircraft.h index 86101810f..abf9cbd0e 100644 --- a/src/navigation/Aircraft.h +++ b/src/navigation/Aircraft.h @@ -386,8 +386,8 @@ class Aircraft { FuelConsumptionUnit m_fuelConsumptionUnit {LiterPerHour}; HorizontalDistanceUnit m_horizontalDistanceUnit {NauticalMile}; Units::Speed m_minimumSpeed {}; - QString m_name {}; - VerticalDistanceUnit m_verticalDistanceUnit {Feet}; + QString m_name; + VerticalDistanceUnit m_verticalDistanceUnit{Feet}; }; } // namespace Navigation diff --git a/src/navigation/Clock.cpp b/src/navigation/Clock.cpp index 86cdb3392..c7019c7ed 100644 --- a/src/navigation/Clock.cpp +++ b/src/navigation/Clock.cpp @@ -19,7 +19,6 @@ ***************************************************************************/ #include "Clock.h" -#include "positioning/PositionProvider.h" #include #include @@ -32,7 +31,7 @@ using namespace std::chrono_literals; Navigation::Clock::Clock(QObject *parent) : GlobalObject(parent) { // We need to update the time regularly. I do not use a simple timer here that emits "timeChanged" once per minute, because I - // want the signal to be emitted right after the full minute. So, I use a timer that once a minute set a single-shot time + // want the signal to be emitted right after the full minute. So, I use a timer that once a minute sets a single-shot timer // that is set to fire up 500ms after the full minute. This design will also work reliably if "timer" get out of sync, // for instance because the app was sleeping for a while. auto *timer = new QTimer(this); diff --git a/src/navigation/FlightRoute.cpp b/src/navigation/FlightRoute.cpp index eb8659e0a..2272fbba6 100644 --- a/src/navigation/FlightRoute.cpp +++ b/src/navigation/FlightRoute.cpp @@ -39,6 +39,10 @@ Navigation::FlightRoute::FlightRoute(QObject *parent) connect(this, &FlightRoute::waypointsChanged, this, &Navigation::FlightRoute::summaryChanged); connect(GlobalObject::navigator(), &Navigation::Navigator::aircraftChanged, this, &Navigation::FlightRoute::summaryChanged); connect(GlobalObject::navigator(), &Navigation::Navigator::windChanged, this, &Navigation::FlightRoute::summaryChanged); + + // Setup Bindings + m_geoPath.setBinding([this]() {return this->computeGeoPath();}); + } @@ -50,7 +54,7 @@ auto Navigation::FlightRoute::boundingRectangle() const -> QGeoRectangle { QGeoRectangle bbox; - for(const auto &_waypoint : m_waypoints) + for(const auto &_waypoint : m_waypoints.value()) { if (!_waypoint.isValid()) { @@ -72,10 +76,10 @@ auto Navigation::FlightRoute::boundingRectangle() const -> QGeoRectangle return bbox; } -auto Navigation::FlightRoute::geoPath() const -> QList +QList Navigation::FlightRoute::computeGeoPath() { QList result; - for(const auto& _waypoint : m_waypoints) + for(const auto& _waypoint : m_waypoints.value()) { if (!_waypoint.isValid()) { @@ -91,12 +95,7 @@ auto Navigation::FlightRoute::midFieldWaypoints() const -> QList result; - if (m_waypoints.isEmpty()) - { - return result; - } - - foreach(auto wpt, m_waypoints) + foreach(auto wpt, m_waypoints.value()) { if (wpt.category() == u"WP") { @@ -183,7 +182,9 @@ auto Navigation::FlightRoute::summary() const -> QString void Navigation::FlightRoute::append(const GeoMaps::Waypoint& waypoint) { - m_waypoints.append(waypoint); + auto newWaypoints = m_waypoints.value(); + newWaypoints.append(waypoint); + m_waypoints = newWaypoints; updateLegs(); emit waypointsChanged(); @@ -196,21 +197,21 @@ void Navigation::FlightRoute::append(const QGeoCoordinate& position) auto Navigation::FlightRoute::canAppend(const GeoMaps::Waypoint &other) const -> bool { - if (m_waypoints.isEmpty()) + if (m_waypoints.value().isEmpty()) { return true; } - return !m_waypoints.last().isNear(other); + return !m_waypoints.value().last().isNear(other); } auto Navigation::FlightRoute::canInsert(const GeoMaps::Waypoint &other) const -> bool { - if (m_waypoints.size() < 2) + if (m_waypoints.value().size() < 2) { return false; } - foreach(const auto& waypoint, m_waypoints) + foreach(const auto& waypoint, m_waypoints.value()) { if (waypoint.isNear(other)) { @@ -222,7 +223,7 @@ auto Navigation::FlightRoute::canInsert(const GeoMaps::Waypoint &other) const -> void Navigation::FlightRoute::clear() { - m_waypoints.clear(); + m_waypoints = QVector(); updateLegs(); emit waypointsChanged(); @@ -230,7 +231,7 @@ void Navigation::FlightRoute::clear() auto Navigation::FlightRoute::contains(const GeoMaps::Waypoint& waypoint) const -> bool { - foreach(auto _waypoint, m_waypoints) + foreach(auto _waypoint, m_waypoints.value()) { if (!_waypoint.isValid()) { @@ -254,19 +255,20 @@ void Navigation::FlightRoute::insert(const GeoMaps::Waypoint& waypoint) int shortestIndex = 0; double shortestRoute = 10e9; - for(int idx=0; idx qsizetype { - - for(auto i=m_waypoints.size()-1; i>=0; i--) + for(auto i=m_waypoints.value().size()-1; i>=0; i--) { - auto _waypoint = m_waypoints.at(i); + auto _waypoint = m_waypoints.value().at(i); if (!_waypoint.isValid()) { continue; @@ -298,7 +300,6 @@ auto Navigation::FlightRoute::lastIndexOf(const GeoMaps::Waypoint& waypoint) con } } return -1; - } auto Navigation::FlightRoute::load(const QString& fileName) -> QString @@ -324,7 +325,7 @@ auto Navigation::FlightRoute::load(const QString& fileName) -> QString return tr("The file '%1' contains too many waypoints. Flight routes with more than 100 waypoints are not supported.").arg(myFileName); } - m_waypoints.clear(); + QVector newWaypoints; foreach(auto waypoint, result) { if (!waypoint.isValid()) @@ -337,14 +338,14 @@ auto Navigation::FlightRoute::load(const QString& fileName) -> QString auto nearest = GlobalObject::geoMapProvider()->closestWaypoint(pos, distPos); if (nearest.type() == u"WP") { - m_waypoints << waypoint; + newWaypoints << waypoint; } else { - m_waypoints << nearest; + newWaypoints << nearest; } - } + m_waypoints = newWaypoints; updateLegs(); emit waypointsChanged(); @@ -353,13 +354,16 @@ auto Navigation::FlightRoute::load(const QString& fileName) -> QString void Navigation::FlightRoute::moveDown(int idx) { + QVector newWaypoints = m_waypoints.value(); + // Paranoid safety checks - if ((idx < 0) || (idx > m_waypoints.size()-2)) + if ((idx < 0) || (idx > newWaypoints.size()-2)) { return; } - m_waypoints.move(idx, idx+1); + newWaypoints.move(idx, idx+1); + m_waypoints = newWaypoints; updateLegs(); emit waypointsChanged(); @@ -367,13 +371,16 @@ void Navigation::FlightRoute::moveDown(int idx) void Navigation::FlightRoute::moveUp(int idx) { + QVector newWaypoints = m_waypoints.value(); + // Paranoid safety checks - if ((idx < 1) || (idx >= m_waypoints.size())) + if ((idx < 1) || (idx >= newWaypoints.size())) { return; } - m_waypoints.move(idx, idx-1); + newWaypoints.move(idx, idx-1); + m_waypoints = newWaypoints; updateLegs(); emit waypointsChanged(); @@ -381,39 +388,47 @@ void Navigation::FlightRoute::moveUp(int idx) void Navigation::FlightRoute::removeWaypoint(int idx) { + QVector newWaypoints = m_waypoints.value(); + // Paranoid safety checks - if ((idx < 0) || (idx >= m_waypoints.size())) + if ((idx < 0) || (idx >= newWaypoints.size())) { return; } - m_waypoints.removeAt(idx); + newWaypoints.removeAt(idx); + m_waypoints = newWaypoints; updateLegs(); emit waypointsChanged(); } void Navigation::FlightRoute::replaceWaypoint(int idx, const GeoMaps::Waypoint& newWaypoint) { + QVector newWaypoints = m_waypoints.value(); + // Paranoid safety checks - if ((idx < 0) || (idx >= m_waypoints.size())) + if ((idx < 0) || (idx >= newWaypoints.size())) { return; } - // If name did not - if (m_waypoints[idx] == newWaypoint) + + // If no change is necessary, then return + if (newWaypoints[idx] == newWaypoint) { return; } - - m_waypoints[idx] = newWaypoint; + newWaypoints[idx] = newWaypoint; + m_waypoints = newWaypoints; updateLegs(); emit waypointsChanged(); } void Navigation::FlightRoute::reverse() { - std::reverse(m_waypoints.begin(), m_waypoints.end()); + QVector newWaypoints = m_waypoints.value(); + std::reverse(newWaypoints.begin(), newWaypoints.end()); + m_waypoints = newWaypoints; updateLegs(); emit waypointsChanged(); } @@ -439,7 +454,7 @@ auto Navigation::FlightRoute::save(const QString& fileName) const -> QString auto Navigation::FlightRoute::suggestedFilename() const -> QString { - if (m_waypoints.size() < 2) + if (m_waypoints.value().size() < 2) { return tr("Flight Route"); } @@ -447,8 +462,8 @@ auto Navigation::FlightRoute::suggestedFilename() const -> QString // // Get name for start point (e.g. "EDTL (LAHR)") // - QString start = m_waypoints.constFirst().ICAOCode(); // ICAO code of start point - QString name = m_waypoints.constFirst().name(); // Name of start point + auto start = m_waypoints.value().constFirst().ICAOCode(); // ICAO code of start point + auto name = m_waypoints.value().constFirst().name(); // Name of start point name.replace(u'(', u""_qs); name.replace(u')', u""_qs); if (name.length() > 11) @@ -470,8 +485,8 @@ auto Navigation::FlightRoute::suggestedFilename() const -> QString // // Get name for end point (e.g. "EDTG (BREMGARTEN)") // - QString end = m_waypoints.constLast().ICAOCode(); // ICAO code of end point - name = m_waypoints.constLast().name(); // Name of end point + QString end = m_waypoints.value().constLast().ICAOCode(); // ICAO code of end point + name = m_waypoints.value().constLast().name(); // Name of end point name.replace(u"("_qs, u""_qs); name.replace(u")"_qs, u""_qs); if (name.length() > 11) @@ -518,7 +533,7 @@ auto Navigation::FlightRoute::suggestedFilename() const -> QString auto Navigation::FlightRoute::toGeoJSON() const -> QByteArray { QJsonArray waypointArray; - foreach(const auto& waypoint, m_waypoints) + foreach(const auto& waypoint, m_waypoints.value()) { if (waypoint.isValid()) { @@ -540,9 +555,9 @@ void Navigation::FlightRoute::updateLegs() { m_legs.clear(); - for(int i=0; i #include #include #include @@ -94,7 +95,7 @@ namespace Navigation * This property holds a list of coordinates of the waypoints, suitable * for drawing the flight path on a QML map. */ - Q_PROPERTY(QList geoPath READ geoPath NOTIFY waypointsChanged) + Q_PROPERTY(QList geoPath READ geoPath BINDABLE bindableGeoPath) /*! \brief List of waypoints in the flight route that are not airfields * @@ -144,7 +145,8 @@ namespace Navigation * * @returns Property geoPath */ - [[nodiscard]] auto geoPath() const -> QList; + [[nodiscard]] QList geoPath() const {return {m_geoPath};} + [[nodiscard]] QBindable> bindableGeoPath() const {return &m_geoPath;} /*! \brief Getter function for the property with the same name * @@ -162,7 +164,7 @@ namespace Navigation * * @returns Property size */ - [[nodiscard]] auto size() const -> qsizetype { return m_waypoints.size(); } + [[nodiscard]] auto size() const -> qsizetype { return m_waypoints.value().size(); } /*! \brief Getter function for the property with the same name * @@ -174,7 +176,7 @@ namespace Navigation * * @returns Property waypoints */ - [[nodiscard]] auto waypoints() const -> QList {return m_waypoints;} + [[nodiscard]] auto waypoints() const -> QList {return {m_waypoints};} // @@ -353,10 +355,14 @@ namespace Navigation private: Q_DISABLE_COPY_MOVE(FlightRoute) + // Computer functions for bindings + QList computeGeoPath(); + // Helper function for method toGPX [[nodiscard]] auto gpxElements(const QString& indent, const QString& tag) const -> QString; - QVector m_waypoints; + QProperty> m_geoPath; + QProperty> m_waypoints; QVector m_legs; diff --git a/src/navigation/FlightRoute_GPX.cpp b/src/navigation/FlightRoute_GPX.cpp index 925cd0c42..53ec286b5 100644 --- a/src/navigation/FlightRoute_GPX.cpp +++ b/src/navigation/FlightRoute_GPX.cpp @@ -106,7 +106,7 @@ auto Navigation::FlightRoute::gpxElements(const QString& indent, const QString& // waypoints // - for(const auto& _waypoint : m_waypoints) { + for(const auto& _waypoint : m_waypoints.value()) { if (!_waypoint.isValid()) { continue; // skip silently diff --git a/src/navigation/RemainingRouteInfo.h b/src/navigation/RemainingRouteInfo.h index a2b3e96f5..1fd6a984f 100644 --- a/src/navigation/RemainingRouteInfo.h +++ b/src/navigation/RemainingRouteInfo.h @@ -148,19 +148,18 @@ class RemainingRouteInfo { Status status {NoRoute}; QString note; - GeoMaps::Waypoint nextWP {}; - Units::Distance nextWP_DIST {}; + GeoMaps::Waypoint nextWP; + Units::Distance nextWP_DIST{}; Units::Timespan nextWP_ETE {}; - QDateTime nextWP_ETA {}; - Units::Angle nextWP_TC {}; + QDateTime nextWP_ETA; + Units::Angle nextWP_TC{}; - GeoMaps::Waypoint finalWP {}; - Units::Distance finalWP_DIST {}; + GeoMaps::Waypoint finalWP; + Units::Distance finalWP_DIST{}; Units::Timespan finalWP_ETE {}; - QDateTime finalWP_ETA {}; + QDateTime finalWP_ETA; }; - /*! \brief Comparison */ auto operator==(const Navigation::RemainingRouteInfo&, const Navigation::RemainingRouteInfo&) -> bool; diff --git a/src/notam/Notam.cpp b/src/notam/NOTAM.cpp similarity index 93% rename from src/notam/Notam.cpp rename to src/notam/NOTAM.cpp index 7977fcec4..e9ec800d1 100644 --- a/src/notam/Notam.cpp +++ b/src/notam/NOTAM.cpp @@ -23,8 +23,8 @@ #include "GlobalObject.h" #include "GlobalSettings.h" -#include "notam/Notam.h" -#include "notam/NotamProvider.h" +#include "notam/NOTAM.h" +#include "notam/NOTAMProvider.h" // Static objects @@ -110,7 +110,7 @@ Q_GLOBAL_STATIC(ContractionList, // Constructor/Destructor // -NOTAM::Notam::Notam(const QJsonObject& jsonObject) +NOTAM::NOTAM::NOTAM(const QJsonObject& jsonObject) { auto notamObject = jsonObject[u"properties"_qs][u"coreNOTAMData"_qs][u"notam"_qs].toObject(); @@ -142,7 +142,7 @@ NOTAM::Notam::Notam(const QJsonObject& jsonObject) // Getter Methods // -QString NOTAM::Notam::cancels() const +QString NOTAM::NOTAM::cancels() const { if (!m_text.contains(*cancelNotamStart)) { @@ -152,7 +152,7 @@ QString NOTAM::Notam::cancels() const } -QJsonObject NOTAM::Notam::GeoJSON() const +QJsonObject NOTAM::NOTAM::GeoJSON() const { QMap m_properties; m_properties[u"CAT"_qs] = u"NOTAM"_qs; @@ -173,7 +173,7 @@ QJsonObject NOTAM::Notam::GeoJSON() const } -bool NOTAM::Notam::isValid() const +bool NOTAM::NOTAM::isValid() const { if (!m_coordinate.isValid()) { @@ -196,7 +196,7 @@ bool NOTAM::Notam::isValid() const // Methods // -QString NOTAM::Notam::richText() const +QString NOTAM::NOTAM::richText() const { QStringList result; @@ -278,33 +278,38 @@ QString NOTAM::Notam::richText() const } -QString NOTAM::Notam::sectionTitle() const +void NOTAM::NOTAM::updateSectionTitle() { if (GlobalObject::notamProvider()->isRead(m_number)) { - return u"Marked as read"_qs; + m_sectionTitle = u"Marked as read"_qs; + return; } if (m_effectiveStart.isValid()) { if (m_effectiveStart < QDateTime::currentDateTimeUtc()) { - return u"Current"_qs; + m_sectionTitle = u"Current"_qs; + return; } if (m_effectiveStart < QDateTime::currentDateTimeUtc().addDays(1)) { - return u"Next 24h"_qs; + m_sectionTitle = u"Next 24h"_qs; + return; } if (m_effectiveStart < QDateTime::currentDateTimeUtc().addDays(90)) { - return u"Next 90 days"_qs; + m_sectionTitle = u"Next 90 days"_qs; + return; } if (m_effectiveStart < QDateTime::currentDateTimeUtc().addDays(90)) { - return u"> 90 days"_qs; + m_sectionTitle = u"> 90 days"_qs; + return; } } - return u"NOTAM"_qs; + m_sectionTitle = u"NOTAM"_qs; } @@ -357,7 +362,7 @@ QGeoCoordinate NOTAM::interpretNOTAMCoordinates(const QString& string) } -QDataStream& NOTAM::operator<<(QDataStream& stream, const NOTAM::Notam& notam) +QDataStream& NOTAM::operator<<(QDataStream& stream, const NOTAM& notam) { stream << notam.m_affectedFIR; stream << notam.m_coordinate; @@ -372,6 +377,7 @@ QDataStream& NOTAM::operator<<(QDataStream& stream, const NOTAM::Notam& notam) stream << notam.m_radius; stream << notam.m_region; stream << notam.m_schedule; + stream << notam.m_sectionTitle; stream << notam.m_text; stream << notam.m_traffic; @@ -379,7 +385,7 @@ QDataStream& NOTAM::operator<<(QDataStream& stream, const NOTAM::Notam& notam) } -QDataStream& NOTAM::operator>>(QDataStream& stream, NOTAM::Notam& notam) +QDataStream& NOTAM::operator>>(QDataStream& stream, NOTAM& notam) { stream >> notam.m_affectedFIR; stream >> notam.m_coordinate; @@ -394,6 +400,7 @@ QDataStream& NOTAM::operator>>(QDataStream& stream, NOTAM::Notam& notam) stream >> notam.m_radius; stream >> notam.m_region; stream >> notam.m_schedule; + stream >> notam.m_sectionTitle; stream >> notam.m_text; stream >> notam.m_traffic; diff --git a/src/notam/Notam.h b/src/notam/NOTAM.h similarity index 81% rename from src/notam/Notam.h rename to src/notam/NOTAM.h index 0c4b2ee61..653a12a2e 100644 --- a/src/notam/Notam.h +++ b/src/notam/NOTAM.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2023 by Stefan Kebekus * + * Copyright (C) 2023-2024 by Stefan Kebekus * * stefan.kebekus@gmail.com * * * * This program is free software; you can redistribute it and/or modify * @@ -30,22 +30,22 @@ namespace NOTAM { /*! \brief This extremely simple class holds a the data item of a NOTAM */ -class Notam { +class NOTAM { Q_GADGET QML_VALUE_TYPE(notam) - friend QDataStream& operator<<(QDataStream& stream, const NOTAM::Notam ¬am); - friend QDataStream& operator>>(QDataStream& stream, NOTAM::Notam& notam); + friend QDataStream& operator<<(QDataStream& stream, const NOTAM& notam); + friend QDataStream& operator>>(QDataStream& stream, NOTAM& notam); public: - /*! \brief Constructs an invalid Notam */ - Notam() = default; + /*! \brief Constructs an invalid NOTAM */ + NOTAM() = default; - /*! \brief Constructs a Notam from GeoJSON data, as provided by the FAA + /*! \brief Constructs a NOTAM from GeoJSON data, as provided by the FAA * * @param jsonObject JSON object, as provided by the FAA */ - explicit Notam(const QJsonObject& jsonObject); + explicit NOTAM(const QJsonObject& jsonObject); // @@ -55,28 +55,28 @@ class Notam { /*! \brief Flight Information Region of this NOTAM */ Q_PROPERTY(QString affectedFIR READ affectedFIR CONSTANT) - /*! \brief Cancels other Notam + /*! \brief Cancels other NOTAM * - * If this is a cancel notam, then this property holds the number - * of the notam that is to be cancelled. Otherwise, this property - * holds an empty string. + * If this is a cancel notam, then this property holds the number of the + * notam that is to be cancelled. Otherwise, this property holds an empty + * string. */ Q_PROPERTY(QString cancels READ cancels CONSTANT) - /*! \brief Coordinates of the Notam */ + /*! \brief Coordinates of the NOTAM */ Q_PROPERTY(QGeoCoordinate coordinate READ coordinate CONSTANT) - /*! \brief Effective end of the Notam, if date is given + /*! \brief Effective end of the NOTAM, if date is given * - * If the effectiveEnd field of the Notam specified a precise date/time, + * If the effectiveEnd field of the NOTAM specified a precise date/time, * then this time is found here. If not, the property contains an invalid * QDateTime. */ Q_PROPERTY(QDateTime effectiveEnd READ effectiveEnd CONSTANT) - /*! \brief Effective start of the Notam, if date is given + /*! \brief Effective start of the NOTAM, if date is given * - * If the effectiveStart field of the Notam specified a precise date/time, + * If the effectiveStart field of the NOTAM specified a precise date/time, * then this time is found here. If not, the property contains an invalid * QDateTime. */ @@ -98,8 +98,12 @@ class Notam { /*! \brief Region where this NOTAM is valid */ Q_PROPERTY(QGeoCircle region READ region CONSTANT) - /*! \brief Section title for this NOTAM */ - Q_PROPERTY(QString sectionTitle READ sectionTitle CONSTANT) + /*! \brief Section title + * + * This member can be set as desired, to allow for section titles + * when displaying NOTAMs in a QML ListView. + */ + Q_PROPERTY(QString sectionTitle MEMBER m_sectionTitle) /*! \brief Traffic entry of the NOTAM */ Q_PROPERTY(QString traffic READ traffic CONSTANT) @@ -169,12 +173,6 @@ class Notam { */ Q_REQUIRED_RESULT QGeoCircle region() const { return m_region; } - /*! \brief Getter function for the property with the same name - * - * @returns Property sectionTitle - */ - Q_REQUIRED_RESULT QString sectionTitle() const; - /*! \brief Getter function for the property with the same name * * @returns Property traffic @@ -193,7 +191,7 @@ class Notam { * * @returns True on equality. */ - Q_REQUIRED_RESULT [[nodiscard]] Q_INVOKABLE bool operator==(const NOTAM::Notam& rhs) const = default; + Q_REQUIRED_RESULT [[nodiscard]] Q_INVOKABLE bool operator==(const NOTAM& rhs) const = default; /*! \brief Check if effectiveEnd is valid and earlier than currentTime * @@ -204,18 +202,25 @@ class Notam { return m_effectiveEnd.isValid() && (m_effectiveEnd < QDateTime::currentDateTimeUtc()); } - /*! \brief Rich text description of the Notam + /*! \brief Rich text description of the NOTAM * * The description and changes with time (e.g. when passing the effective start - * date of the Notam. + * date of the NOTAM. * * @return HTML string */ Q_REQUIRED_RESULT Q_INVOKABLE QString richText() const; + /*! \brief Update section title according to the current time + * + * This method uses the current time to set the NOTAM's section title to + * one of "Marked as read", "Current", "Next 24h", "Next 90 days", "> 90 + * days" or "NOTAM". + */ + void updateSectionTitle(); private: - /* Notam members, as described by the FAA */ + /* NOTAM members, as described by the FAA */ QString m_affectedFIR; QGeoCoordinate m_coordinate; QString m_effectiveEndString; @@ -225,6 +230,7 @@ class Notam { QString m_minimumFL; QString m_number; Units::Distance m_radius; + QString m_sectionTitle; QString m_schedule; QString m_text; QString m_traffic; @@ -262,15 +268,15 @@ QGeoCoordinate interpretNOTAMCoordinates(const QString& string); * * There is no checks for errors of any kind. */ -QDataStream& operator<<(QDataStream& stream, const NOTAM::Notam ¬am); +QDataStream& operator<<(QDataStream& stream, const NOTAM& notam); /*! \brief Deserialization * * There is no checks for errors of any kind. */ -QDataStream& operator>>(QDataStream& stream, NOTAM::Notam& notam); +QDataStream& operator>>(QDataStream& stream, NOTAM& notam); } // namespace NOTAM // Declare meta types -Q_DECLARE_METATYPE(NOTAM::Notam) +Q_DECLARE_METATYPE(NOTAM::NOTAM) diff --git a/src/notam/NotamList.cpp b/src/notam/NOTAMList.cpp similarity index 89% rename from src/notam/NotamList.cpp rename to src/notam/NOTAMList.cpp index 9cd4a4c5e..230a228e7 100644 --- a/src/notam/NotamList.cpp +++ b/src/notam/NOTAMList.cpp @@ -22,11 +22,11 @@ #include #include -#include "notam/NotamList.h" -#include "notam/NotamProvider.h" +#include "notam/NOTAMList.h" +#include "notam/NOTAMProvider.h" -NOTAM::NotamList::NotamList(const QJsonDocument& jsonDoc, const QGeoCircle& region, QSet* cancelledNotamNumbers) +NOTAM::NOTAMList::NOTAMList(const QJsonDocument& jsonDoc, const QGeoCircle& region, QSet* cancelledNotamNumbers) { QSet numbersSeen; @@ -34,7 +34,7 @@ NOTAM::NotamList::NotamList(const QJsonDocument& jsonDoc, const QGeoCircle& regi foreach(auto item, items) { - Notam const notam(item.toObject()); + NOTAM const notam(item.toObject()); // Ignore invalid notams if (!notam.isValid()) @@ -83,7 +83,7 @@ NOTAM::NotamList::NotamList(const QJsonDocument& jsonDoc, const QGeoCircle& regi // Getter Methods // -QString NOTAM::NotamList::summary() const +QString NOTAM::NOTAMList::summary() const { QStringList results; @@ -110,7 +110,7 @@ QString NOTAM::NotamList::summary() const // Methods // -Units::Timespan NOTAM::NotamList::age() const +Units::Timespan NOTAM::NOTAMList::age() const { if (!m_retrieved.isValid()) { @@ -121,9 +121,9 @@ Units::Timespan NOTAM::NotamList::age() const } -NOTAM::NotamList NOTAM::NotamList::cleaned(const QSet& cancelledNotamNumbers) const +NOTAM::NOTAMList NOTAM::NOTAMList::cleaned(const QSet& cancelledNotamNumbers) const { - NotamList result; + NOTAMList result; result.m_region = m_region; result.m_retrieved = m_retrieved; @@ -152,9 +152,9 @@ NOTAM::NotamList NOTAM::NotamList::cleaned(const QSet& cancelledNotamNu } -NOTAM::NotamList NOTAM::NotamList::restricted(const GeoMaps::Waypoint& waypoint) const +NOTAM::NOTAMList NOTAM::NOTAMList::restricted(const GeoMaps::Waypoint& waypoint) const { - NotamList result; + NOTAMList result; result.m_retrieved = m_retrieved; auto radius = qMin(restrictionRadius.toM(), qMax(0.0, m_region.radius() - m_region.center().distanceTo(waypoint.coordinate()))); @@ -182,11 +182,12 @@ NOTAM::NotamList NOTAM::NotamList::restricted(const GeoMaps::Waypoint& waypoint) { continue; } + notam.updateSectionTitle(); result.m_notams.append(notam); } std::sort(result.m_notams.begin(), result.m_notams.end(), - [](const Notam& first, const Notam& second) + [](const NOTAM& first, const NOTAM& second) { auto aRead = GlobalObject::notamProvider()->isRead(first.number()); auto bRead = GlobalObject::notamProvider()->isRead(second.number()); @@ -215,7 +216,7 @@ NOTAM::NotamList NOTAM::NotamList::restricted(const GeoMaps::Waypoint& waypoint) // Non-Member Methods // -QDataStream& NOTAM::operator<<(QDataStream& stream, const NOTAM::NotamList& notamList) +QDataStream& NOTAM::operator<<(QDataStream& stream, const NOTAMList& notamList) { stream << notamList.m_notams; stream << notamList.m_region; @@ -225,7 +226,7 @@ QDataStream& NOTAM::operator<<(QDataStream& stream, const NOTAM::NotamList& nota } -QDataStream& NOTAM::operator>>(QDataStream& stream, NOTAM::NotamList& notamList) +QDataStream& NOTAM::operator>>(QDataStream& stream, NOTAMList& notamList) { stream >> notamList.m_notams; stream >> notamList.m_region; diff --git a/src/notam/NotamList.h b/src/notam/NOTAMList.h similarity index 78% rename from src/notam/NotamList.h rename to src/notam/NOTAMList.h index c4bdf86ec..4314b414c 100644 --- a/src/notam/NotamList.h +++ b/src/notam/NOTAMList.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2023 by Stefan Kebekus * + * Copyright (C) 2023-2024 by Stefan Kebekus * * stefan.kebekus@gmail.com * * * * This program is free software; you can redistribute it and/or modify * @@ -23,7 +23,7 @@ #include #include "geomaps/Waypoint.h" -#include "notam/Notam.h" +#include "notam/NOTAM.h" #include "units/Timespan.h" namespace NOTAM { @@ -36,26 +36,26 @@ namespace NOTAM { * and the region in the member m_region. */ -class NotamList { +class NOTAMList { Q_GADGET QML_VALUE_TYPE(notamList) - friend QDataStream& operator<<(QDataStream& stream, const NOTAM::NotamList& notamList); - friend QDataStream& operator>>(QDataStream& stream, NOTAM::NotamList& notamList); + friend QDataStream& operator<<(QDataStream& stream, const NOTAMList& notamList); + friend QDataStream& operator>>(QDataStream& stream, NOTAMList& notamList); public: - /*! \brief Constructs an empty NotamList + /*! \brief Constructs an empty NOTAMList * - * Constructs an empty NotamList with an invalid region and an invalid + * Constructs an empty NOTAMList with an invalid region and an invalid * QDateTime for the property retrieved. */ - NotamList() = default; + NOTAMList() = default; - /*! \brief Constructs a NotamList from FAA GeoJSON data + /*! \brief Constructs a NOTAMList from FAA GeoJSON data * * This constructor sets the member m_retrieved to - * QDateTime::currentDateTimeUtc(). Invalid Notams and Cancel Notams - * will not be added to the list. + * QDateTime::currentDateTimeUtc(). Invalid Notams and Cancel Notams will + * not be added to the list. * * @param jsonDoc JSON dociment, as provided by the FAA * @@ -64,7 +64,7 @@ class NotamList { * @param cancelledNotamNumbers Pointer to a set where numbers of cancelled * Notams are added. The nullptr is allowed. */ - NotamList(const QJsonDocument& jsonDoc, const QGeoCircle& region, QSet* cancelledNotamNumbers=nullptr); + NOTAMList(const QJsonDocument& jsonDoc, const QGeoCircle& region, QSet* cancelledNotamNumbers=nullptr); @@ -79,7 +79,7 @@ class NotamList { Q_PROPERTY(bool isValid READ isValid) /*! \brief List of Notams */ - Q_PROPERTY(QList notams READ notams) + Q_PROPERTY(QList notams READ notams) /*! \brief Region covered by this list */ Q_PROPERTY(QGeoCircle region READ region) @@ -112,7 +112,7 @@ class NotamList { * * @returns Property notams */ - Q_REQUIRED_RESULT QList notams() const { return m_notams; } + Q_REQUIRED_RESULT QList notams() const { return m_notams; } /*! \brief Getter function for the property with the same name * @@ -140,22 +140,23 @@ class NotamList { /*! \brief Time span between retrieved and now * - * @returns Time span between retrieved and now. If retrieved() is invalid, and invalid time is returned. + * @returns Time span between retrieved and now. If retrieved() is invalid, + * and invalid time is returned. */ Q_REQUIRED_RESULT Units::Timespan age() const; /*! \brief Sublist with expired and duplicated entries removed * - * @param cancelledNotamNumbers Set with numbers of notams that are - * known as cancelled + * @param cancelledNotamNumbers Set with numbers of notams that are known + * as cancelled * * @returns Sublist with expired and duplicated entries removed. */ - Q_REQUIRED_RESULT NOTAM::NotamList cleaned(const QSet& cancelledNotamNumbers) const; + Q_REQUIRED_RESULT NOTAMList cleaned(const QSet& cancelledNotamNumbers) const; /*! \brief Check if outdated * - * A NotamList is outdated if its age is invalid or greater than 24h + * A NOTAMList is outdated if its age is invalid or greater than 24h * * @returns True if outdated */ @@ -163,7 +164,7 @@ class NotamList { /*! \brief Check if list needs update * - * A NotamList needs an update if its age is invalid or greater than 12h + * A NOTAMList needs an update if its age is invalid or greater than 12h * * @returns True if outdated */ @@ -173,16 +174,19 @@ class NotamList { * * @param waypoint Waypoint * - * @returns NotamList with all notams centered within restrictionRadius of the given waypoint, without expired and duplicated NOTAMs. + * @returns NOTAMList with all notams centered within restrictionRadius of + * the given waypoint, without expired and duplicated NOTAMs. Section + * titles are set depending on the current time, using + * NOTAM::updateSectionTitle(). */ - Q_REQUIRED_RESULT NOTAM::NotamList restricted(const GeoMaps::Waypoint& waypoint) const; + Q_REQUIRED_RESULT NOTAMList restricted(const GeoMaps::Waypoint& waypoint) const; /*! \brief Radius used in the method restricted() */ static constexpr Units::Distance restrictionRadius = Units::Distance::fromNM(20.0); private: /* List of Notams */ - QList m_notams; + QList m_notams; /* Region */ QGeoCircle m_region; @@ -195,16 +199,16 @@ class NotamList { * * There is no checks for errors of any kind. */ -QDataStream& operator<<(QDataStream& stream, const NOTAM::NotamList& notamList); +QDataStream& operator<<(QDataStream& stream, const NOTAMList& notamList); /*! \brief Deserialization * * There is no checks for errors of any kind. */ -QDataStream& operator>>(QDataStream& stream, NOTAM::NotamList& notamList); +QDataStream& operator>>(QDataStream& stream, NOTAMList& notamList); } // namespace NOTAM // Declare meta types -Q_DECLARE_METATYPE(NOTAM::NotamList) +Q_DECLARE_METATYPE(NOTAM::NOTAMList) diff --git a/src/notam/NOTAMProvider.cpp b/src/notam/NOTAMProvider.cpp new file mode 100644 index 000000000..36ec5d1c6 --- /dev/null +++ b/src/notam/NOTAMProvider.cpp @@ -0,0 +1,480 @@ +/*************************************************************************** + * Copyright (C) 2023-2024 by Stefan Kebekus * + * stefan.kebekus@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include +#include +#include +#include + +#include "navigation/Navigator.h" +#include "notam/NOTAMProvider.h" +#include "positioning/PositionProvider.h" + +using namespace std::chrono_literals; + + + +// +// Constructor/Destructor +// + +NOTAM::NOTAMProvider::NOTAMProvider(QObject* parent) : + GlobalObject(parent) +{ +} + +void NOTAM::NOTAMProvider::deferredInitialization() +{ + // Load NOTAM data from file in stdFileName, then clean the data (which potentially triggers save()). + QList newNotamLists; + auto inputFile = QFile(m_stdFileName); + if (inputFile.open(QIODevice::ReadOnly)) + { + QDataStream inputStream(&inputFile); + QString magicString; + inputStream >> magicString; + if (magicString == QStringLiteral(GIT_COMMIT)) + { + inputStream >> m_readNotamNumbers; + inputStream >> newNotamLists; + } + } + m_notamLists = cleaned(newNotamLists); + + // Wire up updateData. Check NOTAM database after start, and whenever the flight route changes. + QTimer::singleShot(0, this, &NOTAMProvider::updateData); + connect(navigator()->flightRoute(), &Navigation::FlightRoute::waypointsChanged, this, &NOTAMProvider::updateData); + connect(GlobalObject::positionProvider(), &Positioning::PositionProvider::approximateLastValidCoordinateChanged, this, &NOTAMProvider::updateData); + + // Clean and check the NOTAM database the data every hour. + auto* timer = new QTimer(this); + timer->start(59min); + connect(timer, &QTimer::timeout, this, [this]() { + updateData(); + m_notamLists = cleaned(m_notamLists); + }); + + + // Setup Bindings + m_controlPoints4FlightRoute.setBinding([this]() {return computeControlPoints4FlightRoute();}); + m_geoJSON.setBinding([this]() {return computeGeoJSON();}); + m_lastUpdate.setBinding([this]() {return computeLastUpdate();}); + m_status.setBinding([this]() {return computeStatus();}); + + // Setup Notifiers + // -- Save the NOTAM data every time that the database changes + m_saveNotifier = m_notamLists.addNotifier([this]() {save();}); +} + +NOTAM::NOTAMProvider::~NOTAMProvider() +{ + for(const auto& networkReply : m_networkReplies) + { + if (networkReply.isNull()) + { + continue; + } + disconnect(networkReply, nullptr, this, nullptr); + networkReply->abort(); + delete networkReply; + } + m_networkReplies.clear(); +} + + +// +// Methods +// + +NOTAM::NOTAMList NOTAM::NOTAMProvider::notams(const GeoMaps::Waypoint& waypoint) +{ + // Paranoid safety checks + if (!waypoint.coordinate().isValid()) + { + return {}; + } + + // Check if notams for the location are present in our database. + // Go through the database, oldest to newest. + for(const auto& notamList : m_notamLists.value()) + { + // Disregard outdated notamLists + if (notamList.isOutdated()) + { + continue; + } + + if (notamList.region().contains(waypoint.coordinate())) + { + // If motamList needs an update, then ask for an update + if (notamList.needsUpdate()) + { + startRequest(waypoint.coordinate()); + } + return notamList.restricted(waypoint); + } + } + + // Check if internet requests NOTAMs for the location are pending. + // In that case, return an empty list. + for(const auto& networkReply : m_networkReplies) + { + // Paranoid safety checks + if (networkReply.isNull()) + { + continue; + } + if (!networkReply->isRunning()) + { + continue; + } + auto area = networkReply->property("area").value(); + if (!area.isValid()) + { + continue; + } + if (area.contains(waypoint.coordinate())) + { + return {}; + } + } + + // We have no data for the waypoint and no pending internet requests. So, + // start a new internet request and return an empty list. + startRequest(waypoint.coordinate()); + return {}; +} + +void NOTAM::NOTAMProvider::setRead(const QString& number, bool read) +{ + if (read) + { + m_readNotamNumbers.prepend(number); + if (m_readNotamNumbers.size() > 50) + { + m_readNotamNumbers.remove(50); + } + } + else + { + m_readNotamNumbers.removeAll(number); + } + save(); +} + + +// +// Private Methods +// + +QList NOTAM::NOTAMProvider::cleaned(const QList& notamLists, const QSet& cancelledNotams) +{ + QList newNotamLists; + QSet regionsSeen; + + // Iterate over notamLists, newest lists first + for(const auto& notamList : notamLists) + { + // If this notamList is outdated, then so all all further ones. We can thus end here. + if (notamList.isOutdated()) + { + break; + } + + // If a newer notamList has the same region, then the data in this list + // is irrelevant. Skip over this list. + if (regionsSeen.contains(notamList.region())) + { + continue; + } + + regionsSeen += notamList.region(); + + auto cleanedList = notamList.cleaned(cancelledNotams); + newNotamLists.append(cleanedList); + } + + return newNotamLists; +} + +void NOTAM::NOTAMProvider::downloadFinished() +{ + auto newNotamList = m_notamLists.value(); + QSet cancelledNotams; + + m_networkReplies.removeAll(nullptr); + for(const auto& networkReply : m_networkReplies) + { + // Paranoid safety checks + if (networkReply.isNull()) + { + continue; + } + if (networkReply->isRunning()) + { + continue; + } + if (!networkReply->isFinished()) + { + continue; + } + if (networkReply->error() != QNetworkReply::NoError) + { + // Network error? Then try again in 5 minutes. + QTimer::singleShot(5min, this, &NOTAMProvider::updateData); + networkReply->deleteLater(); + continue; + } + + auto region = networkReply->property("area").value(); + auto data = networkReply->readAll(); + networkReply->deleteLater(); + + auto jsonDoc = QJsonDocument::fromJson(data); + if (jsonDoc.isNull()) + { + continue; + } + NOTAMList const notamList(jsonDoc, region, &cancelledNotams); + newNotamList.prepend(notamList); + } + m_notamLists = cleaned(newNotamList, cancelledNotams); +} + +bool NOTAM::NOTAMProvider::hasDataForPosition(const QGeoCoordinate& position, bool includeDataThatNeedsUpdate, bool includeRunningDownloads) const +{ + if (!position.isValid()) + { + return true; + } + + for(const auto& notamList : m_notamLists.value()) + { + if (notamList.isOutdated()) + { + continue; + } + if (notamList.needsUpdate() && !includeDataThatNeedsUpdate) + { + continue; + } + + const auto region = notamList.region(); + if (!region.isValid()) + { + continue; + } + if (region.radius() - region.center().distanceTo(position) >= minimumRadiusPoint.toM()) + { + return true; + } + } + + if (includeRunningDownloads) + { + for(const auto& networkReply : m_networkReplies) + { + // Paranoid safety checks + if (networkReply.isNull()) + { + continue; + } + if (!networkReply->isRunning()) + { + continue; + } + auto region = networkReply->property("area").value(); + if (region.radius() - region.center().distanceTo(position) >= minimumRadiusPoint.toM()) + { + return true; + } + } + } + + return false; +} + +void NOTAM::NOTAMProvider::save() const +{ + auto outputFile = QFile(m_stdFileName); + if (outputFile.open(QIODevice::WriteOnly)) + { + QDataStream outputStream(&outputFile); + outputStream << QStringLiteral(GIT_COMMIT); + outputStream << m_readNotamNumbers; + outputStream << m_notamLists.value(); + } +} + +void NOTAM::NOTAMProvider::startRequest(const QGeoCoordinate& coordinate) +{ + if (!coordinate.isValid()) + { + return; + } + + // If data exists for marginRadius around that coordinate or if that data is already + // requested for download, then return immediately. + if (hasDataForPosition(coordinate, false, true)) + { + return; + } + + const QGeoCoordinate coordinateRounded( qRound(coordinate.latitude()), qRound(coordinate.longitude()) ); + auto urlString = u"https://enroute-data.akaflieg-freiburg.de/enrouteProxy/notam.php?" + "locationLongitude=%1&" + "locationLatitude=%2&" + "locationRadius=%3&" + "pageSize=1000"_qs + .arg(coordinateRounded.longitude()) + .arg(coordinateRounded.latitude()) + .arg( qRound(requestRadius.toNM()) ); + QNetworkRequest const request(urlString); + + auto* reply = GlobalObject::networkAccessManager()->get(request); + reply->setProperty("area", QVariant::fromValue(QGeoCircle(coordinateRounded, requestRadius.toM())) ); + + connect(reply, &QNetworkReply::finished, this, &NOTAMProvider::downloadFinished); + connect(reply, &QNetworkReply::errorOccurred, this, &NOTAMProvider::downloadFinished); + + m_networkReplies.append(reply); +} + +void NOTAM::NOTAMProvider::updateData() +{ + startRequest(GlobalObject::positionProvider()->approximateLastValidCoordinate()); + for(const auto& pos : m_controlPoints4FlightRoute.value()) + { + startRequest(pos); + } +} + + +// +// Private Members and Member Computing Methods +// + +QList NOTAM::NOTAMProvider::computeControlPoints4FlightRoute() +{ + auto* route = navigator()->flightRoute(); + if (route == nullptr) + { + return {}; + } + + auto minDistControlPoints = (minimumRadiusPoint-minimumRadiusFlightRoute)*2.0; + + QList result; + result += route->geoPath(); + for (const auto& leg : route->legs()) + { + if (leg.distance() > maximumFlightRouteLegLength) + { + continue; + } + + auto startCoordinate = leg.startPoint().coordinate(); + auto endCoordinate = leg.endPoint().coordinate(); + while (startCoordinate.distanceTo(endCoordinate) > minDistControlPoints.toM()) + { + auto azimuth = startCoordinate.azimuthTo(endCoordinate); + startCoordinate = startCoordinate.atDistanceAndAzimuth(minDistControlPoints.toM(), azimuth); + result += startCoordinate; + } + } + + return result; +} + +QByteArray NOTAM::NOTAMProvider::computeGeoJSON() const +{ + QList result; + QSet coordinatesSeen; + + for(const auto& notamList : m_notamLists.value()) + { + for(const auto& notam : notamList.notams()) + { + auto coordinate = notam.coordinate(); + if (!coordinate.isValid()) + { + continue; + } + + // If we already have a waypoint for that coordinate, then don't add another one. + if (coordinatesSeen.contains(coordinate)) + { + continue; + } + coordinatesSeen += coordinate; + result.append(notam.GeoJSON()); + } + } + + QJsonArray waypointArray; + for(const auto& jsonObject : result) + { + waypointArray.append(jsonObject); + } + QJsonObject jsonObj; + jsonObj.insert(QStringLiteral("type"), "FeatureCollection"); + jsonObj.insert(QStringLiteral("features"), waypointArray); + + QJsonDocument doc; + doc.setObject(jsonObj); + return doc.toJson(); +} + +QDateTime NOTAM::NOTAMProvider::computeLastUpdate() const +{ + auto notamLists = m_notamLists.value(); + if (notamLists.isEmpty()) + { + return {}; + } + return notamLists[0].retrieved(); +} + +QString NOTAM::NOTAMProvider::computeStatus() const +{ + if (!hasDataForPosition(GlobalObject::positionProvider()->approximateLastValidCoordinate(), true, false)) + { + return tr("NOTAMs not current around own position, requesting update"); + } + + for(const auto& pos : m_controlPoints4FlightRoute.value()) + { + if (!hasDataForPosition(pos, true, false)) + { + return tr("NOTAMs not current around waypoint, requesting update"); + } + } + return {}; +} + + + + + + +// +// Private Methods +// + diff --git a/src/notam/NOTAMProvider.h b/src/notam/NOTAMProvider.h new file mode 100644 index 000000000..ea43632c3 --- /dev/null +++ b/src/notam/NOTAMProvider.h @@ -0,0 +1,280 @@ +/*************************************************************************** + * Copyright (C) 2023-2024 by Stefan Kebekus * + * stefan.kebekus@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#pragma once + +#include +#include +#include +#include + +#include "GlobalObject.h" +#include "notam/NOTAMList.h" + +namespace NOTAM { + +/*! \brief Manage NOTAM data and download NOTAM data from the FAA if required. + * + * This class attempts to ensure that for any given point in time, current + * NOTAM data is provided for circles of radius minimumRadiusPoint around the + * current position and every waypoint in the flightRoute, as well as a region + * of at least minimumRadiusFlightRoute around the flight route. + * + * There is API to access the data, and to request NOTAM data for arbitrary + * coordinates. + */ + +class NOTAMProvider : public GlobalObject { + Q_OBJECT + QML_ELEMENT + QML_SINGLETON + +public: + explicit NOTAMProvider(QObject* parent = nullptr); + + // deferred initialization + void deferredInitialization() override; + + // No default constructor, important for QML singleton + explicit NOTAMProvider() = delete; + + /*! \brief Standard destructor */ + ~NOTAMProvider() override; + + // factory function for QML singleton + static NOTAMProvider* create(QQmlEngine* /*unused*/, QJSEngine* /*unused*/) + { + return GlobalObject::notamProvider(); + } + + // NOTAM data is considered to cover a given point if the data covers a + // circle of radius minimumRadiusPoint around that point. + static constexpr Units::Distance minimumRadiusPoint = Units::Distance::fromNM(20.0); + + // NOTAM data is considered to cover the flight route if it covers a region of at least + // minimumRadiusFlightRoute around the route + static constexpr Units::Distance minimumRadiusFlightRoute = Units::Distance::fromNM(3.0); + + + // + // Properties + // + + /*! \brief List of NOTAM points + * + * This property holds GeoJSON, to describe points where NOTAMs are active. + */ + Q_PROPERTY(QByteArray geoJSON READ geoJSON BINDABLE bindableGeoJSON) + + /*! \brief Time of last database update + * + * This is the time of the last successful data download from the FAA + * server. The property holds an invalid QDateTime if no data is available. + */ + Q_PROPERTY(QDateTime lastUpdate READ lastUpdate BINDABLE bindableLastUpdate) + + /*! \brief Status + * + * This is a translated, human-readable text with warnings about incomplete + * NOTAM data, or an empty string in case of no warning. + */ + Q_PROPERTY(QString status READ status BINDABLE bindableStatus) + + + // + // Getter Methods + // + + /*! \brief Getter function for property with the same name + * + * @returns Property geoJSON + */ + Q_REQUIRED_RESULT QByteArray geoJSON() const {return m_geoJSON.value();} + + /*! \brief Getter function for property with the same name + * + * @returns Property geoJSON + */ + Q_REQUIRED_RESULT QBindable bindableGeoJSON() const {return &m_geoJSON;} + + /*! \brief Getter function for the property with the same name + * + * @returns Property lastUpdate + */ + Q_REQUIRED_RESULT QDateTime lastUpdate() const {return {m_lastUpdate};} + + /*! \brief Getter function for the property with the same name + * + * @returns Property lastUpdate + */ + Q_REQUIRED_RESULT QBindable bindableLastUpdate() const {return &m_lastUpdate;} + + /*! \brief Getter function for the property with the same name + * + * @returns Property status + */ + Q_REQUIRED_RESULT QString status() const {return {m_status};} + + /*! \brief Getter function for the property with the same name + * + * @returns Property status + */ + Q_REQUIRED_RESULT QBindable bindableStatus() const {return &m_status;} + + + // + // Methods + // + + + /*! \brief NOTAMs for a given waypoint + * + * The returned list is empty and has a valid property "retrieved" if the + * NOTAMProvider is sure that there are no relevant NOTAMs for the given + * waypoint. + * + * The returned list is empty and has an invalid property "retrieved" if + * the NOTAMProvider has no data. + * + * Calling this method might trigger an update of the NOTAM database. + * Consumers can watch the property lastUpdate to learn about database + * updates. + * + * @param waypoint Waypoint for which the notam list is compiled + * + * @returns List of NOTAMS relevant for the waypoint + */ + Q_REQUIRED_RESULT Q_INVOKABLE NOTAMList notams(const GeoMaps::Waypoint& waypoint); + + /*! \brief Check if a NOTAM number is registred as read + * + * @param number Notam number + * + * @returns True is notam is known as read + */ + Q_REQUIRED_RESULT Q_INVOKABLE bool isRead(const QString& number) const { return m_readNotamNumbers.contains(number); } + + /*! \brief Register NOTAM number as read or unread + * + * @param number Notam number + * + * @param read True if notam is to be registred as read + */ + Q_INVOKABLE void setRead(const QString& number, bool read); + +private: + + +private: + Q_DISABLE_COPY_MOVE(NOTAMProvider) + + + // + // Private Methods + // + + // Removes outdated NOTAMs and outdated NOTAMLists. + Q_REQUIRED_RESULT static QList cleaned(const QList& notamLists, const QSet& cancelledNotams = {}); + + // This method reads the incoming data from network replies and adds it to + // the database. It cleans up the list of network replies in + // m_networkReplies. On error, it requests a call to updateData in five + // minutes. This method is connected to signals QNetworkReply::finished and + // QNetworkReply::errorOccurred of the QNetworkReply contained in the list + // in m_networkReply. + void downloadFinished(); + + // Check if current NOTAM data exists for a circle of radius minimalRadius + // around position. This method ignores outdated NOTAM data. An invalid + // position is always considered to be covered. + // + // includeDataThatNeedsUpdate: If true, then also count NOTAM lists that + // need an update as NOTAM data + // + // includeRunningDownloads: If true, then also count running downloads as + // NOTAM data + Q_REQUIRED_RESULT bool hasDataForPosition(const QGeoCoordinate& position, bool includeDataThatNeedsUpdate, bool includeRunningDownloads) const; + + // Save NOTAM data to a file, using the filename found in m_stdFileName. + // There are no error checks of any kind. The propertyNotifier ensures that + // the method save() is called whenever m_notamLists changes. + void save() const; + QPropertyNotifier m_saveNotifier; + + // Request NOTAM data from the FAA, for a circle of radius requestRadius + // around the coordinate. For performance reasons, the request will be + // ignored if existing NOTAM data or ongoing download requests cover the + // position alreads. + void startRequest(const QGeoCoordinate& coordinate); + + // Checks if NOTAM data is available for an area of marginRadius around the + // current position and around the current flight route. If not, requests + // the data. + void updateData(); + + + // + // Private Members and Member Computing Methods + // + + // List with numbers of notams that have been marked as read + QList m_readNotamNumbers; + + // List of pending network requests + QList> m_networkReplies; + + // List of NOTAMLists, sorted so that newest lists come first + QProperty> m_notamLists; + + // This is a list of control points. The computing function guarantees that + // the NOTAM data covers a region of at least marginRadiusFlightRoute around + // the route if the data covers a circle of radius marginRadius around every + // control point point. Exeption: For performance reasons, this guarantee is + // lifted if the flight route contains a leg of size > + // maximumFlightRouteLegLength. + QProperty> m_controlPoints4FlightRoute; + Q_REQUIRED_RESULT static QList computeControlPoints4FlightRoute(); + + // GeoJSON, for use in map + QProperty m_geoJSON; + Q_REQUIRED_RESULT QByteArray computeGeoJSON() const; + + // Time of last update to data + QProperty m_lastUpdate; + Q_REQUIRED_RESULT QDateTime computeLastUpdate() const; + + // Filename for loading/saving NOTAM data + QProperty m_status; + Q_REQUIRED_RESULT QString computeStatus() const; + + // Filename for loading/saving NOTAM data + QString m_stdFileName { QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)+u"/notam.dat"_qs }; + + // NOTAM data is considered to cover the flight route if it covers a region + // of at least marginRadiusFlightRoute around the route + static constexpr Units::Distance maximumFlightRouteLegLength = Units::Distance::fromNM(200.0); + + // Requests for Notam data are requestRadius around given position. This is + // the maximum that FAA API currently allows (FAA max is 100NM) + static constexpr Units::Distance requestRadius = Units::Distance::fromNM(99.0); +}; + +} // namespace NOTAM + diff --git a/src/notam/NotamProvider.cpp b/src/notam/NotamProvider.cpp deleted file mode 100644 index 338a4a413..000000000 --- a/src/notam/NotamProvider.cpp +++ /dev/null @@ -1,582 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2023-2024 by Stefan Kebekus * - * stefan.kebekus@gmail.com * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -#include -#include -#include -#include -#include - -#include "navigation/Navigator.h" -#include "notam/NotamProvider.h" -#include "positioning/PositionProvider.h" - -using namespace std::chrono_literals; - - - -// -// Constructor/Destructor -// - -NOTAM::NotamProvider::NotamProvider(QObject* parent) : - GlobalObject(parent) -{ - // Set stdFileName for saving and loading NOTAM data - m_stdFileName = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)+u"/notam.dat"_qs; -} - - -void NOTAM::NotamProvider::deferredInitialization() -{ - // Wire up updateData. Check NOTAM database every 10 seconds after start, every 11 minutes, and whenever the flight route changes. - auto* timer = new QTimer(this); - timer->start(11min); - connect(timer, &QTimer::timeout, this, &NOTAM::NotamProvider::updateData); - connect(navigator()->flightRoute(), &Navigation::FlightRoute::waypointsChanged, this, &NOTAM::NotamProvider::updateData); - QTimer::singleShot(10s, this, &NOTAM::NotamProvider::updateData); - - // Wire up clean(). Clean the data every 61 minutes. - timer = new QTimer(this); - timer->start(61min); - connect(timer, &QTimer::timeout, this, &NOTAM::NotamProvider::clean); - - // Save the NOTAM data every time that the database changes - connect(this, &NOTAM::NotamProvider::dataChanged, this, &NOTAM::NotamProvider::save, Qt::QueuedConnection); - - // Load NOTAM data from file in stdFileName, then clean the data (which potentially triggers save()). - auto inputFile = QFile(m_stdFileName); - if (inputFile.open(QIODevice::ReadOnly)) - { - QDataStream inputStream(&inputFile); - QString magicString; - inputStream >> magicString; - if (magicString == QStringLiteral(GIT_COMMIT)) - { - inputStream >> m_readNotamNumbers; - inputStream >> m_notamLists; - } - } - clean(); -} - - -NOTAM::NotamProvider::~NotamProvider() -{ - foreach(auto networkReply, m_networkReplies) - { - if (networkReply.isNull()) - { - continue; - } - networkReply->abort(); - } - - m_notamLists.clear(); - m_networkReplies.clear(); -} - - - -// -// Getter Methods -// - -QByteArray NOTAM::NotamProvider::GeoJSON() const -{ - QList result; - QSet coordinatesSeen; - QSet regionsCovered; - - foreach(auto notamList, m_notamLists) - { - foreach(auto notam, notamList.notams()) - { - if (!notam.isValid() || notam.isOutdated()) - { - continue; - } - auto coordinate = notam.coordinate(); - if (!coordinate.isValid()) - { - continue; - } - if (!notamList.region().contains(coordinate)) - { - continue; - } - - - // If we already have a waypoint for that coordinate, then don't add another one. - if (coordinatesSeen.contains(coordinate)) - { - continue; - } - - // If the coordinate has already been handled by an earlier (=newer) notamList, - // then don't add it here. - bool hasBeenCovered = false; - foreach(auto region, regionsCovered) - { - if (region.contains(coordinate)) - { - hasBeenCovered = true; - break; - } - } - if (hasBeenCovered) - { - continue; - } - - coordinatesSeen += coordinate; - result.append(notam.GeoJSON()); - } - - regionsCovered += notamList.region(); - } - - - QJsonArray waypointArray; - foreach (const auto& jsonObject, result) - { - waypointArray.append(jsonObject); - } - - QJsonObject jsonObj; - jsonObj.insert(QStringLiteral("type"), "FeatureCollection"); - jsonObj.insert(QStringLiteral("features"), waypointArray); - - QJsonDocument doc; - doc.setObject(jsonObj); - return doc.toJson(); -} - - -QList NOTAM::NotamProvider::waypoints() const -{ - QList result; - QSet coordinatesSeen; - QSet regionsCovered; - - foreach(auto notamList, m_notamLists) - { - foreach(auto notam, notamList.notams()) - { - if (!notam.isValid() || notam.isOutdated()) - { - continue; - } - auto coordinate = notam.coordinate(); - if (!coordinate.isValid()) - { - continue; - } - if (!notamList.region().contains(coordinate)) - { - continue; - } - - - // If we already have a waypoint for that coordinate, then don't add another one. - if (coordinatesSeen.contains(coordinate)) - { - continue; - } - - // If the coordinate has already been handled by an earlier (=newer) notamList, - // then don't add it here. - bool hasBeenCovered = false; - foreach(auto region, regionsCovered) - { - if (region.contains(coordinate)) - { - hasBeenCovered = true; - break; - } - } - if (hasBeenCovered) - { - continue; - } - - coordinatesSeen += coordinate; - result.append(coordinate); - } - - regionsCovered += notamList.region(); - } - - return result; -} - - - -// -// Methods -// - -NOTAM::NotamList NOTAM::NotamProvider::notams(const GeoMaps::Waypoint& waypoint) -{ - // Paranoid safety checks - if (!waypoint.coordinate().isValid()) - { - return {}; - } - - // Check if notams for the location are present in our database. - // Go through the database, oldest to newest. - foreach (auto notamList, m_notamLists) - { - // Disregard outdated notamLists - if (notamList.isOutdated()) - { - continue; - } - - if (notamList.region().contains(waypoint.coordinate())) - { - // If motamList needs an update, then ask for an update - if (notamList.needsUpdate()) - { - startRequest(waypoint.coordinate()); - } - - return notamList.restricted(waypoint); - } - } - - // Check if internet requests notams for the location are pending. - // In that case, return an empty list. - foreach(auto networkReply, m_networkReplies) - { - // Paranoid safety checks - if (networkReply.isNull()) - { - continue; - } - auto area = networkReply->property("area").value(); - if (!area.isValid()) - { - continue; - } - if (area.contains(waypoint.coordinate())) - { - return {}; - } - } - - // We have no data for the waypoint and no pending internet requests. So, - // start a new internet request and return an empty list. - startRequest(waypoint.coordinate()); - return {}; -} - - -void NOTAM::NotamProvider::setRead(const QString& number, bool read) -{ - if (read) - { - m_readNotamNumbers.prepend(number); - if (m_readNotamNumbers.size() > 50) - { - m_readNotamNumbers.remove(50); - } - } - else - { - m_readNotamNumbers.removeAll(number); - } - save(); -} - - - -// -// Private Slots -// - -void NOTAM::NotamProvider::clean() -{ - - QList newNotamLists; - QSet regionsSeen; - bool haveChange = false; - - // Iterate over notamLists, newest lists first - foreach(auto notamList, m_notamLists) - { - // If this notamList is outdated, then so all all further ones. We can thus end here. - if (notamList.isOutdated()) - { - haveChange = true; - break; - } - - // If a newer notamList has the same region, then the data in this list - // is irrelevant. Skip over this list. - if (regionsSeen.contains(notamList.region())) - { - haveChange = true; - continue; - } - - regionsSeen += notamList.region(); - - auto cleanedList = notamList.cleaned(m_cancelledNotamNumbers); - if (cleanedList.notams().size() != notamList.notams().size()) - { - haveChange = true; - } - newNotamLists.append(cleanedList); - } - m_cancelledNotamNumbers.clear(); - - if (haveChange) - { - m_notamLists = newNotamLists; - emit dataChanged(); - } -} - - -void NOTAM::NotamProvider::downloadFinished() -{ - - bool newDataAdded = false; - m_networkReplies.removeAll(nullptr); - foreach(auto networkReply, m_networkReplies) - { - // Paranoid safety checks - if (networkReply.isNull()) - { - continue; - } - if (networkReply->isRunning()) - { - continue; - } - if (!networkReply->isFinished()) - { - continue; - } - if (networkReply->error() != QNetworkReply::NoError) - { - qWarning() << "FAA NOTAM Server returned with an error." << networkReply->error(); - networkReply->deleteLater(); - continue; - } - - auto region = networkReply->property("area").value(); - auto data = networkReply->readAll(); - networkReply->deleteLater(); - - auto jsonDoc = QJsonDocument::fromJson(data); - if (jsonDoc.isNull()) - { - qWarning() << u"FAA NOTAM Server returned with invalid or empty JSON data."_qs; - continue; - } - NotamList const notamList(jsonDoc, region, &m_cancelledNotamNumbers); - qWarning() << u"FAA NOTAM Server returned with %1 NOTAMs."_qs.arg(notamList.notams().size()); - m_notamLists.prepend(notamList); - newDataAdded = true; - } - - if (newDataAdded) - { - clean(); - m_lastUpdate = QDateTime::currentDateTimeUtc(); - emit dataChanged(); - } -} - - -void NOTAM::NotamProvider::save() const -{ - auto outputFile = QFile(m_stdFileName); - if (outputFile.open(QIODevice::WriteOnly)) - { - QDataStream outputStream(&outputFile); - outputStream << QStringLiteral(GIT_COMMIT); - outputStream << m_readNotamNumbers; - outputStream << m_notamLists; - } -} - - -void NOTAM::NotamProvider::updateData() -{ - // Check if Notam data is available for a circle of marginRadius around - // the current position. - auto position = Positioning::PositionProvider::lastValidCoordinate(); - if (position.isValid()) - { - auto _range = range(position); - if (_range < marginRadius) - { - startRequest(position); - } - } - - auto* route = navigator()->flightRoute(); - if (route != nullptr) - { - foreach(auto leg, route->legs()) - { - QGeoCoordinate const startPoint = leg.startPoint().coordinate(); - if (startPoint.isValid()) - { - auto _range = range(startPoint); - if (_range < marginRadius) - { - startRequest(startPoint); - } - } - - QGeoCoordinate const endPoint = leg.endPoint().coordinate(); - if (endPoint.isValid()) - { - auto _range = range(endPoint); - if (_range < marginRadius) - { - startRequest(endPoint); - } - } - -/* - QGeoCoordinate startPoint = leg.startPoint().coordinate(); - QGeoCoordinate const endPoint = leg.endPoint().coordinate(); - if (!startPoint.isValid() || !endPoint.isValid()) - { - continue; - } - - while(true) - { - // Check if the range at the startPoint at least marginRadius+1NM - // If not, request data for startPoint and start over - auto rangeAtStartPoint = range(startPoint); - if (rangeAtStartPoint < marginRadius+Units::Distance::fromNM(1)) - { - startRequest(startPoint); - continue; - } - - // Check if every point between startPoint and endPoint has a range - // of at least marginRadius. If so, there is nothing to do for this - // and we continue with the next leg. - auto distanceToEndPoint = Units::Distance::fromM(startPoint.distanceTo(endPoint)); - if (rangeAtStartPoint > distanceToEndPoint+marginRadius) - { - break; - } - - // Move the startPoint closer to the endPoint, so all points between - // the new and the old startPoint have a range of at least - // marginRadius. Then start over. - auto azimuth = startPoint.azimuthTo(endPoint); - startPoint = startPoint.atDistanceAndAzimuth((rangeAtStartPoint-marginRadius).toM(), azimuth); - } -*/ - } - } - -} - - - -// -// Private Methods -// - -Units::Distance NOTAM::NotamProvider::range(const QGeoCoordinate& position) -{ - auto result = Units::Distance::fromM(-1.0); - - if (!position.isValid()) - { - return result; - } - - // If we have a NOTAM list that contains the position - // within half its radius, then stop. - foreach (auto notamList, m_notamLists) - { - if (notamList.isOutdated()) - { - continue; - } - - auto region = notamList.region(); - if (!region.isValid()) - { - continue; - } - auto rangeInM = region.radius() - region.center().distanceTo(position); - result = qMax(result, Units::Distance::fromM(rangeInM)); - } - - foreach(auto networkReply, m_networkReplies) - { - // Paranoid safety checks - if (networkReply.isNull()) - { - continue; - } - auto region = networkReply->property("area").value(); - if (!region.isValid()) - { - continue; - } - auto rangeInM = region.radius() - region.center().distanceTo(position); - result = qMax(result, Units::Distance::fromM(rangeInM)); - } - - return result; -} - - -void NOTAM::NotamProvider::startRequest(const QGeoCoordinate& coordinate) -{ - if (!coordinate.isValid()) - { - return; - } - const QGeoCoordinate coordinateRounded( qRound(coordinate.latitude()), qRound(coordinate.longitude()) ); - - auto urlString = u"https://enroute-data.akaflieg-freiburg.de/enrouteProxy/notam.php?" - "locationLongitude=%1&" - "locationLatitude=%2&" - "locationRadius=%3&" - "pageSize=1000"_qs - .arg(coordinateRounded.longitude()) - .arg(coordinateRounded.latitude()) - .arg( qRound(1.2*requestRadius.toNM()) ); - qWarning() << "NOTAM::NotamProvider::startRequest" << urlString; - QNetworkRequest const request(urlString); - - auto* reply = GlobalObject::networkAccessManager()->get(request); - reply->setProperty("area", QVariant::fromValue(QGeoCircle(coordinateRounded, requestRadius.toM())) ); - - m_networkReplies.append(reply); - connect(reply, &QNetworkReply::finished, this, &NOTAM::NotamProvider::downloadFinished); - connect(reply, &QNetworkReply::errorOccurred, this, &NOTAM::NotamProvider::downloadFinished); -} diff --git a/src/notam/NotamProvider.h b/src/notam/NotamProvider.h deleted file mode 100644 index c2d5e7208..000000000 --- a/src/notam/NotamProvider.h +++ /dev/null @@ -1,206 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2023 by Stefan Kebekus * - * stefan.kebekus@gmail.com * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -#pragma once - -#include -#include - -#include "GlobalObject.h" -#include "notam/NotamList.h" - -namespace NOTAM { - -/*! \brief This extremely simple class holds a the data item of a NOTAM */ - -class NotamProvider : public GlobalObject { - Q_OBJECT - QML_ELEMENT - QML_SINGLETON - -public: - explicit NotamProvider(QObject* parent = nullptr); - - // deferred initialization - void deferredInitialization() override; - - // No default constructor, important for QML singleton - explicit NotamProvider() = delete; - - /*! \brief Standard destructor */ - ~NotamProvider() override; - - // factory function for QML singleton - static NOTAM::NotamProvider* create(QQmlEngine* /*unused*/, QJSEngine* /*unused*/) - { - return GlobalObject::notamProvider(); - } - - - - // - // Properties - // - - - /*! \brief List of NOTAM points - * - * This property holds GeoJSON, to describe points where NOTAMs are active. - */ - Q_PROPERTY(QByteArray GeoJSON READ GeoJSON NOTIFY dataChanged) - - /*! \brief Time of last database update */ - Q_PROPERTY(QDateTime lastUpdate READ lastUpdate NOTIFY dataChanged) - - /*! \brief Waypoints with Notam items, for presentation in a map */ - Q_PROPERTY(QList waypoints READ waypoints NOTIFY dataChanged) - - - - // - // Getter Methods - // - - - /*! \brief Getter function for property with the same name - * - * @returns Property GeoJSON - */ - [[nodiscard]] QByteArray GeoJSON() const; - - /*! \brief Getter function for the property with the same name - * - * @returns Property lastUpdate - */ - Q_REQUIRED_RESULT QDateTime lastUpdate() const { return m_lastUpdate; } - - /*! \brief Getter function for the property with the same name - * - * @returns Property waypoints - */ - Q_REQUIRED_RESULT QList waypoints() const; - - - - // - // Methods - // - - /*! \brief Notams for a given waypoint - * - * The returned list is empty and has a valid property "retrieved" if the - * NotamProvider is sure that there are no relevant notams for the given - * waypoint. - * - * The returned list is empty and has an invalid property "retrieved" if - * the NotamProvider has no data. - * - * Calling this method might trigger an update of the Notam database. - * Consumers can watch the property lastUpdate to learn about database - * updates. - * - * @param waypoint Waypoint for which the notam list is compiled - * - * @returns List of Notams relevant for the waypoint - */ - [[nodiscard]] Q_INVOKABLE NOTAM::NotamList notams(const GeoMaps::Waypoint& waypoint); - - /*! \brief Check is a notam number is registred as read - * - * @param number Notam number - * - * @returns True is notam is known as read - */ - [[nodiscard]] Q_INVOKABLE bool isRead(const QString& number) { return m_readNotamNumbers.contains(number); } - - /*! \brief Register notam number as read or unread - * - * @param number Notam number - * - * @param read True if notam is to be registred as read - */ - Q_INVOKABLE void setRead(const QString& number, bool read); - -signals: - /*! \brief Notifier signal */ - void dataChanged(); - -private slots: - // Removes outdated and irrelevant data from the database. This slot is called - // once per hour. - void clean(); - - // This slot is connected to signals QNetworkReply::finished and - // QNetworkReply::errorOccurred of the QNetworkReply contained in the list - // in m_networkReply. This method reads the incoming data and adds it to the - // database - void downloadFinished(); - - // Save NOTAM data to file whose name is found in m_stdFileName. There are - // no error checks of any kind. - void save() const; - - // Checks if NOTAM data is available for an area of marginRadius around the - // current position and around the current flight route. If not, requests - // the data. - void updateData(); - -private: - Q_DISABLE_COPY_MOVE(NotamProvider) - - // Compute the radius of the circle around the waypoint that is covered by - // existing or requested notam data. Returns Units::Distance::fromM(-1) if - // the waypoint is not covered by data. - Units::Distance range(const QGeoCoordinate& position); - - // Request Notam data from the FAA, for a circle of radius requestRadius - // around the coordinate. - void startRequest(const QGeoCoordinate& coordinate); - - // List with numbers of notams that have been marked as read - QList m_readNotamNumbers; - - // Set with numbers of notams that have been cancelled - QSet m_cancelledNotamNumbers; - - // List of pending network requests - QList> m_networkReplies; - - // List of NotamLists, sorted so that newest lists come first - QList m_notamLists; - - // Time of last update to data - QDateTime m_lastUpdate; - - // Filename for loading/saving NOTAM data - QString m_stdFileName; - - // The method updateDate() ensures that data is requested for marginRadius around - // own position and current flight route. - static constexpr Units::Distance marginRadius = Units::Distance::fromNM(5.0); - - // Requests for Notam data are requestRadius around given position. - // This is the maximum that FAA API currently allows (FAA max is 100NM, but - // the number here is multiplied internally with a factor of 1.2) - static constexpr Units::Distance requestRadius = Units::Distance::fromNM(83.0); -}; - -} // namespace NOTAM - diff --git a/src/notification/Notification.h b/src/notification/Notification.h index b59a0e92c..e97e437de 100644 --- a/src/notification/Notification.h +++ b/src/notification/Notification.h @@ -22,11 +22,9 @@ #include #include -#include #include "units/Timespan.h" - namespace Notifications { /*! \brief Base class for all notifications diff --git a/src/platform/FileExchange_Linux.cpp b/src/platform/FileExchange_Linux.cpp index 5de40178e..36e9d33f8 100644 --- a/src/platform/FileExchange_Linux.cpp +++ b/src/platform/FileExchange_Linux.cpp @@ -54,7 +54,7 @@ auto Platform::FileExchange::shareContent(const QByteArray& content, const QStri QMimeDatabase const mimeDataBase; QMimeType const mime = mimeDataBase.mimeTypeForName(mimeType); - auto fileNameX = QFileDialog::getSaveFileName(nullptr, tr("Export flight route"), QDir::homePath()+"/"+fileNameTemplate+"."+mime.preferredSuffix(), tr("%1 (*.%2);;All files (*)").arg(mime.comment(), mime.preferredSuffix())); + auto fileNameX = QFileDialog::getSaveFileName(nullptr, tr("Export Data"), QDir::homePath()+"/"+fileNameTemplate+"."+mime.preferredSuffix(), tr("%1 (*.%2);;All files (*)").arg(mime.comment(), mime.preferredSuffix())); if (fileNameX.isEmpty()) { return QStringLiteral("abort"); diff --git a/src/platform/FileExchange_MacOS.cpp b/src/platform/FileExchange_MacOS.cpp index 54aa69324..f2e89e428 100644 --- a/src/platform/FileExchange_MacOS.cpp +++ b/src/platform/FileExchange_MacOS.cpp @@ -54,7 +54,7 @@ auto Platform::FileExchange::shareContent(const QByteArray& content, const QStri QMimeDatabase const mimeDataBase; QMimeType const mime = mimeDataBase.mimeTypeForName(mimeType); - auto fileNameX = QFileDialog::getSaveFileName(nullptr, tr("Export flight route"), QDir::homePath()+"/"+fileNameTemplate+"."+mime.preferredSuffix(), tr("%1 (*.%2);;All files (*)").arg(mime.comment(), mime.preferredSuffix())); + auto fileNameX = QFileDialog::getSaveFileName(nullptr, tr("Export Data"), QDir::homePath()+"/"+fileNameTemplate+"."+mime.preferredSuffix(), tr("%1 (*.%2);;All files (*)").arg(mime.comment(), mime.preferredSuffix())); if (fileNameX.isEmpty()) { return QStringLiteral("abort"); diff --git a/src/platform/PlatformAdaptor_Abstract.cpp b/src/platform/PlatformAdaptor_Abstract.cpp index c484a2f0c..df3e349d0 100644 --- a/src/platform/PlatformAdaptor_Abstract.cpp +++ b/src/platform/PlatformAdaptor_Abstract.cpp @@ -24,7 +24,7 @@ #include #include - +#include "notam/NOTAMProvider.h" #include "platform/PlatformAdaptor_Abstract.h" #include "qimage.h" @@ -71,11 +71,21 @@ QString Platform::PlatformAdaptor_Abstract::systemInfo() result += u"%1%2\n"_qs.arg("Locale", language()); result += u"
\n"_qs; - QString updateCheckTimeStamp = QSettings().value(QStringLiteral("DataManager/MapListTimeStamp")).toString(); + QString const updateCheckTimeStamp + = QSettings().value(QStringLiteral("DataManager/MapListTimeStamp")).toString(); result += u"

Data

\n"_qs; result += u"\n"_qs; result += u"\n"_qs; result += u"\n"_qs.arg("Last Map Update Check", updateCheckTimeStamp); + auto lastNOTAMUpdate = notamProvider()->lastUpdate(); + if (lastNOTAMUpdate.isValid()) + { + result += u"\n"_qs.arg("Last NOTAM download", notamProvider()->lastUpdate().toString()); + } + else + { + result += u"\n"_qs.arg("Last NOTAM download", "NONE"); + } result += u"
%1%2
%1%2
%1%2

\n"_qs; return result; diff --git a/src/positioning/Geoid.cpp b/src/positioning/Geoid.cpp index e83f165f4..21b6a25fb 100644 --- a/src/positioning/Geoid.cpp +++ b/src/positioning/Geoid.cpp @@ -39,7 +39,7 @@ void Positioning::Geoid::readEGM() { QFile file(QStringLiteral(":/WW15MGH.DAC")); - qint64 egm96_size_2 = (qint64)egm96_size * (qint64)2; + qint64 const egm96_size_2 = (qint64) egm96_size * (qint64) 2; if (!file.open(QIODevice::ReadOnly) || file.size() != (egm96_size_2)) { @@ -49,7 +49,8 @@ void Positioning::Geoid::readEGM() egm.resize(egm96_size); - qint64 nread = file.read(static_cast(static_cast(egm.data())), egm96_size_2); + qint64 const nread = file.read(static_cast(static_cast(egm.data())), + egm96_size_2); file.close(); @@ -104,17 +105,16 @@ auto Positioning::Geoid::separation(const QGeoCoordinate& coord) -> Units::Dista // integer row north and south of latitude // - int north = qFloor(row(latitude)); - int south = (north + 1) < egm96_rows? (north + 1) : north; + int const north = qFloor(row(latitude)); + int const south = (north + 1) < egm96_rows ? (north + 1) : north; // integer column west and east of latitude // - int west = qFloor(col(longitude)) % egm96_cols; - int east = (west + 1) % egm96_cols; + int const west = qFloor(col(longitude)) % egm96_cols; + int const east = (west + 1) % egm96_cols; - auto geoid = [&] (int row, int col) -> qreal - { - int idx = row * egm96_cols + col; + auto geoid = [&](int row, int col) -> qreal { + int const idx = row * egm96_cols + col; return idx >=0 && idx < egm96_size ? egm.at(idx) * 0.01 : 0.0; }; @@ -124,10 +124,8 @@ auto Positioning::Geoid::separation(const QGeoCoordinate& coord) -> Units::Dista double interpolated = 0; double row_dist = row(latitude) - north; double col_dist = col(longitude) - qFloor(col(longitude)); - for (int irow : {north, south}) - { - for (int icol : {west, east}) - { + for (int const irow : {north, south}) { + for (int const icol : {west, east}) { interpolated += geoid(irow, icol) * (1 - row_dist) * (1 - col_dist); col_dist = 1 - col_dist; } diff --git a/src/positioning/PositionInfo.cpp b/src/positioning/PositionInfo.cpp index e03c7ffcb..55d8a2829 100644 --- a/src/positioning/PositionInfo.cpp +++ b/src/positioning/PositionInfo.cpp @@ -22,12 +22,9 @@ #include "geomaps/GeoMapProvider.h" #include "positioning/PositionInfo.h" - Positioning::PositionInfo::PositionInfo(const QGeoPositionInfo &info) -{ - m_positionInfo = info; -} - + : m_positionInfo(info) +{} auto Positioning::PositionInfo::groundSpeed() const -> Units::Speed { diff --git a/src/positioning/PositionInfo.h b/src/positioning/PositionInfo.h index 2f251c4f0..2fcf03e90 100644 --- a/src/positioning/PositionInfo.h +++ b/src/positioning/PositionInfo.h @@ -186,8 +186,8 @@ class PositionInfo private: - QGeoPositionInfo m_positionInfo {}; - Units::Distance m_terrainAMSL {}; + QGeoPositionInfo m_positionInfo; + Units::Distance m_terrainAMSL{}; Units::Distance m_trueAltitudeAGL {}; }; diff --git a/src/positioning/PositionInfoSource_Abstract.h b/src/positioning/PositionInfoSource_Abstract.h index c57428aa5..11492092f 100644 --- a/src/positioning/PositionInfoSource_Abstract.h +++ b/src/positioning/PositionInfoSource_Abstract.h @@ -192,10 +192,10 @@ class PositionInfoSource_Abstract : public QObject { Positioning::PositionInfo m_positionInfo; QTimer m_positionInfoTimer; - QString m_sourceName {}; - QString m_statusString {}; + QString m_sourceName; + QString m_statusString; - bool _receivingPositionInfo {false}; + bool _receivingPositionInfo{false}; }; } // namespace Positioning diff --git a/src/positioning/PositionInfoSource_Satellite.cpp b/src/positioning/PositionInfoSource_Satellite.cpp index 297684c2d..3a75157f9 100644 --- a/src/positioning/PositionInfoSource_Satellite.cpp +++ b/src/positioning/PositionInfoSource_Satellite.cpp @@ -21,15 +21,15 @@ #include "positioning/Geoid.h" #include "positioning/PositionInfoSource_Satellite.h" - -Positioning::PositionInfoSource_Satellite::PositionInfoSource_Satellite(QObject *parent) : PositionInfoSource_Abstract(parent) +Positioning::PositionInfoSource_Satellite::PositionInfoSource_Satellite(QObject *parent) + : PositionInfoSource_Abstract(parent) + , source(QGeoPositionInfoSource::createDefaultSource(this)) { - source = QGeoPositionInfoSource::createDefaultSource(this); if (source != nullptr) { source->setPreferredPositioningMethods(QGeoPositionInfoSource::AllPositioningMethods); source->setUpdateInterval(1000); - QString sName = source->sourceName(); + QString const sName = source->sourceName(); if (sName.isEmpty()) { setSourceName( tr("Built-in receiver") ); } else { diff --git a/src/positioning/PositionInfoSource_Satellite.h b/src/positioning/PositionInfoSource_Satellite.h index bc0931a0e..1524b39c2 100644 --- a/src/positioning/PositionInfoSource_Satellite.h +++ b/src/positioning/PositionInfoSource_Satellite.h @@ -48,6 +48,9 @@ class PositionInfoSource_Satellite : public PositionInfoSource_Abstract */ explicit PositionInfoSource_Satellite(QObject *parent = nullptr); + // Default desctructor + ~PositionInfoSource_Satellite() override = default; + /*! \brief startUpdates * * Requests permissions if necessary and starts to provide data diff --git a/src/positioning/PositionProvider.cpp b/src/positioning/PositionProvider.cpp index 82a60700c..b81690d58 100644 --- a/src/positioning/PositionProvider.cpp +++ b/src/positioning/PositionProvider.cpp @@ -31,11 +31,11 @@ Positioning::PositionProvider::PositionProvider(QObject *parent) : PositionInfoSource_Abstract(parent) { // Restore the last valid coordiante and track - QSettings settings; + QSettings const settings; QGeoCoordinate tmp; - tmp.setLatitude(settings.value(QStringLiteral("PositionProvider/lastValidLatitude"), m_lastValidCoordinate.latitude()).toDouble()); - tmp.setLongitude(settings.value(QStringLiteral("PositionProvider/lastValidLongitude"), m_lastValidCoordinate.longitude()).toDouble()); - tmp.setAltitude(settings.value(QStringLiteral("PositionProvider/lastValidAltitude"), m_lastValidCoordinate.altitude()).toDouble()); + tmp.setLatitude(settings.value(QStringLiteral("PositionProvider/lastValidLatitude"), m_lastValidCoordinate.value().latitude()).toDouble()); + tmp.setLongitude(settings.value(QStringLiteral("PositionProvider/lastValidLongitude"), m_lastValidCoordinate.value().longitude()).toDouble()); + tmp.setAltitude(settings.value(QStringLiteral("PositionProvider/lastValidAltitude"), m_lastValidCoordinate.value().altitude()).toDouble()); if ((tmp.type() == QGeoCoordinate::Coordinate2D) || (tmp.type() == QGeoCoordinate::Coordinate3D)) { m_lastValidCoordinate = tmp; } @@ -62,16 +62,23 @@ Positioning::PositionProvider::PositionProvider(QObject *parent) : PositionInfoS // Update properties updateStatusString(); + m_approximateLastValidCoordinate = m_lastValidCoordinate.value(); + connect(this, &Positioning::PositionProvider::lastValidCoordinateChanged, this, [this]() { + if (m_approximateLastValidCoordinate.value().isValid() + && (m_approximateLastValidCoordinate.value().distanceTo(m_lastValidCoordinate) < 10000)) + { + return; + } + m_approximateLastValidCoordinate = m_lastValidCoordinate.value(); + }); } void Positioning::PositionProvider::deferredInitialization() const { - connect(GlobalObject::trafficDataProvider(), &Traffic::TrafficDataProvider::positionInfoChanged, this, &PositionProvider::onPositionUpdated); connect(GlobalObject::trafficDataProvider(), &Traffic::TrafficDataProvider::pressureAltitudeChanged, this, &PositionProvider::onPressureAltitudeUpdated); - } @@ -189,9 +196,9 @@ void Positioning::PositionProvider::savePositionAndTrack() { // Save the last valid coordinate QSettings settings; - settings.setValue(QStringLiteral("PositionProvider/lastValidLatitude"), m_lastValidCoordinate.latitude()); - settings.setValue(QStringLiteral("PositionProvider/lastValidLongitude"), m_lastValidCoordinate.longitude()); - settings.setValue(QStringLiteral("PositionProvider/lastValidAltitude"), m_lastValidCoordinate.altitude()); + settings.setValue(QStringLiteral("PositionProvider/lastValidLatitude"), m_lastValidCoordinate.value().latitude()); + settings.setValue(QStringLiteral("PositionProvider/lastValidLongitude"), m_lastValidCoordinate.value().longitude()); + settings.setValue(QStringLiteral("PositionProvider/lastValidAltitude"), m_lastValidCoordinate.value().altitude()); // Save the last valid track settings.setValue(QStringLiteral("PositionProvider/lastValidTrack"), m_lastValidTT.toDEG()); @@ -230,7 +237,7 @@ auto Positioning::PositionProvider::lastValidCoordinate() -> QGeoCoordinate if (positionProvider == nullptr) { return {}; } - return positionProvider->m_lastValidCoordinate; + return positionProvider->m_lastValidCoordinate.value(); } diff --git a/src/positioning/PositionProvider.h b/src/positioning/PositionProvider.h index 3ffed1416..3cf72d4dc 100644 --- a/src/positioning/PositionProvider.h +++ b/src/positioning/PositionProvider.h @@ -20,6 +20,7 @@ #pragma once +#include #include #include "GlobalObject.h" @@ -67,12 +68,28 @@ class PositionProvider : public PositionInfoSource_Abstract // No default constructor, important for QML singleton explicit PositionProvider() = delete; + /*! \brief Standard destructor */ + ~PositionProvider() override = default; + + // factory function for QML singleton static Positioning::PositionProvider* create(QQmlEngine* /*unused*/, QJSEngine* /*unused*/) { return GlobalObject::positionProvider(); } + + // + // PROPERTIES + // + + /*! \brief Approximate last valid coordinate + * + * This property equals lastValidCoordinate, except that it is updated only every ten + * minutes. + */ + Q_PROPERTY(QGeoCoordinate approximateLastValidCoordinate READ approximateLastValidCoordinate BINDABLE bindableApproximateLastValidCoordinate) + /*! \brief Last valid coordinate reading * * This property holds the last valid coordinate known. At the first @@ -82,12 +99,6 @@ class PositionProvider : public PositionInfoSource_Abstract */ Q_PROPERTY(QGeoCoordinate lastValidCoordinate READ lastValidCoordinate NOTIFY lastValidCoordinateChanged) - /*! \brief Getter function for the property with the same name - * - * @returns Property lastValidCoordinate - */ - static auto lastValidCoordinate() -> QGeoCoordinate; - /*! \brief Last valid true track * * This property holds the last valid true track known. At the first @@ -96,11 +107,34 @@ class PositionProvider : public PositionInfoSource_Abstract */ Q_PROPERTY(Units::Angle lastValidTT READ lastValidTT NOTIFY lastValidTTChanged) + + // + // Getter Methods + // + + /*! \brief Getter function for the property with the same name + * + * @returns Property approximateLastValidCoordinate + */ + Q_REQUIRED_RESULT QGeoCoordinate approximateLastValidCoordinate() const {return {m_approximateLastValidCoordinate};} + Q_REQUIRED_RESULT QBindable bindableApproximateLastValidCoordinate() const {return &m_approximateLastValidCoordinate;} + + /*! \brief Getter function for the property with the same name + * + * @returns Property lastValidCoordinate + */ + static QGeoCoordinate lastValidCoordinate(); + /*! \brief Getter function for the property with the same name * * @returns Property lastValidTrack */ - static auto lastValidTT() -> Units::Angle; + static Units::Angle lastValidTT(); + + + // + // Methods + // /*! \brief startUpdates * @@ -111,6 +145,9 @@ class PositionProvider : public PositionInfoSource_Abstract Q_INVOKABLE void startUpdates() { satelliteSource.startUpdates(); } signals: + /*! \brief Notifier signal */ + void approximateLastValidCoordinateChanged(); + /*! \brief Notifier signal */ void lastValidTTChanged(Units::Angle); @@ -155,7 +192,8 @@ private slots: PositionInfoSource_Satellite satelliteSource; - QGeoCoordinate m_lastValidCoordinate {EDTF_lat, EDTF_lon, EDTF_ele}; + Q_OBJECT_BINDABLE_PROPERTY(PositionProvider, QGeoCoordinate, m_approximateLastValidCoordinate, &Positioning::PositionProvider::approximateLastValidCoordinateChanged) + QProperty m_lastValidCoordinate {QGeoCoordinate(EDTF_lat, EDTF_lon, EDTF_ele)}; Units::Angle m_lastValidTT {}; }; diff --git a/src/qml/dialogs/FlightRouteAddWPDialog.qml b/src/qml/dialogs/FlightRouteAddWPDialog.qml deleted file mode 100644 index 5fe0ad985..000000000 --- a/src/qml/dialogs/FlightRouteAddWPDialog.qml +++ /dev/null @@ -1,122 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2019-2023 by Stefan Kebekus * - * stefan.kebekus@gmail.com * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -import akaflieg_freiburg.enroute - -import "../items" - -CenteringDialog { - id: dlg - title: qsTr("Add Waypoint to Route") - modal: true - - standardButtons: DialogButtonBox.Cancel - - Component { - id: waypointDelegate - - WordWrappingItemDelegate { - text: model.modelData.twoLineTitle - icon.source: model.modelData.icon - - width: wpList.width - - onClicked: { - PlatformAdaptor.vibrateBrief() - Navigator.flightRoute.append(model.modelData) - close() - } - } - - } - - ColumnLayout { - anchors.fill: parent - - Label { - Layout.fillWidth: true - - text: qsTr("Choose a waypoint from the list below.") - wrapMode: Text.Wrap - textFormat: Text.StyledText - } - - MyTextField { - id: textInput - - Layout.fillWidth: true - - focus: true - - onAccepted: { - if (wpList.model.length > 0) { - PlatformAdaptor.vibrateBrief() - Navigator.flightRoute.append(wpList.model[0]) - close() - } - } - - // On iOS17, the property displayText sees many bounced. - onDisplayTextChanged: debounceTimer.restart() - } - - DecoratedListView { - id: wpList - - Layout.fillHeight: true - Layout.fillWidth: true - Layout.preferredHeight: contentHeight - - clip: true - - // Debounce timer to update the property model only 200ms after the last change of textInput.displayText - Timer { - id: debounceTimer - interval: 200 // 200ms - onTriggered: wpList.model = GeoMapProvider.filteredWaypoints(textInput.displayText) - } - - delegate: waypointDelegate - ScrollIndicator.vertical: ScrollIndicator {} - - Label { - anchors.fill: wpList - anchors.topMargin: font.pixelSize*2 - - visible: (wpList.count === 0) - horizontalAlignment: Text.AlignHCenter - textFormat: Text.StyledText - wrapMode: Text.Wrap - text: (textInput.text === "") - ? qsTr("

Sorry!

No waypoints available. Please make sure that an aviation map is installed.

") - : qsTr("

Sorry!

No waypoints match your filter criteria.

") - onLinkActivated: Qt.openUrlExternally(link) - } - - } - - } - - onOpened: textInput.clear() -} // Page diff --git a/src/qml/dialogs/NotamListDialog.qml b/src/qml/dialogs/NotamListDialog.qml index 220fb9ded..62af12fd1 100644 --- a/src/qml/dialogs/NotamListDialog.qml +++ b/src/qml/dialogs/NotamListDialog.qml @@ -47,7 +47,7 @@ CenteringDialog { width: parent ? parent.width : undefined required property var model - property bool read: NotamProvider.isRead(delItem.model.modelData.number) + property bool read: NOTAMProvider.isRead(delItem.model.modelData.number) contentItem: Label { id: lbl @@ -96,7 +96,7 @@ CenteringDialog { onClicked: { read = !read - NotamProvider.setRead(delItem.model.modelData.number, read) + NOTAMProvider.setRead(delItem.model.modelData.number, read) if (read) seqA.start() } diff --git a/src/qml/dialogs/WaypointDescription.qml b/src/qml/dialogs/WaypointDescription.qml index db7cc1bad..14d315230 100644 --- a/src/qml/dialogs/WaypointDescription.qml +++ b/src/qml/dialogs/WaypointDescription.qml @@ -120,8 +120,8 @@ CenteringDialog { property notamList notamList: { // Mention lastUpdate, so we update whenever there is new data - NotamProvider.lastUpdate - return NotamProvider.notams(waypoint) + NOTAMProvider.lastUpdate + return NOTAMProvider.notams(waypoint) } visible: text !== "" diff --git a/src/qml/dialogs/WaypointEditor.qml b/src/qml/dialogs/WaypointEditor.qml index 6801851fd..9588b3106 100644 --- a/src/qml/dialogs/WaypointEditor.qml +++ b/src/qml/dialogs/WaypointEditor.qml @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2019-2023 by Stefan Kebekus * + * Copyright (C) 2019-2024 by Stefan Kebekus * * stefan.kebekus@gmail.com * * * * This program is free software; you can redistribute it and/or modify * @@ -47,15 +47,15 @@ CenteringDialog { modal: true title: qsTr("Edit Waypoint") - standardButtons: Dialog.Cancel|Dialog.Ok - onAboutToShow: { // This is necessary, because the initial binding "latInput.value: waypoint.coordinate.latitude" // breaks as soon as the user edits the coordinates manually. latInput.value = waypoint.coordinate.latitude longInput.value = waypoint.coordinate.longitude + eleField.valueMeter = waypoint.coordinate.altitude wpNameField.text = waypoint.extendedName wpNotesField.text = waypoint.notes + wpNameField.focus = true } DecoratedScrollView { @@ -89,6 +89,7 @@ CenteringDialog { Layout.minimumWidth: font.pixelSize*5 text: waypoint.extendedName + focus: true } @@ -212,8 +213,6 @@ CenteringDialog { value: waypoint.coordinate.latitude minValue: -90.0 maxValue: 90.0 - - onAcceptableInputChanged: enableOk() } Label { @@ -230,8 +229,6 @@ CenteringDialog { value: waypoint.coordinate.longitude minValue: -180.0 maxValue: 180.0 - - onAcceptableInputChanged: enableOk() } @@ -361,12 +358,24 @@ CenteringDialog { model: [ qsTr("Feet"), qsTr("Meter") ] } - } } - function enableOk() { - waypointEditorDialog.standardButton(DialogButtonBox.Ok).enabled = latInput.acceptableInput && longInput.acceptableInput + + footer: DialogButtonBox { + + Button { + text: qsTr("Cancel") + flat: true + DialogButtonBox.buttonRole: DialogButtonBox.RejectRole + } + + Button { + text: qsTr("OK") + flat: true + DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole + enabled: (wpNameField.displayText !== "") && latInput.acceptableInput && longInput.acceptableInput + } } } // Dialog diff --git a/src/qml/dialogs/WeatherReport.qml b/src/qml/dialogs/WeatherReport.qml index 2b0f152fa..f131762d1 100644 --- a/src/qml/dialogs/WeatherReport.qml +++ b/src/qml/dialogs/WeatherReport.qml @@ -37,7 +37,7 @@ CenteringDialog { modal: true standardButtons: Dialog.Close - title: weatherStation ? weatherStation.extendedName : "" + title: weatherStation ? weatherStation.ICAOCode + " • " + weatherStation.extendedName : "" ColumnLayout { anchors.fill: parent diff --git a/src/qml/items/DegreeInput.qml b/src/qml/items/DegreeInput.qml index a7814b51c..2eb880755 100644 --- a/src/qml/items/DegreeInput.qml +++ b/src/qml/items/DegreeInput.qml @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2019-2023 by Stefan Kebekus * + * Copyright (C) 2019-2024 by Stefan Kebekus * * stefan.kebekus@gmail.com * * * * This program is free software; you can redistribute it and/or modify * @@ -36,10 +36,23 @@ StackLayout { dms_d.acceptableInput && dms_m.acceptableInput && dms_s.acceptableInput && + !isNaN(value) && (value >= minValue) && (value <= maxValue) function setTexts() { + if (isNaN(value)) { + d_d.text = "" + + dm_d.text = "" + dm_m.text = "" + + dms_d.text = "" + dms_m.text = "" + dms_s.text = "" + return + } + var minutes = 60.0*(Math.abs(value) - Math.floor(Math.abs(value))) var seconds = 60.0*(minutes - Math.floor(minutes)) diff --git a/src/qml/items/FlightMap.qml b/src/qml/items/FlightMap.qml index a6c6ce88b..083b414d5 100644 --- a/src/qml/items/FlightMap.qml +++ b/src/qml/items/FlightMap.qml @@ -173,7 +173,7 @@ Map { styleId: "notams" type: "geojson" - property string data: NotamProvider.GeoJSON + property string data: NOTAMProvider.geoJSON } LayerParameter { diff --git a/src/qml/items/MFM.qml b/src/qml/items/MFM.qml index 5d79f63d5..356d830ef 100644 --- a/src/qml/items/MFM.qml +++ b/src/qml/items/MFM.qml @@ -57,8 +57,13 @@ Item { copyrightsVisible: false // We have our own copyrights notice property bool followGPS: true + onFollowGPSChanged: { + if (followGPS) + alignMapToCenter() + } + property real animatedTrack: PositionProvider.lastValidTT.isFinite() ? PositionProvider.lastValidTT.toDEG() : 0 - Behavior on animatedTrack { RotationAnimation {duration: 400; direction: RotationAnimation.Shortest } } + Behavior on animatedTrack { RotationAnimation {duration: 1000; direction: RotationAnimation.Shortest } } // GESTURES @@ -117,7 +122,7 @@ Item { return } - if (Math.abs(pinch.rawBearing-pinch.startBearing) > 5) + if (Math.abs(pinch.rawBearing-pinch.startBearing) > 20) { GlobalSettings.mapBearingPolicy = GlobalSettings.UserDefinedBearingUp } @@ -134,18 +139,27 @@ Item { : PointerDevice.Mouse onWheel: (event) => { const loc = flightMap.toCoordinate(wheel.point.position) - switch (event.modifiers) { - case Qt.NoModifier: + if (event.modifiers === Qt.NoModifier) + { zoomLevelBehavior.enabled = false - flightMap.zoomLevel += event.angleDelta.y / 240 + var newZoom = flightMap.zoomLevel + event.angleDelta.y / 240 + if (newZoom < flightMap.minimumZoomLevel) { + newZoom = flightMap.minimumZoomLevel + } + if (newZoom > flightMap.maximumZoomLevel) { + newZoom = flightMap.maximumZoomLevel + } + flightMap.zoomLevel = newZoom + //flightMap.zoomLevel += event.angleDelta.y / 240 zoomLevelBehavior.enabled = true - break - case Qt.ShiftModifier: + } + else + { bearingBehavior.enabled = false flightMap.bearing += event.angleDelta.y / 5 bearingBehavior.enabled = true - break } + flightMap.followGPS = false; flightMap.alignCoordinateToPoint(loc, wheel.point.position) } } @@ -169,6 +183,10 @@ Item { if (active) { flightMap.followGPS = false + if (GlobalSettings.mapBearingPolicy === GlobalSettings.TTUp) + { + GlobalSettings.mapBearingPolicy = GlobalSettings.UserDefinedBearingUp + } } } } @@ -195,6 +213,7 @@ Item { // If "followGPS" is true, then update the map bearing whenever a new GPS position comes in Binding on bearing { + restoreMode: Binding.RestoreNone when: GlobalSettings.mapBearingPolicy !== GlobalSettings.UserDefinedBearingUp value: GlobalSettings.mapBearingPolicy === GlobalSettings.TTUp ? PositionProvider.lastValidTT.toDEG() : 0 } @@ -210,59 +229,48 @@ Item { // PROPERTY "center" // - // Initially, set the center to the last saved value - center: PositionProvider.lastValidCoordinate - // If "followGPS" is true, then update the map center whenever a new GPS position comes in // or the zoom level changes - Binding on center { - id: centerBinding + property var centerCoordinate: { + // If not in flight, then aircraft stays in center of display + if (Navigator.flightStatus !== Navigator.Flight) + return ownPosition.coordinate + if (!PositionProvider.lastValidTT.isFinite()) + return ownPosition.coordinate - restoreMode: Binding.RestoreNone - when: flightMap.followGPS === true - value: { - // If not in flight, then aircraft stays in center of display - if (Navigator.flightStatus !== Navigator.Flight) - return PositionProvider.lastValidCoordinate - if (!PositionProvider.lastValidTT.isFinite()) - return PositionProvider.lastValidCoordinate - - // Otherwise, we position the aircraft someplace on a circle around the - // center, so that the map shows a larger portion of the airspace ahead - // of the aircraft. The following lines find a good radius for that - // circle, which ensures that the circle does not collide with any of the - // GUI elements. - const xCenter = flightMap.width/2.0 - const yCenter = flightMap.height/2.0 - const radiusInPixel = Math.min( - Math.abs(xCenter-zoomIn.x), - Math.abs(xCenter-followGPSButton.x-followGPSButton.width), - Math.abs(yCenter-northButton.y-northButton.height), - Math.abs(yCenter-zoomIn.y) - ) - const radiusInM = 10000.0*radiusInPixel/flightMap.pixelPer10km - - return PositionProvider.lastValidCoordinate.atDistanceAndAzimuth(radiusInM, PositionProvider.lastValidTT.toDEG()) - } + // Otherwise, we position the aircraft someplace on a circle around the + // center, so that the map shows a larger portion of the airspace ahead + // of the aircraft. The following lines find a good radius for that + // circle, which ensures that the circle does not collide with any of the + // GUI elements. + const radiusInPixel = Math.min(centerItem.width/2.0, centerItem.height/2.0) + const radiusInM = 10000.0*radiusInPixel/flightMap.pixelPer10km + + return ownPosition.coordinate.atDistanceAndAzimuth(radiusInM, animatedTrack) } - // We expect GPS updates every second. So, we choose an animation of duration 1000ms here, to obtain a flowing movement - Behavior on center { - id: centerBindingAnimation - CoordinateAnimation { duration: 1000 } - enabled: true - - function omitAnimationforZoom() { - centerBindingAnimation.enabled = false - omitAnimationForZoomTimer.stop() // stop in case it was already runnnig - omitAnimationForZoomTimer.start() - } + onCenterCoordinateChanged: { + if (!flightMap.followGPS) + return + alignMapToCenter() + } + + property var centerPoint: { + const xCenter = col2.x + centerItem.x + centerItem.width/2.0 + const yCenter = col2.y + centerItem.y + centerItem.height/2.0 + return Qt.point(xCenter, yCenter) } - Timer { - id: omitAnimationForZoomTimer - interval: 410 // little more than time for animation - onTriggered: centerBindingAnimation.enabled = true + onCenterPointChanged: { + if (!flightMap.followGPS) + return + alignMapToCenter() + } + + onMapReadyChanged: alignMapToCenter() + + function alignMapToCenter() { + flightMap.alignCoordinateToPoint(centerCoordinate, centerPoint) } @@ -386,10 +394,7 @@ Item { id: ownPosition coordinate: PositionProvider.lastValidCoordinate - - Behavior on coordinate { - CoordinateAnimation { duration: 1000 } - } + Behavior on coordinate { CoordinateAnimation { duration: 1000 } } Connections { // This is a workaround against a bug in Qt 5.15.2. The position of the MapQuickItem @@ -552,7 +557,6 @@ Item { flightMap.center = waypoint.coordinate } } - } BrightnessContrast { // Graphical effects: increase contrast, reduce brightness in dark mode @@ -590,219 +594,244 @@ Item { } } - RemainingRouteBar { - id: remainingRoute - - visible: !Global.currentVAC.isValid + GridLayout { + anchors.fill: parent + columns: 3 - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - } + RemainingRouteBar { + id: remainingRoute - Pane { - id: airspaceAltLabel + Layout.columnSpan: 3 + Layout.fillWidth: true - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: menuButton.verticalCenter + visible: !Global.currentVAC.isValid + } - topPadding: 0 - bottomPadding: 0 - visible: (!Global.currentVAC.isValid) && GlobalSettings.airspaceAltitudeLimit.isFinite() && !DataManager.baseMapsRaster.hasFile + // Column 1: Main Menu / Vertical Scale / ... + ColumnLayout { + Layout.fillHeight: true + Layout.leftMargin: SafeInsets.left - Label { + MapButton { + id: menuButton - text: { - // Mention - Navigator.aircraft.verticalDistanceUnit + icon.source: "/icons/material/ic_menu.svg" + visible: !Global.currentVAC.isValid - var airspaceAltitudeLimit = GlobalSettings.airspaceAltitudeLimit - var airspaceAltitudeLimitString = Navigator.aircraft.verticalDistanceToString(airspaceAltitudeLimit) - return " "+qsTr("Airspaces up to %1").arg(airspaceAltitudeLimitString)+" " + onClicked: { + PlatformAdaptor.vibrateBrief() + drawer.open() + } } - } - } - MapButton { - id: menuButton - icon.source: "/icons/material/ic_menu.svg" + Item { + Layout.alignment: Qt.AlignHCenter + Layout.fillHeight: true + Layout.preferredWidth: 24 - anchors.left: parent.left - anchors.leftMargin: 0.5*font.pixelSize + SafeInsets.left - anchors.top: remainingRoute.visible ? remainingRoute.bottom : parent.top - anchors.topMargin: 0.5*font.pixelSize + Pane { + opacity: GlobalSettings.nightMode ? 0.3 : 1.0 + visible: (!Global.currentVAC.isValid) && !scale.visible + anchors.fill: parent - visible: !Global.currentVAC.isValid + contentItem: Scale { + id: leftScale - onClicked: { - PlatformAdaptor.vibrateBrief() - drawer.open() - } - } + anchors.fill: parent + color: Material.foreground + + pixelPer10km: flightMap.pixelPer10km + vertical: true + } + } + } + + MapButton { + id: followGPSButton + + icon.source: "/icons/material/ic_my_location.svg" - MapButton { - id: northButton + enabled: !flightMap.followGPS - anchors.horizontalCenter: zoomIn.horizontalCenter - anchors.verticalCenter: menuButton.verticalCenter + onClicked: { + PlatformAdaptor.vibrateBrief() + flightMap.followGPS = true + toast.doToast(qsTr("Map Mode: Autopan")) + } + } - rotation: -flightMap.bearing + MapButton { + id: trafficDataReceiverButton - icon.source: "/icons/NorthArrow.svg" + icon.source: "/icons/material/ic_airplanemode_active.svg" + icon.color: "red" + enabled: !TrafficDataProvider.receivingHeartbeat - onClicked: { - if (GlobalSettings.mapBearingPolicy === GlobalSettings.NUp) { - GlobalSettings.mapBearingPolicy = GlobalSettings.TTUp - toast.doToast(qsTr("Map Mode: Track Up")) - } else { - GlobalSettings.mapBearingPolicy = GlobalSettings.NUp - toast.doToast(qsTr("Map Mode: North Up")) + onClicked: { + PlatformAdaptor.vibrateBrief() + PlatformAdaptor.vibrateBrief() + stackView.pop() + stackView.push("../pages/TrafficReceiver.qml", {"appWindow": view}) + } } } - } - MapButton { - id: followGPSButton + // Colmnn 2: Info Label / Center Item / Copyright / Horizontal Scale + ColumnLayout { + id: col2 - icon.source: "/icons/material/ic_my_location.svg" + Layout.fillHeight: true + Layout.fillWidth: true + Layout.minimumWidth: 0 - enabled: !flightMap.followGPS + Label { + id: airspaceAltLabel - anchors.left: parent.left - anchors.leftMargin: 0.5*font.pixelSize + SafeInsets.left - anchors.bottom: trafficDataReceiverButton.top - anchors.bottomMargin: trafficDataReceiverButton.visible ? 0.5*font.pixelSize : 1.5*font.pixelSize + Layout.alignment: Qt.AlignHCenter + Layout.maximumWidth: col2.width + Layout.topMargin: 14 - onClicked: { - PlatformAdaptor.vibrateBrief() - flightMap.followGPS = true - toast.doToast(qsTr("Map Mode: Autopan")) - } - } + visible: (!Global.currentVAC.isValid) && !DataManager.baseMapsRaster.hasFile && (text !== "") + wrapMode: Text.WordWrap - MapButton { - id: trafficDataReceiverButton + text: { + var resultList = [] - icon.source: "/icons/material/ic_airplanemode_active.svg" - icon.color: "red" - enabled: !TrafficDataProvider.receivingHeartbeat + if (GlobalSettings.airspaceAltitudeLimit.isFinite()) + { + var airspaceAltitudeLimit = GlobalSettings.airspaceAltitudeLimit + var airspaceAltitudeLimitString = Navigator.aircraft.verticalDistanceToString(airspaceAltitudeLimit) + resultList.push(qsTr("Airspaces up to %1").arg(airspaceAltitudeLimitString)) + } + if (DataManager.items.downloading) + resultList.push(qsTr("Downloading Maps and Data")) + if (NOTAMProvider.status !== "") + resultList.push(NOTAMProvider.status) + return resultList.join(" • ") + } - anchors.left: parent.left - anchors.leftMargin: 0.5*font.pixelSize + SafeInsets.left - anchors.bottom: navBar.top - anchors.bottomMargin: visible ? 1.5*font.pixelSize : 0 + leftPadding: font.pixelSize/2.0 + rightPadding: font.pixelSize/2.0 + bottomPadding: font.pixelSize/4.0 + topPadding: font.pixelSize/4.0 + background: Pane { Material.elevation: 1 } + } - onClicked: { - PlatformAdaptor.vibrateBrief() - PlatformAdaptor.vibrateBrief() - stackView.pop() - stackView.push("../pages/TrafficReceiver.qml", {"appWindow": view}) - } - } + Item { + id: centerItem - MapButton { - id: zoomIn + Layout.fillHeight: true + Layout.fillWidth: true + } - icon.source: "/icons/material/ic_add.svg" - enabled: flightMap.zoomLevel < flightMap.maximumZoomLevel - autoRepeat: true + Label { + id: noCopyrightInfo - anchors.right: parent.right - anchors.rightMargin: 0.5*font.pixelSize + SafeInsets.right - anchors.bottom: zoomOut.top - anchors.bottomMargin: 0.5*font.pixelSize + Layout.alignment: Qt.AlignRight - onClicked: { - centerBindingAnimation.omitAnimationforZoom() - PlatformAdaptor.vibrateBrief() - flightMap.zoomLevel += 1 - } - } + visible: (!Global.currentVAC.isValid) + text: " "+qsTr("ⓒ Map Data")+" " + opacity: 0.8 - MapButton { - id: zoomOut + //style: Text.Outline + //styleColor: GlobalSettings.nightMode ? "black" : "white" + background: Pane { opacity: GlobalSettings.nightMode ? 0.3 : 0.8 } + onLinkActivated: { + Global.dialogLoader.active = false + Global.dialogLoader.setSource("../dialogs/LongTextDialog.qml", {title: qsTr("Map Data Copyright Information"), + text: GeoMapProvider.copyrightNotice, + standardButtons: Dialog.Ok}) + Global.dialogLoader.active = true + } + } + + Pane { + id: scale - icon.source: "/icons/material/ic_remove.svg" - enabled: flightMap.zoomLevel > flightMap.minimumZoomLevel - autoRepeat: true + Material.elevation: 1 + Layout.bottomMargin: 14 + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + Layout.preferredHeight: 24 - anchors.right: parent.right - anchors.rightMargin: 0.5*font.pixelSize + SafeInsets.right - anchors.bottom: navBar.top - anchors.bottomMargin: 1.5*font.pixelSize + opacity: GlobalSettings.nightMode ? 0.3 : 1.0 + visible: (!Global.currentVAC.isValid) && (page.height > page.width) + + contentItem: Scale + { + anchors.fill: parent - onClicked: { - centerBindingAnimation.omitAnimationforZoom() - PlatformAdaptor.vibrateBrief() - var newZoomLevel = Math.max(flightMap.zoomLevel - 1, flightMap.minimumZoomLevel) - flightMap.zoomLevel = newZoomLevel + color: Material.foreground + pixelPer10km: flightMap.pixelPer10km + vertical: false + } + } } - } - Scale { - id: leftScale + // Column 3: North Button / Spacer / Zoom In / Zoom Out + ColumnLayout { + Layout.fillHeight: true + Layout.rightMargin: SafeInsets.right - anchors.top: northButton.bottom - anchors.topMargin: 0.5*font.pixelSize - anchors.bottom: followGPSButton.top - anchors.bottomMargin: 0.5*font.pixelSize - anchors.horizontalCenter: followGPSButton.horizontalCenter + MapButton { + id: northButton - opacity: GlobalSettings.nightMode ? 0.3 : 1.0 - visible: (!Global.currentVAC.isValid) && !scale.visible + rotation: -flightMap.bearing - pixelPer10km: flightMap.pixelPer10km - vertical: true - width: 30 - } + icon.source: "/icons/NorthArrow.svg" - Scale { - id: scale + onClicked: { + if (GlobalSettings.mapBearingPolicy === GlobalSettings.NUp) { + GlobalSettings.mapBearingPolicy = GlobalSettings.TTUp + toast.doToast(qsTr("Map Mode: Track Up")) + } else { + GlobalSettings.mapBearingPolicy = GlobalSettings.NUp + toast.doToast(qsTr("Map Mode: North Up")) + } + } + } - anchors.left: followGPSButton.right - anchors.leftMargin: 0.5*font.pixelSize - anchors.right: zoomIn.left - anchors.rightMargin: 0.5*font.pixelSize - anchors.verticalCenter: zoomOut.verticalCenter + Item { + Layout.fillHeight: true + } - opacity: GlobalSettings.nightMode ? 0.3 : 1.0 - visible: (!Global.currentVAC.isValid) && (parent.height > parent.width) + MapButton { + id: zoomIn - pixelPer10km: flightMap.pixelPer10km - vertical: false - height: 30 - } + icon.source: "/icons/material/ic_add.svg" + enabled: flightMap.zoomLevel < flightMap.maximumZoomLevel + autoRepeat: true - Pane { - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: navBar.top - anchors.bottomMargin: 0.4*font.pixelSize - topPadding: 0 - bottomPadding: 0 + onClicked: { + PlatformAdaptor.vibrateBrief() + flightMap.zoomLevel += 1 + } + } - visible: (!Global.currentVAC.isValid) - Label { - id: noCopyrightInfo - text: ""+qsTr("Map Data Copyright Info")+"" - onLinkActivated: { - Global.dialogLoader.active = false - Global.dialogLoader.setSource("../dialogs/LongTextDialog.qml", {title: qsTr("Map Data Copyright Information"), - text: GeoMapProvider.copyrightNotice, - standardButtons: Dialog.Ok}) - Global.dialogLoader.active = true + MapButton { + id: zoomOut + + icon.source: "/icons/material/ic_remove.svg" + enabled: flightMap.zoomLevel > flightMap.minimumZoomLevel + autoRepeat: true + + onClicked: { + PlatformAdaptor.vibrateBrief() + var newZoomLevel = Math.max(flightMap.zoomLevel - 1, flightMap.minimumZoomLevel) + flightMap.zoomLevel = newZoomLevel + } } } - } - - NavBar { - id: navBar - anchors.right: parent.right - anchors.left: parent.left + NavBar { + id: navBar - y: parent.height - height + Layout.fillWidth: true + Layout.columnSpan: 3 + } } + WaypointDescription { id: waypointDescription objectName: "waypointDescription" diff --git a/src/qml/items/MyTextField.qml b/src/qml/items/MyTextField.qml index cc8253f0d..fdf956e1a 100644 --- a/src/qml/items/MyTextField.qml +++ b/src/qml/items/MyTextField.qml @@ -46,6 +46,8 @@ TextField { return width > 5*font.pixelSize } rightPadding: hasClearButton ? toolButton.width : undefined + implicitWidth: 100 // set arbitrary value to avoid binding loop + // Fix problem on iOS Component.onCompleted: PlatformAdaptor.setupInputMethodEventFilter(textField) @@ -65,9 +67,6 @@ TextField { icon.width: font.pixelSize icon.height: font.pixelSize - onClicked: { - textField.clear() - textField.onEditingFinished() - } + onClicked: textField.clear() } } diff --git a/src/qml/items/NavBar.qml b/src/qml/items/NavBar.qml index 4def1bb0c..1620d9d76 100644 --- a/src/qml/items/NavBar.qml +++ b/src/qml/items/NavBar.qml @@ -29,7 +29,7 @@ Rectangle { color: "#AA000000" - height: trueAltitude.implicitHeight + SafeInsets.bottom + implicitHeight: trueAltitude.implicitHeight + SafeInsets.bottom // Dummy control. Used to glean the font size. Control { diff --git a/src/qml/items/RemainingRouteBar.qml b/src/qml/items/RemainingRouteBar.qml index c61952ac3..b1b4ec2ed 100644 --- a/src/qml/items/RemainingRouteBar.qml +++ b/src/qml/items/RemainingRouteBar.qml @@ -30,8 +30,8 @@ Rectangle { // Remaining route info shown in this item property var rri: Navigator.remainingRouteInfo - height: grid.implicitHeight + SafeInsets.top - Behavior on height { NumberAnimation { duration: 100 } } + implicitHeight: grid.implicitHeight + SafeInsets.top + Behavior on implicitHeight { NumberAnimation { duration: 100 } } clip: true diff --git a/src/qml/items/WaypointDelegate.qml b/src/qml/items/WaypointDelegate.qml index ace661c0b..db602b10b 100644 --- a/src/qml/items/WaypointDelegate.qml +++ b/src/qml/items/WaypointDelegate.qml @@ -38,8 +38,8 @@ Item { } } - width: parent ? parent.width : 0 - height: idel.implicitHeight + implicitWidth: parent ? parent.width : 0 + implicitHeight: idel.implicitHeight // Background color according to METAR/FAA flight category Rectangle { diff --git a/src/qml/pages/FlightRouteEditor.qml b/src/qml/pages/FlightRouteEditor.qml index 66c0a0853..936d93669 100644 --- a/src/qml/pages/FlightRouteEditor.qml +++ b/src/qml/pages/FlightRouteEditor.qml @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2019-2023 by Stefan Kebekus * + * Copyright (C) 2019-2024 by Stefan Kebekus * * stefan.kebekus@gmail.com * * * * This program is free software; you can redistribute it and/or modify * @@ -699,9 +699,127 @@ Page { } } - FlightRouteAddWPDialog { + CenteringDialog { id: flightRouteAddWPDialog + title: qsTr("Add Waypoint to Route") + modal: true + + standardButtons: DialogButtonBox.Cancel + + Component { + id: waypointDelegate + + WordWrappingItemDelegate { + text: model.modelData.twoLineTitle + icon.source: model.modelData.icon + + width: wpList.width + + onClicked: { + PlatformAdaptor.vibrateBrief() + Navigator.flightRoute.append(model.modelData) + flightRouteAddWPDialog.close() + } + } + + } + + ColumnLayout { + anchors.fill: parent + + Label { + Layout.fillWidth: true + + text: qsTr("Choose a waypoint from the list below or enter coordinates manually.") + wrapMode: Text.Wrap + textFormat: Text.StyledText + visible: textInput.displayText === "" + onLinkActivated: { + PlatformAdaptor.vibrateBrief() + flightRouteAddWPDialog.close() + addbyCoordinates.open() + } + } + + Item { + Layout.preferredHeight: textInput.font.pixelSize + visible: textInput.displayText === "" + } + + MyTextField { + id: textInput + + Layout.fillWidth: true + + placeholderText: qsTr("Filter by Name") + + focus: true + + onAccepted: { + if (wpList.model.length > 0) { + PlatformAdaptor.vibrateBrief() + Navigator.flightRoute.append(wpList.model[0]) + close() + } + } + + // On iOS17, the property displayText sees many bounces. + onDisplayTextChanged: debounceTimer.restart() + } + + Label { + Layout.fillWidth: true + Layout.topMargin: font.pixelSize + + text: qsTr("

Sorry!

No waypoints match your filter.

") + wrapMode: Text.Wrap + textFormat: Text.StyledText + horizontalAlignment: Text.AlignHCenter + + visible: wpList.model.length === 0 + } + + DecoratedListView { + id: wpList + + Layout.fillHeight: true + Layout.fillWidth: true + Layout.preferredHeight: contentHeight + + clip: true + + // Debounce timer to update the property model only 200ms after the last change of textInput.displayText + Timer { + id: debounceTimer + interval: 200 // 200ms + onTriggered: wpList.model = GeoMapProvider.filteredWaypoints(textInput.displayText) + } + + model: GeoMapProvider.filteredWaypoints(textInput.displayText) + delegate: waypointDelegate + ScrollIndicator.vertical: ScrollIndicator {} + + Label { + anchors.fill: wpList + anchors.topMargin: font.pixelSize*2 + + visible: (wpList.count === 0) + horizontalAlignment: Text.AlignHCenter + textFormat: Text.StyledText + wrapMode: Text.Wrap + text: (textInput.text === "") + ? qsTr("

Sorry!

No waypoints available. Please make sure that an aviation map is installed.

") + : qsTr("

Sorry!

No waypoints match your filter criteria.

") + onLinkActivated: Qt.openUrlExternally(link) + } + + } + + } + + onOpened: textInput.clear() + Connections { target: DemoRunner @@ -712,6 +830,24 @@ Page { } + WaypointEditor { + id: addbyCoordinates + + title: qsTr("Add Waypoint to Route") + modal: true + + onAccepted: { + PlatformAdaptor.vibrateBrief() + let newWP = waypoint.copy() + newWP.name = newName + newWP.notes = newNotes + newWP.coordinate = QtPositioning.coordinate(newLatitude, newLongitude, newAltitudeMeter) + Navigator.flightRoute.append(newWP) + addbyCoordinates.close() + } + + } + LongTextDialog { id: clearDialog diff --git a/src/qml/pages/Nearby.qml b/src/qml/pages/Nearby.qml index 4087894c9..6458bf148 100644 --- a/src/qml/pages/Nearby.qml +++ b/src/qml/pages/Nearby.qml @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2019-2023 by Stefan Kebekus * + * Copyright (C) 2019-2024 by Stefan Kebekus * * stefan.kebekus@gmail.com * * * * This program is free software; you can redistribute it and/or modify * @@ -48,6 +48,7 @@ Page { TabButton { text: "AD" } TabButton { text: "WP" } TabButton { text: "NAV" } + TabButton { icon.source: "/icons/material/ic_search.svg" } } SwipeView { @@ -137,6 +138,53 @@ Page { text: qsTr("

Sorry!

No navaid data available.

") } } + + ColumnLayout { + + Item { + Layout.preferredHeight: textInput.font.pixelSize/4.0 + } + + MyTextField { + id: textInput + + Layout.fillWidth: true + Layout.leftMargin: font.pixelSize/2.0 + Layout.rightMargin: font.pixelSize/2.0 + + focus: true + + placeholderText: qsTr("Filter by Name") + } + + DecoratedListView { + id: naList2 + + Layout.fillHeight: true + Layout.fillWidth: true + + clip: true + + delegate: waypointDelegate + + Binding { + naList2.model: GeoMapProvider.filteredWaypoints(textInput.displayText) + delayed: true + } + + Label { + anchors.fill: parent + anchors.topMargin: font.pixelSize*2 + visible: parent.count === 0 + + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + textFormat: Text.StyledText + wrapMode: Text.Wrap + text: qsTr("

Sorry!

No waypoints match your filter.

") + } + } + } } footer: Footer { diff --git a/src/qml/pages/WaypointLibraryPage.qml b/src/qml/pages/WaypointLibraryPage.qml index 8bf2c5ebc..e8feaab55 100644 --- a/src/qml/pages/WaypointLibraryPage.qml +++ b/src/qml/pages/WaypointLibraryPage.qml @@ -247,149 +247,160 @@ Page { } } - RowLayout { - id: filterRow + Component { + id: waypointDelegate - anchors.left: parent.left - anchors.leftMargin: SafeInsets.left+font.pixelSize - anchors.right: parent.right - anchors.rightMargin: SafeInsets.right+font.pixelSize - anchors.top: parent.top - anchors.topMargin: page.font.pixelSize + RowLayout { + width: wpList.width + height: iDel.height - Label { - Layout.alignment: Qt.AlignBaseline - - text: qsTr("Filter") - } - - MyTextField { - id: textInput + SwipeToDeleteDelegate { + id: iDel + Layout.fillWidth: true - Layout.alignment: Qt.AlignBaseline - Layout.fillWidth: true - } - } + text: modelData.name + icon.source: modelData.icon - Pane { + onClicked: { + PlatformAdaptor.vibrateBrief() + waypointDescription.waypoint = modelData + waypointDescription.open() + } - anchors.top: filterRow.bottom - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right + swipe.onCompleted: { + PlatformAdaptor.vibrateBrief() + removeDialog.waypoint = modelData + removeDialog.open() + } + } - bottomPadding: SafeInsets.bottom - leftPadding: SafeInsets.left - rightPadding: SafeInsets.right - topPadding: font.pixelSize + ToolButton { + id: editButton - Component { - id: waypointDelegate + icon.source: "/icons/material/ic_mode_edit.svg" + onClicked: { + PlatformAdaptor.vibrateBrief() + wpEditor.waypoint = modelData + wpEditor.open() + } + } - RowLayout { - width: wpList.width - height: iDel.height + ToolButton { + id: cptMenuButton - SwipeToDeleteDelegate { - id: iDel - Layout.fillWidth: true + icon.source: "/icons/material/ic_more_horiz.svg" - text: modelData.name - icon.source: modelData.icon + onClicked: { + PlatformAdaptor.vibrateBrief() + cptMenu.open() + } - onClicked: { - PlatformAdaptor.vibrateBrief() - waypointDescription.waypoint = modelData - waypointDescription.open() - } + AutoSizingMenu { + id: cptMenu - swipe.onCompleted: { - PlatformAdaptor.vibrateBrief() - removeDialog.waypoint = modelData - removeDialog.open() - } - } + Action { + id: removeAction + text: qsTr("Remove…") + onTriggered: { + PlatformAdaptor.vibrateBrief() + removeDialog.waypoint = modelData + removeDialog.open() + } + } // removeAction + } // AutoSizingMenu - ToolButton { - id: editButton + } - icon.source: "/icons/material/ic_mode_edit.svg" - onClicked: { - PlatformAdaptor.vibrateBrief() - wpEditor.waypoint = modelData - wpEditor.open() - } - } + } - ToolButton { - id: cptMenuButton + } - icon.source: "/icons/material/ic_more_horiz.svg" - onClicked: { - PlatformAdaptor.vibrateBrief() - cptMenu.open() - } + ColumnLayout { + anchors.fill: parent + anchors.leftMargin: SafeInsets.left + anchors.rightMargin: SafeInsets.right - AutoSizingMenu { - id: cptMenu + Item { + Layout.preferredHeight: textInput.font.pixelSize/4.0 + } - Action { - id: removeAction - text: qsTr("Remove…") - onTriggered: { - PlatformAdaptor.vibrateBrief() - removeDialog.waypoint = modelData - removeDialog.open() - } - } // removeAction - } // AutoSizingMenu - } + MyTextField { + id: textInput - } + Layout.fillWidth: true + Layout.leftMargin: font.pixelSize/2.0 + Layout.rightMargin: font.pixelSize/2.0 + placeholderText: qsTr("Filter by Name") } DecoratedListView { id: wpList - anchors.fill: parent - - leftMargin: SafeInsets.left - rightMargin: SafeInsets.right - bottomMargin: SafeInsets.bottom + Layout.fillWidth: true + Layout.fillHeight: true clip: true - model: { - // Mention waypoints to ensure that the list gets updated - WaypointLibrary.waypoints + model: + Binding { + wpList.model: { + // Mention waypoints to ensure that the list gets updated + WaypointLibrary.waypoints - return WaypointLibrary.filteredWaypoints(textInput.text) + return WaypointLibrary.filteredWaypoints(textInput.displayText) + } + delayed: true } + delegate: waypointDelegate ScrollIndicator.vertical: ScrollIndicator {} } - } - Label { - anchors.fill: parent + Label { + Layout.fillWidth: true + Layout.fillHeight: true + + visible: (wpList.count === 0) + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + leftPadding: font.pixelSize*2 + rightPadding: font.pixelSize*2 + + textFormat: Text.RichText + wrapMode: Text.Wrap + text: (textInput.text === "") + ? qsTr("

Sorry!

No waypoint available. To add a waypoint here, choose 'Add Waypoint' below or double-tap on a point in the moving map.

") + : qsTr("

Sorry!

No waypoints match your filter.

") + } - visible: (wpList.count === 0) - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - leftPadding: font.pixelSize*2 - rightPadding: font.pixelSize*2 - - textFormat: Text.RichText - wrapMode: Text.Wrap - text: (textInput.text === "") - ? qsTr("

Sorry!

No waypoint available. To add a waypoint here, double-tap on a point in the moving map.

") - : qsTr("

Sorry!

No waypoints match your filter criteria.

") } + + footer: Footer { + ColumnLayout { + width: parent.width + + Button { + id: addWPButton + + flat: true + + Layout.alignment: Qt.AlignHCenter + text: qsTr("Add Waypoint") + icon.source: "/icons/material/ic_add_circle.svg" + + onClicked: { + PlatformAdaptor.vibrateBrief() + addWP.open() + } + } + } + } + // This is the name of the file that openFromLibrary will open property string finalFileName; @@ -427,7 +438,6 @@ Page { page.reloadWaypointList() // Re-display aircraft that have been swiped out close() } - } LongTextDialog { @@ -461,6 +471,23 @@ Page { } + WaypointEditor { + id: addWP + + title: qsTr("Add Waypoint") + + onAccepted: { + let newWP = waypoint.copy() + newWP.name = newName + newWP.notes = newNotes + newWP.coordinate = QtPositioning.coordinate(newLatitude, newLongitude, newAltitudeMeter) + WaypointLibrary.add(newWP) + page.reloadWaypointList() + toast.doToast(qsTr("Waypoint added")) + } + + } + WaypointDescription { id: waypointDescription } diff --git a/src/traffic/ConnectionInfo.h b/src/traffic/ConnectionInfo.h index 09fd117f1..bc060d7a6 100644 --- a/src/traffic/ConnectionInfo.h +++ b/src/traffic/ConnectionInfo.h @@ -281,16 +281,16 @@ class ConnectionInfo { // bool m_canConnect { false }; bool m_canonical { false }; - QString m_description {}; - QString m_icon { u"/icons/material/ic_delete.svg"_qs }; + QString m_description; + QString m_icon{u"/icons/material/ic_delete.svg"_qs}; QString m_name { QObject::tr("Invalid Device", "Traffic::ConnectionInfo") }; Traffic::ConnectionInfo::Type m_type { Traffic::ConnectionInfo::Invalid }; // // Private members, depending on m_type // - QBluetoothDeviceInfo m_bluetoothDeviceInfo {}; - quint16 m_port {0}; + QBluetoothDeviceInfo m_bluetoothDeviceInfo; + quint16 m_port{0}; QString m_host; }; diff --git a/src/traffic/ConnectionScanner_Abstract.h b/src/traffic/ConnectionScanner_Abstract.h index 312432221..935662ffc 100644 --- a/src/traffic/ConnectionScanner_Abstract.h +++ b/src/traffic/ConnectionScanner_Abstract.h @@ -147,7 +147,7 @@ public slots: QList m_connectionInfos; // Property error - QString m_error {}; + QString m_error; // Property isScanning bool m_isScanning {false}; diff --git a/src/traffic/FlarmnetDB.h b/src/traffic/FlarmnetDB.h index c58cc5f96..d2fbc9d63 100644 --- a/src/traffic/FlarmnetDB.h +++ b/src/traffic/FlarmnetDB.h @@ -78,7 +78,7 @@ private slots: QPointer flarmnetDBDownloadable; - QCache m_cache {}; + QCache m_cache; }; } // namespace Traffic diff --git a/src/traffic/PasswordDB.h b/src/traffic/PasswordDB.h index eac8e55cf..75cd04d5c 100644 --- a/src/traffic/PasswordDB.h +++ b/src/traffic/PasswordDB.h @@ -151,9 +151,9 @@ class PasswordDB : public QObject { bool m_empty {true}; // Password database - QString passwordDBFileName {}; + QString passwordDBFileName; - QHash m_passwordDB {}; + QHash m_passwordDB; }; } // namespace Traffic diff --git a/src/traffic/TrafficDataProvider.cpp b/src/traffic/TrafficDataProvider.cpp index e597ad7cb..0aeed326d 100644 --- a/src/traffic/TrafficDataProvider.cpp +++ b/src/traffic/TrafficDataProvider.cpp @@ -254,7 +254,7 @@ QList Traffic::TrafficDataProvider::dataSo return result; } -void Traffic::TrafficDataProvider::deferredInitialization() +void Traffic::TrafficDataProvider::deferredInitialization() const { // Try to (re)connect whenever the network situation changes connect(GlobalObject::platformAdaptor(), &Platform::PlatformAdaptor_Abstract::wifiConnected, this, &Traffic::TrafficDataProvider::connectToTrafficReceiver); diff --git a/src/traffic/TrafficDataProvider.h b/src/traffic/TrafficDataProvider.h index 2c4b2bb73..0adc3b3ef 100644 --- a/src/traffic/TrafficDataProvider.h +++ b/src/traffic/TrafficDataProvider.h @@ -365,7 +365,7 @@ private slots: // Intializations that are moved out of the constructor, in order to avoid // nested uses of constructors in Global. - void deferredInitialization(); + void deferredInitialization() const; // Sends out foreflight broadcast message See // https://www.foreflight.com/connect/spec/ @@ -423,8 +423,8 @@ private slots: // Property cache Traffic::Warning m_Warning; QTimer m_WarningTimer; - QString m_trafficReceiverRuntimeError {}; - QString m_trafficReceiverSelfTestError {}; + QString m_trafficReceiverRuntimeError; + QString m_trafficReceiverSelfTestError; // Reconnect QTimer reconnectionTimer; diff --git a/src/traffic/TrafficDataSource_Abstract.h b/src/traffic/TrafficDataSource_Abstract.h index c5f29f077..f165b485e 100644 --- a/src/traffic/TrafficDataSource_Abstract.h +++ b/src/traffic/TrafficDataSource_Abstract.h @@ -458,10 +458,10 @@ public slots: // Property caches bool m_canonical {false}; - QString m_connectivityStatus {}; - QString m_errorString {}; - QString m_trafficReceiverRuntimeError {}; - QString m_trafficReceiverSelfTestError {}; + QString m_connectivityStatus; + QString m_errorString; + QString m_trafficReceiverRuntimeError; + QString m_trafficReceiverSelfTestError; // True altitude of own aircraft. We store these values because the // necessary information to compile a PositionInfo class does not always diff --git a/src/traffic/TrafficFactor_Abstract.h b/src/traffic/TrafficFactor_Abstract.h index 92c164e8c..0b7785f65 100644 --- a/src/traffic/TrafficFactor_Abstract.h +++ b/src/traffic/TrafficFactor_Abstract.h @@ -459,7 +459,7 @@ class TrafficFactor_Abstract : public QObject { // "dispatchUpdateDescription", which whose address is already known to the constructor. virtual void updateDescription(); void dispatchUpdateDescription(); - QString m_description {}; + QString m_description; private: Q_DISABLE_COPY_MOVE(TrafficFactor_Abstract) @@ -469,8 +469,8 @@ class TrafficFactor_Abstract : public QObject { // int m_alarmLevel {0}; bool m_animate {false}; - QString m_callSign {}; - QString m_color {QStringLiteral("red")}; + QString m_callSign; + QString m_color{QStringLiteral("red")}; Units::Distance m_hDist; QString m_ID; AircraftType m_type {AircraftType::unknown}; diff --git a/src/traffic/TrafficFactor_DistanceOnly.h b/src/traffic/TrafficFactor_DistanceOnly.h index 748eee9a3..99dc68b90 100644 --- a/src/traffic/TrafficFactor_DistanceOnly.h +++ b/src/traffic/TrafficFactor_DistanceOnly.h @@ -122,7 +122,7 @@ class TrafficFactor_DistanceOnly : public Traffic::TrafficFactor_Abstract { // // Property values // - QGeoCoordinate m_coordinate {}; + QGeoCoordinate m_coordinate; }; } // namespace Traffic diff --git a/src/ui/ScaleQuickItem.cpp b/src/ui/ScaleQuickItem.cpp index c8a3057ff..963ec796c 100644 --- a/src/ui/ScaleQuickItem.cpp +++ b/src/ui/ScaleQuickItem.cpp @@ -22,7 +22,7 @@ #include #include "GlobalObject.h" -#include "GlobalSettings.h" +//#include "GlobalSettings.h" #include "ScaleQuickItem.h" #include "navigation/Aircraft.h" #include "navigation/Navigator.h" @@ -31,49 +31,54 @@ Ui::ScaleQuickItem::ScaleQuickItem(QQuickItem *parent) : QQuickPaintedItem(parent) { - connect(GlobalObject::navigator(), &Navigation::Navigator::aircraftChanged, this, &QQuickItem::update); setRenderTarget(QQuickPaintedItem::FramebufferObject); } -void Ui::ScaleQuickItem::paint(QPainter *painter) +void Ui::ScaleQuickItem::paint(QPainter* painter) { // Safety check. Continue only if data provided is sane - if (_pixelPer10km < 20) { + if (m_pixelPer10km < 2) { return; } // Pre-compute a few numbers that will be used when drawing qreal pixelPerUnit = NAN; - switch (GlobalObject::navigator()->aircraft().horizontalDistanceUnit()) { + switch (GlobalObject::navigator()->aircraft().horizontalDistanceUnit()) + { case Navigation::Aircraft::Kilometer: - pixelPerUnit = _pixelPer10km * 0.1; + pixelPerUnit = m_pixelPer10km * 0.1; break; case Navigation::Aircraft::StatuteMile: - pixelPerUnit = _pixelPer10km * 0.1609344; + pixelPerUnit = m_pixelPer10km * 0.1609344; break; case Navigation::Aircraft::NauticalMile: - pixelPerUnit = _pixelPer10km * 0.1852; + pixelPerUnit = m_pixelPer10km * 0.1852; break; } - qreal scaleSizeInUnit = _vertical ? (height()-10.0)/pixelPerUnit : (width()-10.0)/pixelPerUnit; - qreal ScaleUnitInUnit = pow(10.0, floor(log10(scaleSizeInUnit))); - int sizeOfUnitInPix = qRound(ScaleUnitInUnit*pixelPerUnit); - qreal sizeOfScaleInUnit = floor(scaleSizeInUnit/ScaleUnitInUnit)*ScaleUnitInUnit; - int sizeOfScaleInPix = qRound(sizeOfScaleInUnit*pixelPerUnit); + qreal const scaleSizeInUnit = m_vertical ? (height() - 10.0) / pixelPerUnit + : (width() - 10.0) / pixelPerUnit; + qreal const ScaleUnitInUnit = pow(10.0, floor(log10(scaleSizeInUnit))); + int const sizeOfUnitInPix = qRound(ScaleUnitInUnit * pixelPerUnit); + qreal const sizeOfScaleInUnit = floor(scaleSizeInUnit / ScaleUnitInUnit) * ScaleUnitInUnit; + int const sizeOfScaleInPix = qRound(sizeOfScaleInUnit * pixelPerUnit); // Compute size of text. Set font to somewhat smaller than standard size. QFont font = painter->font(); - if (font.pointSizeF() > 0.0) { + if (font.pointSizeF() > 0.0) + { font.setPointSizeF(font.pointSizeF()*0.8); - } else { + } + else + { font.setPixelSize(qRound(font.pixelSize()*0.8)); } painter->setFont(font); QString text; - switch (GlobalObject::navigator()->aircraft().horizontalDistanceUnit()) { + switch (GlobalObject::navigator()->aircraft().horizontalDistanceUnit()) + { case Navigation::Aircraft::Kilometer: text = QStringLiteral("%1 km").arg(sizeOfScaleInUnit); break; @@ -84,80 +89,84 @@ void Ui::ScaleQuickItem::paint(QPainter *painter) text = QStringLiteral("%1 nm").arg(sizeOfScaleInUnit); break; } - int textWidth = painter->fontMetrics().horizontalAdvance(text); - int textHeight = painter->fontMetrics().height(); + int const textWidth = painter->fontMetrics().horizontalAdvance(text); + int const textHeight = painter->fontMetrics().height(); // Draw only if width() or height() is large enough - if (_vertical) { - if (height() < textWidth*1.5) { + if (m_vertical) + { + if (height() < textWidth*1.5) + { return; } - } else { - if (width() < textWidth*1.5) { + } + else + { + if (width() < textWidth*1.5) + { return; } } // Coordinates for the left/top point of the scale - int baseX = _vertical ? 8 : qRound((width()-sizeOfScaleInPix)/2.0); - int baseY = _vertical ? qRound((height()-sizeOfScaleInPix)/2.0) : qRound(height()) - 8 ; - - // Draw underlying white, slightly tranparent rectangle - painter->fillRect(0, 0, static_cast(width()), static_cast(height()), QColor(0xff, 0xff, 0xff, 0xe0)); + int const baseX = m_vertical ? 8 : qRound((width() - sizeOfScaleInPix) / 2.0); + int const baseY = m_vertical ? qRound((height() - sizeOfScaleInPix) / 2.0) + : qRound(height()) - 8; // Draw scale - if (_vertical) { - painter->setPen(QPen(Qt::white, 2)); - painter->drawLine(baseX, baseY, baseX, baseY+sizeOfScaleInPix); - painter->drawLine(baseX+3, baseY, baseX-3, baseY); - painter->drawLine(baseX+3, baseY+sizeOfScaleInPix, baseX-3, baseY+sizeOfScaleInPix); - for(int i=1; i*ScaleUnitInUnitdrawLine(baseX, baseY + i*sizeOfUnitInPix, baseX-3, baseY + i*sizeOfUnitInPix); - } - - painter->setPen(QPen(Qt::black, 1)); + if (m_vertical) + { + painter->setPen(QPen(m_color, 1)); painter->drawLine(baseX, baseY, baseX, baseY+sizeOfScaleInPix); painter->drawLine(baseX+3, baseY, baseX-3, baseY); painter->drawLine(baseX+3, baseY+sizeOfScaleInPix, baseX-3, baseY+sizeOfScaleInPix); - for(int i=1; i*ScaleUnitInUnitdrawLine(baseX, baseY + i*sizeOfUnitInPix, baseX-3, baseY + i*sizeOfUnitInPix); } // Draw text painter->rotate(-90.0); painter->drawText(-qRound(height()/2.0)-textWidth/2, baseX+textHeight, text); - } else { - painter->setPen(QPen(Qt::white, 2)); - painter->drawLine(baseX, baseY, baseX+sizeOfScaleInPix, baseY); - painter->drawLine(baseX, baseY+3, baseX, baseY-3); - painter->drawLine(baseX+sizeOfScaleInPix, baseY+3, baseX+sizeOfScaleInPix, baseY-3); - for(int i=1; i*ScaleUnitInUnitdrawLine(baseX + i*sizeOfUnitInPix, baseY, baseX + i*sizeOfUnitInPix, baseY+3); - } - - painter->setPen(QPen(Qt::black, 1)); + } + else + { + painter->setPen(QPen(m_color, 1)); painter->drawLine(baseX, baseY, baseX+sizeOfScaleInPix, baseY); painter->drawLine(baseX, baseY+3, baseX, baseY-3); painter->drawLine(baseX+sizeOfScaleInPix, baseY+3, baseX+sizeOfScaleInPix, baseY-3); - for(int i=1; i*ScaleUnitInUnitdrawLine(baseX + i*sizeOfUnitInPix, baseY, baseX + i*sizeOfUnitInPix, baseY+3); } // Draw text painter->drawText(baseX+sizeOfScaleInPix/2-textWidth/2, baseY-5, text); } +} +void Ui::ScaleQuickItem::setColor(QColor _color) +{ + if (m_color == _color) + { + return; + } + + m_color = _color; + update(); + emit colorChanged(); } void Ui::ScaleQuickItem::setPixelPer10km(qreal _pxp10k) { - if (qFuzzyCompare(_pixelPer10km, _pxp10k)) { + if (qFuzzyCompare(m_pixelPer10km, _pxp10k)) + { return; } - _pixelPer10km = _pxp10k; + m_pixelPer10km = _pxp10k; update(); emit pixelPer10kmChanged(); } @@ -165,11 +174,12 @@ void Ui::ScaleQuickItem::setPixelPer10km(qreal _pxp10k) void Ui::ScaleQuickItem::setVertical(bool newVertical) { - if (_vertical == newVertical) { + if (m_vertical == newVertical) + { return; } - _vertical = newVertical; + m_vertical = newVertical; update(); emit verticalChanged(); } diff --git a/src/ui/ScaleQuickItem.h b/src/ui/ScaleQuickItem.h index fa295fca2..b93e8b414 100644 --- a/src/ui/ScaleQuickItem.h +++ b/src/ui/ScaleQuickItem.h @@ -37,72 +37,89 @@ namespace Ui { class ScaleQuickItem : public QQuickPaintedItem { - Q_OBJECT - QML_NAMED_ELEMENT(Scale) + Q_OBJECT + QML_NAMED_ELEMENT(Scale) public: - /*! \brief Standard constructor - * - * @param parent The standard QObject parent pointer - */ - explicit ScaleQuickItem(QQuickItem *parent = nullptr); - - /*! \brief Number of pixel that represent a distance of 10km on the map */ - Q_PROPERTY(qreal pixelPer10km READ pixelPer10km WRITE setPixelPer10km NOTIFY pixelPer10kmChanged) - - /*! \brief Getter function for the property with the same name - - @returns Property pixelPer10km - */ - [[nodiscard]] auto pixelPer10km() const -> qreal {return _pixelPer10km;} - - /*! \brief Setter function for the property with the same name - - @param _pxp10k Property pixelPer10km - */ - void setPixelPer10km(qreal _pxp10k); - - /*! \brief Determines whether the scale should use km or nm */ - - /*! \brief Re-implemented from QQuickPaintedItem to implement painting - * - * @param painter Pointer to the QPainter used for painting - */ - void paint(QPainter *painter) override; - - /*! \brief Determines whether the scale should be drawn vertically or horizontally - * - * This property defaults to 'false'. - */ - Q_PROPERTY(bool vertical READ vertical WRITE setVertical NOTIFY verticalChanged) - - /*! \brief Getter function for the property with the same name - * - * @returns Property vertical - */ - [[nodiscard]] auto vertical() const -> bool {return _vertical;} - - /*! \brief Setter function for the property with the same name - * - * @param newVertical Property vertical - */ - void setVertical(bool newVertical); + /*! \brief Standard constructor + * + * @param parent The standard QObject parent pointer + */ + explicit ScaleQuickItem(QQuickItem *parent = nullptr); + + // Default destructor + ~ScaleQuickItem() override = default; + + /*! \brief Foreground color */ + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged REQUIRED) + + /*! \brief Getter function for the property with the same name + * + * @returns Property pixelPer10km + */ + [[nodiscard]] QColor color() const {return m_color;} + + /*! \brief Setter function for the property with the same name + * + * @param _color Property color + */ + void setColor(QColor _color); + + /*! \brief Number of pixel that represent a distance of 10km on the map */ + Q_PROPERTY(qreal pixelPer10km READ pixelPer10km WRITE setPixelPer10km NOTIFY pixelPer10kmChanged) + + /*! \brief Getter function for the property with the same name + * + * @returns Property pixelPer10km + */ + [[nodiscard]] auto pixelPer10km() const -> qreal {return m_pixelPer10km;} + + /*! \brief Setter function for the property with the same name + * + * @param _pxp10k Property pixelPer10km + */ + void setPixelPer10km(qreal _pxp10k); + + /*! \brief Re-implemented from QQuickPaintedItem to implement painting + * + * @param painter Pointer to the QPainter used for painting + */ + void paint(QPainter* painter) override; + + /*! \brief Determines whether the scale should be drawn vertically or horizontally + * + * This property defaults to 'false'. + */ + Q_PROPERTY(bool vertical READ vertical WRITE setVertical NOTIFY verticalChanged) + + /*! \brief Getter function for the property with the same name + * + * @returns Property vertical + */ + [[nodiscard]] auto vertical() const -> bool {return m_vertical;} + + /*! \brief Setter function for the property with the same name + * + * @param newVertical Property vertical + */ + void setVertical(bool newVertical); signals: - /*! \brief Notification signal for property with the same name */ - void pixelPer10kmChanged(); + /*! \brief Notification signal for property with the same name */ + void colorChanged(); - /*! \brief Notification signal for property with the same name */ - void useMetricUnitsChanged(); + /*! \brief Notification signal for property with the same name */ + void pixelPer10kmChanged(); - /*! \brief Notification signal for property with the same name */ - void verticalChanged(); + /*! \brief Notification signal for property with the same name */ + void verticalChanged(); private: - Q_DISABLE_COPY_MOVE(ScaleQuickItem) + Q_DISABLE_COPY_MOVE(ScaleQuickItem) - qreal _pixelPer10km {0.0}; - bool _vertical {false}; + qreal m_pixelPer10km {0.0}; + bool m_vertical {false}; + QColor m_color {Qt::black}; }; } // namespace Ui diff --git a/src/units/ByteSize.h b/src/units/ByteSize.h index eddfd1ef7..a7c9ef80d 100644 --- a/src/units/ByteSize.h +++ b/src/units/ByteSize.h @@ -55,7 +55,7 @@ namespace Units { * * @returns True is size is zero */ - Q_INVOKABLE bool isNull() const { return value==0; } + [[nodiscard]] Q_INVOKABLE bool isNull() const { return value == 0; } /*! \brief Conversion from Units::ByteSize to size_t * diff --git a/src/units/Distance.cpp b/src/units/Distance.cpp index 98f089e9d..d3d1e3414 100644 --- a/src/units/Distance.cpp +++ b/src/units/Distance.cpp @@ -20,6 +20,7 @@ #include "units/Distance.h" +#include auto Units::Distance::toString(Units::Distance::DistanceUnit units, bool roundBigNumbers, bool forceSign) const -> QString { @@ -79,7 +80,7 @@ QDataStream& operator<<(QDataStream& stream, const Units::Distance& distance) QDataStream& operator>>(QDataStream& stream, Units::Distance& distance) { - double tmp; + double tmp = NAN; stream >> tmp; distance = Units::Distance::fromM(tmp); return stream; diff --git a/src/weather/METAR.cpp b/src/weather/METAR.cpp index 07982bdd2..f30e2adc8 100644 --- a/src/weather/METAR.cpp +++ b/src/weather/METAR.cpp @@ -279,7 +279,7 @@ auto Weather::METAR::summary() const -> QString { } if (resultList.isEmpty()) { - return {}; + return tr("%1 %2").arg(messageType(), Navigation::Clock::describeTimeDifference(_observationTime)); } return tr("%1 %2: %3").arg(messageType(), Navigation::Clock::describeTimeDifference(_observationTime), resultList.join(QStringLiteral(" • "))); diff --git a/src/weather/WeatherDataProvider.cpp b/src/weather/WeatherDataProvider.cpp index 52a84f513..86af0e5dc 100644 --- a/src/weather/WeatherDataProvider.cpp +++ b/src/weather/WeatherDataProvider.cpp @@ -50,7 +50,7 @@ Weather::WeatherDataProvider::WeatherDataProvider(QObject *parent) : QObject(par // Connect the timer to the update method. This will set backgroundUpdate to the default value, // which is true. So these updates happen in the background. // Schedule the first update in 1 seconds from now - connect(&_updateTimer, &QTimer::timeout, this, [=, this](){ this->update(); }); + connect(&_updateTimer, &QTimer::timeout, this, [this]() { this->update(); }); _updateTimer.setInterval(updateIntervalNormal_ms); _updateTimer.start(); @@ -631,12 +631,13 @@ void Weather::WeatherDataProvider::update(bool isBackgroundUpdate) bBox.setWidth( bBox.width() + 2.0/factor ); { - QString urlString = u"https://enroute-data.akaflieg-freiburg.de/enrouteProxy/metar.php?format=xml&bbox=%1,%2,%3,%4"_qs - .arg(bBox.bottomLeft().latitude()) - .arg(bBox.bottomLeft().longitude()) - .arg(bBox.topRight().latitude()) - .arg(bBox.topRight().longitude()); - QUrl url = QUrl(urlString); + QString const urlString + = u"https://enroute-data.akaflieg-freiburg.de/enrouteProxy/metar.php?format=xml&bbox=%1,%2,%3,%4"_qs + .arg(bBox.bottomLeft().latitude()) + .arg(bBox.bottomLeft().longitude()) + .arg(bBox.topRight().latitude()) + .arg(bBox.topRight().longitude()); + QUrl const url = QUrl(urlString); QNetworkRequest request(url); request.setRawHeader("accept", "application/xml"); QPointer const reply = GlobalObject::networkAccessManager()->get(request); @@ -646,12 +647,13 @@ void Weather::WeatherDataProvider::update(bool isBackgroundUpdate) } { - QString urlString = u"https://enroute-data.akaflieg-freiburg.de/enrouteProxy/taf.php?format=xml&bbox=%1,%2,%3,%4"_qs - .arg(bBox.bottomLeft().latitude()) - .arg(bBox.bottomLeft().longitude()) - .arg(bBox.topRight().latitude()) - .arg(bBox.topRight().longitude()); - QUrl url = QUrl(urlString); + QString const urlString + = u"https://enroute-data.akaflieg-freiburg.de/enrouteProxy/taf.php?format=xml&bbox=%1,%2,%3,%4"_qs + .arg(bBox.bottomLeft().latitude()) + .arg(bBox.bottomLeft().longitude()) + .arg(bBox.topRight().latitude()) + .arg(bBox.topRight().longitude()); + QUrl const url = QUrl(urlString); QNetworkRequest request(url); request.setRawHeader("accept", "application/xml"); QPointer const reply = GlobalObject::networkAccessManager()->get(request);