diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp index 20944459..4cda1e58 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -139,6 +139,16 @@ public: } ~MainWindowImpl() { + auto& generalSettings = settings::GeneralSettings::Instance(); + + auto& customStyleUrl = generalSettings.custom_style_url(); + auto& customStyleDrawLayer = generalSettings.custom_style_draw_layer(); + + customStyleUrl.UnregisterValueChangedCallback( + customStyleUrlChangedCallbackUuid_); + customStyleDrawLayer.UnregisterValueChangedCallback( + customStyleDrawLayerChangedCallbackUuid_); + clockTimer_.stop(); threadPool_.join(); } @@ -153,6 +163,7 @@ public: void ConnectOtherSignals(); void HandleFocusChange(QWidget* focused); void InitializeLayerDisplayActions(); + void PopulateCustomMapStyle(); void PopulateMapStyles(); void SelectElevation(map::MapWidget* mapWidget, float elevation); void SelectRadarProduct(map::MapWidget* mapWidget, @@ -202,6 +213,10 @@ public: QTimer clockTimer_ {}; + bool customStyleAvailable_ {false}; + boost::uuids::uuid customStyleDrawLayerChangedCallbackUuid_ {}; + boost::uuids::uuid customStyleUrlChangedCallbackUuid_ {}; + std::shared_ptr alertManager_; std::shared_ptr hotkeyManager_ { manager::HotkeyManager::Instance()}; @@ -750,7 +765,8 @@ void MainWindowImpl::ConfigureMapStyles() { std::string styleName = mapSettings.map_style(i).GetValue(); - if (std::find_if(mapProviderInfo.mapStyles_.cbegin(), + if ((customStyleAvailable_ && styleName == "Custom") || + std::find_if(mapProviderInfo.mapStyles_.cbegin(), mapProviderInfo.mapStyles_.cend(), [&](const auto& mapStyle) { return mapStyle.name_ == styleName; @@ -1262,6 +1278,36 @@ void MainWindowImpl::HandleFocusChange(QWidget* focused) } } +void MainWindowImpl::PopulateCustomMapStyle() +{ + auto& generalSettings = settings::GeneralSettings::Instance(); + + auto customStyleUrl = generalSettings.custom_style_url().GetValue(); + auto customStyleDrawLayer = + generalSettings.custom_style_draw_layer().GetValue(); + + bool newCustomStyleAvailable = + !customStyleUrl.empty() && !customStyleDrawLayer.empty(); + + if (newCustomStyleAvailable != customStyleAvailable_) + { + static const QString kCustom {"Custom"}; + + if (newCustomStyleAvailable) + { + + mainWindow_->ui->mapStyleComboBox->addItem(kCustom); + } + else + { + int index = mainWindow_->ui->mapStyleComboBox->findText(kCustom); + mainWindow_->ui->mapStyleComboBox->removeItem(index); + } + + customStyleAvailable_ = newCustomStyleAvailable; + } +} + void MainWindowImpl::PopulateMapStyles() { const auto& mapProviderInfo = map::GetMapProviderInfo(mapProvider_); @@ -1270,6 +1316,22 @@ void MainWindowImpl::PopulateMapStyles() mainWindow_->ui->mapStyleComboBox->addItem( QString::fromStdString(mapStyle.name_)); } + + auto& generalSettings = settings::GeneralSettings::Instance(); + + auto& customStyleUrl = generalSettings.custom_style_url(); + auto& customStyleDrawLayer = generalSettings.custom_style_draw_layer(); + + customStyleUrlChangedCallbackUuid_ = + customStyleUrl.RegisterValueChangedCallback( + [this]([[maybe_unused]] const std::string& value) + { PopulateCustomMapStyle(); }); + customStyleDrawLayerChangedCallbackUuid_ = + customStyleDrawLayer.RegisterValueChangedCallback( + [this]([[maybe_unused]] const std::string& value) + { PopulateCustomMapStyle(); }); + + PopulateCustomMapStyle(); } void MainWindowImpl::SelectElevation(map::MapWidget* mapWidget, float elevation) diff --git a/scwx-qt/source/scwx/qt/map/map_provider.cpp b/scwx-qt/source/scwx/qt/map/map_provider.cpp index 73364ca9..586d012c 100644 --- a/scwx-qt/source/scwx/qt/map/map_provider.cpp +++ b/scwx-qt/source/scwx/qt/map/map_provider.cpp @@ -178,6 +178,11 @@ static const std::unordered_map mapProviderInfo_ { .drawBelow_ {"aeroway_runway", "Aeroway"}}}}}, {MapProvider::Unknown, MapProviderInfo {}}}; +bool MapStyle::IsValid() const +{ + return !url_.empty() && !drawBelow_.empty(); +} + MapProvider GetMapProvider(const std::string& name) { auto result = diff --git a/scwx-qt/source/scwx/qt/map/map_provider.hpp b/scwx-qt/source/scwx/qt/map/map_provider.hpp index cec68e92..5bdd67fc 100644 --- a/scwx-qt/source/scwx/qt/map/map_provider.hpp +++ b/scwx-qt/source/scwx/qt/map/map_provider.hpp @@ -28,6 +28,8 @@ struct MapStyle std::string name_; std::string url_; std::vector drawBelow_; + + bool IsValid() const; }; struct MapProviderInfo diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index 5b27fc9f..1455636e 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -117,11 +118,15 @@ public: // Initialize ImGui Qt backend ImGui_ImplQt_Init(); + InitializeCustomStyles(); + ConnectSignals(); } ~MapWidgetImpl() { + DeinitializeCustomStyles(); + // Set ImGui Context ImGui::SetCurrentContext(imGuiContext_); @@ -149,10 +154,12 @@ public: const std::string& before); void ConnectMapSignals(); void ConnectSignals(); + void DeinitializeCustomStyles() const; void HandleHotkeyPressed(types::Hotkey hotkey, bool isAutoRepeat); void HandleHotkeyReleased(types::Hotkey hotkey); void HandleHotkeyUpdates(); void ImGuiCheckFonts(); + void InitializeCustomStyles(); void InitializeNewRadarProductView(const std::string& colorPalette); void RadarProductManagerConnect(); void RadarProductManagerDisconnect(); @@ -187,9 +194,15 @@ public: std::vector> genericLayers_ {}; + const std::vector emptyStyles_ {}; + std::vector customStyles_ { + MapStyle {.name_ {"Custom"}, .url_ {}, .drawBelow_ {}}}; QStringList styleLayers_; types::LayerVector customLayers_; + boost::uuids::uuid customStyleUrlChangedCallbackId_ {}; + boost::uuids::uuid customStyleDrawBelowChangedCallbackId_ {}; + ImGuiContext* imGuiContext_; std::string imGuiContextName_; bool imGuiRendererInitialized_; @@ -269,6 +282,48 @@ MapWidget::~MapWidget() makeCurrent(); } +void MapWidgetImpl::InitializeCustomStyles() +{ + auto& generalSettings = settings::GeneralSettings::Instance(); + + auto& customStyleUrl = generalSettings.custom_style_url(); + auto& customStyleDrawLayer = generalSettings.custom_style_draw_layer(); + + auto& customStyle = customStyles_.at(0); + customStyle.url_ = customStyleUrl.GetValue(); + customStyle.drawBelow_.push_back(customStyleDrawLayer.GetValue()); + + customStyleUrlChangedCallbackId_ = + customStyleUrl.RegisterValueChangedCallback( + [this](const std::string& url) { customStyles_[0].url_ = url; }); + customStyleDrawBelowChangedCallbackId_ = + customStyleDrawLayer.RegisterValueChangedCallback( + [this](const std::string& drawLayer) + { + if (!drawLayer.empty()) + { + customStyles_[0].drawBelow_ = {drawLayer}; + } + else + { + customStyles_[0].drawBelow_.clear(); + } + }); +} + +void MapWidgetImpl::DeinitializeCustomStyles() const +{ + auto& generalSettings = settings::GeneralSettings::Instance(); + + auto& customStyleUrl = generalSettings.custom_style_url(); + auto& customStyleDrawLayer = generalSettings.custom_style_draw_layer(); + + customStyleUrl.UnregisterValueChangedCallback( + customStyleUrlChangedCallbackId_); + customStyleDrawLayer.UnregisterValueChangedCallback( + customStyleDrawBelowChangedCallbackId_); +} + void MapWidgetImpl::ConnectMapSignals() { connect(map_.get(), @@ -283,8 +338,8 @@ void MapWidgetImpl::ConnectMapSignals() QTextDocument document {}; document.setHtml(copyrightsHtml); - // HTML cannot currently be included in ImGui windows. Where links - // can't be included, remove "Improve this map". + // HTML cannot currently be included in ImGui windows. Where + // links can't be included, remove "Improve this map". std::string copyrights {document.toPlainText().toStdString()}; boost::erase_all(copyrights, "Improve this map"); boost::trim_right(copyrights); @@ -924,7 +979,16 @@ void MapWidget::SetMapStyle(const std::string& styleName) { const auto mapProvider = p->context_->map_provider(); const auto& mapProviderInfo = GetMapProviderInfo(mapProvider); - auto& styles = mapProviderInfo.mapStyles_; + auto& fixedStyles = mapProviderInfo.mapStyles_; + + auto styles = boost::join(fixedStyles, + p->customStyles_[0].IsValid() ? p->customStyles_ : + p->emptyStyles_); + + if (p->currentStyleIndex_ >= styles.size()) + { + p->currentStyleIndex_ = 0; + } for (size_t i = 0u; i < styles.size(); ++i) { @@ -937,7 +1001,7 @@ void MapWidget::SetMapStyle(const std::string& styleName) util::maplibre::SetMapStyleUrl(p->context_, styles[i].url_); - if (++p->currentStyleIndex_ == styles.size()) + if (++p->currentStyleIndex_ >= styles.size()) { p->currentStyleIndex_ = 0; } @@ -974,7 +1038,16 @@ void MapWidget::changeStyle() { const auto mapProvider = p->context_->map_provider(); const auto& mapProviderInfo = GetMapProviderInfo(mapProvider); - auto& styles = mapProviderInfo.mapStyles_; + auto& fixedStyles = mapProviderInfo.mapStyles_; + + auto styles = boost::join(fixedStyles, + p->customStyles_[0].IsValid() ? p->customStyles_ : + p->emptyStyles_); + + if (p->currentStyleIndex_ >= styles.size()) + { + p->currentStyleIndex_ = 0; + } p->currentStyle_ = &styles[p->currentStyleIndex_]; @@ -983,7 +1056,7 @@ void MapWidget::changeStyle() util::maplibre::SetMapStyleUrl(p->context_, styles[p->currentStyleIndex_].url_); - if (++p->currentStyleIndex_ == styles.size()) + if (++p->currentStyleIndex_ >= styles.size()) { p->currentStyleIndex_ = 0; } @@ -1011,7 +1084,16 @@ std::string MapWidgetImpl::FindMapSymbologyLayer() { // Perform case-insensitive matching RE2 re {"(?i)" + styleLayer}; - return RE2::FullMatch(layer, re); + if (re.ok()) + { + return RE2::FullMatch(layer, re); + } + else + { + // Fall back to basic comparison if RE + // doesn't compile + return layer == styleLayer; + } }); if (it != currentStyle_->drawBelow_.cend()) @@ -1618,8 +1700,8 @@ void MapWidgetImpl::RadarProductManagerConnect() // Select loaded record auto record = request->radar_product_record(); - // Validate record, and verify current map context still - // displays site and product + // Validate record, and verify current map context + // still displays site and product if (record != nullptr && radarProductManager_ != nullptr && radarProductManager_->radar_id() ==