diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index a548b1f4..52d26ccc 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -309,10 +309,12 @@ set(UI_UI source/scwx/qt/ui/about_dialog.ui source/scwx/qt/ui/serial_port_dialog.ui source/scwx/qt/ui/update_dialog.ui source/scwx/qt/ui/wfo_dialog.ui) -set(HDR_UI_SETTINGS source/scwx/qt/ui/settings/hotkey_settings_widget.hpp +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/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..f433156d --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/settings/alert_palette_settings_widget.cpp @@ -0,0 +1,239 @@ +#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"; + +struct PhenomenonInfo +{ + bool hasObservedTag_ {false}; + bool hasTornadoPossibleTag_ {false}; + std::vector threatCategories_ { + awips::ThreatCategory::Base}; +}; + +static const boost::unordered_flat_map + phenomenaInfo_ {{awips::Phenomenon::Marine, + PhenomenonInfo {.hasTornadoPossibleTag_ {true}}}, + {awips::Phenomenon::FlashFlood, + PhenomenonInfo {.threatCategories_ { + awips::ThreatCategory::Base, + awips::ThreatCategory::Considerable, + awips::ThreatCategory::Catastrophic}}}, + {awips::Phenomenon::SevereThunderstorm, + PhenomenonInfo {.hasTornadoPossibleTag_ {true}, + .threatCategories_ { + awips::ThreatCategory::Base, + awips::ThreatCategory::Considerable, + awips::ThreatCategory::Destructive}}}, + {awips::Phenomenon::SnowSquall, PhenomenonInfo {}}, + {awips::Phenomenon::Tornado, + PhenomenonInfo {.hasObservedTag_ {true}, + .threatCategories_ { + awips::ThreatCategory::Base, + awips::ThreatCategory::Considerable, + awips::ThreatCategory::Catastrophic}}}}; + +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() = default; + + void + AddPhenomenonLine(const std::string& name, 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_; + + boost::unordered_flat_map phenomenonPages_ {}; +}; + +AlertPaletteSettingsWidget::AlertPaletteSettingsWidget(QWidget* parent) : + SettingsPageWidget(parent), p {std::make_shared(this)} +{ +} + +AlertPaletteSettingsWidget::~AlertPaletteSettingsWidget() = default; + +void AlertPaletteSettingsWidget::Impl::SetupUi() +{ + // Setup primary widget layout + QGridLayout* gridLayout = new QGridLayout(self_); + gridLayout->setContentsMargins(0, 0, 0, 0); + self_->setLayout(gridLayout); + + QWidget* phenomenonIndexPane = new QWidget(self_); + phenomenonPagesWidget_->setSizePolicy(QSizePolicy::Policy::Expanding, + QSizePolicy::Policy::Preferred); + + 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); + + // Setup phenomenon index pane + QVBoxLayout* phenomenonIndexLayout = new QVBoxLayout(self_); + phenomenonIndexPane->setLayout(phenomenonIndexLayout); + + QLabel* phenomenonLabel = new QLabel(tr("Phenomenon:"), self_); + phenomenonListView_->setSizePolicy(QSizePolicy::Policy::Minimum, + QSizePolicy::Policy::Expanding); + + phenomenonIndexLayout->addWidget(phenomenonLabel); + phenomenonIndexLayout->addWidget(phenomenonListView_); + + // Setup stacked widget + auto& paletteSettings = settings::PaletteSettings::Instance(); + Q_UNUSED(paletteSettings); + + 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); +} + +void AlertPaletteSettingsWidget::Impl::ConnectSignals() +{ + QObject::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); + } + } + }); +} + +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& phenomenonInfo = phenomenaInfo_.at(phenomenon); + + int row = 0; + + // Add a blank label to align left and right widgets + gridLayout->addWidget(new QLabel(self_), row++, 0); + + AddPhenomenonLine("Active", gridLayout, row++); + + if (phenomenonInfo.hasObservedTag_) + { + AddPhenomenonLine("Observed", gridLayout, row++); + } + + if (phenomenonInfo.hasTornadoPossibleTag_) + { + AddPhenomenonLine("Tornado Possible", gridLayout, row++); + } + + for (auto& category : phenomenonInfo.threatCategories_) + { + if (category == awips::ThreatCategory::Base) + { + continue; + } + + AddPhenomenonLine( + awips::GetThreatCategoryName(category), gridLayout, row++); + } + + AddPhenomenonLine("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, QGridLayout* layout, int row) +{ + layout->addWidget(new QLabel(tr(name.c_str()), self_), row, 0); + layout->addWidget(new LineLabel(self_), row, 1); + layout->addWidget(new QToolButton(self_), row, 2); +} + +} // 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/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/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