diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 78d45217..06646bbe 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -169,9 +169,11 @@ set(HDR_REQUEST source/scwx/qt/request/download_request.hpp source/scwx/qt/request/nexrad_file_request.hpp) set(SRC_REQUEST source/scwx/qt/request/download_request.cpp source/scwx/qt/request/nexrad_file_request.cpp) -set(HDR_SETTINGS source/scwx/qt/settings/audio_settings.hpp +set(HDR_SETTINGS source/scwx/qt/settings/alert_palette_settings.hpp + source/scwx/qt/settings/audio_settings.hpp source/scwx/qt/settings/general_settings.hpp source/scwx/qt/settings/hotkey_settings.hpp + source/scwx/qt/settings/line_settings.hpp source/scwx/qt/settings/map_settings.hpp source/scwx/qt/settings/palette_settings.hpp source/scwx/qt/settings/product_settings.hpp @@ -185,9 +187,11 @@ set(HDR_SETTINGS source/scwx/qt/settings/audio_settings.hpp source/scwx/qt/settings/text_settings.hpp source/scwx/qt/settings/ui_settings.hpp source/scwx/qt/settings/unit_settings.hpp) -set(SRC_SETTINGS source/scwx/qt/settings/audio_settings.cpp +set(SRC_SETTINGS source/scwx/qt/settings/alert_palette_settings.cpp + source/scwx/qt/settings/audio_settings.cpp source/scwx/qt/settings/general_settings.cpp source/scwx/qt/settings/hotkey_settings.cpp + source/scwx/qt/settings/line_settings.cpp source/scwx/qt/settings/map_settings.cpp source/scwx/qt/settings/palette_settings.cpp source/scwx/qt/settings/product_settings.cpp @@ -240,8 +244,8 @@ set(HDR_UI source/scwx/qt/ui/about_dialog.hpp source/scwx/qt/ui/animation_dock_widget.hpp source/scwx/qt/ui/collapsible_group.hpp source/scwx/qt/ui/county_dialog.hpp - source/scwx/qt/ui/wfo_dialog.hpp source/scwx/qt/ui/download_dialog.hpp + source/scwx/qt/ui/edit_line_dialog.hpp source/scwx/qt/ui/flow_layout.hpp source/scwx/qt/ui/gps_info_dialog.hpp source/scwx/qt/ui/hotkey_edit.hpp @@ -252,6 +256,7 @@ set(HDR_UI source/scwx/qt/ui/about_dialog.hpp source/scwx/qt/ui/level2_products_widget.hpp source/scwx/qt/ui/level2_settings_widget.hpp source/scwx/qt/ui/level3_products_widget.hpp + source/scwx/qt/ui/line_label.hpp source/scwx/qt/ui/open_url_dialog.hpp source/scwx/qt/ui/placefile_dialog.hpp source/scwx/qt/ui/placefile_settings_widget.hpp @@ -259,15 +264,16 @@ set(HDR_UI source/scwx/qt/ui/about_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) + source/scwx/qt/ui/update_dialog.hpp + source/scwx/qt/ui/wfo_dialog.hpp) set(SRC_UI source/scwx/qt/ui/about_dialog.cpp source/scwx/qt/ui/alert_dialog.cpp source/scwx/qt/ui/alert_dock_widget.cpp source/scwx/qt/ui/animation_dock_widget.cpp source/scwx/qt/ui/collapsible_group.cpp source/scwx/qt/ui/county_dialog.cpp - source/scwx/qt/ui/wfo_dialog.cpp source/scwx/qt/ui/download_dialog.cpp + source/scwx/qt/ui/edit_line_dialog.cpp source/scwx/qt/ui/flow_layout.cpp source/scwx/qt/ui/gps_info_dialog.cpp source/scwx/qt/ui/hotkey_edit.cpp @@ -278,6 +284,7 @@ set(SRC_UI source/scwx/qt/ui/about_dialog.cpp source/scwx/qt/ui/level2_products_widget.cpp source/scwx/qt/ui/level2_settings_widget.cpp source/scwx/qt/ui/level3_products_widget.cpp + source/scwx/qt/ui/line_label.cpp source/scwx/qt/ui/open_url_dialog.cpp source/scwx/qt/ui/placefile_dialog.cpp source/scwx/qt/ui/placefile_settings_widget.cpp @@ -285,14 +292,15 @@ set(SRC_UI source/scwx/qt/ui/about_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) + source/scwx/qt/ui/update_dialog.cpp + source/scwx/qt/ui/wfo_dialog.cpp) set(UI_UI source/scwx/qt/ui/about_dialog.ui source/scwx/qt/ui/alert_dialog.ui source/scwx/qt/ui/alert_dock_widget.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/wfo_dialog.ui + source/scwx/qt/ui/edit_line_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 @@ -303,11 +311,14 @@ set(UI_UI source/scwx/qt/ui/about_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/update_dialog.ui + source/scwx/qt/ui/wfo_dialog.ui) +set(HDR_UI_SETTINGS source/scwx/qt/ui/settings/alert_palette_settings_widget.hpp + source/scwx/qt/ui/settings/hotkey_settings_widget.hpp source/scwx/qt/ui/settings/settings_page_widget.hpp source/scwx/qt/ui/settings/unit_settings_widget.hpp) -set(SRC_UI_SETTINGS source/scwx/qt/ui/settings/hotkey_settings_widget.cpp +set(SRC_UI_SETTINGS source/scwx/qt/ui/settings/alert_palette_settings_widget.cpp + source/scwx/qt/ui/settings/hotkey_settings_widget.cpp source/scwx/qt/ui/settings/settings_page_widget.cpp source/scwx/qt/ui/settings/unit_settings_widget.cpp) set(HDR_UI_SETUP source/scwx/qt/ui/setup/audio_codec_page.hpp diff --git a/scwx-qt/source/scwx/qt/map/alert_layer.cpp b/scwx-qt/source/scwx/qt/map/alert_layer.cpp index cc8bc29b..5ed4e458 100644 --- a/scwx-qt/source/scwx/qt/map/alert_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/alert_layer.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -40,6 +41,8 @@ struct AlertTypeHash> size_t operator()(const std::pair& x) const; }; +static bool IsAlertActive(const std::shared_ptr& segment); + class AlertLayerHandler : public QObject { Q_OBJECT @@ -111,25 +114,27 @@ signals: class AlertLayer::Impl { public: + struct LineData + { + boost::gil::rgba32f_pixel_t borderColor_ {}; + boost::gil::rgba32f_pixel_t highlightColor_ {}; + boost::gil::rgba32f_pixel_t lineColor_ {}; + + std::size_t borderWidth_ {}; + std::size_t highlightWidth_ {}; + std::size_t lineWidth_ {}; + }; + explicit Impl(AlertLayer* self, std::shared_ptr context, awips::Phenomenon phenomenon) : self_ {self}, phenomenon_ {phenomenon}, + ibw_ {awips::ibw::GetImpactBasedWarningInfo(phenomenon)}, geoLines_ {{false, std::make_shared(context)}, {true, std::make_shared(context)}} { - auto& paletteSettings = settings::PaletteSettings::Instance(); - - for (auto alertActive : {false, true}) - { - lineColor_.emplace( - alertActive, - util::color::ToRgba32fPixelT( - paletteSettings.alert_color(phenomenon_, alertActive) - .GetValue())); - } - + UpdateLineData(); ConnectSignals(); ScheduleRefresh(); } @@ -158,6 +163,10 @@ public: const QPointF& mouseGlobalPos); void ScheduleRefresh(); + LineData& GetLineData(const std::shared_ptr& segment, + bool alertActive); + void UpdateLineData(); + void AddLine(std::shared_ptr& geoLines, std::shared_ptr& di, const common::Coordinate& p1, @@ -176,6 +185,9 @@ public: bool enableHover, boost::container::stable_vector< std::shared_ptr>& drawItems); + void UpdateLines(); + + static LineData CreateLineData(const settings::LineSettings& lineSettings); boost::asio::thread_pool threadPool_ {1u}; @@ -184,7 +196,8 @@ public: boost::asio::system_timer refreshTimer_ {threadPool_}; std::mutex refreshMutex_; - const awips::Phenomenon phenomenon_; + const awips::Phenomenon phenomenon_; + const awips::ibw::ImpactBasedWarningInfo& ibw_; std::unique_ptr receiver_ {std::make_unique()}; @@ -199,12 +212,18 @@ public: segmentsByLine_; std::mutex linesMutex_ {}; - std::unordered_map lineColor_; + std::unordered_map + threatCategoryLineData_; + LineData observedLineData_ {}; + LineData tornadoPossibleLineData_ {}; + LineData inactiveLineData_ {}; std::chrono::system_clock::time_point selectedTime_ {}; std::shared_ptr lastHoverDi_ {nullptr}; std::string tooltip_ {}; + + std::vector connections_ {}; }; AlertLayer::AlertLayer(std::shared_ptr context, @@ -289,6 +308,15 @@ void AlertLayer::Deinitialize() DrawLayer::Deinitialize(); } +bool IsAlertActive(const std::shared_ptr& segment) +{ + auto& vtec = segment->header_->vtecString_.front(); + auto action = vtec.pVtec_.action(); + bool alertActive = (action != awips::PVtec::Action::Canceled); + + return alertActive; +} + void AlertLayerHandler::HandleAlert(const types::TextEventKey& key, size_t messageIndex) { @@ -331,10 +359,9 @@ void AlertLayerHandler::HandleAlert(const types::TextEventKey& key, continue; } - auto& vtec = segment->header_->vtecString_.front(); - auto action = vtec.pVtec_.action(); - awips::Phenomenon phenomenon = vtec.pVtec_.phenomenon(); - bool alertActive = (action != awips::PVtec::Action::Canceled); + auto& vtec = segment->header_->vtecString_.front(); + awips::Phenomenon phenomenon = vtec.pVtec_.phenomenon(); + bool alertActive = IsAlertActive(segment); auto& segmentsForType = segmentsByType_[{key.phenomenon_, alertActive}]; @@ -393,6 +420,8 @@ void AlertLayer::Impl::ConnectAlertHandlerSignals() void AlertLayer::Impl::ConnectSignals() { + auto& alertPaletteSettings = + settings::PaletteSettings::Instance().alert_palette(phenomenon_); auto timelineManager = manager::TimelineManager::Instance(); QObject::connect(timelineManager.get(), @@ -400,6 +429,13 @@ void AlertLayer::Impl::ConnectSignals() receiver_.get(), [this](std::chrono::system_clock::time_point dateTime) { selectedTime_ = dateTime; }); + + connections_.push_back(alertPaletteSettings.changed_signal().connect( + [this]() + { + UpdateLineData(); + UpdateLines(); + })); } void AlertLayer::Impl::ScheduleRefresh() @@ -439,14 +475,12 @@ void AlertLayer::Impl::AddAlert( { auto& segment = segmentRecord->segment_; - auto& vtec = segment->header_->vtecString_.front(); - auto action = vtec.pVtec_.action(); - bool alertActive = (action != awips::PVtec::Action::Canceled); + bool alertActive = IsAlertActive(segment); auto& startTime = segmentRecord->segmentBegin_; auto& endTime = segmentRecord->segmentEnd_; - auto& lineColor = lineColor_.at(alertActive); - auto& geoLines = geoLines_.at(alertActive); + auto& lineData = GetLineData(segment, alertActive); + auto& geoLines = geoLines_.at(alertActive); const auto& coordinates = segment->codedLocation_->coordinates(); @@ -462,30 +496,51 @@ void AlertLayer::Impl::AddAlert( // If draw items were added if (drawItems.second) { + const float borderWidth = lineData.borderWidth_; + const float highlightWidth = lineData.highlightWidth_; + const float lineWidth = lineData.lineWidth_; + + const float totalHighlightWidth = lineWidth + (highlightWidth * 2.0f); + const float totalBorderWidth = totalHighlightWidth + (borderWidth * 2.0f); + + constexpr bool borderHover = true; + constexpr bool highlightHover = false; + constexpr bool lineHover = false; + // Add border AddLines(geoLines, coordinates, - kBlack_, - 5.0f, + lineData.borderColor_, + totalBorderWidth, startTime, endTime, - true, + borderHover, drawItems.first->second); - // Add only border to segmentsByLine_ + // Add border to segmentsByLine_ for (auto& di : drawItems.first->second) { segmentsByLine_.insert({di, segmentRecord}); } + // Add highlight + AddLines(geoLines, + coordinates, + lineData.highlightColor_, + totalHighlightWidth, + startTime, + endTime, + highlightHover, + drawItems.first->second); + // Add line AddLines(geoLines, coordinates, - lineColor, - 3.0f, + lineData.lineColor_, + lineWidth, startTime, endTime, - false, + lineHover, drawItems.first->second); } } @@ -499,11 +554,8 @@ void AlertLayer::Impl::UpdateAlert( auto it = linesBySegment_.find(segmentRecord); if (it != linesBySegment_.cend()) { - auto& segment = segmentRecord->segment_; - - auto& vtec = segment->header_->vtecString_.front(); - auto action = vtec.pVtec_.action(); - bool alertActive = (action != awips::PVtec::Action::Canceled); + auto& segment = segmentRecord->segment_; + bool alertActive = IsAlertActive(segment); auto& geoLines = geoLines_.at(alertActive); @@ -590,6 +642,54 @@ void AlertLayer::Impl::AddLine(std::shared_ptr& geoLines, } } +void AlertLayer::Impl::UpdateLines() +{ + std::unique_lock lock {linesMutex_}; + + for (auto& segmentLine : linesBySegment_) + { + auto& segmentRecord = segmentLine.first; + auto& geoLineDrawItems = segmentLine.second; + auto& segment = segmentRecord->segment_; + bool alertActive = IsAlertActive(segment); + auto& lineData = GetLineData(segment, alertActive); + auto& geoLines = geoLines_.at(alertActive); + + const float borderWidth = lineData.borderWidth_; + const float highlightWidth = lineData.highlightWidth_; + const float lineWidth = lineData.lineWidth_; + + const float totalHighlightWidth = lineWidth + (highlightWidth * 2.0f); + const float totalBorderWidth = totalHighlightWidth + (borderWidth * 2.0f); + + // Border, highlight and line + std::size_t linesPerType = geoLineDrawItems.size() / 3; + + // Border + for (auto& borderLine : geoLineDrawItems | std::views::take(linesPerType)) + { + geoLines->SetLineModulate(borderLine, lineData.borderColor_); + geoLines->SetLineWidth(borderLine, totalBorderWidth); + } + + // Highlight + for (auto& highlightLine : geoLineDrawItems | + std::views::drop(linesPerType) | + std::views::take(linesPerType)) + { + geoLines->SetLineModulate(highlightLine, lineData.highlightColor_); + geoLines->SetLineWidth(highlightLine, totalHighlightWidth); + } + + // Line + for (auto& line : geoLineDrawItems | std::views::drop(linesPerType * 2)) + { + geoLines->SetLineModulate(line, lineData.lineColor_); + geoLines->SetLineWidth(line, lineWidth); + } + } +} + void AlertLayer::Impl::HandleGeoLinesEvent( std::shared_ptr& di, QEvent* ev) { @@ -638,6 +738,79 @@ void AlertLayer::Impl::HandleGeoLinesHover( } } +AlertLayer::Impl::LineData +AlertLayer::Impl::CreateLineData(const settings::LineSettings& lineSettings) +{ + return LineData { + .borderColor_ {lineSettings.GetBorderColorRgba32f()}, + .highlightColor_ {lineSettings.GetHighlightColorRgba32f()}, + .lineColor_ {lineSettings.GetLineColorRgba32f()}, + .borderWidth_ = + static_cast(lineSettings.border_width().GetValue()), + .highlightWidth_ = + static_cast(lineSettings.highlight_width().GetValue()), + .lineWidth_ = + static_cast(lineSettings.line_width().GetValue())}; +} + +void AlertLayer::Impl::UpdateLineData() +{ + auto& alertPalette = + settings::PaletteSettings().Instance().alert_palette(phenomenon_); + + for (auto threatCategory : ibw_.threatCategories_) + { + auto& palette = alertPalette.threat_category(threatCategory); + threatCategoryLineData_.insert_or_assign(threatCategory, + CreateLineData(palette)); + } + + if (ibw_.hasObservedTag_) + { + observedLineData_ = CreateLineData(alertPalette.observed()); + } + + if (ibw_.hasTornadoPossibleTag_) + { + tornadoPossibleLineData_ = + CreateLineData(alertPalette.tornado_possible()); + } + + inactiveLineData_ = CreateLineData(alertPalette.inactive()); +} + +AlertLayer::Impl::LineData& AlertLayer::Impl::GetLineData( + const std::shared_ptr& segment, bool alertActive) +{ + if (!alertActive) + { + return inactiveLineData_; + } + + for (auto& threatCategory : ibw_.threatCategories_) + { + if (segment->threatCategory_ == threatCategory) + { + if (threatCategory == awips::ibw::ThreatCategory::Base) + { + if (ibw_.hasObservedTag_ && segment->observed_) + { + return observedLineData_; + } + + if (ibw_.hasTornadoPossibleTag_ && segment->tornadoPossible_) + { + return tornadoPossibleLineData_; + } + } + + return threatCategoryLineData_.at(threatCategory); + } + } + + return threatCategoryLineData_.at(awips::ibw::ThreatCategory::Base); +}; + AlertLayerHandler& AlertLayerHandler::Instance() { static AlertLayerHandler alertLayerHandler_ {}; diff --git a/scwx-qt/source/scwx/qt/model/alert_model.cpp b/scwx-qt/source/scwx/qt/model/alert_model.cpp index d4bb1111..fed7cc17 100644 --- a/scwx-qt/source/scwx/qt/model/alert_model.cpp +++ b/scwx-qt/source/scwx/qt/model/alert_model.cpp @@ -10,7 +10,6 @@ #include #include - #include #include @@ -37,9 +36,9 @@ public: explicit AlertModelImpl(); ~AlertModelImpl() = default; - bool GetObserved(const types::TextEventKey& key); - awips::ThreatCategory GetThreatCategory(const types::TextEventKey& key); - bool GetTornadoPossible(const types::TextEventKey& key); + bool GetObserved(const types::TextEventKey& key); + awips::ibw::ThreatCategory GetThreatCategory(const types::TextEventKey& key); + bool GetTornadoPossible(const types::TextEventKey& key); static std::string GetCounties(const types::TextEventKey& key); static std::string GetState(const types::TextEventKey& key); @@ -61,7 +60,7 @@ public: types::TextEventHash> observedMap_; std::unordered_map> threatCategoryMap_; std::unordered_map> - distanceMap_; - scwx::common::Coordinate previousPosition_; + distanceMap_; + scwx::common::Coordinate previousPosition_; }; AlertModel::AlertModel(QObject* parent) : @@ -158,7 +157,7 @@ QVariant AlertModel::data(const QModelIndex& index, int role) const case static_cast(Column::ThreatCategory): if (role == Qt::DisplayRole) { - return QString::fromStdString(awips::GetThreatCategoryName( + return QString::fromStdString(awips::ibw::GetThreatCategoryName( p->GetThreatCategory(textEventKey))); } else @@ -439,10 +438,10 @@ bool AlertModelImpl::GetObserved(const types::TextEventKey& key) return observed; } -awips::ThreatCategory +awips::ibw::ThreatCategory AlertModelImpl::GetThreatCategory(const types::TextEventKey& key) { - awips::ThreatCategory threatCategory = awips::ThreatCategory::Base; + awips::ibw::ThreatCategory threatCategory = awips::ibw::ThreatCategory::Base; auto it = threatCategoryMap_.find(key); if (it != threatCategoryMap_.cend()) diff --git a/scwx-qt/source/scwx/qt/settings/alert_palette_settings.cpp b/scwx-qt/source/scwx/qt/settings/alert_palette_settings.cpp new file mode 100644 index 00000000..c684e1ed --- /dev/null +++ b/scwx-qt/source/scwx/qt/settings/alert_palette_settings.cpp @@ -0,0 +1,241 @@ +#include +#include + +#include + +#include +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace settings +{ + +static const std::string logPrefix_ = + "scwx::qt::settings::alert_palette_settings"; + +static const boost::gil::rgba8_pixel_t kColorBlack_ {0, 0, 0, 255}; + +struct LineData +{ + boost::gil::rgba8_pixel_t borderColor_ {kColorBlack_}; + boost::gil::rgba8_pixel_t highlightColor_ {kColorBlack_}; + boost::gil::rgba8_pixel_t lineColor_; + std::int64_t borderWidth_ {1}; + std::int64_t highlightWidth_ {0}; + std::int64_t lineWidth_ {3}; +}; + +typedef boost::unordered_flat_map + ThreatCategoryPalette; + +static const boost::unordered_flat_map + kThreatCategoryPalettes_ // + {{awips::Phenomenon::Marine, + {{awips::ibw::ThreatCategory::Base, {.lineColor_ {255, 127, 0, 255}}}}}, + {awips::Phenomenon::FlashFlood, + {{awips::ibw::ThreatCategory::Base, {.lineColor_ {0, 255, 0, 255}}}, + {awips::ibw::ThreatCategory::Considerable, + {.highlightColor_ {0, 255, 0, 255}, + .lineColor_ {kColorBlack_}, + .highlightWidth_ = 1, + .lineWidth_ = 1}}, + {awips::ibw::ThreatCategory::Catastrophic, + {.highlightColor_ {0, 255, 0, 255}, + .lineColor_ {255, 0, 0, 255}, + .highlightWidth_ = 1, + .lineWidth_ = 1}}}}, + {awips::Phenomenon::SevereThunderstorm, + {{awips::ibw::ThreatCategory::Base, {.lineColor_ {255, 255, 0, 255}}}, + {awips::ibw::ThreatCategory::Considerable, + {.highlightColor_ {255, 255, 0, 255}, + .lineColor_ {255, 0, 0, 255}, + .highlightWidth_ = 1, + .lineWidth_ = 1}}, + {awips::ibw::ThreatCategory::Destructive, + {.highlightColor_ {255, 255, 0, 255}, + .lineColor_ {255, 0, 0, 255}, + .highlightWidth_ = 1, + .lineWidth_ = 2}}}}, + {awips::Phenomenon::SnowSquall, + {{awips::ibw::ThreatCategory::Base, {.lineColor_ {0, 255, 255, 255}}}}}, + {awips::Phenomenon::Tornado, + {{awips::ibw::ThreatCategory::Base, {.lineColor_ {255, 0, 0, 255}}}, + {awips::ibw::ThreatCategory::Considerable, + {.lineColor_ {255, 0, 255, 255}}}, + {awips::ibw::ThreatCategory::Catastrophic, + {.highlightColor_ {255, 0, 255, 255}, + .lineColor_ {kColorBlack_}, + .highlightWidth_ = 1, + .lineWidth_ = 1}}}}}; + +static const boost::unordered_flat_map + kObservedPalettes_ // + {{awips::Phenomenon::Tornado, + {.highlightColor_ {255, 0, 0, 255}, + .lineColor_ {kColorBlack_}, + .highlightWidth_ = 1, + .lineWidth_ = 1}}}; + +static const boost::unordered_flat_map + kTornadoPossiblePalettes_ // + {{awips::Phenomenon::Marine, + {.highlightColor_ {255, 127, 0, 255}, + .lineColor_ {kColorBlack_}, + .highlightWidth_ = 1, + .lineWidth_ = 1}}, + {awips::Phenomenon::SevereThunderstorm, + {.highlightColor_ {255, 255, 0, 255}, + .lineColor_ {kColorBlack_}, + .highlightWidth_ = 1, + .lineWidth_ = 1}}}; + +static const boost::unordered_flat_map + kInactivePalettes_ // + { + {awips::Phenomenon::Marine, {.lineColor_ {127, 63, 0, 255}}}, + {awips::Phenomenon::FlashFlood, {.lineColor_ {0, 127, 0, 255}}}, + {awips::Phenomenon::SevereThunderstorm, {.lineColor_ {127, 127, 0, 255}}}, + {awips::Phenomenon::SnowSquall, {.lineColor_ {0, 127, 127, 255}}}, + {awips::Phenomenon::Tornado, {.lineColor_ {127, 0, 0, 255}}}, + }; + +class AlertPaletteSettings::Impl +{ +public: + explicit Impl(awips::Phenomenon phenomenon) : phenomenon_ {phenomenon} + { + const auto& info = awips::ibw::GetImpactBasedWarningInfo(phenomenon); + + const auto& threatCategoryPalettes = + kThreatCategoryPalettes_.at(phenomenon); + + for (auto& threatCategory : info.threatCategories_) + { + std::string threatCategoryName = + awips::ibw::GetThreatCategoryName(threatCategory); + boost::algorithm::to_lower(threatCategoryName); + auto result = + threatCategoryMap_.emplace(threatCategory, threatCategoryName); + auto& lineSettings = result.first->second; + + SetDefaultLineData(lineSettings, + threatCategoryPalettes.at(threatCategory)); + } + + if (info.hasObservedTag_) + { + SetDefaultLineData(observed_, kObservedPalettes_.at(phenomenon)); + } + + if (info.hasTornadoPossibleTag_) + { + SetDefaultLineData(tornadoPossible_, + kTornadoPossiblePalettes_.at(phenomenon)); + } + + SetDefaultLineData(inactive_, kInactivePalettes_.at(phenomenon)); + } + ~Impl() {} + + static void SetDefaultLineData(LineSettings& lineSettings, + const LineData& lineData); + + awips::Phenomenon phenomenon_; + + std::map threatCategoryMap_ {}; + + LineSettings observed_ {"observed"}; + LineSettings tornadoPossible_ {"tornado_possible"}; + LineSettings inactive_ {"inactive"}; +}; + +AlertPaletteSettings::AlertPaletteSettings(awips::Phenomenon phenomenon) : + SettingsCategory(awips::GetPhenomenonCode(phenomenon)), + p(std::make_unique(phenomenon)) +{ + auto& info = awips::ibw::GetImpactBasedWarningInfo(p->phenomenon_); + for (auto& threatCategory : p->threatCategoryMap_) + { + RegisterSubcategory(threatCategory.second); + } + + if (info.hasObservedTag_) + { + RegisterSubcategory(p->observed_); + } + + if (info.hasTornadoPossibleTag_) + { + RegisterSubcategory(p->tornadoPossible_); + } + + RegisterSubcategory(p->inactive_); + + SetDefaults(); +} + +AlertPaletteSettings::~AlertPaletteSettings() = default; + +AlertPaletteSettings::AlertPaletteSettings(AlertPaletteSettings&&) noexcept = + default; +AlertPaletteSettings& +AlertPaletteSettings::operator=(AlertPaletteSettings&&) noexcept = default; + +LineSettings& AlertPaletteSettings::threat_category( + awips::ibw::ThreatCategory threatCategory) const +{ + auto it = p->threatCategoryMap_.find(threatCategory); + if (it != p->threatCategoryMap_.cend()) + { + return it->second; + } + return p->threatCategoryMap_.at(awips::ibw::ThreatCategory::Base); +} + +LineSettings& AlertPaletteSettings::inactive() const +{ + return p->inactive_; +} + +LineSettings& AlertPaletteSettings::observed() const +{ + return p->observed_; +} + +LineSettings& AlertPaletteSettings::tornado_possible() const +{ + return p->tornadoPossible_; +} + +void AlertPaletteSettings::Impl::SetDefaultLineData(LineSettings& lineSettings, + const LineData& lineData) +{ + lineSettings.border_color().SetDefault( + util::color::ToArgbString(lineData.borderColor_)); + lineSettings.highlight_color().SetDefault( + util::color::ToArgbString(lineData.highlightColor_)); + lineSettings.line_color().SetDefault( + util::color::ToArgbString(lineData.lineColor_)); + + lineSettings.border_width().SetDefault(lineData.borderWidth_); + lineSettings.highlight_width().SetDefault(lineData.highlightWidth_); + lineSettings.line_width().SetDefault(lineData.lineWidth_); +} + +bool operator==(const AlertPaletteSettings& lhs, + const AlertPaletteSettings& rhs) +{ + return (lhs.p->phenomenon_ == rhs.p->phenomenon_ && + lhs.p->threatCategoryMap_ == rhs.p->threatCategoryMap_ && + lhs.p->inactive_ == rhs.p->inactive_ && + lhs.p->observed_ == rhs.p->observed_ && + lhs.p->tornadoPossible_ == rhs.p->tornadoPossible_); +} + +} // namespace settings +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/settings/alert_palette_settings.hpp b/scwx-qt/source/scwx/qt/settings/alert_palette_settings.hpp new file mode 100644 index 00000000..152a6351 --- /dev/null +++ b/scwx-qt/source/scwx/qt/settings/alert_palette_settings.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace settings +{ + +class AlertPaletteSettings : public SettingsCategory +{ +public: + explicit AlertPaletteSettings(awips::Phenomenon phenomenon); + ~AlertPaletteSettings(); + + AlertPaletteSettings(const AlertPaletteSettings&) = delete; + AlertPaletteSettings& operator=(const AlertPaletteSettings&) = delete; + + AlertPaletteSettings(AlertPaletteSettings&&) noexcept; + AlertPaletteSettings& operator=(AlertPaletteSettings&&) noexcept; + + LineSettings& + threat_category(awips::ibw::ThreatCategory threatCategory) const; + LineSettings& inactive() const; + LineSettings& observed() const; + LineSettings& tornado_possible() const; + + friend bool operator==(const AlertPaletteSettings& lhs, + const AlertPaletteSettings& rhs); + +private: + class Impl; + std::unique_ptr p; +}; + +} // namespace settings +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/settings/line_settings.cpp b/scwx-qt/source/scwx/qt/settings/line_settings.cpp new file mode 100644 index 00000000..45337fa2 --- /dev/null +++ b/scwx-qt/source/scwx/qt/settings/line_settings.cpp @@ -0,0 +1,155 @@ +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace settings +{ + +static const std::string logPrefix_ = "scwx::qt::settings::line_settings"; + +static const boost::gil::rgba8_pixel_t kTransparentColor_ {0, 0, 0, 0}; +static const std::string kTransparentColorString_ { + util::color::ToArgbString(kTransparentColor_)}; + +static const boost::gil::rgba8_pixel_t kBlackColor_ {0, 0, 0, 255}; +static const std::string kBlackColorString_ { + util::color::ToArgbString(kBlackColor_)}; + +static const boost::gil::rgba8_pixel_t kWhiteColor_ {255, 255, 255, 255}; +static const std::string kWhiteColorString_ { + util::color::ToArgbString(kWhiteColor_)}; + +class LineSettings::Impl +{ +public: + explicit Impl() + { + lineColor_.SetDefault(kWhiteColorString_); + highlightColor_.SetDefault(kTransparentColorString_); + borderColor_.SetDefault(kBlackColorString_); + + lineWidth_.SetDefault(3); + highlightWidth_.SetDefault(0); + borderWidth_.SetDefault(1); + + lineWidth_.SetMinimum(1); + highlightWidth_.SetMinimum(0); + borderWidth_.SetMinimum(0); + + lineWidth_.SetMaximum(9); + highlightWidth_.SetMaximum(9); + borderWidth_.SetMaximum(9); + + lineColor_.SetValidator(&util::color::ValidateArgbString); + highlightColor_.SetValidator(&util::color::ValidateArgbString); + borderColor_.SetValidator(&util::color::ValidateArgbString); + } + ~Impl() {} + + SettingsVariable lineColor_ {"line_color"}; + SettingsVariable highlightColor_ {"highlight_color"}; + SettingsVariable borderColor_ {"border_color"}; + + SettingsVariable lineWidth_ {"line_width"}; + SettingsVariable highlightWidth_ {"highlight_width"}; + SettingsVariable borderWidth_ {"border_width"}; +}; + +LineSettings::LineSettings(const std::string& name) : + SettingsCategory(name), p(std::make_unique()) +{ + RegisterVariables({&p->lineColor_, + &p->highlightColor_, + &p->borderColor_, + &p->lineWidth_, + &p->highlightWidth_, + &p->borderWidth_}); + SetDefaults(); +} +LineSettings::~LineSettings() = default; + +LineSettings::LineSettings(LineSettings&&) noexcept = default; +LineSettings& LineSettings::operator=(LineSettings&&) noexcept = default; + +SettingsVariable& LineSettings::border_color() const +{ + return p->borderColor_; +} + +SettingsVariable& LineSettings::highlight_color() const +{ + return p->highlightColor_; +} + +SettingsVariable& LineSettings::line_color() const +{ + return p->lineColor_; +} + +SettingsVariable& LineSettings::border_width() const +{ + return p->borderWidth_; +} + +SettingsVariable& LineSettings::highlight_width() const +{ + return p->highlightWidth_; +} + +SettingsVariable& LineSettings::line_width() const +{ + return p->lineWidth_; +} + +boost::gil::rgba32f_pixel_t LineSettings::GetBorderColorRgba32f() const +{ + return util::color::ToRgba32fPixelT(p->borderColor_.GetValue()); +} + +boost::gil::rgba32f_pixel_t LineSettings::GetHighlightColorRgba32f() const +{ + return util::color::ToRgba32fPixelT(p->highlightColor_.GetValue()); +} + +boost::gil::rgba32f_pixel_t LineSettings::GetLineColorRgba32f() const +{ + return util::color::ToRgba32fPixelT(p->lineColor_.GetValue()); +} + +void LineSettings::StageValues(boost::gil::rgba8_pixel_t borderColor, + boost::gil::rgba8_pixel_t highlightColor, + boost::gil::rgba8_pixel_t lineColor, + std::int64_t borderWidth, + std::int64_t highlightWidth, + std::int64_t lineWidth) +{ + set_block_signals(true); + + p->borderColor_.StageValue(util::color::ToArgbString(borderColor)); + p->highlightColor_.StageValue(util::color::ToArgbString(highlightColor)); + p->lineColor_.StageValue(util::color::ToArgbString(lineColor)); + p->borderWidth_.StageValue(borderWidth); + p->highlightWidth_.StageValue(highlightWidth); + p->lineWidth_.StageValue(lineWidth); + + set_block_signals(false); + + staged_signal()(); +} + +bool operator==(const LineSettings& lhs, const LineSettings& rhs) +{ + return (lhs.p->borderColor_ == rhs.p->borderColor_ && + lhs.p->highlightColor_ == rhs.p->highlightColor_ && + lhs.p->lineColor_ == rhs.p->lineColor_ && + lhs.p->borderWidth_ == rhs.p->borderWidth_ && + lhs.p->highlightWidth_ == rhs.p->highlightWidth_ && + lhs.p->lineWidth_ == rhs.p->lineWidth_); +} + +} // namespace settings +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/settings/line_settings.hpp b/scwx-qt/source/scwx/qt/settings/line_settings.hpp new file mode 100644 index 00000000..6f6988a2 --- /dev/null +++ b/scwx-qt/source/scwx/qt/settings/line_settings.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include +#include + +#include +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace settings +{ + +class LineSettings : public SettingsCategory +{ +public: + explicit LineSettings(const std::string& name); + ~LineSettings(); + + LineSettings(const LineSettings&) = delete; + LineSettings& operator=(const LineSettings&) = delete; + + LineSettings(LineSettings&&) noexcept; + LineSettings& operator=(LineSettings&&) noexcept; + + SettingsVariable& border_color() const; + SettingsVariable& highlight_color() const; + SettingsVariable& line_color() const; + + SettingsVariable& border_width() const; + SettingsVariable& highlight_width() const; + SettingsVariable& line_width() const; + + boost::gil::rgba32f_pixel_t GetBorderColorRgba32f() const; + boost::gil::rgba32f_pixel_t GetHighlightColorRgba32f() const; + boost::gil::rgba32f_pixel_t GetLineColorRgba32f() const; + + void StageValues(boost::gil::rgba8_pixel_t borderColor, + boost::gil::rgba8_pixel_t highlightColor, + boost::gil::rgba8_pixel_t lineColor, + std::int64_t borderWidth, + std::int64_t highlightWidth, + std::int64_t lineWidth); + + friend bool operator==(const LineSettings& lhs, const LineSettings& rhs); + +private: + class Impl; + std::unique_ptr p; +}; + +} // namespace settings +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/settings/palette_settings.cpp b/scwx-qt/source/scwx/qt/settings/palette_settings.cpp index 9d9c93fa..c5902fb3 100644 --- a/scwx-qt/source/scwx/qt/settings/palette_settings.cpp +++ b/scwx-qt/source/scwx/qt/settings/palette_settings.cpp @@ -2,9 +2,9 @@ #include #include +#include #include #include -#include namespace scwx { @@ -76,59 +76,20 @@ static const awips::Phenomenon kDefaultPhenomenon_ {awips::Phenomenon::Marine}; class PaletteSettings::Impl { public: - explicit Impl() + explicit Impl(PaletteSettings* self) : self_ {self} { - palette_.reserve(kPaletteKeys_.size()); - - for (const auto& name : kPaletteKeys_) - { - const std::string& defaultValue = kDefaultPalettes_.at(name); - - auto result = - palette_.emplace(name, SettingsVariable {name}); - - SettingsVariable& settingsVariable = result.first->second; - - settingsVariable.SetDefault(defaultValue); - - variables_.push_back(&settingsVariable); - }; - - activeAlertColor_.reserve(kAlertColors_.size()); - inactiveAlertColor_.reserve(kAlertColors_.size()); - - for (auto& alert : kAlertColors_) - { - std::string phenomenonCode = awips::GetPhenomenonCode(alert.first); - std::string activeName = fmt::format("{}-active", phenomenonCode); - std::string inactiveName = fmt::format("{}-inactive", phenomenonCode); - - auto activeResult = activeAlertColor_.emplace( - alert.first, SettingsVariable {activeName}); - auto inactiveResult = inactiveAlertColor_.emplace( - alert.first, SettingsVariable {inactiveName}); - - SettingsVariable& activeVariable = - activeResult.first->second; - SettingsVariable& inactiveVariable = - inactiveResult.first->second; - - activeVariable.SetDefault( - util::color::ToArgbString(alert.second.first)); - inactiveVariable.SetDefault( - util::color::ToArgbString(alert.second.second)); - - activeVariable.SetValidator(&ValidateColor); - inactiveVariable.SetValidator(&ValidateColor); - - variables_.push_back(&activeVariable); - variables_.push_back(&inactiveVariable); - } + InitializeColorTables(); + InitializeLegacyAlerts(); + InitializeAlerts(); } ~Impl() {} - static bool ValidateColor(const std::string& value); + void InitializeColorTables(); + void InitializeLegacyAlerts(); + void InitializeAlerts(); + + PaletteSettings* self_; std::unordered_map> palette_ {}; std::unordered_map> @@ -136,16 +97,13 @@ public: std::unordered_map> inactiveAlertColor_ {}; std::vector variables_ {}; + + std::unordered_map + alertPaletteMap_ {}; }; -bool PaletteSettings::Impl::ValidateColor(const std::string& value) -{ - static constexpr LazyRE2 re = {"#[0-9A-Fa-f]{8}"}; - return RE2::FullMatch(value, *re); -} - PaletteSettings::PaletteSettings() : - SettingsCategory("palette"), p(std::make_unique()) + SettingsCategory("palette"), p(std::make_unique(this)) { RegisterVariables(p->variables_); SetDefaults(); @@ -158,6 +116,75 @@ PaletteSettings::PaletteSettings(PaletteSettings&&) noexcept = default; PaletteSettings& PaletteSettings::operator=(PaletteSettings&&) noexcept = default; +void PaletteSettings::Impl::InitializeColorTables() +{ + palette_.reserve(kPaletteKeys_.size()); + + for (const auto& name : kPaletteKeys_) + { + const std::string& defaultValue = kDefaultPalettes_.at(name); + + auto result = + palette_.emplace(name, SettingsVariable {name}); + + SettingsVariable& settingsVariable = result.first->second; + + settingsVariable.SetDefault(defaultValue); + + variables_.push_back(&settingsVariable); + }; +} + +void PaletteSettings::Impl::InitializeLegacyAlerts() +{ + activeAlertColor_.reserve(kAlertColors_.size()); + inactiveAlertColor_.reserve(kAlertColors_.size()); + + for (auto& alert : kAlertColors_) + { + std::string phenomenonCode = awips::GetPhenomenonCode(alert.first); + std::string activeName = fmt::format("{}-active", phenomenonCode); + std::string inactiveName = fmt::format("{}-inactive", phenomenonCode); + + auto activeResult = activeAlertColor_.emplace( + alert.first, SettingsVariable {activeName}); + auto inactiveResult = inactiveAlertColor_.emplace( + alert.first, SettingsVariable {inactiveName}); + + SettingsVariable& activeVariable = + activeResult.first->second; + SettingsVariable& inactiveVariable = + inactiveResult.first->second; + + activeVariable.SetDefault(util::color::ToArgbString(alert.second.first)); + inactiveVariable.SetDefault( + util::color::ToArgbString(alert.second.second)); + + activeVariable.SetValidator(&util::color::ValidateArgbString); + inactiveVariable.SetValidator(&util::color::ValidateArgbString); + + variables_.push_back(&activeVariable); + variables_.push_back(&inactiveVariable); + } +} + +void PaletteSettings::Impl::InitializeAlerts() +{ + std::vector alertSettings {}; + + for (auto phenomenon : PaletteSettings::alert_phenomena()) + { + auto result = alertPaletteMap_.emplace(phenomenon, phenomenon); + auto& it = result.first; + AlertPaletteSettings& alertPaletteSettings = it->second; + + // Variable registration + alertSettings.push_back(&alertPaletteSettings); + } + + self_->RegisterSubcategoryArray("alerts", alertSettings); +} + SettingsVariable& PaletteSettings::palette(const std::string& name) const { @@ -194,6 +221,12 @@ PaletteSettings::alert_color(awips::Phenomenon phenomenon, bool active) const } } +AlertPaletteSettings& +PaletteSettings::alert_palette(awips::Phenomenon phenomenon) +{ + return p->alertPaletteMap_.at(phenomenon); +} + const std::vector& PaletteSettings::alert_phenomena() { static const std::vector kAlertPhenomena_ { diff --git a/scwx-qt/source/scwx/qt/settings/palette_settings.hpp b/scwx-qt/source/scwx/qt/settings/palette_settings.hpp index c0f7985a..eb52e600 100644 --- a/scwx-qt/source/scwx/qt/settings/palette_settings.hpp +++ b/scwx-qt/source/scwx/qt/settings/palette_settings.hpp @@ -1,7 +1,9 @@ #pragma once +#include #include #include +#include #include #include @@ -29,6 +31,7 @@ public: SettingsVariable& palette(const std::string& name) const; SettingsVariable& alert_color(awips::Phenomenon phenomenon, bool active) const; + AlertPaletteSettings& alert_palette(awips::Phenomenon); static const std::vector& alert_phenomena(); diff --git a/scwx-qt/source/scwx/qt/settings/settings_category.cpp b/scwx-qt/source/scwx/qt/settings/settings_category.cpp index e6c929f8..75a46bf8 100644 --- a/scwx-qt/source/scwx/qt/settings/settings_category.cpp +++ b/scwx-qt/source/scwx/qt/settings/settings_category.cpp @@ -21,11 +21,21 @@ public: ~Impl() {} + void ConnectSubcategory(SettingsCategory& category); + void ConnectVariable(SettingsVariableBase* variable); + const std::string name_; std::vector>> subcategoryArrays_; + std::vector subcategories_; std::vector variables_; + + boost::signals2::signal changedSignal_ {}; + boost::signals2::signal stagedSignal_ {}; + bool blockSignals_ {false}; + + std::vector connections_ {}; }; SettingsCategory::SettingsCategory(const std::string& name) : @@ -38,13 +48,88 @@ SettingsCategory::SettingsCategory(SettingsCategory&&) noexcept = default; SettingsCategory& SettingsCategory::operator=(SettingsCategory&&) noexcept = default; +bool SettingsCategory::IsDefault() const +{ + bool isDefault = true; + + // Get subcategory array defaults + for (auto& subcategoryArray : p->subcategoryArrays_) + { + for (auto& subcategory : subcategoryArray.second) + { + isDefault = isDefault && subcategory->IsDefault(); + } + } + + // Get subcategory defaults + for (auto& subcategory : p->subcategories_) + { + isDefault = isDefault && subcategory->IsDefault(); + } + + // Get variable defaults + for (auto& variable : p->variables_) + { + isDefault = isDefault && variable->IsDefault(); + } + + return isDefault; +} + +bool SettingsCategory::IsDefaultStaged() const +{ + bool isDefaultStaged = true; + + // Get subcategory array defaults + for (auto& subcategoryArray : p->subcategoryArrays_) + { + for (auto& subcategory : subcategoryArray.second) + { + isDefaultStaged = isDefaultStaged && subcategory->IsDefaultStaged(); + } + } + + // Get subcategory defaults + for (auto& subcategory : p->subcategories_) + { + isDefaultStaged = isDefaultStaged && subcategory->IsDefaultStaged(); + } + + // Get variable defaults + for (auto& variable : p->variables_) + { + isDefaultStaged = isDefaultStaged && variable->IsDefaultStaged(); + } + + return isDefaultStaged; +} + std::string SettingsCategory::name() const { return p->name_; } +boost::signals2::signal& SettingsCategory::changed_signal() +{ + return p->changedSignal_; +} + +boost::signals2::signal& SettingsCategory::staged_signal() +{ + return p->stagedSignal_; +} + +void SettingsCategory::set_block_signals(bool blockSignals) +{ + p->blockSignals_ = blockSignals; +} + void SettingsCategory::SetDefaults() { + // Don't allow individual variables to invoke the signal when operating over + // the entire category + p->blockSignals_ = true; + // Set subcategory array defaults for (auto& subcategoryArray : p->subcategoryArrays_) { @@ -54,11 +139,129 @@ void SettingsCategory::SetDefaults() } } + // Set subcategory defaults + for (auto& subcategory : p->subcategories_) + { + subcategory->SetDefaults(); + } + // Set variable defaults for (auto& variable : p->variables_) { variable->SetValueToDefault(); } + + // Unblock signals + p->blockSignals_ = false; + + p->changedSignal_(); + p->stagedSignal_(); +} + +void SettingsCategory::StageDefaults() +{ + // Don't allow individual variables to invoke the signal when operating over + // the entire category + p->blockSignals_ = true; + + // Stage subcategory array defaults + for (auto& subcategoryArray : p->subcategoryArrays_) + { + for (auto& subcategory : subcategoryArray.second) + { + subcategory->StageDefaults(); + } + } + + // Stage subcategory defaults + for (auto& subcategory : p->subcategories_) + { + subcategory->StageDefaults(); + } + + // Stage variable defaults + for (auto& variable : p->variables_) + { + variable->StageDefault(); + } + + // Unblock signals + p->blockSignals_ = false; + + p->stagedSignal_(); +} + +bool SettingsCategory::Commit() +{ + bool committed = false; + + // Don't allow individual variables to invoke the signal when operating over + // the entire category + p->blockSignals_ = true; + + // Commit subcategory arrays + for (auto& subcategoryArray : p->subcategoryArrays_) + { + for (auto& subcategory : subcategoryArray.second) + { + committed |= subcategory->Commit(); + } + } + + // Commit subcategories + for (auto& subcategory : p->subcategories_) + { + committed |= subcategory->Commit(); + } + + // Commit variables + for (auto& variable : p->variables_) + { + committed |= variable->Commit(); + } + + // Unblock signals + p->blockSignals_ = false; + + if (committed) + { + p->changedSignal_(); + } + + return committed; +} + +void SettingsCategory::Reset() +{ + // Don't allow individual variables to invoke the signal when operating over + // the entire category + p->blockSignals_ = true; + + // Reset subcategory arrays + for (auto& subcategoryArray : p->subcategoryArrays_) + { + for (auto& subcategory : subcategoryArray.second) + { + subcategory->Reset(); + } + } + + // Reset subcategories + for (auto& subcategory : p->subcategories_) + { + subcategory->Reset(); + } + + // Reset variables + for (auto& variable : p->variables_) + { + variable->Reset(); + } + + // Unblock signals + p->blockSignals_ = false; + + p->stagedSignal_(); } bool SettingsCategory::ReadJson(const boost::json::object& json) @@ -111,6 +314,12 @@ bool SettingsCategory::ReadJson(const boost::json::object& json) } } + // Read subcategories + for (auto& subcategory : p->subcategories_) + { + validated &= subcategory->ReadJson(object); + } + // Read variables for (auto& variable : p->variables_) { @@ -154,6 +363,12 @@ void SettingsCategory::WriteJson(boost::json::object& json) const object.insert_or_assign(subcategoryArray.first, arrayObject); } + // Write subcategories + for (auto& subcategory : p->subcategories_) + { + subcategory->WriteJson(object); + } + // Write variables for (auto& variable : p->variables_) { @@ -163,6 +378,12 @@ void SettingsCategory::WriteJson(boost::json::object& json) const json.insert_or_assign(p->name_, object); } +void SettingsCategory::RegisterSubcategory(SettingsCategory& subcategory) +{ + p->ConnectSubcategory(subcategory); + p->subcategories_.push_back(&subcategory); +} + void SettingsCategory::RegisterSubcategoryArray( const std::string& name, std::vector& subcategories) { @@ -172,22 +393,92 @@ void SettingsCategory::RegisterSubcategoryArray( std::transform(subcategories.begin(), subcategories.end(), std::back_inserter(newSubcategories.second), - [](SettingsCategory& subcategory) { return &subcategory; }); + [this](SettingsCategory& subcategory) + { + p->ConnectSubcategory(subcategory); + return &subcategory; + }); +} + +void SettingsCategory::RegisterSubcategoryArray( + const std::string& name, std::vector& subcategories) +{ + auto& newSubcategories = p->subcategoryArrays_.emplace_back( + name, std::vector {}); + + std::transform(subcategories.begin(), + subcategories.end(), + std::back_inserter(newSubcategories.second), + [this](SettingsCategory* subcategory) + { + p->ConnectSubcategory(*subcategory); + return subcategory; + }); } void SettingsCategory::RegisterVariables( std::initializer_list variables) { + for (auto& variable : variables) + { + p->ConnectVariable(variable); + } p->variables_.insert(p->variables_.end(), variables); } void SettingsCategory::RegisterVariables( std::vector variables) { + for (auto& variable : variables) + { + p->ConnectVariable(variable); + } p->variables_.insert( p->variables_.end(), variables.cbegin(), variables.cend()); } +void SettingsCategory::Impl::ConnectSubcategory(SettingsCategory& category) +{ + connections_.emplace_back(category.changed_signal().connect( + [this]() + { + if (!blockSignals_) + { + changedSignal_(); + } + })); + + connections_.emplace_back(category.staged_signal().connect( + [this]() + { + if (!blockSignals_) + { + stagedSignal_(); + } + })); +} + +void SettingsCategory::Impl::ConnectVariable(SettingsVariableBase* variable) +{ + connections_.emplace_back(variable->changed_signal().connect( + [this]() + { + if (!blockSignals_) + { + changedSignal_(); + } + })); + + connections_.emplace_back(variable->staged_signal().connect( + [this]() + { + if (!blockSignals_) + { + stagedSignal_(); + } + })); +} + } // namespace settings } // namespace qt } // namespace scwx diff --git a/scwx-qt/source/scwx/qt/settings/settings_category.hpp b/scwx-qt/source/scwx/qt/settings/settings_category.hpp index 2da7b9ab..167af06a 100644 --- a/scwx-qt/source/scwx/qt/settings/settings_category.hpp +++ b/scwx-qt/source/scwx/qt/settings/settings_category.hpp @@ -6,6 +6,7 @@ #include #include +#include namespace scwx { @@ -28,11 +29,61 @@ public: std::string name() const; + /** + * Gets the signal invoked when a variable within the category is changed. + * + * @return Changed signal + */ + boost::signals2::signal& changed_signal(); + + /** + * Gets the signal invoked when a variable within the category is staged. + * + * @return Staged signal + */ + boost::signals2::signal& staged_signal(); + + /** + * Gets whether or not all settings variables are currently set to default + * values. + * + * @return true if all settings variables are currently set to default + * values, otherwise false. + */ + bool IsDefault() const; + + /** + * Gets whether or not all settings variables currently have staged values + * set to default. + * + * @return true if all settings variables currently have staged values set + * to default, otherwise false. + */ + bool IsDefaultStaged() const; + /** * Set all variables to their defaults. */ void SetDefaults(); + /** + * Stage all variables to their defaults. + */ + void StageDefaults(); + + /** + * Sets the current value of all variables to the staged value. + * + * @return true if any staged value was committed, false if no staged values + * are present. + */ + bool Commit(); + + /** + * Clears the staged value of all variables. + */ + void Reset(); + /** * Reads the variables from the JSON object. * @@ -50,12 +101,18 @@ public: */ virtual void WriteJson(boost::json::object& json) const; + void RegisterSubcategory(SettingsCategory& subcategory); void RegisterSubcategoryArray(const std::string& name, std::vector& subcategories); + void RegisterSubcategoryArray(const std::string& name, + std::vector& subcategories); void RegisterVariables(std::initializer_list variables); void RegisterVariables(std::vector variables); +protected: + void set_block_signals(bool blockSignals); + private: class Impl; std::unique_ptr p; diff --git a/scwx-qt/source/scwx/qt/settings/settings_variable.cpp b/scwx-qt/source/scwx/qt/settings/settings_variable.cpp index 71db421e..a5387937 100644 --- a/scwx-qt/source/scwx/qt/settings/settings_variable.cpp +++ b/scwx-qt/source/scwx/qt/settings/settings_variable.cpp @@ -65,6 +65,18 @@ inline auto FormatParameter(const T& value) } } +template +bool SettingsVariable::IsDefault() const +{ + return p->value_ == p->default_; +} + +template +bool SettingsVariable::IsDefaultStaged() const +{ + return p->staged_.value_or(p->value_) == p->default_; +} + template T SettingsVariable::GetValue() const { @@ -81,10 +93,13 @@ bool SettingsVariable::SetValue(const T& value) p->value_ = (p->transform_ != nullptr) ? p->transform_(value) : value; validated = true; + changed_signal()(); for (auto& callback : p->valueChangedCallbackFunctions_) { callback.second(p->value_); } + + staged_signal()(); for (auto& callback : p->valueStagedCallbackFunctions_) { callback.second(p->value_); @@ -129,10 +144,13 @@ bool SettingsVariable::SetValueOrDefault(const T& value) p->value_ = p->default_; } + changed_signal()(); for (auto& callback : p->valueChangedCallbackFunctions_) { callback.second(p->value_); } + + staged_signal()(); for (auto& callback : p->valueStagedCallbackFunctions_) { callback.second(p->value_); @@ -146,10 +164,13 @@ void SettingsVariable::SetValueToDefault() { p->value_ = p->default_; + changed_signal()(); for (auto& callback : p->valueChangedCallbackFunctions_) { callback.second(p->value_); } + + staged_signal()(); for (auto& callback : p->valueStagedCallbackFunctions_) { callback.second(p->value_); @@ -168,6 +189,7 @@ void SettingsVariable::StageDefault() p->staged_.reset(); } + staged_signal()(); for (auto& callback : p->valueStagedCallbackFunctions_) { callback.second(p->default_); @@ -194,6 +216,7 @@ bool SettingsVariable::StageValue(const T& value) validated = true; + staged_signal()(); for (auto& callback : p->valueStagedCallbackFunctions_) { callback.second(transformed); @@ -214,10 +237,13 @@ bool SettingsVariable::Commit() p->staged_.reset(); committed = true; + changed_signal()(); for (auto& callback : p->valueChangedCallbackFunctions_) { callback.second(p->value_); } + + staged_signal()(); for (auto& callback : p->valueStagedCallbackFunctions_) { callback.second(p->value_); @@ -232,6 +258,7 @@ void SettingsVariable::Reset() { p->staged_.reset(); + staged_signal()(); for (auto& callback : p->valueStagedCallbackFunctions_) { callback.second(p->value_); @@ -336,10 +363,13 @@ bool SettingsVariable::ReadValue(const boost::json::object& json) p->value_ = p->default_; } + changed_signal()(); for (auto& callback : p->valueChangedCallbackFunctions_) { callback.second(p->value_); } + + staged_signal()(); for (auto& callback : p->valueStagedCallbackFunctions_) { callback.second(p->value_); diff --git a/scwx-qt/source/scwx/qt/settings/settings_variable.hpp b/scwx-qt/source/scwx/qt/settings/settings_variable.hpp index 72d61dde..2c3b2a07 100644 --- a/scwx-qt/source/scwx/qt/settings/settings_variable.hpp +++ b/scwx-qt/source/scwx/qt/settings/settings_variable.hpp @@ -29,7 +29,7 @@ public: typedef std::function ValueCallbackFunction; explicit SettingsVariable(const std::string& name); - ~SettingsVariable(); + virtual ~SettingsVariable(); SettingsVariable(const SettingsVariable&) = delete; SettingsVariable& operator=(const SettingsVariable&) = delete; @@ -37,6 +37,24 @@ public: SettingsVariable(SettingsVariable&&) noexcept; SettingsVariable& operator=(SettingsVariable&&) noexcept; + /** + * Gets whether or not the settings variable is currently set to its default + * value. + * + * @return true if the settings variable is currently set to its default + * value, otherwise false. + */ + bool IsDefault() const override; + + /** + * Gets whether or not the settings variable currently has its staged value + * set to default. + * + * @return true if the settings variable currently has its staged value set + * to default, otherwise false. + */ + bool IsDefaultStaged() const override; + /** * Gets the current value of the settings variable. * @@ -96,7 +114,7 @@ public: /** * Clears the staged value of the settings variable. */ - void Reset(); + void Reset() override; /** * Gets the staged value of the settings variable, if defined. diff --git a/scwx-qt/source/scwx/qt/settings/settings_variable_base.cpp b/scwx-qt/source/scwx/qt/settings/settings_variable_base.cpp index 55ce72ea..7e31fb5f 100644 --- a/scwx-qt/source/scwx/qt/settings/settings_variable_base.cpp +++ b/scwx-qt/source/scwx/qt/settings/settings_variable_base.cpp @@ -18,6 +18,9 @@ public: ~Impl() {} const std::string name_; + + boost::signals2::signal changedSignal_ {}; + boost::signals2::signal stagedSignal_ {}; }; SettingsVariableBase::SettingsVariableBase(const std::string& name) : @@ -38,6 +41,16 @@ std::string SettingsVariableBase::name() const return p->name_; } +boost::signals2::signal& SettingsVariableBase::changed_signal() +{ + return p->changedSignal_; +} + +boost::signals2::signal& SettingsVariableBase::staged_signal() +{ + return p->stagedSignal_; +} + bool SettingsVariableBase::Equals(const SettingsVariableBase& o) const { return p->name_ == o.p->name_; diff --git a/scwx-qt/source/scwx/qt/settings/settings_variable_base.hpp b/scwx-qt/source/scwx/qt/settings/settings_variable_base.hpp index f0444f45..f4e48934 100644 --- a/scwx-qt/source/scwx/qt/settings/settings_variable_base.hpp +++ b/scwx-qt/source/scwx/qt/settings/settings_variable_base.hpp @@ -4,6 +4,7 @@ #include #include +#include namespace scwx { @@ -19,7 +20,7 @@ class SettingsVariableBase { protected: explicit SettingsVariableBase(const std::string& name); - ~SettingsVariableBase(); + virtual ~SettingsVariableBase(); public: SettingsVariableBase(const SettingsVariableBase&) = delete; @@ -30,6 +31,38 @@ public: std::string name() const; + /** + * Gets the signal invoked when the settings variable is changed. + * + * @return Changed signal + */ + boost::signals2::signal& changed_signal(); + + /** + * Gets the signal invoked when the settings variable is staged. + * + * @return Staged signal + */ + boost::signals2::signal& staged_signal(); + + /** + * Gets whether or not the settings variable is currently set to its default + * value. + * + * @return true if the settings variable is currently set to its default + * value, otherwise false. + */ + virtual bool IsDefault() const = 0; + + /** + * Gets whether or not the settings variable currently has its staged value + * set to default. + * + * @return true if the settings variable currently has its staged value set + * to default, otherwise false. + */ + virtual bool IsDefaultStaged() const = 0; + /** * Sets the current value of the settings variable to default. */ @@ -48,6 +81,11 @@ public: */ virtual bool Commit() = 0; + /** + * Clears the staged value of the settings variable. + */ + virtual void Reset() = 0; + /** * Reads the value from the JSON object. If the read value is out of range, * the value is set to the minimum or maximum. If the read value fails diff --git a/scwx-qt/source/scwx/qt/ui/edit_line_dialog.cpp b/scwx-qt/source/scwx/qt/ui/edit_line_dialog.cpp new file mode 100644 index 00000000..5202e732 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/edit_line_dialog.cpp @@ -0,0 +1,316 @@ +#include "edit_line_dialog.hpp" +#include "ui_edit_line_dialog.h" + +#include +#include +#include + +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace ui +{ + +static const std::string logPrefix_ = "scwx::qt::ui::edit_line_dialog"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +class EditLineDialog::Impl +{ +public: + struct EditComponent + { + void ConnectSignals(EditLineDialog* self) + { + QObject::connect(colorLineEdit_, + &QLineEdit::textEdited, + self, + [=, this](const QString& text) + { + boost::gil::rgba8_pixel_t color = + util::color::ToRgba8PixelT(text.toStdString()); + self->p->set_color(*this, color, false); + }); + + QObject::connect(colorButton_, + &QAbstractButton::clicked, + self, + [=, this]() { self->p->ShowColorDialog(*this); }); + + QObject::connect(widthSpinBox_, + &QSpinBox::valueChanged, + self, + [=, this](int width) + { self->p->set_width(*this, width); }); + } + + boost::gil::rgba8_pixel_t color_; + std::size_t width_; + QFrame* colorFrame_ {nullptr}; + QLineEdit* colorLineEdit_ {nullptr}; + QToolButton* colorButton_ {nullptr}; + QSpinBox* widthSpinBox_ {nullptr}; + }; + + explicit Impl(EditLineDialog* self) : + self_ {self}, lineLabel_ {new LineLabel(self)} + { + } + ~Impl() = default; + + void SetDefaults(); + void ShowColorDialog(EditComponent& component); + void UpdateLineLabel(); + + void set_color(EditComponent& component, + boost::gil::rgba8_pixel_t color, + bool updateLineEdit = true); + void set_width(EditComponent& component, std::size_t width); + + static void SetBackgroundColor(const std::string& value, QFrame* frame); + + EditLineDialog* self_; + + LineLabel* lineLabel_; + + boost::gil::rgba8_pixel_t defaultBorderColor_ {0, 0, 0, 255}; + boost::gil::rgba8_pixel_t defaultHighlightColor_ {0, 0, 0, 0}; + boost::gil::rgba8_pixel_t defaultLineColor_ {255, 255, 255, 255}; + + std::size_t defaultBorderWidth_ {1u}; + std::size_t defaultHighlightWidth_ {0u}; + std::size_t defaultLineWidth_ {3u}; + + EditComponent borderComponent_ {}; + EditComponent highlightComponent_ {}; + EditComponent lineComponent_ {}; +}; + +EditLineDialog::EditLineDialog(QWidget* parent) : + QDialog(parent), + p {std::make_unique(this)}, + ui(new Ui::EditLineDialog) +{ + ui->setupUi(this); + + p->borderComponent_.colorFrame_ = ui->borderColorFrame; + p->borderComponent_.colorLineEdit_ = ui->borderColorLineEdit; + p->borderComponent_.colorButton_ = ui->borderColorButton; + p->borderComponent_.widthSpinBox_ = ui->borderWidthSpinBox; + + p->highlightComponent_.colorFrame_ = ui->highlightColorFrame; + p->highlightComponent_.colorLineEdit_ = ui->highlightColorLineEdit; + p->highlightComponent_.colorButton_ = ui->highlightColorButton; + p->highlightComponent_.widthSpinBox_ = ui->highlightWidthSpinBox; + + p->lineComponent_.colorFrame_ = ui->lineColorFrame; + p->lineComponent_.colorLineEdit_ = ui->lineColorLineEdit; + p->lineComponent_.colorButton_ = ui->lineColorButton; + p->lineComponent_.widthSpinBox_ = ui->lineWidthSpinBox; + + p->SetDefaults(); + + p->lineLabel_->setMinimumWidth(72); + + QHBoxLayout* lineLabelContainerLayout = + static_cast(ui->lineLabelContainer->layout()); + lineLabelContainerLayout->insertWidget(1, p->lineLabel_); + + p->borderComponent_.ConnectSignals(this); + p->highlightComponent_.ConnectSignals(this); + p->lineComponent_.ConnectSignals(this); + + QObject::connect(ui->buttonBox, + &QDialogButtonBox::clicked, + this, + [this](QAbstractButton* button) + { + QDialogButtonBox::ButtonRole role = + ui->buttonBox->buttonRole(button); + + switch (role) + { + case QDialogButtonBox::ButtonRole::ResetRole: // Reset + p->SetDefaults(); + break; + + default: + break; + } + }); +} + +EditLineDialog::~EditLineDialog() +{ + delete ui; +} + +boost::gil::rgba8_pixel_t EditLineDialog::border_color() const +{ + return p->borderComponent_.color_; +} + +boost::gil::rgba8_pixel_t EditLineDialog::highlight_color() const +{ + return p->highlightComponent_.color_; +} + +boost::gil::rgba8_pixel_t EditLineDialog::line_color() const +{ + return p->lineComponent_.color_; +} + +std::size_t EditLineDialog::border_width() const +{ + return p->borderComponent_.width_; +} + +std::size_t EditLineDialog::highlight_width() const +{ + return p->highlightComponent_.width_; +} + +std::size_t EditLineDialog::line_width() const +{ + return p->lineComponent_.width_; +} + +void EditLineDialog::set_border_color(boost::gil::rgba8_pixel_t color) +{ + p->set_color(p->borderComponent_, color); +} + +void EditLineDialog::set_highlight_color(boost::gil::rgba8_pixel_t color) +{ + p->set_color(p->highlightComponent_, color); +} + +void EditLineDialog::set_line_color(boost::gil::rgba8_pixel_t color) +{ + p->set_color(p->lineComponent_, color); +} + +void EditLineDialog::set_border_width(std::size_t width) +{ + p->set_width(p->borderComponent_, width); +} + +void EditLineDialog::set_highlight_width(std::size_t width) +{ + p->set_width(p->highlightComponent_, width); +} + +void EditLineDialog::set_line_width(std::size_t width) +{ + p->set_width(p->lineComponent_, width); +} + +void EditLineDialog::Impl::set_color(EditComponent& component, + boost::gil::rgba8_pixel_t color, + bool updateLineEdit) +{ + const std::string argbString {util::color::ToArgbString(color)}; + + component.color_ = color; + SetBackgroundColor(argbString, component.colorFrame_); + + if (updateLineEdit) + { + component.colorLineEdit_->setText(QString::fromStdString(argbString)); + } + + UpdateLineLabel(); +} + +void EditLineDialog::Impl::set_width(EditComponent& component, + std::size_t width) +{ + component.width_ = width; + component.widthSpinBox_->setValue(static_cast(width)); + + UpdateLineLabel(); +} + +void EditLineDialog::Impl::UpdateLineLabel() +{ + lineLabel_->set_border_color(borderComponent_.color_); + lineLabel_->set_highlight_color(highlightComponent_.color_); + lineLabel_->set_line_color(lineComponent_.color_); + + lineLabel_->set_border_width(borderComponent_.width_); + lineLabel_->set_highlight_width(highlightComponent_.width_); + lineLabel_->set_line_width(lineComponent_.width_); +} + +void EditLineDialog::Initialize(boost::gil::rgba8_pixel_t borderColor, + boost::gil::rgba8_pixel_t highlightColor, + boost::gil::rgba8_pixel_t lineColor, + std::size_t borderWidth, + std::size_t highlightWidth, + std::size_t lineWidth) +{ + p->defaultBorderColor_ = borderColor; + p->defaultHighlightColor_ = highlightColor; + p->defaultLineColor_ = lineColor; + + p->defaultBorderWidth_ = borderWidth; + p->defaultHighlightWidth_ = highlightWidth; + p->defaultLineWidth_ = lineWidth; + + p->SetDefaults(); +} + +void EditLineDialog::Impl::SetDefaults() +{ + self_->set_border_color(defaultBorderColor_); + self_->set_highlight_color(defaultHighlightColor_); + self_->set_line_color(defaultLineColor_); + + self_->set_border_width(defaultBorderWidth_); + self_->set_highlight_width(defaultHighlightWidth_); + self_->set_line_width(defaultLineWidth_); +} + +void EditLineDialog::Impl::ShowColorDialog(EditComponent& component) +{ + QColorDialog* dialog = new QColorDialog(self_); + + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->setOption(QColorDialog::ColorDialogOption::ShowAlphaChannel); + + QColor initialColor(component.colorLineEdit_->text()); + if (initialColor.isValid()) + { + dialog->setCurrentColor(initialColor); + } + + QObject::connect( + dialog, + &QColorDialog::colorSelected, + self_, + [this, &component](const QColor& qColor) + { + QString colorName = qColor.name(QColor::NameFormat::HexArgb); + boost::gil::rgba8_pixel_t color = + util::color::ToRgba8PixelT(colorName.toStdString()); + + logger_->info("Selected color: {}", colorName.toStdString()); + set_color(component, color); + }); + + dialog->open(); +} + +void EditLineDialog::Impl::SetBackgroundColor(const std::string& value, + QFrame* frame) +{ + frame->setStyleSheet( + QString::fromStdString(fmt::format("background-color: {}", value))); +} + +} // namespace ui +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/edit_line_dialog.hpp b/scwx-qt/source/scwx/qt/ui/edit_line_dialog.hpp new file mode 100644 index 00000000..2f8ea3ce --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/edit_line_dialog.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include + +#include + +namespace Ui +{ +class EditLineDialog; +} + +namespace scwx +{ +namespace qt +{ +namespace ui +{ + +class EditLineDialog : public QDialog +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(EditLineDialog) + +public: + explicit EditLineDialog(QWidget* parent = nullptr); + ~EditLineDialog(); + + boost::gil::rgba8_pixel_t border_color() const; + boost::gil::rgba8_pixel_t highlight_color() const; + boost::gil::rgba8_pixel_t line_color() const; + + std::size_t border_width() const; + std::size_t highlight_width() const; + std::size_t line_width() const; + + void set_border_color(boost::gil::rgba8_pixel_t color); + void set_highlight_color(boost::gil::rgba8_pixel_t color); + void set_line_color(boost::gil::rgba8_pixel_t color); + + void set_border_width(std::size_t width); + void set_highlight_width(std::size_t width); + void set_line_width(std::size_t width); + + void Initialize(boost::gil::rgba8_pixel_t borderColor, + boost::gil::rgba8_pixel_t highlightColor, + boost::gil::rgba8_pixel_t lineColor, + std::size_t borderWidth, + std::size_t highlightWidth, + std::size_t lineWidth); + +private: + class Impl; + std::unique_ptr p; + Ui::EditLineDialog* ui; +}; + +} // namespace ui +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/edit_line_dialog.ui b/scwx-qt/source/scwx/qt/ui/edit_line_dialog.ui new file mode 100644 index 00000000..bc133bf9 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/edit_line_dialog.ui @@ -0,0 +1,306 @@ + + + EditLineDialog + + + + 0 + 0 + 350 + 225 + + + + Edit Line + + + + + + + true + + + + Component + + + + + + + #ff000000 + + + + + + + 0 + + + 9 + + + + + + + ... + + + + :/res/icons/font-awesome-6/palette-solid.svg:/res/icons/font-awesome-6/palette-solid.svg + + + + + + + ... + + + + :/res/icons/font-awesome-6/palette-solid.svg:/res/icons/font-awesome-6/palette-solid.svg + + + + + + + Border + + + + + + + #ff000000 + + + + + + + Line + + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok|QDialogButtonBox::StandardButton::Reset + + + + + + + + 24 + 24 + + + + QFrame::Shape::Box + + + QFrame::Shadow::Plain + + + + + + + 0 + + + 9 + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + 24 + 24 + + + + QFrame::Shape::Box + + + QFrame::Shadow::Plain + + + + + + + + true + + + + Color + + + + + + + #ff000000 + + + + + + + + 24 + 24 + + + + QFrame::Shape::Box + + + QFrame::Shadow::Plain + + + + + + + + true + + + + Width + + + + + + + 1 + + + 9 + + + + + + + Highlight + + + + + + + ... + + + + :/res/icons/font-awesome-6/palette-solid.svg:/res/icons/font-awesome-6/palette-solid.svg + + + + + + + + 0 + 45 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Orientation::Horizontal + + + + + + + Qt::Orientation::Horizontal + + + + + + + + + + + + + + buttonBox + accepted() + EditLineDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + EditLineDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/scwx-qt/source/scwx/qt/ui/line_label.cpp b/scwx-qt/source/scwx/qt/ui/line_label.cpp new file mode 100644 index 00000000..0b2f8f75 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/line_label.cpp @@ -0,0 +1,239 @@ +#include +#include +#include + +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace ui +{ + +static const std::string logPrefix_ = "scwx::qt::ui::line_label"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +class LineLabel::Impl +{ +public: + explicit Impl(LineLabel* self) : self_ {self} {}; + ~Impl() = default; + + QImage GenerateImage() const; + void UpdateLineLabel(const settings::LineSettings& lineSettings); + + LineLabel* self_; + + std::size_t borderWidth_ {1}; + std::size_t highlightWidth_ {1}; + std::size_t lineWidth_ {3}; + + boost::gil::rgba8_pixel_t borderColor_ {0, 0, 0, 255}; + boost::gil::rgba8_pixel_t highlightColor_ {255, 255, 0, 255}; + boost::gil::rgba8_pixel_t lineColor_ {0, 0, 255, 255}; + + QPixmap pixmap_ {}; + bool pixmapDirty_ {true}; + + boost::signals2::scoped_connection settingsStaged_ {}; +}; + +LineLabel::LineLabel(QWidget* parent) : + QFrame(parent), p {std::make_unique(this)} +{ +} + +LineLabel::~LineLabel() {} + +boost::gil::rgba8_pixel_t LineLabel::border_color() const +{ + return p->borderColor_; +} + +boost::gil::rgba8_pixel_t LineLabel::highlight_color() const +{ + return p->highlightColor_; +} + +boost::gil::rgba8_pixel_t LineLabel::line_color() const +{ + return p->lineColor_; +} + +std::size_t LineLabel::border_width() const +{ + return p->borderWidth_; +} + +std::size_t LineLabel::highlight_width() const +{ + return p->highlightWidth_; +} + +std::size_t LineLabel::line_width() const +{ + return p->lineWidth_; +} + +void LineLabel::set_border_width(std::size_t width) +{ + p->borderWidth_ = width; + p->pixmapDirty_ = true; + updateGeometry(); + update(); +} + +void LineLabel::set_highlight_width(std::size_t width) +{ + p->highlightWidth_ = width; + p->pixmapDirty_ = true; + updateGeometry(); + update(); +} + +void LineLabel::set_line_width(std::size_t width) +{ + p->lineWidth_ = width; + p->pixmapDirty_ = true; + updateGeometry(); + update(); +} + +void LineLabel::set_border_color(boost::gil::rgba8_pixel_t color) +{ + p->borderColor_ = color; + p->pixmapDirty_ = true; + update(); +} + +void LineLabel::set_highlight_color(boost::gil::rgba8_pixel_t color) +{ + p->highlightColor_ = color; + p->pixmapDirty_ = true; + update(); +} + +void LineLabel::set_line_color(boost::gil::rgba8_pixel_t color) +{ + p->lineColor_ = color; + p->pixmapDirty_ = true; + update(); +} + +void LineLabel::set_line_settings(settings::LineSettings& lineSettings) +{ + p->settingsStaged_ = lineSettings.staged_signal().connect( + [this, &lineSettings]() { p->UpdateLineLabel(lineSettings); }); + + p->UpdateLineLabel(lineSettings); +} + +void LineLabel::Impl::UpdateLineLabel( + const settings::LineSettings& lineSettings) +{ + self_->set_border_color(util::color::ToRgba8PixelT( + lineSettings.border_color().GetStagedOrValue())); + self_->set_highlight_color(util::color::ToRgba8PixelT( + lineSettings.highlight_color().GetStagedOrValue())); + self_->set_line_color( + util::color::ToRgba8PixelT(lineSettings.line_color().GetStagedOrValue())); + + self_->set_border_width(lineSettings.border_width().GetStagedOrValue()); + self_->set_highlight_width( + lineSettings.highlight_width().GetStagedOrValue()); + self_->set_line_width(lineSettings.line_width().GetStagedOrValue()); +} + +QSize LineLabel::minimumSizeHint() const +{ + return sizeHint(); +} + +QSize LineLabel::sizeHint() const +{ + QMargins margins = contentsMargins(); + + const std::size_t width = 1; + const std::size_t height = + (p->borderWidth_ + p->highlightWidth_) * 2 + p->lineWidth_; + + return QSize(static_cast(width) + margins.left() + margins.right(), + static_cast(height) + margins.top() + margins.bottom()); +} + +void LineLabel::paintEvent(QPaintEvent* e) +{ + logger_->trace("paintEvent"); + + QFrame::paintEvent(e); + + if (p->pixmapDirty_) + { + QImage image = p->GenerateImage(); + p->pixmap_ = QPixmap::fromImage(image); + p->pixmapDirty_ = false; + } + + // Don't stretch the line pixmap vertically + QRect rect = contentsRect(); + if (rect.height() > p->pixmap_.height()) + { + int dy = rect.height() - p->pixmap_.height(); + int dy1 = dy / 2; + int dy2 = dy - dy1; + rect.adjust(0, dy1, 0, -dy2); + } + + QPainter painter(this); + painter.drawPixmap(rect, p->pixmap_); +} + +QImage LineLabel::Impl::GenerateImage() const +{ + const QRgb borderRgba = qRgba(static_cast(borderColor_[0]), + static_cast(borderColor_[1]), + static_cast(borderColor_[2]), + static_cast(borderColor_[3])); + const QRgb highlightRgba = qRgba(static_cast(highlightColor_[0]), + static_cast(highlightColor_[1]), + static_cast(highlightColor_[2]), + static_cast(highlightColor_[3])); + const QRgb lineRgba = qRgba(static_cast(lineColor_[0]), + static_cast(lineColor_[1]), + static_cast(lineColor_[2]), + static_cast(lineColor_[3])); + + const std::size_t width = 1; + const std::size_t height = (borderWidth_ + highlightWidth_) * 2 + lineWidth_; + + QImage image(static_cast(width), + static_cast(height), + QImage::Format::Format_ARGB32); + + std::size_t y = 0; + for (std::size_t i = 0; i < borderWidth_; ++i, ++y) + { + image.setPixel(0, static_cast(y), borderRgba); + image.setPixel(0, static_cast(height - 1 - y), borderRgba); + } + + for (std::size_t i = 0; i < highlightWidth_; ++i, ++y) + { + image.setPixel(0, static_cast(y), highlightRgba); + image.setPixel(0, static_cast(height - 1 - y), highlightRgba); + } + + for (std::size_t i = 0; i < lineWidth_; ++i, ++y) + { + image.setPixel(0, static_cast(y), lineRgba); + image.setPixel(0, static_cast(height - 1 - y), lineRgba); + } + + return image; +} + +} // namespace ui +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/line_label.hpp b/scwx-qt/source/scwx/qt/ui/line_label.hpp new file mode 100644 index 00000000..b746a98e --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/line_label.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include + +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace ui +{ + +class LineLabel : public QFrame +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(LineLabel) + +public: + explicit LineLabel(QWidget* parent = nullptr); + ~LineLabel(); + + boost::gil::rgba8_pixel_t border_color() const; + boost::gil::rgba8_pixel_t highlight_color() const; + boost::gil::rgba8_pixel_t line_color() const; + + std::size_t border_width() const; + std::size_t highlight_width() const; + std::size_t line_width() const; + + void set_border_color(boost::gil::rgba8_pixel_t color); + void set_highlight_color(boost::gil::rgba8_pixel_t color); + void set_line_color(boost::gil::rgba8_pixel_t color); + + void set_border_width(std::size_t width); + void set_highlight_width(std::size_t width); + void set_line_width(std::size_t width); + + void set_line_settings(settings::LineSettings& lineSettings); + +protected: + QSize minimumSizeHint() const override; + QSize sizeHint() const override; + void paintEvent(QPaintEvent* e) override; + +private: + class Impl; + std::unique_ptr p; +}; + +} // namespace ui +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/settings/alert_palette_settings_widget.cpp b/scwx-qt/source/scwx/qt/ui/settings/alert_palette_settings_widget.cpp new file mode 100644 index 00000000..a3ae4642 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/settings/alert_palette_settings_widget.cpp @@ -0,0 +1,292 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace ui +{ + +static const std::string logPrefix_ = + "scwx::qt::ui::settings::alert_palette_settings_widget"; + +class AlertPaletteSettingsWidget::Impl +{ +public: + explicit Impl(AlertPaletteSettingsWidget* self) : + self_ {self}, + phenomenonPagesWidget_ {new QStackedWidget(self)}, + phenomenonListView_ {new QListWidget(self)}, + editLineDialog_ {new EditLineDialog(self)} + { + SetupUi(); + ConnectSignals(); + } + ~Impl() {}; + + void AddPhenomenonLine(const std::string& name, + settings::LineSettings& lineSettings, + QGridLayout* layout, + int row); + QWidget* CreateStackedWidgetPage(awips::Phenomenon phenomenon); + void ConnectSignals(); + void SelectPhenomenon(awips::Phenomenon phenomenon); + void SetupUi(); + + AlertPaletteSettingsWidget* self_; + + QStackedWidget* phenomenonPagesWidget_; + QListWidget* phenomenonListView_; + + EditLineDialog* editLineDialog_; + settings::LineSettings* activeLineSettings_ {nullptr}; + + boost::unordered_flat_map phenomenonPages_ {}; + + std::vector connections_ {}; +}; + +AlertPaletteSettingsWidget::AlertPaletteSettingsWidget(QWidget* parent) : + SettingsPageWidget(parent), p {std::make_shared(this)} +{ +} + +AlertPaletteSettingsWidget::~AlertPaletteSettingsWidget() = default; + +void AlertPaletteSettingsWidget::Impl::SetupUi() +{ + // Setup phenomenon index pane + QLabel* phenomenonLabel = new QLabel(tr("Phenomenon:"), self_); + phenomenonPagesWidget_->setSizePolicy(QSizePolicy::Policy::MinimumExpanding, + QSizePolicy::Policy::Preferred); + + // Setup stacked widget + for (auto& phenomenon : settings::PaletteSettings::alert_phenomena()) + { + QWidget* phenomenonWidget = CreateStackedWidgetPage(phenomenon); + phenomenonPagesWidget_->addWidget(phenomenonWidget); + + phenomenonPages_.insert_or_assign(phenomenon, phenomenonWidget); + + phenomenonListView_->addItem( + QString::fromStdString(awips::GetPhenomenonText(phenomenon))); + } + + phenomenonListView_->setCurrentRow(0); + + // Create phenomenon index pane layout + QVBoxLayout* phenomenonIndexLayout = new QVBoxLayout(self_); + phenomenonIndexLayout->addWidget(phenomenonLabel); + phenomenonIndexLayout->addWidget(phenomenonListView_); + + QWidget* phenomenonIndexPane = new QWidget(self_); + phenomenonIndexPane->setLayout(phenomenonIndexLayout); + + // Create primary widget layout + QGridLayout* gridLayout = new QGridLayout(self_); + gridLayout->setContentsMargins(0, 0, 0, 0); + gridLayout->addWidget(phenomenonIndexPane, 0, 0); + gridLayout->addWidget(phenomenonPagesWidget_, 0, 1); + + QSpacerItem* spacer = + new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding); + gridLayout->addItem(spacer, 1, 0); + + self_->setLayout(gridLayout); +} + +void AlertPaletteSettingsWidget::Impl::ConnectSignals() +{ + connect( + phenomenonListView_->selectionModel(), + &QItemSelectionModel::selectionChanged, + self_, + [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; + } + + if (selected.size() > 0) + { + QModelIndex selectedIndex = selected[0].indexes()[0]; + QVariant variantData = + phenomenonListView_->model()->data(selectedIndex); + if (variantData.typeId() == QMetaType::QString) + { + awips::Phenomenon phenomenon = awips::GetPhenomenonFromText( + variantData.toString().toStdString()); + SelectPhenomenon(phenomenon); + } + } + }); + + connect(editLineDialog_, + &EditLineDialog::accepted, + self_, + [this]() + { + // If the active line label was set + if (activeLineSettings_ != nullptr) + { + // Update the active line settings with selected line settings + activeLineSettings_->StageValues( + editLineDialog_->border_color(), + editLineDialog_->highlight_color(), + editLineDialog_->line_color(), + editLineDialog_->border_width(), + editLineDialog_->highlight_width(), + editLineDialog_->line_width()); + + // Reset the active line settings + activeLineSettings_ = nullptr; + } + }); +} + +void AlertPaletteSettingsWidget::Impl::SelectPhenomenon( + awips::Phenomenon phenomenon) +{ + auto it = phenomenonPages_.find(phenomenon); + if (it != phenomenonPages_.cend()) + { + phenomenonPagesWidget_->setCurrentWidget(it->second); + } +} + +QWidget* AlertPaletteSettingsWidget::Impl::CreateStackedWidgetPage( + awips::Phenomenon phenomenon) +{ + QWidget* page = new QWidget(self_); + QGridLayout* gridLayout = new QGridLayout(self_); + page->setLayout(gridLayout); + + const auto& impactBasedWarningInfo = + awips::ibw::GetImpactBasedWarningInfo(phenomenon); + + auto& alertPalette = + settings::PaletteSettings::Instance().alert_palette(phenomenon); + + int row = 0; + + // Add a blank label to align left and right widgets + gridLayout->addWidget(new QLabel(self_), row++, 0); + + AddPhenomenonLine( + "Active", + alertPalette.threat_category(awips::ibw::ThreatCategory::Base), + gridLayout, + row++); + + if (impactBasedWarningInfo.hasObservedTag_) + { + AddPhenomenonLine("Observed", alertPalette.observed(), gridLayout, row++); + } + + if (impactBasedWarningInfo.hasTornadoPossibleTag_) + { + AddPhenomenonLine("Tornado Possible", + alertPalette.tornado_possible(), + gridLayout, + row++); + } + + for (auto& category : impactBasedWarningInfo.threatCategories_) + { + if (category == awips::ibw::ThreatCategory::Base) + { + continue; + } + + AddPhenomenonLine(awips::ibw::GetThreatCategoryName(category), + alertPalette.threat_category(category), + gridLayout, + row++); + } + + AddPhenomenonLine("Inactive", alertPalette.inactive(), gridLayout, row++); + + QSpacerItem* spacer = + new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding); + gridLayout->addItem(spacer, row, 0); + + return page; +} + +void AlertPaletteSettingsWidget::Impl::AddPhenomenonLine( + const std::string& name, + settings::LineSettings& lineSettings, + QGridLayout* layout, + int row) +{ + QToolButton* toolButton = new QToolButton(self_); + toolButton->setText(tr("...")); + + LineLabel* lineLabel = new LineLabel(self_); + lineLabel->set_line_settings(lineSettings); + + QToolButton* resetButton = new QToolButton(self_); + resetButton->setIcon( + QIcon {":/res/icons/font-awesome-6/rotate-left-solid.svg"}); + resetButton->setVisible(!lineSettings.IsDefaultStaged()); + + layout->addWidget(new QLabel(tr(name.c_str()), self_), row, 0); + layout->addWidget(lineLabel, row, 1); + layout->addWidget(toolButton, row, 2); + layout->addWidget(resetButton, row, 3); + + self_->AddSettingsCategory(&lineSettings); + + connect(toolButton, + &QAbstractButton::clicked, + self_, + [this, lineLabel, &lineSettings]() + { + // Set the active line label for when the dialog is finished + activeLineSettings_ = &lineSettings; + + // Initialize dialog with current line settings + editLineDialog_->Initialize(lineLabel->border_color(), + lineLabel->highlight_color(), + lineLabel->line_color(), + lineLabel->border_width(), + lineLabel->highlight_width(), + lineLabel->line_width()); + + // Show the dialog + editLineDialog_->show(); + }); + + connect(resetButton, + &QAbstractButton::clicked, + self_, + [&lineSettings]() { lineSettings.StageDefaults(); }); + + connections_.emplace_back(lineSettings.staged_signal().connect( + [resetButton, &lineSettings]() + { resetButton->setVisible(!lineSettings.IsDefaultStaged()); })); +} + +} // namespace ui +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/settings/alert_palette_settings_widget.hpp b/scwx-qt/source/scwx/qt/ui/settings/alert_palette_settings_widget.hpp new file mode 100644 index 00000000..45f03e36 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/settings/alert_palette_settings_widget.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace ui +{ + +class AlertPaletteSettingsWidget : public SettingsPageWidget +{ + Q_OBJECT + +public: + explicit AlertPaletteSettingsWidget(QWidget* parent = nullptr); + ~AlertPaletteSettingsWidget(); + +private: + class Impl; + std::shared_ptr p; +}; + +} // namespace ui +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/settings/settings_page_widget.cpp b/scwx-qt/source/scwx/qt/ui/settings/settings_page_widget.cpp index d174fbbd..41c43817 100644 --- a/scwx-qt/source/scwx/qt/ui/settings/settings_page_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/settings/settings_page_widget.cpp @@ -19,6 +19,7 @@ public: explicit Impl() {} ~Impl() = default; + std::vector categories_; std::vector settings_; }; @@ -29,6 +30,12 @@ SettingsPageWidget::SettingsPageWidget(QWidget* parent) : SettingsPageWidget::~SettingsPageWidget() = default; +void SettingsPageWidget::AddSettingsCategory( + settings::SettingsCategory* category) +{ + p->categories_.push_back(category); +} + void SettingsPageWidget::AddSettingsInterface( settings::SettingsInterfaceBase* setting) { @@ -39,6 +46,11 @@ bool SettingsPageWidget::CommitChanges() { bool committed = false; + for (auto& category : p->categories_) + { + committed |= category->Commit(); + } + for (auto& setting : p->settings_) { committed |= setting->Commit(); @@ -49,6 +61,11 @@ bool SettingsPageWidget::CommitChanges() void SettingsPageWidget::DiscardChanges() { + for (auto& category : p->categories_) + { + category->Reset(); + } + for (auto& setting : p->settings_) { setting->Reset(); 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 228badd6..39d8647a 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 @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -61,6 +62,15 @@ public: protected: void AddSettingsInterface(settings::SettingsInterfaceBase* setting); + /** + * Commits and resets all settings within a category upon page commit or + * reset. The use of SettingsInterface is preferred, as it allows the binding + * of widgets to these actions. + * + * @param [in] category Settings category + */ + void AddSettingsCategory(settings::SettingsCategory* category); + private: class Impl; std::shared_ptr p; diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp index cd72b4ee..c35607a9 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp @@ -23,11 +23,12 @@ #include #include #include -#include #include #include +#include #include #include +#include #include #include #include @@ -181,7 +182,6 @@ public: void SetupTextTab(); void SetupHotkeysTab(); - void ShowColorDialog(QLineEdit* lineEdit); void UpdateRadarDialogLocation(const std::string& id); void UpdateAlertRadarDialogLocation(const std::string& id); @@ -203,8 +203,7 @@ public: const std::string& value, QLabel* imageLabel); static std::string - RadarSiteLabel(std::shared_ptr& radarSite); - static void SetBackgroundColor(const std::string& value, QFrame* frame); + RadarSiteLabel(std::shared_ptr& radarSite); SettingsDialog* self_; RadarSiteDialog* radarSiteDialog_; @@ -224,6 +223,7 @@ public: manager::PositionManager::Instance()}; std::vector settingsPages_ {}; + AlertPaletteSettingsWidget* alertPaletteSettingsWidget_ {}; HotkeySettingsWidget* hotkeySettingsWidget_ {}; UnitSettingsWidget* unitSettingsWidget_ {}; @@ -252,12 +252,6 @@ public: std::unordered_map> colorTables_ {}; - std::unordered_map> - activeAlertColors_ {}; - std::unordered_map> - inactiveAlertColors_ {}; settings::SettingsInterface alertAudioSoundFile_ {}; settings::SettingsInterface alertAudioLocationMethod_ {}; @@ -358,24 +352,23 @@ void SettingsDialogImpl::ConnectSignals() self_, [this]() { alertAudioRadarSiteDialog_->show(); }); - QObject::connect(alertAudioRadarSiteDialog_, - &RadarSiteDialog::accepted, - self_, - [this]() - { - std::string id = - alertAudioRadarSiteDialog_->radar_site(); + QObject::connect( + alertAudioRadarSiteDialog_, + &RadarSiteDialog::accepted, + self_, + [this]() + { + std::string id = alertAudioRadarSiteDialog_->radar_site(); - std::shared_ptr radarSite = - config::RadarSite::Get(id); + std::shared_ptr radarSite = + config::RadarSite::Get(id); - if (radarSite != nullptr) - { - self_->ui->alertAudioRadarSiteComboBox - ->setCurrentText(QString::fromStdString( - RadarSiteLabel(radarSite))); - } - }); + if (radarSite != nullptr) + { + self_->ui->alertAudioRadarSiteComboBox->setCurrentText( + QString::fromStdString(RadarSiteLabel(radarSite))); + } + }); QObject::connect(self_->ui->gpsSourceSelectButton, &QAbstractButton::clicked, @@ -803,123 +796,14 @@ void SettingsDialogImpl::SetupPalettesColorTablesTab() void SettingsDialogImpl::SetupPalettesAlertsTab() { - settings::PaletteSettings& paletteSettings = - settings::PaletteSettings::Instance(); - // Palettes > Alerts - QGridLayout* alertsLayout = - reinterpret_cast(self_->ui->alertsFrame->layout()); + QVBoxLayout* layout = new QVBoxLayout(self_->ui->alertsPalette); - QLabel* phenomenonLabel = new QLabel(QObject::tr("Phenomenon"), self_); - QLabel* activeLabel = new QLabel(QObject::tr("Active"), self_); - QLabel* inactiveLabel = new QLabel(QObject::tr("Inactive"), self_); + alertPaletteSettingsWidget_ = + new AlertPaletteSettingsWidget(self_->ui->hotkeys); + layout->addWidget(alertPaletteSettingsWidget_); - QFont boldFont; - boldFont.setBold(true); - phenomenonLabel->setFont(boldFont); - activeLabel->setFont(boldFont); - inactiveLabel->setFont(boldFont); - - alertsLayout->addWidget(phenomenonLabel, 0, 0); - alertsLayout->addWidget(activeLabel, 0, 1, 1, 4); - alertsLayout->addWidget(inactiveLabel, 0, 5, 1, 4); - - auto& alertPhenomena = settings::PaletteSettings::alert_phenomena(); - - activeAlertColors_.reserve(alertPhenomena.size()); - inactiveAlertColors_.reserve(alertPhenomena.size()); - - int alertsRow = 1; - for (auto& phenomenon : alertPhenomena) - { - QFrame* activeFrame = new QFrame(self_); - QFrame* inactiveFrame = new QFrame(self_); - - QLineEdit* activeEdit = new QLineEdit(self_); - QLineEdit* inactiveEdit = new QLineEdit(self_); - - QToolButton* activeButton = new QToolButton(self_); - QToolButton* inactiveButton = new QToolButton(self_); - QToolButton* activeResetButton = new QToolButton(self_); - QToolButton* inactiveResetButton = new QToolButton(self_); - - activeFrame->setMinimumHeight(24); - activeFrame->setMinimumWidth(24); - activeFrame->setFrameShape(QFrame::Shape::Box); - activeFrame->setFrameShadow(QFrame::Shadow::Plain); - inactiveFrame->setMinimumHeight(24); - inactiveFrame->setMinimumWidth(24); - inactiveFrame->setFrameShape(QFrame::Shape::Box); - inactiveFrame->setFrameShadow(QFrame::Shadow::Plain); - - activeButton->setIcon( - QIcon {":/res/icons/font-awesome-6/palette-solid.svg"}); - inactiveButton->setIcon( - QIcon {":/res/icons/font-awesome-6/palette-solid.svg"}); - activeResetButton->setIcon( - QIcon {":/res/icons/font-awesome-6/rotate-left-solid.svg"}); - inactiveResetButton->setIcon( - QIcon {":/res/icons/font-awesome-6/rotate-left-solid.svg"}); - - alertsLayout->addWidget( - new QLabel(QObject::tr(awips::GetPhenomenonText(phenomenon).c_str()), - self_), - alertsRow, - 0); - alertsLayout->addWidget(activeFrame, alertsRow, 1); - alertsLayout->addWidget(activeEdit, alertsRow, 2); - alertsLayout->addWidget(activeButton, alertsRow, 3); - alertsLayout->addWidget(activeResetButton, alertsRow, 4); - alertsLayout->addWidget(inactiveFrame, alertsRow, 5); - alertsLayout->addWidget(inactiveEdit, alertsRow, 6); - alertsLayout->addWidget(inactiveButton, alertsRow, 7); - alertsLayout->addWidget(inactiveResetButton, alertsRow, 8); - ++alertsRow; - - // Create settings interface - auto activeResult = activeAlertColors_.emplace( - phenomenon, settings::SettingsInterface {}); - auto inactiveResult = inactiveAlertColors_.emplace( - phenomenon, settings::SettingsInterface {}); - auto& activeColor = activeResult.first->second; - auto& inactiveColor = inactiveResult.first->second; - - // Add to settings list - settings_.push_back(&activeColor); - settings_.push_back(&inactiveColor); - - auto& activeSetting = paletteSettings.alert_color(phenomenon, true); - auto& inactiveSetting = paletteSettings.alert_color(phenomenon, false); - - activeColor.SetSettingsVariable(activeSetting); - activeColor.SetEditWidget(activeEdit); - activeColor.SetResetButton(activeResetButton); - - inactiveColor.SetSettingsVariable(inactiveSetting); - inactiveColor.SetEditWidget(inactiveEdit); - inactiveColor.SetResetButton(inactiveResetButton); - - SetBackgroundColor(activeSetting.GetValue(), activeFrame); - SetBackgroundColor(inactiveSetting.GetValue(), inactiveFrame); - - activeSetting.RegisterValueStagedCallback( - [activeFrame](const std::string& value) - { SetBackgroundColor(value, activeFrame); }); - inactiveSetting.RegisterValueStagedCallback( - [inactiveFrame](const std::string& value) - { SetBackgroundColor(value, inactiveFrame); }); - - QObject::connect(activeButton, - &QAbstractButton::clicked, - self_, - [=, this]() - { ShowColorDialog(activeEdit); }); - QObject::connect(inactiveButton, - &QAbstractButton::clicked, - self_, - [=, this]() - { ShowColorDialog(inactiveEdit); }); - } + settingsPages_.push_back(alertPaletteSettingsWidget_); } void SettingsDialogImpl::SetupUnitsTab() @@ -953,8 +837,7 @@ void SettingsDialogImpl::SetupAudioTab() locationMethod == types::LocationMethod::RadarSite; bool countyEntryEnabled = locationMethod == types::LocationMethod::County; - bool wfoEntryEnabled = - locationMethod == types::LocationMethod::WFO; + bool wfoEntryEnabled = locationMethod == types::LocationMethod::WFO; self_->ui->alertAudioLatitudeSpinBox->setEnabled( coordinateEntryEnabled); @@ -972,10 +855,8 @@ void SettingsDialogImpl::SetupAudioTab() self_->ui->resetAlertAudioRadarSiteButton->setEnabled( radarSiteEntryEnable); - self_->ui->alertAudioRadiusSpinBox->setEnabled( - radiusEntryEnable); - self_->ui->resetAlertAudioRadiusButton->setEnabled( - radiusEntryEnable); + self_->ui->alertAudioRadiusSpinBox->setEnabled(radiusEntryEnable); + self_->ui->resetAlertAudioRadiusButton->setEnabled(radiusEntryEnable); self_->ui->alertAudioCountyLineEdit->setEnabled(countyEntryEnabled); self_->ui->alertAudioCountySelectButton->setEnabled( @@ -1091,8 +972,7 @@ void SettingsDialogImpl::SetupAudioTab() alertAudioRadius_.SetSettingsVariable(audioSettings.alert_radius()); alertAudioRadius_.SetEditWidget(self_->ui->alertAudioRadiusSpinBox); - alertAudioRadius_.SetResetButton( - self_->ui->resetAlertAudioRadiusButton); + alertAudioRadius_.SetResetButton(self_->ui->resetAlertAudioRadiusButton); alertAudioRadius_.SetUnitLabel(self_->ui->alertAudioRadiusUnitsLabel); auto alertAudioRadiusUpdateUnits = [this](const std::string& newValue) { @@ -1206,14 +1086,10 @@ void SettingsDialogImpl::SetupAudioTab() alertAudioCounty_.SetEditWidget(self_->ui->alertAudioCountyLineEdit); alertAudioCounty_.SetResetButton(self_->ui->resetAlertAudioCountyButton); - QObject::connect( - self_->ui->alertAudioWFOSelectButton, - &QAbstractButton::clicked, - self_, - [this]() - { - wfoDialog_->show(); - }); + QObject::connect(self_->ui->alertAudioWFOSelectButton, + &QAbstractButton::clicked, + self_, + [this]() { wfoDialog_->show(); }); QObject::connect(wfoDialog_, &WFODialog::accepted, self_, @@ -1232,9 +1108,8 @@ void SettingsDialogImpl::SetupAudioTab() self_, [this](const QString& text) { - std::string wfoName = - config::CountyDatabase::GetWFOName( - text.toStdString()); + std::string wfoName = config::CountyDatabase::GetWFOName( + text.toStdString()); self_->ui->alertAudioWFOLabel->setText( QString::fromStdString(wfoName)); }); @@ -1242,7 +1117,6 @@ void SettingsDialogImpl::SetupAudioTab() alertAudioWFO_.SetSettingsVariable(audioSettings.alert_wfo()); alertAudioWFO_.SetEditWidget(self_->ui->alertAudioWFOLineEdit); alertAudioWFO_.SetResetButton(self_->ui->resetAlertAudioWFOButton); - } void SettingsDialogImpl::SetupTextTab() @@ -1384,44 +1258,6 @@ void SettingsDialogImpl::LoadColorTablePreview(const std::string& key, }); } -void SettingsDialogImpl::ShowColorDialog(QLineEdit* lineEdit) -{ - QColorDialog* dialog = new QColorDialog(self_); - - dialog->setAttribute(Qt::WA_DeleteOnClose); - dialog->setOption(QColorDialog::ColorDialogOption::ShowAlphaChannel); - - QColor initialColor(lineEdit->text()); - if (initialColor.isValid()) - { - dialog->setCurrentColor(initialColor); - } - - QObject::connect( - dialog, - &QColorDialog::colorSelected, - self_, - [lineEdit](const QColor& color) - { - QString colorName = color.name(QColor::NameFormat::HexArgb); - - logger_->info("Selected color: {}", colorName.toStdString()); - lineEdit->setText(colorName); - - // setText does not emit the textEdited signal - Q_EMIT lineEdit->textEdited(colorName); - }); - - dialog->open(); -} - -void SettingsDialogImpl::SetBackgroundColor(const std::string& value, - QFrame* frame) -{ - frame->setStyleSheet( - QString::fromStdString(fmt::format("background-color: {}", value))); -} - void SettingsDialogImpl::UpdateRadarDialogLocation(const std::string& id) { std::shared_ptr radarSite = config::RadarSite::Get(id); @@ -1444,8 +1280,6 @@ void SettingsDialogImpl::UpdateAlertRadarDialogLocation(const std::string& id) } } - - QFont SettingsDialogImpl::GetSelectedFont() { std::string fontFamily = fontFamilies_.at(selectedFontCategory_) diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui index 88dacbbc..444e6705 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui @@ -122,7 +122,7 @@ - 3 + 0 @@ -136,8 +136,8 @@ 0 0 - 274 - 691 + 513 + 622 @@ -610,8 +610,8 @@ 0 0 - 98 - 28 + 506 + 383 @@ -634,49 +634,10 @@ - + Alerts - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - Qt::Orientation::Vertical - - - - 20 - 239 - - - - - diff --git a/scwx-qt/source/scwx/qt/util/color.cpp b/scwx-qt/source/scwx/qt/util/color.cpp index 6e193dc9..16060bb9 100644 --- a/scwx-qt/source/scwx/qt/util/color.cpp +++ b/scwx-qt/source/scwx/qt/util/color.cpp @@ -1,6 +1,7 @@ #include #include +#include #include namespace scwx @@ -38,6 +39,12 @@ boost::gil::rgba32f_pixel_t ToRgba32fPixelT(const std::string& argbString) rgba8Pixel[3] / 255.0f}; } +bool ValidateArgbString(const std::string& argbString) +{ + static constexpr LazyRE2 re = {"#[0-9A-Fa-f]{8}"}; + return RE2::FullMatch(argbString, *re); +} + } // namespace color } // namespace util } // namespace qt diff --git a/scwx-qt/source/scwx/qt/util/color.hpp b/scwx-qt/source/scwx/qt/util/color.hpp index 73ca07f1..6d90fe56 100644 --- a/scwx-qt/source/scwx/qt/util/color.hpp +++ b/scwx-qt/source/scwx/qt/util/color.hpp @@ -39,6 +39,15 @@ boost::gil::rgba8_pixel_t ToRgba8PixelT(const std::string& argbString); */ boost::gil::rgba32f_pixel_t ToRgba32fPixelT(const std::string& argbString); +/** + * Validates an ARGB string used by Qt libraries. + * + * @param argbString + * + * @return Validity of ARGB string + */ +bool ValidateArgbString(const std::string& argbString); + } // namespace color } // namespace util } // namespace qt diff --git a/test/data b/test/data index 20a1ca17..40a367ca 160000 --- a/test/data +++ b/test/data @@ -1 +1 @@ -Subproject commit 20a1ca1752499222d33869e37148321936ca6354 +Subproject commit 40a367ca89b5b197353ca58dea547a3e3407c7f3 diff --git a/wxdata/include/scwx/awips/impact_based_warnings.hpp b/wxdata/include/scwx/awips/impact_based_warnings.hpp index a7b22288..3d956893 100644 --- a/wxdata/include/scwx/awips/impact_based_warnings.hpp +++ b/wxdata/include/scwx/awips/impact_based_warnings.hpp @@ -1,11 +1,16 @@ #pragma once +#include + #include +#include namespace scwx { namespace awips { +namespace ibw +{ enum class ThreatCategory : int { @@ -17,8 +22,18 @@ enum class ThreatCategory : int Unknown }; +struct ImpactBasedWarningInfo +{ + bool hasObservedTag_ {false}; + bool hasTornadoPossibleTag_ {false}; + std::vector threatCategories_ {ThreatCategory::Base}; +}; + +const ImpactBasedWarningInfo& GetImpactBasedWarningInfo(Phenomenon phenomenon); + ThreatCategory GetThreatCategory(const std::string& name); const std::string& GetThreatCategoryName(ThreatCategory threatCategory); +} // namespace ibw } // namespace awips } // namespace scwx diff --git a/wxdata/include/scwx/awips/phenomenon.hpp b/wxdata/include/scwx/awips/phenomenon.hpp index 013a9947..234e473a 100644 --- a/wxdata/include/scwx/awips/phenomenon.hpp +++ b/wxdata/include/scwx/awips/phenomenon.hpp @@ -69,6 +69,7 @@ enum class Phenomenon }; Phenomenon GetPhenomenon(const std::string& code); +Phenomenon GetPhenomenonFromText(const std::string& text); const std::string& GetPhenomenonCode(Phenomenon phenomenon); const std::string& GetPhenomenonText(Phenomenon phenomenon); diff --git a/wxdata/include/scwx/awips/text_product_message.hpp b/wxdata/include/scwx/awips/text_product_message.hpp index dec4af09..b043494f 100644 --- a/wxdata/include/scwx/awips/text_product_message.hpp +++ b/wxdata/include/scwx/awips/text_product_message.hpp @@ -64,9 +64,9 @@ struct Segment std::optional codedLocation_ {}; std::optional codedMotion_ {}; - bool observed_ {false}; - ThreatCategory threatCategory_ {ThreatCategory::Base}; - bool tornadoPossible_ {false}; + bool observed_ {false}; + ibw::ThreatCategory threatCategory_ {ibw::ThreatCategory::Base}; + bool tornadoPossible_ {false}; Segment() = default; diff --git a/wxdata/source/scwx/awips/impact_based_warnings.cpp b/wxdata/source/scwx/awips/impact_based_warnings.cpp index 75f04d1e..40a20051 100644 --- a/wxdata/source/scwx/awips/impact_based_warnings.cpp +++ b/wxdata/source/scwx/awips/impact_based_warnings.cpp @@ -4,13 +4,40 @@ #include #include +#include namespace scwx { namespace awips { +namespace ibw +{ -static const std::string logPrefix_ = "scwx::awips::impact_based_warnings"; +static const std::string logPrefix_ = "scwx::awips::ibw::impact_based_warnings"; + +static const boost::unordered_flat_map + impactBasedWarningInfo_ { + {Phenomenon::Marine, + ImpactBasedWarningInfo {.hasTornadoPossibleTag_ = true}}, + {Phenomenon::FlashFlood, + ImpactBasedWarningInfo { + .threatCategories_ {ThreatCategory::Base, + ThreatCategory::Considerable, + ThreatCategory::Catastrophic}}}, + {Phenomenon::SevereThunderstorm, + ImpactBasedWarningInfo { + .hasTornadoPossibleTag_ = true, + .threatCategories_ {ThreatCategory::Base, + ThreatCategory::Considerable, + ThreatCategory::Destructive}}}, + {Phenomenon::SnowSquall, ImpactBasedWarningInfo {}}, + {Phenomenon::Tornado, + ImpactBasedWarningInfo { + .hasObservedTag_ = true, + .threatCategories_ {ThreatCategory::Base, + ThreatCategory::Considerable, + ThreatCategory::Catastrophic}}}, + {Phenomenon::Unknown, ImpactBasedWarningInfo {}}}; static const std::unordered_map threatCategoryName_ {{ThreatCategory::Base, "Base"}, @@ -20,6 +47,16 @@ static const std::unordered_map {ThreatCategory::Catastrophic, "Catastrophic"}, {ThreatCategory::Unknown, "?"}}; +const ImpactBasedWarningInfo& GetImpactBasedWarningInfo(Phenomenon phenomenon) +{ + auto it = impactBasedWarningInfo_.find(phenomenon); + if (it != impactBasedWarningInfo_.cend()) + { + return it->second; + } + return impactBasedWarningInfo_.at(Phenomenon::Unknown); +} + SCWX_GET_ENUM(ThreatCategory, GetThreatCategory, threatCategoryName_) const std::string& GetThreatCategoryName(ThreatCategory threatCategory) @@ -27,5 +64,6 @@ const std::string& GetThreatCategoryName(ThreatCategory threatCategory) return threatCategoryName_.at(threatCategory); } +} // namespace ibw } // namespace awips } // namespace scwx diff --git a/wxdata/source/scwx/awips/phenomenon.cpp b/wxdata/source/scwx/awips/phenomenon.cpp index ee8e549d..c8154509 100644 --- a/wxdata/source/scwx/awips/phenomenon.cpp +++ b/wxdata/source/scwx/awips/phenomenon.cpp @@ -77,64 +77,65 @@ static const PhenomenonCodesBimap phenomenonCodes_ = (Phenomenon::FreezingSpray, "ZY") // (Phenomenon::Unknown, "??"); -static const std::unordered_map phenomenonText_ { - {Phenomenon::AshfallLand, "Ashfall (land)"}, // - {Phenomenon::AirStagnation, "Air Stagnation"}, // - {Phenomenon::BeachHazard, "Beach Hazard"}, // - {Phenomenon::BriskWind, "Brisk Wind"}, // - {Phenomenon::Blizzard, "Blizzard"}, // - {Phenomenon::CoastalFlood, "Coastal Flood"}, // - {Phenomenon::DebrisFlow, "Debris Flow"}, // - {Phenomenon::DustStorm, "Dust Storm"}, // - {Phenomenon::BlowingDust, "Blowing Dust"}, // - {Phenomenon::ExtremeCold, "Extreme Cold"}, // - {Phenomenon::ExcessiveHeat, "Excessive Heat"}, // - {Phenomenon::ExtremeWind, "Extreme Wind"}, // - {Phenomenon::Flood, "Flood"}, // - {Phenomenon::FlashFlood, "Flash Flood"}, // - {Phenomenon::DenseFogLand, "Dense Fog (land)"}, // - {Phenomenon::Flood, "Flood (Forecast Points)"}, // - {Phenomenon::Frost, "Frost"}, // - {Phenomenon::FireWeather, "Fire Weather"}, // - {Phenomenon::Freeze, "Freeze"}, // - {Phenomenon::Gale, "Gale"}, // - {Phenomenon::HurricaneForceWind, "Hurricane Force Wind"}, // - {Phenomenon::Heat, "Heat"}, // - {Phenomenon::Hurricane, "Hurricane"}, // - {Phenomenon::HighWind, "High Wind"}, // - {Phenomenon::Hydrologic, "Hydrologic"}, // - {Phenomenon::HardFreeze, "Hard Freeze"}, // - {Phenomenon::IceStorm, "Ice Storm"}, // - {Phenomenon::LakeEffectSnow, "Lake Effect Snow"}, // - {Phenomenon::LowWater, "Low Water"}, // - {Phenomenon::LakeshoreFlood, "Lakeshore Flood"}, // - {Phenomenon::LakeWind, "Lake Wind"}, // - {Phenomenon::Marine, "Marine"}, // - {Phenomenon::DenseFogMarine, "Dense Fog (marine)"}, // - {Phenomenon::AshfallMarine, "Ashfall (marine)"}, // - {Phenomenon::DenseSmokeMarine, "Dense Smoke (marine)"}, // - {Phenomenon::RipCurrentRisk, "Rip Current Risk"}, // - {Phenomenon::SmallCraft, "Small Craft"}, // - {Phenomenon::HazardousSeas, "Hazardous Seas"}, // - {Phenomenon::DenseSmokeLand, "Dense Smoke (land)"}, // - {Phenomenon::Storm, "Storm"}, // - {Phenomenon::StormSurge, "Storm Surge"}, // - {Phenomenon::SnowSquall, "Snow Squall"}, // - {Phenomenon::HighSurf, "High Surf"}, // - {Phenomenon::SevereThunderstorm, "Severe Thunderstorm"}, // - {Phenomenon::Tornado, "Tornado"}, // - {Phenomenon::TropicalStorm, "Tropical Storm"}, // - {Phenomenon::Tsunami, "Tsunami"}, // - {Phenomenon::Typhoon, "Typhoon"}, // - {Phenomenon::HeavyFreezingSpray, "Heavy Freezing Spray"}, // - {Phenomenon::WindChill, "Wind Chill"}, // - {Phenomenon::Wind, "Wind"}, // - {Phenomenon::WinterStorm, "Winter Storm"}, // - {Phenomenon::WinterWeather, "Winter Weather"}, // - {Phenomenon::FreezingFog, "Freezing Fog"}, // - {Phenomenon::FreezingRain, "Freezing Rain"}, // - {Phenomenon::FreezingSpray, "Freezing Spray"}, // - {Phenomenon::Unknown, "Unknown"}}; +static const PhenomenonCodesBimap phenomenonText_ = + boost::assign::list_of // + (Phenomenon::AshfallLand, "Ashfall (land)") // + (Phenomenon::AirStagnation, "Air Stagnation") // + (Phenomenon::BeachHazard, "Beach Hazard") // + (Phenomenon::BriskWind, "Brisk Wind") // + (Phenomenon::Blizzard, "Blizzard") // + (Phenomenon::CoastalFlood, "Coastal Flood") // + (Phenomenon::DebrisFlow, "Debris Flow") // + (Phenomenon::DustStorm, "Dust Storm") // + (Phenomenon::BlowingDust, "Blowing Dust") // + (Phenomenon::ExtremeCold, "Extreme Cold") // + (Phenomenon::ExcessiveHeat, "Excessive Heat") // + (Phenomenon::ExtremeWind, "Extreme Wind") // + (Phenomenon::Flood, "Flood") // + (Phenomenon::FlashFlood, "Flash Flood") // + (Phenomenon::DenseFogLand, "Dense Fog (land)") // + (Phenomenon::Flood, "Flood (Forecast Points)") // + (Phenomenon::Frost, "Frost") // + (Phenomenon::FireWeather, "Fire Weather") // + (Phenomenon::Freeze, "Freeze") // + (Phenomenon::Gale, "Gale") // + (Phenomenon::HurricaneForceWind, "Hurricane Force Wind") // + (Phenomenon::Heat, "Heat") // + (Phenomenon::Hurricane, "Hurricane") // + (Phenomenon::HighWind, "High Wind") // + (Phenomenon::Hydrologic, "Hydrologic") // + (Phenomenon::HardFreeze, "Hard Freeze") // + (Phenomenon::IceStorm, "Ice Storm") // + (Phenomenon::LakeEffectSnow, "Lake Effect Snow") // + (Phenomenon::LowWater, "Low Water") // + (Phenomenon::LakeshoreFlood, "Lakeshore Flood") // + (Phenomenon::LakeWind, "Lake Wind") // + (Phenomenon::Marine, "Marine") // + (Phenomenon::DenseFogMarine, "Dense Fog (marine)") // + (Phenomenon::AshfallMarine, "Ashfall (marine)") // + (Phenomenon::DenseSmokeMarine, "Dense Smoke (marine)") // + (Phenomenon::RipCurrentRisk, "Rip Current Risk") // + (Phenomenon::SmallCraft, "Small Craft") // + (Phenomenon::HazardousSeas, "Hazardous Seas") // + (Phenomenon::DenseSmokeLand, "Dense Smoke (land)") // + (Phenomenon::Storm, "Storm") // + (Phenomenon::StormSurge, "Storm Surge") // + (Phenomenon::SnowSquall, "Snow Squall") // + (Phenomenon::HighSurf, "High Surf") // + (Phenomenon::SevereThunderstorm, "Severe Thunderstorm") // + (Phenomenon::Tornado, "Tornado") // + (Phenomenon::TropicalStorm, "Tropical Storm") // + (Phenomenon::Tsunami, "Tsunami") // + (Phenomenon::Typhoon, "Typhoon") // + (Phenomenon::HeavyFreezingSpray, "Heavy Freezing Spray") // + (Phenomenon::WindChill, "Wind Chill") // + (Phenomenon::Wind, "Wind") // + (Phenomenon::WinterStorm, "Winter Storm") // + (Phenomenon::WinterWeather, "Winter Weather") // + (Phenomenon::FreezingFog, "Freezing Fog") // + (Phenomenon::FreezingRain, "Freezing Rain") // + (Phenomenon::FreezingSpray, "Freezing Spray") // + (Phenomenon::Unknown, "Unknown"); Phenomenon GetPhenomenon(const std::string& code) { @@ -154,6 +155,24 @@ Phenomenon GetPhenomenon(const std::string& code) return phenomenon; } +Phenomenon GetPhenomenonFromText(const std::string& text) +{ + Phenomenon phenomenon; + + if (phenomenonText_.right.find(text) != phenomenonText_.right.end()) + { + phenomenon = phenomenonText_.right.at(text); + } + else + { + phenomenon = Phenomenon::Unknown; + + logger_->debug("Unrecognized code: \"{}\"", text); + } + + return phenomenon; +} + const std::string& GetPhenomenonCode(Phenomenon phenomenon) { return phenomenonCodes_.left.at(phenomenon); @@ -161,7 +180,7 @@ const std::string& GetPhenomenonCode(Phenomenon phenomenon) const std::string& GetPhenomenonText(Phenomenon phenomenon) { - return phenomenonText_.at(phenomenon); + return phenomenonText_.left.at(phenomenon); } } // namespace awips diff --git a/wxdata/source/scwx/awips/text_product_message.cpp b/wxdata/source/scwx/awips/text_product_message.cpp index 5128aee8..54ce7e25 100644 --- a/wxdata/source/scwx/awips/text_product_message.cpp +++ b/wxdata/source/scwx/awips/text_product_message.cpp @@ -378,7 +378,7 @@ void ParseCodedInformation(std::shared_ptr segment, segment->tornadoPossible_ = true; } - else if (segment->threatCategory_ == ThreatCategory::Base && + else if (segment->threatCategory_ == ibw::ThreatCategory::Base && (threatTagIt = std::find_if(kThreatCategoryTags.cbegin(), kThreatCategoryTags.cend(), [&it](const std::string& tag) { @@ -389,10 +389,23 @@ void ParseCodedInformation(std::shared_ptr segment, const std::string threatCategoryName = it->substr(threatTagIt->length()); - ThreatCategory threatCategory = GetThreatCategory(threatCategoryName); - if (threatCategory == ThreatCategory::Unknown) + ibw::ThreatCategory threatCategory = + ibw::GetThreatCategory(threatCategoryName); + + switch (threatCategory) { - threatCategory = ThreatCategory::Base; + case ibw::ThreatCategory::Significant: + // "Significant" is no longer an official tag, and has largely been + // replaced with "Considerable". + threatCategory = ibw::ThreatCategory::Considerable; + break; + + case ibw::ThreatCategory::Unknown: + threatCategory = ibw::ThreatCategory::Base; + break; + + default: + break; } segment->threatCategory_ = threatCategory;