From b7a258d143f7e8b567270c62d059f082d9f0baf6 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 5 May 2024 02:14:45 -0500 Subject: [PATCH 01/15] Add NMEA settings --- .../scwx/qt/settings/general_settings.cpp | 51 ++++++++++++++++--- .../scwx/qt/settings/general_settings.hpp | 3 ++ .../source/scwx/qt/types/location_types.cpp | 11 ++++ .../source/scwx/qt/types/location_types.hpp | 15 ++++++ test/data | 2 +- 5 files changed, 74 insertions(+), 8 deletions(-) diff --git a/scwx-qt/source/scwx/qt/settings/general_settings.cpp b/scwx-qt/source/scwx/qt/settings/general_settings.cpp index 7f1dabcd..fe73a7d5 100644 --- a/scwx-qt/source/scwx/qt/settings/general_settings.cpp +++ b/scwx-qt/source/scwx/qt/settings/general_settings.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,8 @@ public: types::GetDefaultTimeZoneName(types::DefaultTimeZone::Radar); std::string defaultMapProviderValue = map::GetMapProviderName(map::MapProvider::MapTiler); + std::string defaultPositioningPlugin = + types::GetPositioningPluginName(types::PositioningPlugin::Default); std::string defaultThemeValue = types::GetUiStyleName(types::UiStyle::Default); @@ -44,6 +47,7 @@ public: boost::to_lower(defaultDefaultAlertActionValue); boost::to_lower(defaultDefaultTimeZoneValue); boost::to_lower(defaultMapProviderValue); + boost::to_lower(defaultPositioningPlugin); boost::to_lower(defaultThemeValue); antiAliasingEnabled_.SetDefault(true); @@ -61,6 +65,9 @@ public: mapProvider_.SetDefault(defaultMapProviderValue); mapboxApiKey_.SetDefault("?"); maptilerApiKey_.SetDefault("?"); + nmeaBaudRate_.SetDefault(9600); + nmeaSource_.SetDefault(""); + positioningPlugin_.SetDefault(defaultPositioningPlugin); showMapAttribution_.SetDefault(true); showMapCenter_.SetDefault(false); showMapLogo_.SetDefault(true); @@ -83,6 +90,8 @@ public: loopSpeed_.SetMaximum(99.99); loopTime_.SetMinimum(1); loopTime_.SetMaximum(1440); + nmeaBaudRate_.SetMinimum(1); + nmeaBaudRate_.SetMaximum(999999999); clockFormat_.SetValidator( SCWX_SETTINGS_ENUM_VALIDATOR(scwx::util::ClockFormat, @@ -104,6 +113,10 @@ public: { return !value.empty(); }); maptilerApiKey_.SetValidator([](const std::string& value) { return !value.empty(); }); + positioningPlugin_.SetValidator( + SCWX_SETTINGS_ENUM_VALIDATOR(types::PositioningPlugin, + types::PositioningPluginIterator(), + types::GetPositioningPluginName)); theme_.SetValidator( // SCWX_SETTINGS_ENUM_VALIDATOR(types::UiStyle, // types::UiStyleIterator(), @@ -128,13 +141,16 @@ public: SettingsVariable loopSpeed_ {"loop_speed"}; SettingsVariable loopTime_ {"loop_time"}; SettingsVariable mapProvider_ {"map_provider"}; - SettingsVariable mapboxApiKey_ {"mapbox_api_key"}; - SettingsVariable maptilerApiKey_ {"maptiler_api_key"}; - SettingsVariable showMapAttribution_ {"show_map_attribution"}; - SettingsVariable showMapCenter_ {"show_map_center"}; - SettingsVariable showMapLogo_ {"show_map_logo"}; - SettingsVariable theme_ {"theme"}; - SettingsVariable trackLocation_ {"track_location"}; + SettingsVariable mapboxApiKey_ {"mapbox_api_key"}; + SettingsVariable maptilerApiKey_ {"maptiler_api_key"}; + SettingsVariable nmeaBaudRate_ {"nmea_baud_rate"}; + SettingsVariable nmeaSource_ {"nmea_source"}; + SettingsVariable positioningPlugin_ {"positioning_plugin"}; + SettingsVariable showMapAttribution_ {"show_map_attribution"}; + SettingsVariable showMapCenter_ {"show_map_center"}; + SettingsVariable showMapLogo_ {"show_map_logo"}; + SettingsVariable theme_ {"theme"}; + SettingsVariable trackLocation_ {"track_location"}; SettingsVariable updateNotificationsEnabled_ {"update_notifications"}; SettingsVariable warningsProvider_ {"warnings_provider"}; }; @@ -157,6 +173,9 @@ GeneralSettings::GeneralSettings() : &p->mapProvider_, &p->mapboxApiKey_, &p->maptilerApiKey_, + &p->nmeaBaudRate_, + &p->nmeaSource_, + &p->positioningPlugin_, &p->showMapAttribution_, &p->showMapCenter_, &p->showMapLogo_, @@ -248,6 +267,21 @@ SettingsVariable& GeneralSettings::maptiler_api_key() const return p->maptilerApiKey_; } +SettingsVariable& GeneralSettings::nmea_baud_rate() const +{ + return p->nmeaBaudRate_; +} + +SettingsVariable& GeneralSettings::nmea_source() const +{ + return p->nmeaSource_; +} + +SettingsVariable& GeneralSettings::positioning_plugin() const +{ + return p->positioningPlugin_; +} + SettingsVariable& GeneralSettings::show_map_attribution() const { return p->showMapAttribution_; @@ -319,6 +353,9 @@ bool operator==(const GeneralSettings& lhs, const GeneralSettings& rhs) lhs.p->mapProvider_ == rhs.p->mapProvider_ && lhs.p->mapboxApiKey_ == rhs.p->mapboxApiKey_ && lhs.p->maptilerApiKey_ == rhs.p->maptilerApiKey_ && + lhs.p->nmeaBaudRate_ == rhs.p->nmeaBaudRate_ && + lhs.p->nmeaSource_ == rhs.p->nmeaSource_ && + lhs.p->positioningPlugin_ == rhs.p->positioningPlugin_ && lhs.p->showMapAttribution_ == rhs.p->showMapAttribution_ && lhs.p->showMapCenter_ == rhs.p->showMapCenter_ && lhs.p->showMapLogo_ == rhs.p->showMapLogo_ && diff --git a/scwx-qt/source/scwx/qt/settings/general_settings.hpp b/scwx-qt/source/scwx/qt/settings/general_settings.hpp index 65c3536f..4c162616 100644 --- a/scwx-qt/source/scwx/qt/settings/general_settings.hpp +++ b/scwx-qt/source/scwx/qt/settings/general_settings.hpp @@ -40,6 +40,9 @@ public: SettingsVariable& map_provider() const; SettingsVariable& mapbox_api_key() const; SettingsVariable& maptiler_api_key() const; + SettingsVariable& nmea_baud_rate() const; + SettingsVariable& nmea_source() const; + SettingsVariable& positioning_plugin() const; SettingsVariable& show_map_attribution() const; SettingsVariable& show_map_center() const; SettingsVariable& show_map_logo() const; diff --git a/scwx-qt/source/scwx/qt/types/location_types.cpp b/scwx-qt/source/scwx/qt/types/location_types.cpp index 63ec414c..b1c759de 100644 --- a/scwx-qt/source/scwx/qt/types/location_types.cpp +++ b/scwx-qt/source/scwx/qt/types/location_types.cpp @@ -19,13 +19,24 @@ static const std::unordered_map {LocationMethod::All, "All"}, {LocationMethod::Unknown, "?"}}; +static const std::unordered_map + positioningPluginName_ {{PositioningPlugin::Default, "Default"}, + {PositioningPlugin::Nmea, "NMEA"}, + {PositioningPlugin::Unknown, "?"}}; + SCWX_GET_ENUM(LocationMethod, GetLocationMethod, locationMethodName_) +SCWX_GET_ENUM(PositioningPlugin, GetPositioningPlugin, positioningPluginName_) const std::string& GetLocationMethodName(LocationMethod locationMethod) { return locationMethodName_.at(locationMethod); } +const std::string& GetPositioningPluginName(PositioningPlugin positioningPlugin) +{ + return positioningPluginName_.at(positioningPlugin); +} + } // namespace types } // namespace qt } // namespace scwx diff --git a/scwx-qt/source/scwx/qt/types/location_types.hpp b/scwx-qt/source/scwx/qt/types/location_types.hpp index 3c9407de..65d32f1e 100644 --- a/scwx-qt/source/scwx/qt/types/location_types.hpp +++ b/scwx-qt/source/scwx/qt/types/location_types.hpp @@ -23,9 +23,24 @@ typedef scwx::util:: Iterator LocationMethodIterator; +enum class PositioningPlugin +{ + Default, + Nmea, + Unknown +}; +typedef scwx::util::Iterator + PositioningPluginIterator; + LocationMethod GetLocationMethod(const std::string& name); const std::string& GetLocationMethodName(LocationMethod locationMethod); +PositioningPlugin GetPositioningPlugin(const std::string& name); +const std::string& +GetPositioningPluginName(PositioningPlugin positioningPlugin); + } // namespace types } // namespace qt } // namespace scwx diff --git a/test/data b/test/data index af115273..35e3e40d 160000 --- a/test/data +++ b/test/data @@ -1 +1 @@ -Subproject commit af115273844804d29c502b5ecbb94eee2b4b02f4 +Subproject commit 35e3e40d63bc020dfdc50c438c700c368fdf32fc From 827015395734becf89ccb1f5808a785ffc2eb5d5 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 5 May 2024 02:15:21 -0500 Subject: [PATCH 02/15] Add NMEA to settings dialog --- .../qt/ui/settings/settings_page_widget.hpp | 35 ++ .../qt/ui/settings/unit_settings_widget.cpp | 30 - scwx-qt/source/scwx/qt/ui/settings_dialog.cpp | 236 ++----- scwx-qt/source/scwx/qt/ui/settings_dialog.ui | 586 ++++++++++-------- 4 files changed, 412 insertions(+), 475 deletions(-) diff --git a/scwx-qt/source/scwx/qt/ui/settings/settings_page_widget.hpp b/scwx-qt/source/scwx/qt/ui/settings/settings_page_widget.hpp index e2c2ea59..228badd6 100644 --- a/scwx-qt/source/scwx/qt/ui/settings/settings_page_widget.hpp +++ b/scwx-qt/source/scwx/qt/ui/settings/settings_page_widget.hpp @@ -4,6 +4,41 @@ #include +#define SCWX_ENUM_MAP_FROM_VALUE(Iterator, ToName) \ + [](const std::string& text) -> std::string \ + { \ + for (auto enumValue : Iterator) \ + { \ + const std::string enumName = ToName(enumValue); \ + \ + if (boost::iequals(text, enumName)) \ + { \ + /* Return label */ \ + return enumName; \ + } \ + } \ + \ + /* Label not found, return unknown */ \ + return "?"; \ + } + +#define SCWX_SETTINGS_COMBO_BOX(settingsInterface, comboBox, Iterator, ToName) \ + for (const auto& enumValue : Iterator) \ + { \ + comboBox->addItem(QString::fromStdString(ToName(enumValue))); \ + } \ + \ + settingsInterface.SetMapFromValueFunction( \ + SCWX_ENUM_MAP_FROM_VALUE(Iterator, ToName)); \ + settingsInterface.SetMapToValueFunction( \ + [](std::string text) -> std::string \ + { \ + boost::to_lower(text); \ + return text; \ + }); \ + \ + settingsInterface.SetEditWidget(comboBox); + namespace scwx { namespace qt diff --git a/scwx-qt/source/scwx/qt/ui/settings/unit_settings_widget.cpp b/scwx-qt/source/scwx/qt/ui/settings/unit_settings_widget.cpp index fc1a6b9a..946d21ce 100644 --- a/scwx-qt/source/scwx/qt/ui/settings/unit_settings_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/settings/unit_settings_widget.cpp @@ -12,36 +12,6 @@ #include #include -#define SCWX_SETTINGS_COMBO_BOX(settingsInterface, comboBox, Iterator, ToName) \ - for (const auto& enumValue : Iterator) \ - { \ - comboBox->addItem(QString::fromStdString(ToName(enumValue))); \ - } \ - \ - settingsInterface.SetMapFromValueFunction( \ - [](const std::string& text) -> std::string \ - { \ - for (const auto& enumValue : Iterator) \ - { \ - const std::string valueName = ToName(enumValue); \ - \ - if (boost::iequals(text, valueName)) \ - { \ - return valueName; \ - } \ - } \ - \ - return "?"; \ - }); \ - settingsInterface.SetMapToValueFunction( \ - [](std::string text) -> std::string \ - { \ - boost::to_lower(text); \ - return text; \ - }); \ - \ - settingsInterface.SetEditWidget(comboBox); - namespace scwx { namespace qt diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp index 6830c4e6..9cc80abf 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp @@ -96,24 +96,6 @@ static const std::unordered_map {"VIL", {0u, 255u, 1.0f, 2.5f}}, {"???", {0u, 15u, 0.0f, 1.0f}}}; -#define SCWX_ENUM_MAP_FROM_VALUE(Type, Iterator, ToName) \ - [](const std::string& text) -> std::string \ - { \ - for (Type enumValue : Iterator) \ - { \ - const std::string enumName = ToName(enumValue); \ - \ - if (boost::iequals(text, enumName)) \ - { \ - /* Return label */ \ - return enumName; \ - } \ - } \ - \ - /* Label not found, return unknown */ \ - return "?"; \ - } - class SettingsDialogImpl { public: @@ -134,6 +116,9 @@ public: &defaultAlertAction_, &clockFormat_, &defaultTimeZone_, + &positioningPlugin_, + &nmeaBaudRate_, + &nmeaSource_, &warningsProvider_, &antiAliasingEnabled_, &showMapAttribution_, @@ -235,6 +220,9 @@ public: settings::SettingsInterface defaultAlertAction_ {}; settings::SettingsInterface clockFormat_ {}; settings::SettingsInterface defaultTimeZone_ {}; + settings::SettingsInterface positioningPlugin_ {}; + settings::SettingsInterface nmeaBaudRate_ {}; + settings::SettingsInterface nmeaSource_ {}; settings::SettingsInterface theme_ {}; settings::SettingsInterface warningsProvider_ {}; settings::SettingsInterface antiAliasingEnabled_ {}; @@ -467,38 +455,11 @@ void SettingsDialogImpl::SetupGeneralTab() settings::GeneralSettings& generalSettings = settings::GeneralSettings::Instance(); - for (const auto& uiStyle : types::UiStyleIterator()) - { - self_->ui->themeComboBox->addItem( - QString::fromStdString(types::GetUiStyleName(uiStyle))); - } - theme_.SetSettingsVariable(generalSettings.theme()); - theme_.SetMapFromValueFunction( - [](const std::string& text) -> std::string - { - for (types::UiStyle uiStyle : types::UiStyleIterator()) - { - const std::string uiStyleName = types::GetUiStyleName(uiStyle); - - if (boost::iequals(text, uiStyleName)) - { - // Return UI style label - return uiStyleName; - } - } - - // UI style label not found, return unknown - return "?"; - }); - theme_.SetMapToValueFunction( - [](std::string text) -> std::string - { - // Convert label to lower case and return - boost::to_lower(text); - return text; - }); - theme_.SetEditWidget(self_->ui->themeComboBox); + SCWX_SETTINGS_COMBO_BOX(theme_, + self_->ui->themeComboBox, + types::UiStyleIterator(), + types::GetUiStyleName); theme_.SetResetButton(self_->ui->resetThemeButton); auto radarSites = config::RadarSite::GetAll(); @@ -561,39 +522,11 @@ void SettingsDialogImpl::SetupGeneralTab() gridHeight_.SetEditWidget(self_->ui->gridHeightSpinBox); gridHeight_.SetResetButton(self_->ui->resetGridHeightButton); - for (const auto& mapProvider : map::MapProviderIterator()) - { - self_->ui->mapProviderComboBox->addItem( - QString::fromStdString(map::GetMapProviderName(mapProvider))); - } - mapProvider_.SetSettingsVariable(generalSettings.map_provider()); - mapProvider_.SetMapFromValueFunction( - [](const std::string& text) -> std::string - { - for (map::MapProvider mapProvider : map::MapProviderIterator()) - { - const std::string mapProviderName = - map::GetMapProviderName(mapProvider); - - if (boost::iequals(text, mapProviderName)) - { - // Return map provider label - return mapProviderName; - } - } - - // Map provider label not found, return unknown - return "?"; - }); - mapProvider_.SetMapToValueFunction( - [](std::string text) -> std::string - { - // Convert label to lower case and return - boost::to_lower(text); - return text; - }); - mapProvider_.SetEditWidget(self_->ui->mapProviderComboBox); + SCWX_SETTINGS_COMBO_BOX(mapProvider_, + self_->ui->mapProviderComboBox, + map::MapProviderIterator(), + map::GetMapProviderName); mapProvider_.SetResetButton(self_->ui->resetMapProviderButton); mapboxApiKey_.SetSettingsVariable(generalSettings.mapbox_api_key()); @@ -604,70 +537,43 @@ void SettingsDialogImpl::SetupGeneralTab() mapTilerApiKey_.SetEditWidget(self_->ui->mapTilerApiKeyLineEdit); mapTilerApiKey_.SetResetButton(self_->ui->resetMapTilerApiKeyButton); - for (const auto& alertAction : types::AlertActionIterator()) - { - self_->ui->defaultAlertActionComboBox->addItem( - QString::fromStdString(types::GetAlertActionName(alertAction))); - } - defaultAlertAction_.SetSettingsVariable( generalSettings.default_alert_action()); - defaultAlertAction_.SetMapFromValueFunction( - SCWX_ENUM_MAP_FROM_VALUE(types::AlertAction, - types::AlertActionIterator(), - types::GetAlertActionName)); - defaultAlertAction_.SetMapToValueFunction( - [](std::string text) -> std::string - { - // Convert label to lower case and return - boost::to_lower(text); - return text; - }); - defaultAlertAction_.SetEditWidget(self_->ui->defaultAlertActionComboBox); + SCWX_SETTINGS_COMBO_BOX(defaultAlertAction_, + self_->ui->defaultAlertActionComboBox, + types::AlertActionIterator(), + types::GetAlertActionName); defaultAlertAction_.SetResetButton(self_->ui->resetDefaultAlertActionButton); - for (const auto& clockFormat : scwx::util::ClockFormatIterator()) - { - self_->ui->clockFormatComboBox->addItem( - QString::fromStdString(scwx::util::GetClockFormatName(clockFormat))); - } - clockFormat_.SetSettingsVariable(generalSettings.clock_format()); - clockFormat_.SetMapFromValueFunction( - SCWX_ENUM_MAP_FROM_VALUE(scwx::util::ClockFormat, - scwx::util::ClockFormatIterator(), - scwx::util::GetClockFormatName)); - clockFormat_.SetMapToValueFunction( - [](std::string text) -> std::string - { - // Convert label to lower case and return - boost::to_lower(text); - return text; - }); - clockFormat_.SetEditWidget(self_->ui->clockFormatComboBox); + SCWX_SETTINGS_COMBO_BOX(clockFormat_, + self_->ui->clockFormatComboBox, + scwx::util::ClockFormatIterator(), + scwx::util::GetClockFormatName); clockFormat_.SetResetButton(self_->ui->resetClockFormatButton); - for (const auto& timeZone : types::DefaultTimeZoneIterator()) - { - self_->ui->defaultTimeZoneComboBox->addItem( - QString::fromStdString(types::GetDefaultTimeZoneName(timeZone))); - } - defaultTimeZone_.SetSettingsVariable(generalSettings.default_time_zone()); - defaultTimeZone_.SetMapFromValueFunction( - SCWX_ENUM_MAP_FROM_VALUE(types::DefaultTimeZone, - types::DefaultTimeZoneIterator(), - types::GetDefaultTimeZoneName)); - defaultTimeZone_.SetMapToValueFunction( - [](std::string text) -> std::string - { - // Convert label to lower case and return - boost::to_lower(text); - return text; - }); - defaultTimeZone_.SetEditWidget(self_->ui->defaultTimeZoneComboBox); + SCWX_SETTINGS_COMBO_BOX(defaultTimeZone_, + self_->ui->defaultTimeZoneComboBox, + types::DefaultTimeZoneIterator(), + types::GetDefaultTimeZoneName); defaultTimeZone_.SetResetButton(self_->ui->resetDefaultTimeZoneButton); + positioningPlugin_.SetSettingsVariable(generalSettings.positioning_plugin()); + SCWX_SETTINGS_COMBO_BOX(positioningPlugin_, + self_->ui->positioningPluginComboBox, + types::PositioningPluginIterator(), + types::GetPositioningPluginName); + positioningPlugin_.SetResetButton(self_->ui->resetPositioningPluginButton); + + nmeaBaudRate_.SetSettingsVariable(generalSettings.nmea_baud_rate()); + nmeaBaudRate_.SetEditWidget(self_->ui->nmeaBaudRateSpinBox); + nmeaBaudRate_.SetResetButton(self_->ui->resetNmeaBaudRateButton); + + nmeaSource_.SetSettingsVariable(generalSettings.nmea_source()); + nmeaSource_.SetEditWidget(self_->ui->nmeaSourceLineEdit); + nmeaSource_.SetResetButton(self_->ui->resetNmeaSourceButton); + warningsProvider_.SetSettingsVariable(generalSettings.warnings_provider()); warningsProvider_.SetEditWidget(self_->ui->warningsProviderLineEdit); warningsProvider_.SetResetButton(self_->ui->resetWarningsProviderButton); @@ -991,27 +897,12 @@ void SettingsDialogImpl::SetupAudioTab() dialog->open(); }); - for (const auto& locationMethod : types::LocationMethodIterator()) - { - self_->ui->alertAudioLocationMethodComboBox->addItem( - QString::fromStdString(types::GetLocationMethodName(locationMethod))); - } - alertAudioLocationMethod_.SetSettingsVariable( audioSettings.alert_location_method()); - alertAudioLocationMethod_.SetMapFromValueFunction( - SCWX_ENUM_MAP_FROM_VALUE(types::LocationMethod, - types::LocationMethodIterator(), - types::GetLocationMethodName)); - alertAudioLocationMethod_.SetMapToValueFunction( - [](std::string text) -> std::string - { - // Convert label to lower case and return - boost::to_lower(text); - return text; - }); - alertAudioLocationMethod_.SetEditWidget( - self_->ui->alertAudioLocationMethodComboBox); + SCWX_SETTINGS_COMBO_BOX(alertAudioLocationMethod_, + self_->ui->alertAudioLocationMethodComboBox, + types::LocationMethodIterator(), + types::GetLocationMethodName); alertAudioLocationMethod_.SetResetButton( self_->ui->resetAlertAudioLocationMethodButton); @@ -1159,40 +1050,11 @@ void SettingsDialogImpl::SetupTextTab() hoverTextWrap_.SetEditWidget(self_->ui->hoverTextWrapSpinBox); hoverTextWrap_.SetResetButton(self_->ui->resetHoverTextWrapButton); - for (const auto& tooltipMethod : types::TooltipMethodIterator()) - { - self_->ui->tooltipMethodComboBox->addItem( - QString::fromStdString(types::GetTooltipMethodName(tooltipMethod))); - } - tooltipMethod_.SetSettingsVariable(textSettings.tooltip_method()); - tooltipMethod_.SetMapFromValueFunction( - [](const std::string& text) -> std::string - { - for (types::TooltipMethod tooltipMethod : - types::TooltipMethodIterator()) - { - const std::string tooltipMethodName = - types::GetTooltipMethodName(tooltipMethod); - - if (boost::iequals(text, tooltipMethodName)) - { - // Return tooltip method label - return tooltipMethodName; - } - } - - // Tooltip method label not found, return unknown - return "?"; - }); - tooltipMethod_.SetMapToValueFunction( - [](std::string text) -> std::string - { - // Convert label to lower case and return - boost::to_lower(text); - return text; - }); - tooltipMethod_.SetEditWidget(self_->ui->tooltipMethodComboBox); + SCWX_SETTINGS_COMBO_BOX(tooltipMethod_, + self_->ui->tooltipMethodComboBox, + types::TooltipMethodIterator(), + types::GetTooltipMethodName); tooltipMethod_.SetResetButton(self_->ui->resetTooltipMethodButton); placefileTextDropShadowEnabled_.SetSettingsVariable( diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui index bb3761f7..4364ba58 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui @@ -17,10 +17,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -38,7 +38,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -48,13 +48,13 @@ - QAbstractScrollArea::AdjustToContents + QAbstractScrollArea::SizeAdjustPolicy::AdjustToContents - QListView::Adjust + QListView::ResizeMode::Adjust - QListView::ListMode + QListView::ViewMode::ListMode true @@ -137,14 +137,14 @@ 0 0 513 - 482 + 566 - QFrame::NoFrame + QFrame::Shape::NoFrame @@ -159,231 +159,7 @@ 0 - - - - - - - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg - - - - - - - Mapbox API Key - - - - - - - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg - - - - - - - Default Time Zone - - - - - - - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg - - - - - - - Clock Format - - - - - - - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg - - - - - - - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg - - - - - - - Grid Height - - - - - - - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg - - - - - - - - - - - - - Warnings Provider - - - - - - - - - - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg - - - - - - - QLineEdit::Password - - - - - - - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg - - - - - - - - - - - - - - - - ... - - - - - - - Theme - - - - - - - MapTiler API Key - - - - - - - Map Provider - - - - - - - Default Alert Action - - - - - - - Grid Width - - - - - - - Default Radar Site - - - - - - - - - - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg - - - - - - - - - - QLineEdit::Password - - - - + ... @@ -394,7 +170,32 @@ - + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + Clock Format + + + + + + + NMEA Source + + + + ... @@ -405,6 +206,275 @@ + + + + Mapbox API Key + + + + + + + Grid Height + + + + + + + ... + + + + + + + Warnings Provider + + + + + + + + + + Grid Width + + + + + + + + + + Default Alert Action + + + + + + + Default Time Zone + + + + + + + NMEA Baud Rate + + + + + + + + + + MapTiler API Key + + + + + + + + + + Theme + + + + + + + + + + Default Radar Site + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + QLineEdit::EchoMode::Password + + + + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + Map Provider + + + + + + + QLineEdit::EchoMode::Password + + + + + + + Positioning Plugin + + + + + + + + + + 1 + + + 999999999 + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + @@ -453,7 +523,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -491,15 +561,15 @@ 0 0 - 506 - 383 + 63 + 18 - Qt::Vertical + Qt::Orientation::Vertical @@ -523,10 +593,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -547,7 +617,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -769,7 +839,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -786,19 +856,19 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Plain + QFrame::Shadow::Plain - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -829,10 +899,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -850,7 +920,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -863,10 +933,10 @@ - QFrame::Panel + QFrame::Shape::Panel - QFrame::Plain + QFrame::Shadow::Plain @@ -875,7 +945,7 @@ Tornado Warning expires in 15 minutes - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter true @@ -944,7 +1014,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -974,10 +1044,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -1058,7 +1128,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -1080,10 +1150,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Discard|QDialogButtonBox::Ok|QDialogButtonBox::RestoreDefaults + QDialogButtonBox::StandardButton::Apply|QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Discard|QDialogButtonBox::StandardButton::Ok|QDialogButtonBox::StandardButton::RestoreDefaults From 4545d87670bb1cd4131b8560d0104cc0c893106b Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 5 May 2024 02:15:54 -0500 Subject: [PATCH 03/15] Create an NMEA position source if configured to do so --- .../scwx/qt/manager/position_manager.cpp | 149 +++++++++++++++--- 1 file changed, 124 insertions(+), 25 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/position_manager.cpp b/scwx-qt/source/scwx/qt/manager/position_manager.cpp index 23357444..a6d1e2f1 100644 --- a/scwx-qt/source/scwx/qt/manager/position_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/position_manager.cpp @@ -1,7 +1,10 @@ #include +#include +#include #include #include +#include #include #include @@ -23,37 +26,37 @@ public: explicit Impl(PositionManager* self) : self_ {self}, trackingUuid_ {boost::uuids::random_generator()()} { - // TODO: macOS requires permission - geoPositionInfoSource_ = - QGeoPositionInfoSource::createDefaultSource(self); + auto& generalSettings = settings::GeneralSettings::Instance(); - if (geoPositionInfoSource_ != nullptr) - { - logger_->debug("Using position source: {}", - geoPositionInfoSource_->sourceName().toStdString()); + logger_->debug( + "Available sources: {}", + QGeoPositionInfoSource::availableSources().join(", ").toStdString()); - QObject::connect(geoPositionInfoSource_, - &QGeoPositionInfoSource::positionUpdated, - self_, - [this](const QGeoPositionInfo& info) - { - auto coordinate = info.coordinate(); + CreatePositionSource(); - if (coordinate != position_.coordinate()) - { - logger_->debug("Position updated: {}, {}", - coordinate.latitude(), - coordinate.longitude()); - } + positioningPluginCallbackUuid_ = + generalSettings.positioning_plugin().RegisterValueChangedCallback( + [this](const std::string&) { CreatePositionSource(); }); + nmeaBaudRateCallbackUuid_ = + generalSettings.nmea_baud_rate().RegisterValueChangedCallback( + [this](const std::int64_t&) { CreatePositionSource(); }); + nmeaSourceCallbackUuid_ = + generalSettings.nmea_source().RegisterValueChangedCallback( + [this](const std::string&) { CreatePositionSource(); }); + } + ~Impl() + { + auto& generalSettings = settings::GeneralSettings::Instance(); - position_ = info; - - Q_EMIT self_->PositionUpdated(info); - }); - } + generalSettings.positioning_plugin().UnregisterValueChangedCallback( + positioningPluginCallbackUuid_); + generalSettings.nmea_baud_rate().UnregisterValueChangedCallback( + nmeaBaudRateCallbackUuid_); + generalSettings.nmea_source().UnregisterValueChangedCallback( + nmeaSourceCallbackUuid_); } - ~Impl() {} + void CreatePositionSource(); PositionManager* self_; @@ -62,8 +65,19 @@ public: std::set uuids_ {}; + std::mutex positionSourceMutex_ {}; + QGeoPositionInfoSource* geoPositionInfoSource_ {}; QGeoPositionInfo position_ {}; + + types::PositioningPlugin lastPositioningPlugin_ { + types::PositioningPlugin::Unknown}; + std::int64_t lastNmeaBaudRate_ {-1}; + std::string lastNmeaSource_ {"?"}; + + boost::uuids::uuid positioningPluginCallbackUuid_ {}; + boost::uuids::uuid nmeaBaudRateCallbackUuid_ {}; + boost::uuids::uuid nmeaSourceCallbackUuid_ {}; }; PositionManager::PositionManager() : p(std::make_unique(this)) {} @@ -79,9 +93,94 @@ bool PositionManager::IsLocationTracked() return p->trackingEnabled_; } +void PositionManager::Impl::CreatePositionSource() +{ + auto& generalSettings = settings::GeneralSettings::Instance(); + + types::PositioningPlugin positioningPlugin = types::GetPositioningPlugin( + generalSettings.positioning_plugin().GetValue()); + std::int64_t nmeaBaudRate = generalSettings.nmea_baud_rate().GetValue(); + std::string nmeaSource = generalSettings.nmea_source().GetValue(); + + if (positioningPlugin == lastPositioningPlugin_ && + nmeaBaudRate == lastNmeaBaudRate_ && nmeaSource == lastNmeaSource_) + { + return; + } + + QGeoPositionInfoSource* positionSource = nullptr; + + // TODO: macOS requires permission + if (positioningPlugin == types::PositioningPlugin::Default) + { + positionSource = QGeoPositionInfoSource::createDefaultSource(self_); + } + else if (positioningPlugin == types::PositioningPlugin::Nmea) + { + QVariantMap params {}; + params["nmea.source"] = QString::fromStdString(nmeaSource); + params["nmea.baudrate"] = static_cast(nmeaBaudRate); + + positionSource = + QGeoPositionInfoSource::createSource("nmea", params, self_); + } + + if (positionSource != nullptr) + { + logger_->debug("Using position source: {}", + positionSource->sourceName().toStdString()); + + QObject::connect(positionSource, + &QGeoPositionInfoSource::positionUpdated, + self_, + [this](const QGeoPositionInfo& info) + { + auto coordinate = info.coordinate(); + + if (coordinate != position_.coordinate()) + { + logger_->debug("Position updated: {}, {}", + coordinate.latitude(), + coordinate.longitude()); + } + + position_ = info; + + Q_EMIT self_->PositionUpdated(info); + }); + } + else + { + logger_->error("Unable to create position source for plugin: {}", + types::GetPositioningPluginName(positioningPlugin)); + return; + } + + lastPositioningPlugin_ = positioningPlugin; + lastNmeaBaudRate_ = nmeaBaudRate; + lastNmeaSource_ = nmeaSource; + + std::unique_lock lock {positionSourceMutex_}; + + if (geoPositionInfoSource_ != nullptr) + { + geoPositionInfoSource_->stopUpdates(); + delete geoPositionInfoSource_; + } + + geoPositionInfoSource_ = positionSource; + + if (!uuids_.empty()) + { + positionSource->startUpdates(); + } +} + void PositionManager::EnablePositionUpdates(boost::uuids::uuid uuid, bool enabled) { + std::unique_lock lock {p->positionSourceMutex_}; + if (p->geoPositionInfoSource_ == nullptr) { return; From 523c26ed470f911b6e8b27c879a28ae9216c7248 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 12 May 2024 11:44:58 -0500 Subject: [PATCH 04/15] Add serial port dependency --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6efc38a6..102f2aab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: msvc_version: 2022 qt_version: 6.6.2 qt_arch: win64_msvc2019_64 - qt_modules: qtimageformats qtmultimedia qtpositioning + qt_modules: qtimageformats qtmultimedia qtpositioning qtserialport qt_tools: '' conan_arch: x86_64 conan_compiler: Visual Studio @@ -46,7 +46,7 @@ jobs: compiler: gcc qt_version: 6.6.2 qt_arch: gcc_64 - qt_modules: qtimageformats qtmultimedia qtpositioning + qt_modules: qtimageformats qtmultimedia qtpositioning qtserialport qt_tools: '' conan_arch: x86_64 conan_compiler: gcc From 230c96e450c8fefa44083194cc1454561795cce3 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 14 May 2024 00:33:57 -0500 Subject: [PATCH 05/15] Initial serial port dialog work --- ACKNOWLEDGEMENTS.md | 2 +- scwx-qt/scwx-qt.cmake | 6 + .../source/scwx/qt/ui/serial_port_dialog.cpp | 199 ++++++++++++++++++ .../source/scwx/qt/ui/serial_port_dialog.hpp | 35 +++ .../source/scwx/qt/ui/serial_port_dialog.ui | 92 ++++++++ 5 files changed, 333 insertions(+), 1 deletion(-) create mode 100644 scwx-qt/source/scwx/qt/ui/serial_port_dialog.cpp create mode 100644 scwx-qt/source/scwx/qt/ui/serial_port_dialog.hpp create mode 100644 scwx-qt/source/scwx/qt/ui/serial_port_dialog.ui diff --git a/ACKNOWLEDGEMENTS.md b/ACKNOWLEDGEMENTS.md index 4b5474f2..154d0f6c 100644 --- a/ACKNOWLEDGEMENTS.md +++ b/ACKNOWLEDGEMENTS.md @@ -34,7 +34,7 @@ Supercell Wx uses code from the following dependencies: | [MapLibre Native](https://maplibre.org/projects/maplibre-native/) | [BSD 2-Clause "Simplified" License](https://spdx.org/licenses/BSD-2-Clause.html) | | [nunicode](https://bitbucket.org/alekseyt/nunicode/src/master/) | [MIT License](https://spdx.org/licenses/MIT.html) | Modified for MapLibre Native | | [OpenSSL](https://www.openssl.org/) | [OpenSSL License](https://spdx.org/licenses/OpenSSL.html) | -| [Qt](https://www.qt.io/) | [GNU Lesser General Public License v3.0 only](https://spdx.org/licenses/LGPL-3.0-only.html) | Qt Core, Qt GUI, Qt Multimedia, Qt Network, Qt OpenGL, Qt Positioning, Qt SQL, Qt SVG, Qt Widgets
Additional Licenses: https://doc.qt.io/qt-6/licenses-used-in-qt.html | +| [Qt](https://www.qt.io/) | [GNU Lesser General Public License v3.0 only](https://spdx.org/licenses/LGPL-3.0-only.html) | Qt Core, Qt GUI, Qt Multimedia, Qt Network, Qt OpenGL, Qt Positioning, Qt Serial Port, Qt SQL, Qt SVG, Qt Widgets
Additional Licenses: https://doc.qt.io/qt-6/licenses-used-in-qt.html | | [re2](https://github.com/google/re2) | [BSD 3-Clause "New" or "Revised" License](https://spdx.org/licenses/BSD-3-Clause.html) | | [spdlog](https://github.com/gabime/spdlog) | [MIT License](https://spdx.org/licenses/MIT.html) | | [SQLite](https://www.sqlite.org/) | Public Domain | diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index cda6c7f2..451c2c81 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -28,6 +28,7 @@ find_package(QT NAMES Qt6 OpenGL OpenGLWidgets Positioning + SerialPort Svg Widgets REQUIRED) @@ -39,6 +40,7 @@ find_package(Qt${QT_VERSION_MAJOR} OpenGL OpenGLWidgets Positioning + SerialPort Svg Widgets REQUIRED) @@ -248,6 +250,7 @@ set(HDR_UI source/scwx/qt/ui/about_dialog.hpp source/scwx/qt/ui/placefile_settings_widget.hpp source/scwx/qt/ui/progress_dialog.hpp source/scwx/qt/ui/radar_site_dialog.hpp + source/scwx/qt/ui/serial_port_dialog.hpp source/scwx/qt/ui/settings_dialog.hpp source/scwx/qt/ui/update_dialog.hpp) set(SRC_UI source/scwx/qt/ui/about_dialog.cpp @@ -272,6 +275,7 @@ set(SRC_UI source/scwx/qt/ui/about_dialog.cpp source/scwx/qt/ui/progress_dialog.cpp source/scwx/qt/ui/radar_site_dialog.cpp source/scwx/qt/ui/settings_dialog.cpp + source/scwx/qt/ui/serial_port_dialog.cpp source/scwx/qt/ui/update_dialog.cpp) set(UI_UI source/scwx/qt/ui/about_dialog.ui source/scwx/qt/ui/alert_dialog.ui @@ -287,6 +291,7 @@ set(UI_UI source/scwx/qt/ui/about_dialog.ui source/scwx/qt/ui/progress_dialog.ui source/scwx/qt/ui/radar_site_dialog.ui source/scwx/qt/ui/settings_dialog.ui + source/scwx/qt/ui/serial_port_dialog.ui source/scwx/qt/ui/update_dialog.ui) set(HDR_UI_SETTINGS source/scwx/qt/ui/settings/hotkey_settings_widget.hpp source/scwx/qt/ui/settings/settings_page_widget.hpp @@ -597,6 +602,7 @@ target_link_libraries(scwx-qt PUBLIC Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::OpenGLWidgets Qt${QT_VERSION_MAJOR}::Multimedia Qt${QT_VERSION_MAJOR}::Positioning + Qt${QT_VERSION_MAJOR}::SerialPort Qt${QT_VERSION_MAJOR}::Svg Boost::json Boost::timer diff --git a/scwx-qt/source/scwx/qt/ui/serial_port_dialog.cpp b/scwx-qt/source/scwx/qt/ui/serial_port_dialog.cpp new file mode 100644 index 00000000..6901e4b6 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/serial_port_dialog.cpp @@ -0,0 +1,199 @@ +#define __STDC_WANT_LIB_EXT1__ 1 + +#include "serial_port_dialog.hpp" +#include "ui_serial_port_dialog.h" + +#include + +#include +#include +#include +#include + +#if defined(_WIN32) +# include +# include +# include +#endif + +namespace scwx +{ +namespace qt +{ +namespace ui +{ + +static const std::string logPrefix_ = "scwx::qt::ui::serial_port_dialog"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +class SerialPortDialog::Impl +{ +public: + explicit Impl(SerialPortDialog* self) : + self_ {self}, model_ {new QStandardItemModel(self)} + { + } + ~Impl() = default; + + void LogSerialPortInfo(const QSerialPortInfo& info); + void ReadComPortSettings(); + void RefreshSerialDevices(); + + SerialPortDialog* self_; + QStandardItemModel* model_; + + std::string selectedSerialPort_ {"?"}; +}; + +SerialPortDialog::SerialPortDialog(QWidget* parent) : + QDialog(parent), + p {std::make_unique(this)}, + ui(new Ui::SerialPortDialog) +{ + ui->setupUi(this); + + connect(ui->refreshButton, + &QAbstractButton::clicked, + this, + [this]() { p->RefreshSerialDevices(); }); +} + +SerialPortDialog::~SerialPortDialog() +{ + delete ui; +} + +std::string SerialPortDialog::serial_port() +{ + return p->selectedSerialPort_; +} + +void SerialPortDialog::Impl::LogSerialPortInfo(const QSerialPortInfo& info) +{ + logger_->debug("Serial Port: {}", info.portName().toStdString()); + logger_->debug(" Description: {}", info.description().toStdString()); + logger_->debug(" System Loc: {}", info.systemLocation().toStdString()); + logger_->debug(" Manufacturer: {}", info.manufacturer().toStdString()); + logger_->debug(" Vendor ID: {}", info.vendorIdentifier()); + logger_->debug(" Product ID: {}", info.productIdentifier()); + logger_->debug(" Serial No: {}", info.serialNumber().toStdString()); +} + +void SerialPortDialog::Impl::RefreshSerialDevices() +{ + QList availablePorts = QSerialPortInfo::availablePorts(); + + for (auto& port : availablePorts) + { + LogSerialPortInfo(port); + } + + ReadComPortSettings(); +} + +void SerialPortDialog::Impl::ReadComPortSettings() +{ +#if defined(_WIN32) + const LPCTSTR lpSubKey = + TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Ports"); + DWORD ulOptions = 0; + REGSAM samDesired = KEY_READ; + HKEY hkResult; + LSTATUS status; + + // Open Port Settings Key + status = RegOpenKeyEx( + HKEY_LOCAL_MACHINE, lpSubKey, ulOptions, samDesired, &hkResult); + + if (status == ERROR_SUCCESS) + { + DWORD dwIndex = 0; + TCHAR valueName[MAX_PATH]; + LPDWORD lpReserved = nullptr; + DWORD type; + TCHAR valueData[64]; + char buffer[MAX_PATH]; // Buffer for string conversion + + // Number of characters, not including terminating null + static constexpr DWORD maxValueNameSize = + sizeof(valueName) / sizeof(TCHAR) - 1; + DWORD valueNameSize = maxValueNameSize; + + // Number of bytes + DWORD valueDataSize = sizeof(valueData); + + static constexpr std::size_t bufferSize = sizeof(buffer); + + // Enumerate each port value + while ((status = RegEnumValue(hkResult, + dwIndex, + valueName, + &valueNameSize, + lpReserved, + &type, + (LPBYTE) &valueData, + &valueDataSize)) == ERROR_SUCCESS || + status == ERROR_MORE_DATA) + { + // Validate port value + if (status == ERROR_SUCCESS && // + type == REG_SZ && // + valueNameSize >= 5 && // COM#: + valueNameSize < sizeof(buffer) - 1 && // Strip off : + valueDataSize > sizeof(TCHAR) && // Null character + _tcsncmp(valueName, TEXT("COM"), 3) == 0) + { + errno_t error; + std::size_t returnValue; + + // Get port name + if ((error = wcstombs_s(&returnValue, + buffer, + sizeof(buffer), + valueName, + valueNameSize - 1)) != 0) + { + logger_->error( + "Error converting registry value name to string: {}", + returnValue); + continue; + } + + std::string portName = buffer; + + // Get port data + if ((error = wcstombs_s(&returnValue, + buffer, + sizeof(buffer), + valueData, + sizeof(buffer) - 1)) != 0) + { + logger_->error( + "Error converting registry value data to string: {}", + returnValue); + continue; + } + + std::string portData = buffer; + + logger_->debug("Port {} has data \"{}\"", portName, portData); + } + + valueNameSize = maxValueNameSize; + valueDataSize = sizeof(valueData); + ++dwIndex; + } + + RegCloseKey(hkResult); + } + else + { + logger_->warn("Could not open COM port settings registry key: {}", + status); + } +#endif +} + +} // namespace ui +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/serial_port_dialog.hpp b/scwx-qt/source/scwx/qt/ui/serial_port_dialog.hpp new file mode 100644 index 00000000..8bd3ee2c --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/serial_port_dialog.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include + +namespace Ui +{ +class SerialPortDialog; +} + +namespace scwx +{ +namespace qt +{ +namespace ui +{ +class SerialPortDialog : public QDialog +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(SerialPortDialog) + +public: + explicit SerialPortDialog(QWidget* parent = nullptr); + ~SerialPortDialog(); + + std::string serial_port(); + +private: + class Impl; + std::unique_ptr p; + Ui::SerialPortDialog* ui; +}; + +} // namespace ui +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/serial_port_dialog.ui b/scwx-qt/source/scwx/qt/ui/serial_port_dialog.ui new file mode 100644 index 00000000..87065d6a --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/serial_port_dialog.ui @@ -0,0 +1,92 @@ + + + SerialPortDialog + + + + 0 + 0 + 400 + 300 + + + + Select Serial Port + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + &Refresh + + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + + + + + + + + + + + + buttonBox + accepted() + SerialPortDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + SerialPortDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + From cb14fba3b0c83cb6f8dbebba35b89f9e76303dbd Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 14 May 2024 22:10:39 -0500 Subject: [PATCH 06/15] Get serial bus reported device description --- scwx-qt/scwx-qt.cmake | 1 + .../source/scwx/qt/ui/serial_port_dialog.cpp | 206 +++++++++++++++++- 2 files changed, 200 insertions(+), 7 deletions(-) diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 451c2c81..47340552 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -608,6 +608,7 @@ target_link_libraries(scwx-qt PUBLIC Qt${QT_VERSION_MAJOR}::Widgets Boost::timer QMapLibre::Core $<$:opengl32> + $<$:SetupAPI> Fontconfig::Fontconfig GeographicLib::GeographicLib GEOS::geos diff --git a/scwx-qt/source/scwx/qt/ui/serial_port_dialog.cpp b/scwx-qt/source/scwx/qt/ui/serial_port_dialog.cpp index 6901e4b6..69dabd6f 100644 --- a/scwx-qt/source/scwx/qt/ui/serial_port_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/serial_port_dialog.cpp @@ -12,8 +12,12 @@ #if defined(_WIN32) # include -# include +# include +# include +# include +# include # include +# include #endif namespace scwx @@ -36,9 +40,17 @@ public: ~Impl() = default; void LogSerialPortInfo(const QSerialPortInfo& info); + void ReadComPortProperties(); void ReadComPortSettings(); void RefreshSerialDevices(); +#if defined(_WIN32) + static std::string GetDevicePropertyString(HDEVINFO& deviceInfoSet, + SP_DEVINFO_DATA& deviceInfoData, + DEVPROPKEY propertyKey); + static std::string GetRegistryValueDataString(HKEY hKey, LPCTSTR lpValue); +#endif + SerialPortDialog* self_; QStandardItemModel* model_; @@ -88,9 +100,70 @@ void SerialPortDialog::Impl::RefreshSerialDevices() LogSerialPortInfo(port); } + ReadComPortProperties(); ReadComPortSettings(); } +void SerialPortDialog::Impl::ReadComPortProperties() +{ +#if defined(_WIN32) + GUID classGuid = GUID_DEVCLASS_PORTS; + PCWSTR enumerator = nullptr; + HWND hwndParent = nullptr; + DWORD flags = DIGCF_PRESENT; + HDEVINFO deviceInfoSet; + + // Retrieve COM port devices + deviceInfoSet = + SetupDiGetClassDevs(&classGuid, enumerator, hwndParent, flags); + if (deviceInfoSet == INVALID_HANDLE_VALUE) + { + logger_->error("Error getting COM port devices"); + return; + } + + DWORD memberIndex = 0; + SP_DEVINFO_DATA deviceInfoData {}; + deviceInfoData.cbSize = sizeof(deviceInfoData); + flags = 0; + + // For each COM port device + while (SetupDiEnumDeviceInfo(deviceInfoSet, memberIndex++, &deviceInfoData)) + { + DWORD scope = DICS_FLAG_GLOBAL; + DWORD hwProfile = 0; + DWORD keyType = DIREG_DEV; + REGSAM samDesired = KEY_READ; + HKEY devRegKey = SetupDiOpenDevRegKey( + deviceInfoSet, &deviceInfoData, scope, hwProfile, keyType, samDesired); + + if (devRegKey == INVALID_HANDLE_VALUE) + { + logger_->error("Unable to open device registry key: {}", + GetLastError()); + continue; + } + + // Read Port Name and Device Description + std::string portName = + GetRegistryValueDataString(devRegKey, TEXT("PortName")); + + if (portName.empty()) + { + // Ignore device without port name + continue; + } + + std::string busReportedDeviceDesc = GetDevicePropertyString( + deviceInfoSet, deviceInfoData, DEVPKEY_Device_BusReportedDeviceDesc); + + logger_->debug("Port: {} ({})", portName, busReportedDeviceDesc); + + RegCloseKey(devRegKey); + } +#endif +} + void SerialPortDialog::Impl::ReadComPortSettings() { #if defined(_WIN32) @@ -126,12 +199,12 @@ void SerialPortDialog::Impl::ReadComPortSettings() // Enumerate each port value while ((status = RegEnumValue(hkResult, - dwIndex, + dwIndex++, valueName, &valueNameSize, lpReserved, &type, - (LPBYTE) &valueData, + reinterpret_cast(&valueData), &valueDataSize)) == ERROR_SUCCESS || status == ERROR_MORE_DATA) { @@ -176,24 +249,143 @@ void SerialPortDialog::Impl::ReadComPortSettings() std::string portData = buffer; - logger_->debug("Port {} has data \"{}\"", portName, portData); + logger_->debug("Port Settings: {} ({})", portName, portData); } valueNameSize = maxValueNameSize; valueDataSize = sizeof(valueData); - ++dwIndex; } RegCloseKey(hkResult); } else { - logger_->warn("Could not open COM port settings registry key: {}", - status); + logger_->error("Could not open COM port settings registry key: {}", + status); } #endif } +#if defined(_WIN32) +std::string +SerialPortDialog::Impl::GetDevicePropertyString(HDEVINFO& deviceInfoSet, + SP_DEVINFO_DATA& deviceInfoData, + DEVPROPKEY propertyKey) +{ + std::string devicePropertyString {}; + + DEVPROPTYPE propertyType = 0; + std::vector propertyBuffer {}; + DWORD requiredSize = 0; + DWORD flags = 0; + + BOOL status = SetupDiGetDeviceProperty(deviceInfoSet, + &deviceInfoData, + &propertyKey, + &propertyType, + nullptr, + 0, + &requiredSize, + flags); + + if (requiredSize > 0) + { + propertyBuffer.reserve(requiredSize / sizeof(TCHAR)); + + status = SetupDiGetDeviceProperty( + deviceInfoSet, + &deviceInfoData, + &propertyKey, + &propertyType, + reinterpret_cast(propertyBuffer.data()), + static_cast(propertyBuffer.capacity() * sizeof(TCHAR)), + &requiredSize, + flags); + } + + if (status && requiredSize > 0) + { + errno_t error; + std::size_t returnValue; + + devicePropertyString.resize(requiredSize / sizeof(TCHAR)); + + if ((error = wcstombs_s(&returnValue, + devicePropertyString.data(), + devicePropertyString.size(), + propertyBuffer.data(), + _TRUNCATE)) != 0) + { + logger_->error("Error converting device property string: {}", + returnValue); + devicePropertyString.clear(); + } + else if (!devicePropertyString.empty()) + { + // Remove trailing null + devicePropertyString.erase(devicePropertyString.size() - 1); + } + } + + return devicePropertyString; +} + +std::string SerialPortDialog::Impl::GetRegistryValueDataString(HKEY hKey, + LPCTSTR lpValue) +{ + std::string dataString {}; + + LPCTSTR lpSubKey = nullptr; + DWORD dwFlags = RRF_RT_REG_SZ; // Restrict type to REG_SZ + DWORD dwType; + + std::vector dataBuffer {}; + DWORD dataBufferSize = 0; + + LSTATUS status = RegGetValue( + hKey, lpSubKey, lpValue, dwFlags, &dwType, nullptr, &dataBufferSize); + + if (status == ERROR_SUCCESS && dataBufferSize > 0) + { + dataBuffer.reserve(dataBufferSize / sizeof(TCHAR)); + + status = RegGetValue(hKey, + lpSubKey, + lpValue, + dwFlags, + &dwType, + reinterpret_cast(dataBuffer.data()), + &dataBufferSize); + } + + if (status == ERROR_SUCCESS && dataBufferSize > 0) + { + errno_t error; + std::size_t returnValue; + + dataString.resize(dataBufferSize / sizeof(TCHAR)); + + if ((error = wcstombs_s(&returnValue, + dataString.data(), + dataString.size(), + dataBuffer.data(), + _TRUNCATE)) != 0) + { + logger_->error("Error converting registry value data string: {}", + returnValue); + dataString.clear(); + } + else if (!dataString.empty()) + { + // Remove trailing null + dataString.erase(dataString.size() - 1); + } + } + + return dataString; +} +#endif + } // namespace ui } // namespace qt } // namespace scwx From 27b05677537eac8b1f411f1d4547e60b51beb456 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 14 May 2024 23:02:05 -0500 Subject: [PATCH 07/15] Store serial port information in member variables --- .../source/scwx/qt/ui/serial_port_dialog.cpp | 110 ++++++++++++++++-- 1 file changed, 102 insertions(+), 8 deletions(-) diff --git a/scwx-qt/source/scwx/qt/ui/serial_port_dialog.cpp b/scwx-qt/source/scwx/qt/ui/serial_port_dialog.cpp index 69dabd6f..33593681 100644 --- a/scwx-qt/source/scwx/qt/ui/serial_port_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/serial_port_dialog.cpp @@ -4,6 +4,9 @@ #include "ui_serial_port_dialog.h" #include +#include + +#include #include #include @@ -33,6 +36,25 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_); class SerialPortDialog::Impl { public: + struct PortProperties + { + std::string busReportedDeviceDescription_ {}; + }; + + struct PortSettings + { + int baudRate_ {-1}; // Positive + std::string parity_ {"n"}; // [e]ven, [o]dd, [m]ark, [s]pace, [n]one + int dataBits_ {8}; // [4-8] + float stopBits_ {1}; // 1, 1.5, 2 + std::string + flowControl_ {}; // "" (none), "p" (hardware), "x" (Xon / Xoff) + }; + + typedef std::unordered_map PortInfoMap; + typedef std::unordered_map PortPropertiesMap; + typedef std::unordered_map PortSettingsMap; + explicit Impl(SerialPortDialog* self) : self_ {self}, model_ {new QStandardItemModel(self)} { @@ -40,10 +62,14 @@ public: ~Impl() = default; void LogSerialPortInfo(const QSerialPortInfo& info); - void ReadComPortProperties(); - void ReadComPortSettings(); void RefreshSerialDevices(); + static void ReadComPortProperties(PortPropertiesMap& portPropertiesMap); + static void ReadComPortSettings(PortSettingsMap& portSettingsMap); + static void StorePortSettings(const std::string& portName, + const std::string& settingsString, + PortSettingsMap& portSettingsMap); + #if defined(_WIN32) static std::string GetDevicePropertyString(HDEVINFO& deviceInfoSet, SP_DEVINFO_DATA& deviceInfoData, @@ -55,6 +81,10 @@ public: QStandardItemModel* model_; std::string selectedSerialPort_ {"?"}; + + PortInfoMap portInfoMap_ {}; + PortPropertiesMap portPropertiesMap_ {}; + PortSettingsMap portSettingsMap_ {}; }; SerialPortDialog::SerialPortDialog(QWidget* parent) : @@ -95,16 +125,26 @@ void SerialPortDialog::Impl::RefreshSerialDevices() { QList availablePorts = QSerialPortInfo::availablePorts(); + PortInfoMap newPortInfoMap {}; + PortPropertiesMap newPortPropertiesMap {}; + PortSettingsMap newPortSettingsMap {}; + for (auto& port : availablePorts) { LogSerialPortInfo(port); + newPortInfoMap.insert_or_assign(port.portName().toStdString(), port); } - ReadComPortProperties(); - ReadComPortSettings(); + ReadComPortProperties(newPortPropertiesMap); + ReadComPortSettings(newPortSettingsMap); + + portInfoMap_.swap(newPortInfoMap); + portPropertiesMap_.swap(newPortPropertiesMap); + portSettingsMap_.swap(newPortSettingsMap); } -void SerialPortDialog::Impl::ReadComPortProperties() +void SerialPortDialog::Impl::ReadComPortProperties( + PortPropertiesMap& portPropertiesMap) { #if defined(_WIN32) GUID classGuid = GUID_DEVCLASS_PORTS; @@ -154,17 +194,22 @@ void SerialPortDialog::Impl::ReadComPortProperties() continue; } - std::string busReportedDeviceDesc = GetDevicePropertyString( + PortProperties properties {}; + properties.busReportedDeviceDescription_ = GetDevicePropertyString( deviceInfoSet, deviceInfoData, DEVPKEY_Device_BusReportedDeviceDesc); - logger_->debug("Port: {} ({})", portName, busReportedDeviceDesc); + logger_->debug( + "Port: {} ({})", portName, properties.busReportedDeviceDescription_); + + portPropertiesMap.emplace(portName, std::move(properties)); RegCloseKey(devRegKey); } #endif } -void SerialPortDialog::Impl::ReadComPortSettings() +void SerialPortDialog::Impl::ReadComPortSettings( + PortSettingsMap& portSettingsMap) { #if defined(_WIN32) const LPCTSTR lpSubKey = @@ -250,6 +295,8 @@ void SerialPortDialog::Impl::ReadComPortSettings() std::string portData = buffer; logger_->debug("Port Settings: {} ({})", portName, portData); + + StorePortSettings(portName, portData, portSettingsMap); } valueNameSize = maxValueNameSize; @@ -266,6 +313,53 @@ void SerialPortDialog::Impl::ReadComPortSettings() #endif } +void SerialPortDialog::Impl::StorePortSettings( + const std::string& portName, + const std::string& settingsString, + PortSettingsMap& portSettingsMap) +{ + PortSettings portSettings {}; + + std::vector tokenList = + util::ParseTokens(settingsString, {",", ",", ",", ",", ","}); + + try + { + if (tokenList.size() >= 1) + { + // Positive integer + portSettings.baudRate_ = std::stoi(tokenList.at(0)); + } + if (tokenList.size() >= 2) + { + // [e]ven, [o]dd, [m]ark, [s]pace, [n]one + portSettings.parity_ = tokenList.at(1); + } + if (tokenList.size() >= 3) + { + // [4-8] + portSettings.dataBits_ = std::stoi(tokenList.at(2)); + } + if (tokenList.size() >= 4) + { + // 1, 1.5, 2 + portSettings.stopBits_ = std::stof(tokenList.at(3)); + } + if (tokenList.size() >= 5) + { + // "" (none), "p" (hardware), "x" (Xon / Xoff) + portSettings.flowControl_ = tokenList.at(4); + } + + portSettingsMap.emplace(portName, std::move(portSettings)); + } + catch (const std::exception&) + { + logger_->error( + "Could not parse {} port settings: {}", portName, settingsString); + } +} + #if defined(_WIN32) std::string SerialPortDialog::Impl::GetDevicePropertyString(HDEVINFO& deviceInfoSet, From f263011e975340c761db027888e07786cc6b4c0d Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 15 May 2024 22:51:36 -0500 Subject: [PATCH 08/15] Add serial port information to tree view --- .../source/scwx/qt/ui/serial_port_dialog.cpp | 138 +++++++++++++++++- .../source/scwx/qt/ui/serial_port_dialog.hpp | 1 + .../source/scwx/qt/ui/serial_port_dialog.ui | 12 +- 3 files changed, 145 insertions(+), 6 deletions(-) diff --git a/scwx-qt/source/scwx/qt/ui/serial_port_dialog.cpp b/scwx-qt/source/scwx/qt/ui/serial_port_dialog.cpp index 33593681..4fc91660 100644 --- a/scwx-qt/source/scwx/qt/ui/serial_port_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/serial_port_dialog.cpp @@ -56,13 +56,16 @@ public: typedef std::unordered_map PortSettingsMap; explicit Impl(SerialPortDialog* self) : - self_ {self}, model_ {new QStandardItemModel(self)} + self_ {self}, + model_ {new QStandardItemModel(self)}, + proxyModel_ {new QSortFilterProxyModel(self)} { } ~Impl() = default; void LogSerialPortInfo(const QSerialPortInfo& info); void RefreshSerialDevices(); + void UpdateModel(); static void ReadComPortProperties(PortPropertiesMap& portPropertiesMap); static void ReadComPortSettings(PortSettingsMap& portSettingsMap); @@ -77,8 +80,9 @@ public: static std::string GetRegistryValueDataString(HKEY hKey, LPCTSTR lpValue); #endif - SerialPortDialog* self_; - QStandardItemModel* model_; + SerialPortDialog* self_; + QStandardItemModel* model_; + QSortFilterProxyModel* proxyModel_; std::string selectedSerialPort_ {"?"}; @@ -94,10 +98,67 @@ SerialPortDialog::SerialPortDialog(QWidget* parent) : { ui->setupUi(this); + p->proxyModel_->setSourceModel(p->model_); + ui->serialPortView->setModel(p->proxyModel_); + ui->serialPortView->setEditTriggers( + QAbstractItemView::EditTrigger::NoEditTriggers); + ui->serialPortView->sortByColumn(0, Qt::SortOrder::AscendingOrder); + + p->RefreshSerialDevices(); + + ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok) + ->setEnabled(false); + connect(ui->refreshButton, &QAbstractButton::clicked, this, [this]() { p->RefreshSerialDevices(); }); + + connect(ui->serialPortView, + &QTreeView::doubleClicked, + this, + [this]() { Q_EMIT accept(); }); + connect( + ui->serialPortView->selectionModel(), + &QItemSelectionModel::selectionChanged, + this, + [this](const QItemSelection& selected, const QItemSelection& deselected) + { + if (selected.size() == 0 && deselected.size() == 0) + { + // Items which stay selected but change their index are not + // included in selected and deselected. Thus, this signal might + // be emitted with both selected and deselected empty, if only + // the indices of selected items change. + return; + } + + ui->buttonBox->button(QDialogButtonBox::Ok) + ->setEnabled(selected.size() > 0); + + if (selected.size() > 0) + { + QModelIndex selectedIndex = + p->proxyModel_->mapToSource(selected[0].indexes()[0]); + selectedIndex = p->model_->index(selectedIndex.row(), 0); + QVariant variantData = p->model_->data(selectedIndex); + if (variantData.typeId() == QMetaType::QString) + { + p->selectedSerialPort_ = variantData.toString().toStdString(); + } + else + { + logger_->warn("Unexpected selection data type"); + p->selectedSerialPort_ = std::string {"?"}; + } + } + else + { + p->selectedSerialPort_ = std::string {"?"}; + } + + logger_->debug("Selected: {}", p->selectedSerialPort_); + }); } SerialPortDialog::~SerialPortDialog() @@ -110,6 +171,19 @@ std::string SerialPortDialog::serial_port() return p->selectedSerialPort_; } +int SerialPortDialog::baud_rate() +{ + int baudRate = -1; + + auto it = p->portSettingsMap_.find(p->selectedSerialPort_); + if (it != p->portSettingsMap_.cend()) + { + baudRate = it->second.baudRate_; + } + + return baudRate; +} + void SerialPortDialog::Impl::LogSerialPortInfo(const QSerialPortInfo& info) { logger_->debug("Serial Port: {}", info.portName().toStdString()); @@ -141,10 +215,64 @@ void SerialPortDialog::Impl::RefreshSerialDevices() portInfoMap_.swap(newPortInfoMap); portPropertiesMap_.swap(newPortPropertiesMap); portSettingsMap_.swap(newPortSettingsMap); + + UpdateModel(); +} + +void SerialPortDialog::Impl::UpdateModel() +{ +#if defined(_WIN32) + static const QStringList headerLabels { + tr("Port"), tr("Description"), tr("Device")}; +#else + static const QStringList headerLabels {tr("Port"), tr("Description")}; +#endif + + // Clear existing serial ports + model_->clear(); + + // Reset selected serial port and disable OK button + selectedSerialPort_ = std::string {"?"}; + self_->ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok) + ->setEnabled(false); + + // Reset headers + model_->setHorizontalHeaderLabels(headerLabels); + + QStandardItem* root = model_->invisibleRootItem(); + + for (auto& port : portInfoMap_) + { + const QString portName = port.second.portName(); + const QString description = port.second.description(); + +#if defined(_WIN32) + QString device {}; + + auto portPropertiesIt = portPropertiesMap_.find(port.first); + if (portPropertiesIt != portPropertiesMap_.cend()) + { + device = QString::fromStdString( + portPropertiesIt->second.busReportedDeviceDescription_); + } + + root->appendRow({new QStandardItem(portName), + new QStandardItem(description), + new QStandardItem(device)}); +#else + root->appendRow( + {new QStandardItem(portName), new QStandardItem(description)}); +#endif + } + + for (int column = 0; column < model_->columnCount(); column++) + { + self_->ui->serialPortView->resizeColumnToContents(column); + } } void SerialPortDialog::Impl::ReadComPortProperties( - PortPropertiesMap& portPropertiesMap) + [[maybe_unused]] PortPropertiesMap& portPropertiesMap) { #if defined(_WIN32) GUID classGuid = GUID_DEVCLASS_PORTS; @@ -209,7 +337,7 @@ void SerialPortDialog::Impl::ReadComPortProperties( } void SerialPortDialog::Impl::ReadComPortSettings( - PortSettingsMap& portSettingsMap) + [[maybe_unused]] PortSettingsMap& portSettingsMap) { #if defined(_WIN32) const LPCTSTR lpSubKey = diff --git a/scwx-qt/source/scwx/qt/ui/serial_port_dialog.hpp b/scwx-qt/source/scwx/qt/ui/serial_port_dialog.hpp index 8bd3ee2c..9a9f1c64 100644 --- a/scwx-qt/source/scwx/qt/ui/serial_port_dialog.hpp +++ b/scwx-qt/source/scwx/qt/ui/serial_port_dialog.hpp @@ -23,6 +23,7 @@ public: ~SerialPortDialog(); std::string serial_port(); + int baud_rate(); private: class Impl; diff --git a/scwx-qt/source/scwx/qt/ui/serial_port_dialog.ui b/scwx-qt/source/scwx/qt/ui/serial_port_dialog.ui index 87065d6a..02935862 100644 --- a/scwx-qt/source/scwx/qt/ui/serial_port_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/serial_port_dialog.ui @@ -15,7 +15,17 @@
- + + + true + + + 0 + + + true + + From d4515f08e04df9b569d4f1bf71781a16b0dd962b Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 15 May 2024 23:13:42 -0500 Subject: [PATCH 09/15] Add GPS source selection to settings dialog --- scwx-qt/source/scwx/qt/ui/settings_dialog.cpp | 56 ++- scwx-qt/source/scwx/qt/ui/settings_dialog.ui | 373 +++++++++--------- 2 files changed, 242 insertions(+), 187 deletions(-) diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp index 9cc80abf..e0996855 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -102,6 +103,7 @@ public: explicit SettingsDialogImpl(SettingsDialog* self) : self_ {self}, radarSiteDialog_ {new RadarSiteDialog(self)}, + gpsSourceDialog_ {new SerialPortDialog(self)}, countyDialog_ {new CountyDialog(self)}, fontDialog_ {new QFontDialog(self)}, fontCategoryModel_ {new QStandardItemModel(self)}, @@ -193,10 +195,11 @@ public: RadarSiteLabel(std::shared_ptr& radarSite); static void SetBackgroundColor(const std::string& value, QFrame* frame); - SettingsDialog* self_; - RadarSiteDialog* radarSiteDialog_; - CountyDialog* countyDialog_; - QFontDialog* fontDialog_; + SettingsDialog* self_; + RadarSiteDialog* radarSiteDialog_; + SerialPortDialog* gpsSourceDialog_; + CountyDialog* countyDialog_; + QFontDialog* fontDialog_; QStandardItemModel* fontCategoryModel_; @@ -332,6 +335,32 @@ void SettingsDialogImpl::ConnectSignals() } }); + QObject::connect(self_->ui->gpsSourceSelectButton, + &QAbstractButton::clicked, + self_, + [this]() { gpsSourceDialog_->show(); }); + + QObject::connect(gpsSourceDialog_, + &SerialPortDialog::accepted, + self_, + [this]() + { + std::string serialPort = gpsSourceDialog_->serial_port(); + int baudRate = gpsSourceDialog_->baud_rate(); + + if (!serialPort.empty() && serialPort != "?") + { + std::string source = + fmt::format("serial:{}", serialPort); + nmeaSource_.StageValue(source); + } + + if (baudRate > 0) + { + self_->ui->nmeaBaudRateSpinBox->setValue(baudRate); + } + }); + // Update the Radar Site dialog "map" location with the currently selected // radar site auto& defaultRadarSite = *defaultRadarSite_.GetSettingsVariable(); @@ -559,6 +588,25 @@ void SettingsDialogImpl::SetupGeneralTab() types::GetDefaultTimeZoneName); defaultTimeZone_.SetResetButton(self_->ui->resetDefaultTimeZoneButton); + QObject::connect( + self_->ui->positioningPluginComboBox, + &QComboBox::currentTextChanged, + self_, + [this](const QString& text) + { + types::PositioningPlugin positioningPlugin = + types::GetPositioningPlugin(text.toStdString()); + + bool gpsSourceEnabled = + positioningPlugin == types::PositioningPlugin::Nmea; + + self_->ui->nmeaSourceLineEdit->setEnabled(gpsSourceEnabled); + self_->ui->gpsSourceSelectButton->setEnabled(gpsSourceEnabled); + self_->ui->nmeaBaudRateSpinBox->setEnabled(gpsSourceEnabled); + self_->ui->resetNmeaSourceButton->setEnabled(gpsSourceEnabled); + self_->ui->resetNmeaBaudRateButton->setEnabled(gpsSourceEnabled); + }); + positioningPlugin_.SetSettingsVariable(generalSettings.positioning_plugin()); SCWX_SETTINGS_COMBO_BOX(positioningPlugin_, self_->ui->positioningPluginComboBox, diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui index 4364ba58..fe0efa6e 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui @@ -159,6 +159,48 @@ 0 + + + + MapTiler API Key + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + + + + Default Time Zone + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + @@ -170,8 +212,18 @@ - - + + + + Map Provider + + + + + + + + ... @@ -181,21 +233,51 @@
- - + + + + + + + + - Clock Format + ... - - - - NMEA Source + + + + QLineEdit::EchoMode::Password - + + + + GPS Plugin + + + + + + + GPS Baud Rate + + + + + + + + + + ... + + + + ... @@ -206,10 +288,47 @@ - - + + - Mapbox API Key + Grid Width + + + + + + + + + + + + + + + + GPS Source + + + + + + + + + + Default Radar Site + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg @@ -220,84 +339,24 @@ - - + + ... + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + - - - Warnings Provider - - - - - - - - - - Grid Width - - - - - - - - - - Default Alert Action - - - - - - - Default Time Zone - - - - - - - NMEA Baud Rate - - - - - - - - - - MapTiler API Key - - - - - - - Theme - - - - - - - Default Radar Site - - - @@ -309,83 +368,27 @@ - - - - - + + - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + Mapbox API Key - - - - - + + - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + Clock Format - - - - - - - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg - - - - - - - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg - - - - - + + QLineEdit::EchoMode::Password - - - - - - - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg - - - - - - @@ -397,7 +400,45 @@ - + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + + + + Warnings Provider + + + + + + + 1 + + + 999999999 + + + + + + + Default Alert Action + + + + ... @@ -408,41 +449,7 @@ - - - - Map Provider - - - - - - - QLineEdit::EchoMode::Password - - - - - - - Positioning Plugin - - - - - - - - - - 1 - - - 999999999 - - - - + ... @@ -453,8 +460,8 @@ - - + + ... @@ -464,8 +471,8 @@
- - + + ... From 44698a593feb60d806130a1f276bd17c4cafb2b8 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 15 May 2024 23:20:46 -0500 Subject: [PATCH 10/15] Update detailed serial port and GPS logging from debug to trace --- .../scwx/qt/manager/position_manager.cpp | 2 +- .../source/scwx/qt/ui/serial_port_dialog.cpp | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/position_manager.cpp b/scwx-qt/source/scwx/qt/manager/position_manager.cpp index a6d1e2f1..f806bfe2 100644 --- a/scwx-qt/source/scwx/qt/manager/position_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/position_manager.cpp @@ -139,7 +139,7 @@ void PositionManager::Impl::CreatePositionSource() if (coordinate != position_.coordinate()) { - logger_->debug("Position updated: {}, {}", + logger_->trace("Position updated: {}, {}", coordinate.latitude(), coordinate.longitude()); } diff --git a/scwx-qt/source/scwx/qt/ui/serial_port_dialog.cpp b/scwx-qt/source/scwx/qt/ui/serial_port_dialog.cpp index 4fc91660..d489c1d6 100644 --- a/scwx-qt/source/scwx/qt/ui/serial_port_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/serial_port_dialog.cpp @@ -186,13 +186,13 @@ int SerialPortDialog::baud_rate() void SerialPortDialog::Impl::LogSerialPortInfo(const QSerialPortInfo& info) { - logger_->debug("Serial Port: {}", info.portName().toStdString()); - logger_->debug(" Description: {}", info.description().toStdString()); - logger_->debug(" System Loc: {}", info.systemLocation().toStdString()); - logger_->debug(" Manufacturer: {}", info.manufacturer().toStdString()); - logger_->debug(" Vendor ID: {}", info.vendorIdentifier()); - logger_->debug(" Product ID: {}", info.productIdentifier()); - logger_->debug(" Serial No: {}", info.serialNumber().toStdString()); + logger_->trace("Serial Port: {}", info.portName().toStdString()); + logger_->trace(" Description: {}", info.description().toStdString()); + logger_->trace(" System Loc: {}", info.systemLocation().toStdString()); + logger_->trace(" Manufacturer: {}", info.manufacturer().toStdString()); + logger_->trace(" Vendor ID: {}", info.vendorIdentifier()); + logger_->trace(" Product ID: {}", info.productIdentifier()); + logger_->trace(" Serial No: {}", info.serialNumber().toStdString()); } void SerialPortDialog::Impl::RefreshSerialDevices() @@ -326,7 +326,7 @@ void SerialPortDialog::Impl::ReadComPortProperties( properties.busReportedDeviceDescription_ = GetDevicePropertyString( deviceInfoSet, deviceInfoData, DEVPKEY_Device_BusReportedDeviceDesc); - logger_->debug( + logger_->trace( "Port: {} ({})", portName, properties.busReportedDeviceDescription_); portPropertiesMap.emplace(portName, std::move(properties)); @@ -422,7 +422,7 @@ void SerialPortDialog::Impl::ReadComPortSettings( std::string portData = buffer; - logger_->debug("Port Settings: {} ({})", portName, portData); + logger_->trace("Port Settings: {} ({})", portName, portData); StorePortSettings(portName, portData, portSettingsMap); } From 2efdd1b046975b970486708e0c7e0f83e7219b59 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 16 May 2024 23:04:53 -0500 Subject: [PATCH 11/15] Only create a new position source once when multiple settings change --- .../scwx/qt/manager/position_manager.cpp | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/position_manager.cpp b/scwx-qt/source/scwx/qt/manager/position_manager.cpp index f806bfe2..cebf9924 100644 --- a/scwx-qt/source/scwx/qt/manager/position_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/position_manager.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -36,13 +37,27 @@ public: positioningPluginCallbackUuid_ = generalSettings.positioning_plugin().RegisterValueChangedCallback( - [this](const std::string&) { CreatePositionSource(); }); + [this](const std::string&) + { createPositionSourcePending_ = true; }); nmeaBaudRateCallbackUuid_ = generalSettings.nmea_baud_rate().RegisterValueChangedCallback( - [this](const std::int64_t&) { CreatePositionSource(); }); + [this](const std::int64_t&) + { createPositionSourcePending_ = true; }); nmeaSourceCallbackUuid_ = generalSettings.nmea_source().RegisterValueChangedCallback( - [this](const std::string&) { CreatePositionSource(); }); + [this](const std::string&) + { createPositionSourcePending_ = true; }); + + connect(&SettingsManager::Instance(), + &SettingsManager::SettingsSaved, + self_, + [this]() + { + if (createPositionSourcePending_) + { + CreatePositionSource(); + } + }); } ~Impl() { @@ -78,6 +93,8 @@ public: boost::uuids::uuid positioningPluginCallbackUuid_ {}; boost::uuids::uuid nmeaBaudRateCallbackUuid_ {}; boost::uuids::uuid nmeaSourceCallbackUuid_ {}; + + bool createPositionSourcePending_ {false}; }; PositionManager::PositionManager() : p(std::make_unique(this)) {} @@ -97,6 +114,8 @@ void PositionManager::Impl::CreatePositionSource() { auto& generalSettings = settings::GeneralSettings::Instance(); + createPositionSourcePending_ = false; + types::PositioningPlugin positioningPlugin = types::GetPositioningPlugin( generalSettings.positioning_plugin().GetValue()); std::int64_t nmeaBaudRate = generalSettings.nmea_baud_rate().GetValue(); From 8f7076cd0972608c0010bc73e41ab846d9b5b2c3 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 18 May 2024 11:08:48 -0500 Subject: [PATCH 12/15] Add thread manager for QThreads --- scwx-qt/scwx-qt.cmake | 2 + scwx-qt/source/scwx/qt/main/main.cpp | 4 + .../source/scwx/qt/manager/thread_manager.cpp | 83 +++++++++++++++++++ .../source/scwx/qt/manager/thread_manager.hpp | 37 +++++++++ 4 files changed, 126 insertions(+) create mode 100644 scwx-qt/source/scwx/qt/manager/thread_manager.cpp create mode 100644 scwx-qt/source/scwx/qt/manager/thread_manager.hpp diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 47340552..cb48e893 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -99,6 +99,7 @@ set(HDR_MANAGER source/scwx/qt/manager/alert_manager.hpp source/scwx/qt/manager/resource_manager.hpp source/scwx/qt/manager/settings_manager.hpp source/scwx/qt/manager/text_event_manager.hpp + source/scwx/qt/manager/thread_manager.hpp source/scwx/qt/manager/timeline_manager.hpp source/scwx/qt/manager/update_manager.hpp) set(SRC_MANAGER source/scwx/qt/manager/alert_manager.cpp @@ -113,6 +114,7 @@ set(SRC_MANAGER source/scwx/qt/manager/alert_manager.cpp source/scwx/qt/manager/resource_manager.cpp source/scwx/qt/manager/settings_manager.cpp source/scwx/qt/manager/text_event_manager.cpp + source/scwx/qt/manager/thread_manager.cpp source/scwx/qt/manager/timeline_manager.cpp source/scwx/qt/manager/update_manager.cpp) set(HDR_MAP source/scwx/qt/map/alert_layer.hpp diff --git a/scwx-qt/source/scwx/qt/main/main.cpp b/scwx-qt/source/scwx/qt/main/main.cpp index 2d8073da..b6dcf5f0 100644 --- a/scwx-qt/source/scwx/qt/main/main.cpp +++ b/scwx-qt/source/scwx/qt/main/main.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -129,6 +130,9 @@ int main(int argc, char* argv[]) // Deinitialize application scwx::qt::manager::RadarProductManager::Cleanup(); + // Stop Qt Threads + scwx::qt::manager::ThreadManager::Instance().StopThreads(); + // Gracefully stop the io_context main loop work.reset(); threadPool.join(); diff --git a/scwx-qt/source/scwx/qt/manager/thread_manager.cpp b/scwx-qt/source/scwx/qt/manager/thread_manager.cpp new file mode 100644 index 00000000..8b12edb0 --- /dev/null +++ b/scwx-qt/source/scwx/qt/manager/thread_manager.cpp @@ -0,0 +1,83 @@ +#include +#include + +#include + +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace manager +{ + +static const std::string logPrefix_ = "scwx::qt::manager::thread_manager"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +class ThreadManager::Impl +{ +public: + explicit Impl() {} + ~Impl() {} + + std::mutex mutex_ {}; + + boost::unordered_flat_map threadMap_ {}; +}; + +ThreadManager::ThreadManager() : p(std::make_unique()) {} +ThreadManager::~ThreadManager() = default; + +QThread* ThreadManager::thread(const std::string& id, bool autoStart) +{ + std::unique_lock lock {p->mutex_}; + QThread* thread = nullptr; + + auto it = p->threadMap_.find(id); + if (it != p->threadMap_.cend()) + { + thread = it->second; + } + + if (thread == nullptr) + { + logger_->debug("Creating thread: {}", id); + + thread = new QThread(this); + p->threadMap_.insert_or_assign(id, thread); + + if (autoStart) + { + thread->start(); + } + } + + return thread; +} + +void ThreadManager::StopThreads() +{ + std::unique_lock lock {p->mutex_}; + + logger_->debug("Stopping threads"); + + for (auto& thread : p->threadMap_) + { + thread.second->quit(); + thread.second->deleteLater(); + } + + p->threadMap_.clear(); +} + +ThreadManager& ThreadManager::Instance() +{ + static ThreadManager threadManager_ {}; + return threadManager_; +} + +} // namespace manager +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/manager/thread_manager.hpp b/scwx-qt/source/scwx/qt/manager/thread_manager.hpp new file mode 100644 index 00000000..496d30ba --- /dev/null +++ b/scwx-qt/source/scwx/qt/manager/thread_manager.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace manager +{ + +class ThreadManager : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(ThreadManager) + +public: + explicit ThreadManager(); + ~ThreadManager(); + + QThread* thread(const std::string& id, bool autoStart = true); + + void StopThreads(); + + static ThreadManager& Instance(); + +private: + class Impl; + std::unique_ptr p; +}; + +} // namespace manager +} // namespace qt +} // namespace scwx From f976e88359efd8c20740d9a77660f76320a5caaa Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 18 May 2024 14:33:13 -0500 Subject: [PATCH 13/15] Properly manage QThread termination --- .../source/scwx/qt/manager/thread_manager.cpp | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/thread_manager.cpp b/scwx-qt/source/scwx/qt/manager/thread_manager.cpp index 8b12edb0..78924434 100644 --- a/scwx-qt/source/scwx/qt/manager/thread_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/thread_manager.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include @@ -63,11 +64,24 @@ void ThreadManager::StopThreads() logger_->debug("Stopping threads"); - for (auto& thread : p->threadMap_) - { - thread.second->quit(); - thread.second->deleteLater(); - } + std::for_each(std::execution::par_unseq, + p->threadMap_.begin(), + p->threadMap_.end(), + [](auto& thread) + { + logger_->trace("Stopping thread: {}", thread.first); + + thread.second->quit(); + if (!thread.second->wait(5000)) + { + logger_->warn("Terminating thread: {}", thread.first); + + thread.second->terminate(); + thread.second->wait(); + } + + delete thread.second; + }); p->threadMap_.clear(); } From 69309ca8e6538ee51888995c2fd81f247fa0b402 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 18 May 2024 14:35:22 -0500 Subject: [PATCH 14/15] Move QGeoPositionInfoSource to non-GUI thread --- .../scwx/qt/manager/position_manager.cpp | 48 ++++++++++++++----- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/position_manager.cpp b/scwx-qt/source/scwx/qt/manager/position_manager.cpp index cebf9924..fea9cc36 100644 --- a/scwx-qt/source/scwx/qt/manager/position_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/position_manager.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -9,6 +10,7 @@ #include #include +#include #include namespace scwx @@ -29,11 +31,13 @@ public: { auto& generalSettings = settings::GeneralSettings::Instance(); + gpsParent_->moveToThread(gpsThread_); + logger_->debug( "Available sources: {}", QGeoPositionInfoSource::availableSources().join(", ").toStdString()); - CreatePositionSource(); + CreatePositionSourceAsync(); positioningPluginCallbackUuid_ = generalSettings.positioning_plugin().RegisterValueChangedCallback( @@ -55,7 +59,7 @@ public: { if (createPositionSourcePending_) { - CreatePositionSource(); + CreatePositionSourceAsync(); } }); } @@ -69,11 +73,16 @@ public: nmeaBaudRateCallbackUuid_); generalSettings.nmea_source().UnregisterValueChangedCallback( nmeaSourceCallbackUuid_); + + gpsParent_->deleteLater(); } void CreatePositionSource(); + void CreatePositionSourceAsync(); + void EnablePositionUpdates(boost::uuids::uuid uuid, bool enabled); PositionManager* self_; + QThread* gpsThread_ {ThreadManager::Instance().thread("position_manager")}; boost::uuids::uuid trackingUuid_; bool trackingEnabled_ {false}; @@ -82,6 +91,7 @@ public: std::mutex positionSourceMutex_ {}; + QObject* gpsParent_ {new QObject}; QGeoPositionInfoSource* geoPositionInfoSource_ {}; QGeoPositionInfo position_ {}; @@ -110,6 +120,12 @@ bool PositionManager::IsLocationTracked() return p->trackingEnabled_; } +void PositionManager::Impl::CreatePositionSourceAsync() +{ + QMetaObject::invokeMethod(QAbstractEventDispatcher::instance(gpsThread_), + [this]() { CreatePositionSource(); }); +} + void PositionManager::Impl::CreatePositionSource() { auto& generalSettings = settings::GeneralSettings::Instance(); @@ -132,7 +148,7 @@ void PositionManager::Impl::CreatePositionSource() // TODO: macOS requires permission if (positioningPlugin == types::PositioningPlugin::Default) { - positionSource = QGeoPositionInfoSource::createDefaultSource(self_); + positionSource = QGeoPositionInfoSource::createDefaultSource(gpsParent_); } else if (positioningPlugin == types::PositioningPlugin::Nmea) { @@ -141,7 +157,7 @@ void PositionManager::Impl::CreatePositionSource() params["nmea.baudrate"] = static_cast(nmeaBaudRate); positionSource = - QGeoPositionInfoSource::createSource("nmea", params, self_); + QGeoPositionInfoSource::createSource("nmea", params, gpsParent_); } if (positionSource != nullptr) @@ -198,29 +214,37 @@ void PositionManager::Impl::CreatePositionSource() void PositionManager::EnablePositionUpdates(boost::uuids::uuid uuid, bool enabled) { - std::unique_lock lock {p->positionSourceMutex_}; + QMetaObject::invokeMethod(QAbstractEventDispatcher::instance(p->gpsThread_), + [=, this]() + { p->EnablePositionUpdates(uuid, enabled); }); +} - if (p->geoPositionInfoSource_ == nullptr) +void PositionManager::Impl::EnablePositionUpdates(boost::uuids::uuid uuid, + bool enabled) +{ + std::unique_lock lock {positionSourceMutex_}; + + if (geoPositionInfoSource_ == nullptr) { return; } if (enabled) { - if (p->uuids_.empty()) + if (uuids_.empty()) { - p->geoPositionInfoSource_->startUpdates(); + geoPositionInfoSource_->startUpdates(); } - p->uuids_.insert(uuid); + uuids_.insert(uuid); } else { - p->uuids_.erase(uuid); + uuids_.erase(uuid); - if (p->uuids_.empty()) + if (uuids_.empty()) { - p->geoPositionInfoSource_->stopUpdates(); + geoPositionInfoSource_->stopUpdates(); } } } From 1cce358a52eee3ab88168c495832110cfa6d9a62 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 19 May 2024 00:41:25 -0500 Subject: [PATCH 15/15] GPS info dialog --- .../res/icons/font-awesome-6/copy-regular.svg | 1 + scwx-qt/scwx-qt.cmake | 3 + scwx-qt/scwx-qt.qrc | 1 + scwx-qt/source/scwx/qt/main/main_window.cpp | 11 + scwx-qt/source/scwx/qt/main/main_window.hpp | 1 + scwx-qt/source/scwx/qt/main/main_window.ui | 30 +- scwx-qt/source/scwx/qt/ui/gps_info_dialog.cpp | 201 +++++++++++++ scwx-qt/source/scwx/qt/ui/gps_info_dialog.hpp | 36 +++ scwx-qt/source/scwx/qt/ui/gps_info_dialog.ui | 266 ++++++++++++++++++ 9 files changed, 538 insertions(+), 12 deletions(-) create mode 100644 scwx-qt/res/icons/font-awesome-6/copy-regular.svg create mode 100644 scwx-qt/source/scwx/qt/ui/gps_info_dialog.cpp create mode 100644 scwx-qt/source/scwx/qt/ui/gps_info_dialog.hpp create mode 100644 scwx-qt/source/scwx/qt/ui/gps_info_dialog.ui diff --git a/scwx-qt/res/icons/font-awesome-6/copy-regular.svg b/scwx-qt/res/icons/font-awesome-6/copy-regular.svg new file mode 100644 index 00000000..72d18b16 --- /dev/null +++ b/scwx-qt/res/icons/font-awesome-6/copy-regular.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index cb48e893..156b622e 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -239,6 +239,7 @@ set(HDR_UI source/scwx/qt/ui/about_dialog.hpp source/scwx/qt/ui/county_dialog.hpp source/scwx/qt/ui/download_dialog.hpp source/scwx/qt/ui/flow_layout.hpp + source/scwx/qt/ui/gps_info_dialog.hpp source/scwx/qt/ui/hotkey_edit.hpp source/scwx/qt/ui/imgui_debug_dialog.hpp source/scwx/qt/ui/imgui_debug_widget.hpp @@ -263,6 +264,7 @@ set(SRC_UI source/scwx/qt/ui/about_dialog.cpp source/scwx/qt/ui/county_dialog.cpp source/scwx/qt/ui/download_dialog.cpp source/scwx/qt/ui/flow_layout.cpp + source/scwx/qt/ui/gps_info_dialog.cpp source/scwx/qt/ui/hotkey_edit.cpp source/scwx/qt/ui/imgui_debug_dialog.cpp source/scwx/qt/ui/imgui_debug_widget.cpp @@ -285,6 +287,7 @@ set(UI_UI source/scwx/qt/ui/about_dialog.ui source/scwx/qt/ui/animation_dock_widget.ui source/scwx/qt/ui/collapsible_group.ui source/scwx/qt/ui/county_dialog.ui + source/scwx/qt/ui/gps_info_dialog.ui source/scwx/qt/ui/imgui_debug_dialog.ui source/scwx/qt/ui/layer_dialog.ui source/scwx/qt/ui/open_url_dialog.ui diff --git a/scwx-qt/scwx-qt.qrc b/scwx-qt/scwx-qt.qrc index 6817307a..c9e00337 100644 --- a/scwx-qt/scwx-qt.qrc +++ b/scwx-qt/scwx-qt.qrc @@ -32,6 +32,7 @@ res/icons/font-awesome-6/angles-up-solid.svg res/icons/font-awesome-6/backward-step-solid.svg res/icons/font-awesome-6/book-solid.svg + res/icons/font-awesome-6/copy-regular.svg res/icons/font-awesome-6/discord.svg res/icons/font-awesome-6/earth-americas-solid.svg res/icons/font-awesome-6/font-solid.svg diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp index a74533f2..a0a44fe4 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -85,6 +86,7 @@ public: alertDockWidget_ {nullptr}, animationDockWidget_ {nullptr}, aboutDialog_ {nullptr}, + gpsInfoDialog_ {nullptr}, imGuiDebugDialog_ {nullptr}, layerDialog_ {nullptr}, placefileDialog_ {nullptr}, @@ -190,6 +192,7 @@ public: ui::AlertDockWidget* alertDockWidget_; ui::AnimationDockWidget* animationDockWidget_; ui::AboutDialog* aboutDialog_; + ui::GpsInfoDialog* gpsInfoDialog_; ui::ImGuiDebugDialog* imGuiDebugDialog_; ui::LayerDialog* layerDialog_; ui::PlacefileDialog* placefileDialog_; @@ -264,6 +267,9 @@ MainWindow::MainWindow(QWidget* parent) : p->alertDockWidget_->setVisible(false); addDockWidget(Qt::BottomDockWidgetArea, p->alertDockWidget_); + // GPS Info Dialog + p->gpsInfoDialog_ = new ui::GpsInfoDialog(this); + // Configure Menu ui->menuView->insertAction(ui->actionRadarToolbox, ui->radarToolboxDock->toggleViewAction()); @@ -535,6 +541,11 @@ void MainWindow::on_actionExit_triggered() close(); } +void MainWindow::on_actionGpsInfo_triggered() +{ + p->gpsInfoDialog_->show(); +} + void MainWindow::on_actionColorTable_triggered(bool checked) { p->layerModel_->SetLayerDisplayed(types::LayerType::Information, diff --git a/scwx-qt/source/scwx/qt/main/main_window.hpp b/scwx-qt/source/scwx/qt/main/main_window.hpp index d0adf225..33043308 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.hpp +++ b/scwx-qt/source/scwx/qt/main/main_window.hpp @@ -38,6 +38,7 @@ private slots: void on_actionOpenTextEvent_triggered(); void on_actionSettings_triggered(); void on_actionExit_triggered(); + void on_actionGpsInfo_triggered(); void on_actionColorTable_triggered(bool checked); void on_actionRadarRange_triggered(bool checked); void on_actionRadarSites_triggered(bool checked); diff --git a/scwx-qt/source/scwx/qt/main/main_window.ui b/scwx-qt/source/scwx/qt/main/main_window.ui index aeb4517f..9fab1adf 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.ui +++ b/scwx-qt/source/scwx/qt/main/main_window.ui @@ -39,7 +39,7 @@ 0 0 1024 - 21 + 33
@@ -85,6 +85,7 @@ +
@@ -135,13 +136,13 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - Qt::ScrollBarAsNeeded + Qt::ScrollBarPolicy::ScrollBarAsNeeded - QAbstractScrollArea::AdjustToContents + QAbstractScrollArea::SizeAdjustPolicy::AdjustToContents true @@ -151,8 +152,8 @@ 0 0 - 193 - 688 + 190 + 686 @@ -171,10 +172,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -209,10 +210,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Raised + QFrame::Shadow::Raised @@ -260,7 +261,7 @@ :/res/icons/font-awesome-6/star-solid.svg:/res/icons/font-awesome-6/star-solid.svg - QToolButton::InstantPopup + QToolButton::ToolButtonPopupMode::InstantPopup @@ -340,7 +341,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -481,6 +482,11 @@ Radar &Sites + + + &GPS Info + + diff --git a/scwx-qt/source/scwx/qt/ui/gps_info_dialog.cpp b/scwx-qt/source/scwx/qt/ui/gps_info_dialog.cpp new file mode 100644 index 00000000..05863221 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/gps_info_dialog.cpp @@ -0,0 +1,201 @@ +#include "gps_info_dialog.hpp" +#include "ui_gps_info_dialog.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace ui +{ + +static const QString kDisabledString_ = "---"; + +class GpsInfoDialog::Impl +{ +public: + explicit Impl(GpsInfoDialog* self) : self_ {self} {}; + ~Impl() = default; + + std::shared_ptr positionManager_ { + manager::PositionManager::Instance()}; + + void Update(const QGeoPositionInfo& info, bool updateTime = true); + + GpsInfoDialog* self_; +}; + +GpsInfoDialog::GpsInfoDialog(QWidget* parent) : + QDialog(parent), p {std::make_unique(this)}, ui(new Ui::GpsInfoDialog) +{ + ui->setupUi(this); + + p->Update({}, false); + + connect(p->positionManager_.get(), + &manager::PositionManager::PositionUpdated, + this, + [this](const QGeoPositionInfo& info) { p->Update(info); }); + + connect(ui->copyCoordinateButton, + &QAbstractButton::clicked, + this, + [this]() + { + QClipboard* clipboard = QGuiApplication::clipboard(); + clipboard->setText(ui->coordinateLabel->text()); + }); +} + +GpsInfoDialog::~GpsInfoDialog() +{ + delete ui; +} + +void GpsInfoDialog::Impl::Update(const QGeoPositionInfo& info, bool updateTime) +{ + auto coordinate = info.coordinate(); + + if (coordinate.isValid()) + { + const QString latitude = QString::fromStdString( + common::GetLatitudeString(coordinate.latitude())); + const QString longitude = QString::fromStdString( + common::GetLongitudeString(coordinate.longitude())); + + self_->ui->coordinateLabel->setText( + QString("%1, %2").arg(latitude).arg(longitude)); + } + else + { + self_->ui->coordinateLabel->setText(kDisabledString_); + } + + if (coordinate.type() == QGeoCoordinate::CoordinateType::Coordinate3D) + { + units::length::meters altitude {coordinate.altitude()}; + self_->ui->altitudeLabel->setText( + QString::fromStdString(units::to_string(altitude))); + } + else + { + self_->ui->altitudeLabel->setText(kDisabledString_); + } + + if (info.hasAttribute(QGeoPositionInfo::Attribute::Direction)) + { + units::angle::degrees direction { + info.attribute(QGeoPositionInfo::Attribute::Direction)}; + self_->ui->directionLabel->setText( + QString::fromStdString(units::to_string(direction))); + } + else + { + self_->ui->directionLabel->setText(kDisabledString_); + } + + if (info.hasAttribute(QGeoPositionInfo::Attribute::GroundSpeed)) + { + units::velocity::meters_per_second groundSpeed { + info.attribute(QGeoPositionInfo::Attribute::GroundSpeed)}; + self_->ui->groundSpeedLabel->setText( + QString::fromStdString(units::to_string(groundSpeed))); + } + else + { + self_->ui->groundSpeedLabel->setText(kDisabledString_); + } + + if (info.hasAttribute(QGeoPositionInfo::Attribute::VerticalSpeed)) + { + units::velocity::meters_per_second verticalSpeed { + info.attribute(QGeoPositionInfo::Attribute::VerticalSpeed)}; + self_->ui->verticalSpeedLabel->setText( + QString::fromStdString(units::to_string(verticalSpeed))); + } + else + { + self_->ui->verticalSpeedLabel->setText(kDisabledString_); + } + + if (info.hasAttribute(QGeoPositionInfo::Attribute::MagneticVariation)) + { + units::angle::degrees magneticVariation { + info.attribute(QGeoPositionInfo::Attribute::MagneticVariation)}; + self_->ui->magneticVariationLabel->setText( + QString::fromStdString(units::to_string(magneticVariation))); + } + else + { + self_->ui->magneticVariationLabel->setText(kDisabledString_); + } + + if (info.hasAttribute(QGeoPositionInfo::Attribute::HorizontalAccuracy)) + { + units::length::meters horizontalAccuracy { + info.attribute(QGeoPositionInfo::Attribute::HorizontalAccuracy)}; + if (!std::isnan(horizontalAccuracy.value())) + { + self_->ui->horizontalAccuracyLabel->setText( + QString::fromStdString(units::to_string(horizontalAccuracy))); + } + else + { + self_->ui->horizontalAccuracyLabel->setText(kDisabledString_); + } + } + else + { + self_->ui->horizontalAccuracyLabel->setText(kDisabledString_); + } + + if (info.hasAttribute(QGeoPositionInfo::Attribute::VerticalAccuracy)) + { + units::length::meters verticalAccuracy { + info.attribute(QGeoPositionInfo::Attribute::VerticalAccuracy)}; + if (!std::isnan(verticalAccuracy.value())) + { + self_->ui->verticalAccuracyLabel->setText( + QString::fromStdString(units::to_string(verticalAccuracy))); + } + else + { + self_->ui->verticalAccuracyLabel->setText(kDisabledString_); + } + } + else + { + self_->ui->verticalAccuracyLabel->setText(kDisabledString_); + } + + if (info.hasAttribute(QGeoPositionInfo::Attribute::DirectionAccuracy)) + { + units::angle::degrees directionAccuracy { + info.attribute(QGeoPositionInfo::Attribute::DirectionAccuracy)}; + self_->ui->directionAccuracyLabel->setText( + QString::fromStdString(units::to_string(directionAccuracy))); + } + else + { + self_->ui->directionAccuracyLabel->setText(kDisabledString_); + } + + if (updateTime) + { + self_->ui->lastUpdateLabel->setText( + info.timestamp().toString(Qt::DateFormat::ISODate)); + } +} + +} // namespace ui +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/gps_info_dialog.hpp b/scwx-qt/source/scwx/qt/ui/gps_info_dialog.hpp new file mode 100644 index 00000000..6330c5c4 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/gps_info_dialog.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include + +namespace Ui +{ +class GpsInfoDialog; +} + +namespace scwx +{ +namespace qt +{ +namespace ui +{ + +class GpsInfoDialog : public QDialog +{ + Q_OBJECT + +private: + Q_DISABLE_COPY(GpsInfoDialog) + +public: + explicit GpsInfoDialog(QWidget* parent = nullptr); + ~GpsInfoDialog(); + +private: + class Impl; + std::unique_ptr p; + Ui::GpsInfoDialog* ui; +}; + +} // namespace ui +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/gps_info_dialog.ui b/scwx-qt/source/scwx/qt/ui/gps_info_dialog.ui new file mode 100644 index 00000000..61e50b1c --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/gps_info_dialog.ui @@ -0,0 +1,266 @@ + + + GpsInfoDialog + + + + 0 + 0 + 313 + 292 + + + + GPS Info + + + + + + + 6 + + + + + Direction Accuracy + + + + + + + + + + + + + + + + + + + + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + Magnetic Variation + + + + + + + Direction + + + + + + + Vertical Speed + + + + + + + + + + + + + + + + + + + + + Vertical Accuracy + + + + + + + ... + + + + :/res/icons/font-awesome-6/copy-regular.svg:/res/icons/font-awesome-6/copy-regular.svg + + + + + + + Horizonal Accuracy + + + + + + + + + + + + + + Ground Speed + + + + + + + + + + + + + + + 0 + 0 + + + + + + + + + + + Coordinate + + + + + + + + + + + + + + Altitude + + + + + + + Last Update + + + + + + + Never + + + + + verticalAccuracyLabel + label_11 + groundSpeedLabel + magneticVariationLabel + label_2 + coordinateLabel + altitudeLabel + directionAccuracyLabel + label_5 + label_15 + label_13 + verticalSpeedLabel + label_7 + label + horizontalAccuracyLabel + label_17 + directionLabel + label_9 + copyCoordinateButton + label_3 + lastUpdateLabel + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Close + + + + + + + + + + + buttonBox + accepted() + GpsInfoDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + GpsInfoDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +