Merge pull request #277 from dpaulat/feature/enhanced-alerts-3

Add Customizable Alert Lines
This commit is contained in:
Dan Paulat 2024-09-30 19:22:47 -05:00 committed by GitHub
commit 50cf49568d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 2813 additions and 427 deletions

View file

@ -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

View file

@ -9,6 +9,7 @@
#include <chrono>
#include <mutex>
#include <ranges>
#include <unordered_map>
#include <unordered_set>
@ -40,6 +41,8 @@ struct AlertTypeHash<std::pair<awips::Phenomenon, bool>>
size_t operator()(const std::pair<awips::Phenomenon, bool>& x) const;
};
static bool IsAlertActive(const std::shared_ptr<const awips::Segment>& 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<MapContext> context,
awips::Phenomenon phenomenon) :
self_ {self},
phenomenon_ {phenomenon},
ibw_ {awips::ibw::GetImpactBasedWarningInfo(phenomenon)},
geoLines_ {{false, std::make_shared<gl::draw::GeoLines>(context)},
{true, std::make_shared<gl::draw::GeoLines>(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<const awips::Segment>& segment,
bool alertActive);
void UpdateLineData();
void AddLine(std::shared_ptr<gl::draw::GeoLines>& geoLines,
std::shared_ptr<gl::draw::GeoLineDrawItem>& di,
const common::Coordinate& p1,
@ -176,6 +185,9 @@ public:
bool enableHover,
boost::container::stable_vector<
std::shared_ptr<gl::draw::GeoLineDrawItem>>& 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<QObject> receiver_ {std::make_unique<QObject>()};
@ -199,12 +212,18 @@ public:
segmentsByLine_;
std::mutex linesMutex_ {};
std::unordered_map<bool, boost::gil::rgba32f_pixel_t> lineColor_;
std::unordered_map<awips::ibw::ThreatCategory, LineData>
threatCategoryLineData_;
LineData observedLineData_ {};
LineData tornadoPossibleLineData_ {};
LineData inactiveLineData_ {};
std::chrono::system_clock::time_point selectedTime_ {};
std::shared_ptr<const gl::draw::GeoLineDrawItem> lastHoverDi_ {nullptr};
std::string tooltip_ {};
std::vector<boost::signals2::scoped_connection> connections_ {};
};
AlertLayer::AlertLayer(std::shared_ptr<MapContext> context,
@ -289,6 +308,15 @@ void AlertLayer::Deinitialize()
DrawLayer::Deinitialize();
}
bool IsAlertActive(const std::shared_ptr<const awips::Segment>& 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<gl::draw::GeoLines>& 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<gl::draw::GeoLineDrawItem>& 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<std::size_t>(lineSettings.border_width().GetValue()),
.highlightWidth_ =
static_cast<std::size_t>(lineSettings.highlight_width().GetValue()),
.lineWidth_ =
static_cast<std::size_t>(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<const awips::Segment>& 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_ {};

View file

@ -10,7 +10,6 @@
#include <scwx/util/strings.hpp>
#include <scwx/util/time.hpp>
#include <format>
#include <QApplication>
@ -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<types::TextEventKey>>
observedMap_;
std::unordered_map<types::TextEventKey,
awips::ThreatCategory,
awips::ibw::ThreatCategory,
types::TextEventHash<types::TextEventKey>>
threatCategoryMap_;
std::unordered_map<types::TextEventKey,
@ -75,8 +74,8 @@ public:
std::unordered_map<types::TextEventKey,
double,
types::TextEventHash<types::TextEventKey>>
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<int>(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())

View file

@ -0,0 +1,241 @@
#include <scwx/qt/settings/alert_palette_settings.hpp>
#include <scwx/qt/util/color.hpp>
#include <map>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/gil.hpp>
#include <boost/unordered/unordered_flat_map.hpp>
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<awips::ibw::ThreatCategory, LineData>
ThreatCategoryPalette;
static const boost::unordered_flat_map<awips::Phenomenon, ThreatCategoryPalette>
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<awips::Phenomenon, LineData>
kObservedPalettes_ //
{{awips::Phenomenon::Tornado,
{.highlightColor_ {255, 0, 0, 255},
.lineColor_ {kColorBlack_},
.highlightWidth_ = 1,
.lineWidth_ = 1}}};
static const boost::unordered_flat_map<awips::Phenomenon, LineData>
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<awips::Phenomenon, LineData>
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<awips::ibw::ThreatCategory, LineSettings> threatCategoryMap_ {};
LineSettings observed_ {"observed"};
LineSettings tornadoPossible_ {"tornado_possible"};
LineSettings inactive_ {"inactive"};
};
AlertPaletteSettings::AlertPaletteSettings(awips::Phenomenon phenomenon) :
SettingsCategory(awips::GetPhenomenonCode(phenomenon)),
p(std::make_unique<Impl>(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

View file

@ -0,0 +1,46 @@
#pragma once
#include <scwx/qt/settings/line_settings.hpp>
#include <scwx/qt/settings/settings_category.hpp>
#include <scwx/awips/impact_based_warnings.hpp>
#include <scwx/awips/phenomenon.hpp>
#include <memory>
#include <string>
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<Impl> p;
};
} // namespace settings
} // namespace qt
} // namespace scwx

View file

@ -0,0 +1,155 @@
#include <scwx/qt/settings/line_settings.hpp>
#include <scwx/qt/util/color.hpp>
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<std::string> lineColor_ {"line_color"};
SettingsVariable<std::string> highlightColor_ {"highlight_color"};
SettingsVariable<std::string> borderColor_ {"border_color"};
SettingsVariable<std::int64_t> lineWidth_ {"line_width"};
SettingsVariable<std::int64_t> highlightWidth_ {"highlight_width"};
SettingsVariable<std::int64_t> borderWidth_ {"border_width"};
};
LineSettings::LineSettings(const std::string& name) :
SettingsCategory(name), p(std::make_unique<Impl>())
{
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<std::string>& LineSettings::border_color() const
{
return p->borderColor_;
}
SettingsVariable<std::string>& LineSettings::highlight_color() const
{
return p->highlightColor_;
}
SettingsVariable<std::string>& LineSettings::line_color() const
{
return p->lineColor_;
}
SettingsVariable<std::int64_t>& LineSettings::border_width() const
{
return p->borderWidth_;
}
SettingsVariable<std::int64_t>& LineSettings::highlight_width() const
{
return p->highlightWidth_;
}
SettingsVariable<std::int64_t>& 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

View file

@ -0,0 +1,58 @@
#pragma once
#include <scwx/qt/settings/settings_category.hpp>
#include <scwx/qt/settings/settings_variable.hpp>
#include <memory>
#include <string>
#include <boost/gil/typedefs.hpp>
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<std::string>& border_color() const;
SettingsVariable<std::string>& highlight_color() const;
SettingsVariable<std::string>& line_color() const;
SettingsVariable<std::int64_t>& border_width() const;
SettingsVariable<std::int64_t>& highlight_width() const;
SettingsVariable<std::int64_t>& 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<Impl> p;
};
} // namespace settings
} // namespace qt
} // namespace scwx

View file

@ -2,9 +2,9 @@
#include <scwx/qt/settings/settings_variable.hpp>
#include <scwx/qt/util/color.hpp>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/gil.hpp>
#include <fmt/format.h>
#include <re2/re2.h>
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<std::string> {name});
SettingsVariable<std::string>& 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<std::string> {activeName});
auto inactiveResult = inactiveAlertColor_.emplace(
alert.first, SettingsVariable<std::string> {inactiveName});
SettingsVariable<std::string>& activeVariable =
activeResult.first->second;
SettingsVariable<std::string>& 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<std::string, SettingsVariable<std::string>> palette_ {};
std::unordered_map<awips::Phenomenon, SettingsVariable<std::string>>
@ -136,16 +97,13 @@ public:
std::unordered_map<awips::Phenomenon, SettingsVariable<std::string>>
inactiveAlertColor_ {};
std::vector<SettingsVariableBase*> variables_ {};
std::unordered_map<awips::Phenomenon, AlertPaletteSettings>
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<Impl>())
SettingsCategory("palette"), p(std::make_unique<Impl>(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<std::string> {name});
SettingsVariable<std::string>& 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<std::string> {activeName});
auto inactiveResult = inactiveAlertColor_.emplace(
alert.first, SettingsVariable<std::string> {inactiveName});
SettingsVariable<std::string>& activeVariable =
activeResult.first->second;
SettingsVariable<std::string>& 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<SettingsCategory*> 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<std::string>&
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<awips::Phenomenon>& PaletteSettings::alert_phenomena()
{
static const std::vector<awips::Phenomenon> kAlertPhenomena_ {

View file

@ -1,7 +1,9 @@
#pragma once
#include <scwx/qt/settings/alert_palette_settings.hpp>
#include <scwx/qt/settings/settings_category.hpp>
#include <scwx/qt/settings/settings_variable.hpp>
#include <scwx/awips/impact_based_warnings.hpp>
#include <scwx/awips/phenomenon.hpp>
#include <memory>
@ -29,6 +31,7 @@ public:
SettingsVariable<std::string>& palette(const std::string& name) const;
SettingsVariable<std::string>& alert_color(awips::Phenomenon phenomenon,
bool active) const;
AlertPaletteSettings& alert_palette(awips::Phenomenon);
static const std::vector<awips::Phenomenon>& alert_phenomena();

View file

@ -21,11 +21,21 @@ public:
~Impl() {}
void ConnectSubcategory(SettingsCategory& category);
void ConnectVariable(SettingsVariableBase* variable);
const std::string name_;
std::vector<std::pair<std::string, std::vector<SettingsCategory*>>>
subcategoryArrays_;
std::vector<SettingsCategory*> subcategories_;
std::vector<SettingsVariableBase*> variables_;
boost::signals2::signal<void()> changedSignal_ {};
boost::signals2::signal<void()> stagedSignal_ {};
bool blockSignals_ {false};
std::vector<boost::signals2::scoped_connection> 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<void()>& SettingsCategory::changed_signal()
{
return p->changedSignal_;
}
boost::signals2::signal<void()>& 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<SettingsCategory>& 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<SettingsCategory*>& subcategories)
{
auto& newSubcategories = p->subcategoryArrays_.emplace_back(
name, std::vector<SettingsCategory*> {});
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<SettingsVariableBase*> variables)
{
for (auto& variable : variables)
{
p->ConnectVariable(variable);
}
p->variables_.insert(p->variables_.end(), variables);
}
void SettingsCategory::RegisterVariables(
std::vector<SettingsVariableBase*> 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

View file

@ -6,6 +6,7 @@
#include <string>
#include <boost/json/object.hpp>
#include <boost/signals2/signal.hpp>
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<void()>& changed_signal();
/**
* Gets the signal invoked when a variable within the category is staged.
*
* @return Staged signal
*/
boost::signals2::signal<void()>& 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<SettingsCategory>& subcategories);
void RegisterSubcategoryArray(const std::string& name,
std::vector<SettingsCategory*>& subcategories);
void
RegisterVariables(std::initializer_list<SettingsVariableBase*> variables);
void RegisterVariables(std::vector<SettingsVariableBase*> variables);
protected:
void set_block_signals(bool blockSignals);
private:
class Impl;
std::unique_ptr<Impl> p;

View file

@ -65,6 +65,18 @@ inline auto FormatParameter(const T& value)
}
}
template<class T>
bool SettingsVariable<T>::IsDefault() const
{
return p->value_ == p->default_;
}
template<class T>
bool SettingsVariable<T>::IsDefaultStaged() const
{
return p->staged_.value_or(p->value_) == p->default_;
}
template<class T>
T SettingsVariable<T>::GetValue() const
{
@ -81,10 +93,13 @@ bool SettingsVariable<T>::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<T>::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<T>::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<T>::StageDefault()
p->staged_.reset();
}
staged_signal()();
for (auto& callback : p->valueStagedCallbackFunctions_)
{
callback.second(p->default_);
@ -194,6 +216,7 @@ bool SettingsVariable<T>::StageValue(const T& value)
validated = true;
staged_signal()();
for (auto& callback : p->valueStagedCallbackFunctions_)
{
callback.second(transformed);
@ -214,10 +237,13 @@ bool SettingsVariable<T>::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<T>::Reset()
{
p->staged_.reset();
staged_signal()();
for (auto& callback : p->valueStagedCallbackFunctions_)
{
callback.second(p->value_);
@ -336,10 +363,13 @@ bool SettingsVariable<T>::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_);

View file

@ -29,7 +29,7 @@ public:
typedef std::function<void(const T& value)> 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.

View file

@ -18,6 +18,9 @@ public:
~Impl() {}
const std::string name_;
boost::signals2::signal<void()> changedSignal_ {};
boost::signals2::signal<void()> stagedSignal_ {};
};
SettingsVariableBase::SettingsVariableBase(const std::string& name) :
@ -38,6 +41,16 @@ std::string SettingsVariableBase::name() const
return p->name_;
}
boost::signals2::signal<void()>& SettingsVariableBase::changed_signal()
{
return p->changedSignal_;
}
boost::signals2::signal<void()>& SettingsVariableBase::staged_signal()
{
return p->stagedSignal_;
}
bool SettingsVariableBase::Equals(const SettingsVariableBase& o) const
{
return p->name_ == o.p->name_;

View file

@ -4,6 +4,7 @@
#include <string>
#include <boost/json/object.hpp>
#include <boost/signals2/signal.hpp>
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<void()>& changed_signal();
/**
* Gets the signal invoked when the settings variable is staged.
*
* @return Staged signal
*/
boost::signals2::signal<void()>& 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

View file

@ -0,0 +1,316 @@
#include "edit_line_dialog.hpp"
#include "ui_edit_line_dialog.h"
#include <scwx/qt/ui/line_label.hpp>
#include <scwx/qt/util/color.hpp>
#include <scwx/util/logger.hpp>
#include <fmt/format.h>
#include <QColorDialog>
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<Impl>(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<QHBoxLayout*>(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<int>(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

View file

@ -0,0 +1,59 @@
#pragma once
#include <QDialog>
#include <boost/gil/typedefs.hpp>
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<Impl> p;
Ui::EditLineDialog* ui;
};
} // namespace ui
} // namespace qt
} // namespace scwx

View file

@ -0,0 +1,306 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>EditLineDialog</class>
<widget class="QDialog" name="EditLineDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>350</width>
<height>225</height>
</rect>
</property>
<property name="windowTitle">
<string>Edit Line</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="componentLabel">
<property name="font">
<font>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Component</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLineEdit" name="highlightColorLineEdit">
<property name="text">
<string>#ff000000</string>
</property>
</widget>
</item>
<item row="3" column="4">
<widget class="QSpinBox" name="borderWidthSpinBox">
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>9</number>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QToolButton" name="lineColorButton">
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../../scwx-qt.qrc">
<normaloff>:/res/icons/font-awesome-6/palette-solid.svg</normaloff>:/res/icons/font-awesome-6/palette-solid.svg</iconset>
</property>
</widget>
</item>
<item row="3" column="3">
<widget class="QToolButton" name="borderColorButton">
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../../scwx-qt.qrc">
<normaloff>:/res/icons/font-awesome-6/palette-solid.svg</normaloff>:/res/icons/font-awesome-6/palette-solid.svg</iconset>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="borderLabel">
<property name="text">
<string>Border</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLineEdit" name="lineColorLineEdit">
<property name="text">
<string>#ff000000</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="lineLabel">
<property name="text">
<string>Line</string>
</property>
</widget>
</item>
<item row="6" column="0" colspan="5">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok|QDialogButtonBox::StandardButton::Reset</set>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QFrame" name="lineColorFrame">
<property name="minimumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::Shape::Box</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Plain</enum>
</property>
</widget>
</item>
<item row="2" column="4">
<widget class="QSpinBox" name="highlightWidthSpinBox">
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>9</number>
</property>
</widget>
</item>
<item row="5" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="1">
<widget class="QFrame" name="highlightColorFrame">
<property name="minimumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::Shape::Box</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Plain</enum>
</property>
</widget>
</item>
<item row="0" column="1" colspan="3">
<widget class="QLabel" name="colorLabel">
<property name="font">
<font>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Color</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QLineEdit" name="borderColorLineEdit">
<property name="text">
<string>#ff000000</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QFrame" name="borderColorFrame">
<property name="minimumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::Shape::Box</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Plain</enum>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QLabel" name="widthLabel">
<property name="font">
<font>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Width</string>
</property>
</widget>
</item>
<item row="1" column="4">
<widget class="QSpinBox" name="lineWidthSpinBox">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>9</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="highlightLabel">
<property name="text">
<string>Highlight</string>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QToolButton" name="highlightColorButton">
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../../scwx-qt.qrc">
<normaloff>:/res/icons/font-awesome-6/palette-solid.svg</normaloff>:/res/icons/font-awesome-6/palette-solid.svg</iconset>
</property>
</widget>
</item>
<item row="4" column="0" colspan="5">
<widget class="QWidget" name="lineLabelContainer" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>45</height>
</size>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
</spacer>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../../../../scwx-qt.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>EditLineDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>EditLineDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View file

@ -0,0 +1,239 @@
#include <scwx/qt/ui/line_label.hpp>
#include <scwx/qt/util/color.hpp>
#include <scwx/util/logger.hpp>
#include <QEvent>
#include <QPainter>
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<Impl>(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<int>(width) + margins.left() + margins.right(),
static_cast<int>(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<int>(borderColor_[0]),
static_cast<int>(borderColor_[1]),
static_cast<int>(borderColor_[2]),
static_cast<int>(borderColor_[3]));
const QRgb highlightRgba = qRgba(static_cast<int>(highlightColor_[0]),
static_cast<int>(highlightColor_[1]),
static_cast<int>(highlightColor_[2]),
static_cast<int>(highlightColor_[3]));
const QRgb lineRgba = qRgba(static_cast<int>(lineColor_[0]),
static_cast<int>(lineColor_[1]),
static_cast<int>(lineColor_[2]),
static_cast<int>(lineColor_[3]));
const std::size_t width = 1;
const std::size_t height = (borderWidth_ + highlightWidth_) * 2 + lineWidth_;
QImage image(static_cast<int>(width),
static_cast<int>(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<int>(y), borderRgba);
image.setPixel(0, static_cast<int>(height - 1 - y), borderRgba);
}
for (std::size_t i = 0; i < highlightWidth_; ++i, ++y)
{
image.setPixel(0, static_cast<int>(y), highlightRgba);
image.setPixel(0, static_cast<int>(height - 1 - y), highlightRgba);
}
for (std::size_t i = 0; i < lineWidth_; ++i, ++y)
{
image.setPixel(0, static_cast<int>(y), lineRgba);
image.setPixel(0, static_cast<int>(height - 1 - y), lineRgba);
}
return image;
}
} // namespace ui
} // namespace qt
} // namespace scwx

View file

@ -0,0 +1,55 @@
#pragma once
#include <scwx/qt/settings/line_settings.hpp>
#include <QFrame>
#include <boost/gil/typedefs.hpp>
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<Impl> p;
};
} // namespace ui
} // namespace qt
} // namespace scwx

View file

@ -0,0 +1,292 @@
#include <scwx/qt/ui/settings/alert_palette_settings_widget.hpp>
#include <scwx/qt/ui/edit_line_dialog.hpp>
#include <scwx/qt/ui/line_label.hpp>
#include <scwx/qt/settings/palette_settings.hpp>
#include <scwx/awips/impact_based_warnings.hpp>
#include <scwx/awips/phenomenon.hpp>
#include <boost/unordered/unordered_flat_map.hpp>
#include <QGridLayout>
#include <QLabel>
#include <QListWidget>
#include <QStackedWidget>
#include <QToolButton>
#include <QVBoxLayout>
#include <boost/signals2.hpp>
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<awips::Phenomenon, QWidget*> phenomenonPages_ {};
std::vector<boost::signals2::scoped_connection> connections_ {};
};
AlertPaletteSettingsWidget::AlertPaletteSettingsWidget(QWidget* parent) :
SettingsPageWidget(parent), p {std::make_shared<Impl>(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

View file

@ -0,0 +1,29 @@
#pragma once
#include <scwx/qt/ui/settings/settings_page_widget.hpp>
#include <QWidget>
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<Impl> p;
};
} // namespace ui
} // namespace qt
} // namespace scwx

View file

@ -19,6 +19,7 @@ public:
explicit Impl() {}
~Impl() = default;
std::vector<settings::SettingsCategory*> categories_;
std::vector<settings::SettingsInterfaceBase*> 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();

View file

@ -1,5 +1,6 @@
#pragma once
#include <scwx/qt/settings/settings_category.hpp>
#include <scwx/qt/settings/settings_interface_base.hpp>
#include <QWidget>
@ -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<Impl> p;

View file

@ -23,11 +23,12 @@
#include <scwx/qt/types/time_types.hpp>
#include <scwx/qt/types/unit_types.hpp>
#include <scwx/qt/ui/county_dialog.hpp>
#include <scwx/qt/ui/wfo_dialog.hpp>
#include <scwx/qt/ui/radar_site_dialog.hpp>
#include <scwx/qt/ui/serial_port_dialog.hpp>
#include <scwx/qt/ui/settings/alert_palette_settings_widget.hpp>
#include <scwx/qt/ui/settings/hotkey_settings_widget.hpp>
#include <scwx/qt/ui/settings/unit_settings_widget.hpp>
#include <scwx/qt/ui/wfo_dialog.hpp>
#include <scwx/qt/util/color.hpp>
#include <scwx/qt/util/file.hpp>
#include <scwx/util/logger.hpp>
@ -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<config::RadarSite>& radarSite);
static void SetBackgroundColor(const std::string& value, QFrame* frame);
RadarSiteLabel(std::shared_ptr<config::RadarSite>& radarSite);
SettingsDialog* self_;
RadarSiteDialog* radarSiteDialog_;
@ -224,6 +223,7 @@ public:
manager::PositionManager::Instance()};
std::vector<SettingsPageWidget*> settingsPages_ {};
AlertPaletteSettingsWidget* alertPaletteSettingsWidget_ {};
HotkeySettingsWidget* hotkeySettingsWidget_ {};
UnitSettingsWidget* unitSettingsWidget_ {};
@ -252,12 +252,6 @@ public:
std::unordered_map<std::string, settings::SettingsInterface<std::string>>
colorTables_ {};
std::unordered_map<awips::Phenomenon,
settings::SettingsInterface<std::string>>
activeAlertColors_ {};
std::unordered_map<awips::Phenomenon,
settings::SettingsInterface<std::string>>
inactiveAlertColors_ {};
settings::SettingsInterface<std::string> alertAudioSoundFile_ {};
settings::SettingsInterface<std::string> 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<config::RadarSite> radarSite =
config::RadarSite::Get(id);
std::shared_ptr<config::RadarSite> 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<QGridLayout*>(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<std::string> {});
auto inactiveResult = inactiveAlertColors_.emplace(
phenomenon, settings::SettingsInterface<std::string> {});
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<config::RadarSite> 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_)

View file

@ -122,7 +122,7 @@
</sizepolicy>
</property>
<property name="currentIndex">
<number>3</number>
<number>0</number>
</property>
<widget class="QWidget" name="general">
<layout class="QVBoxLayout" name="verticalLayout_2">
@ -136,8 +136,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>274</width>
<height>691</height>
<width>513</width>
<height>622</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
@ -610,8 +610,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>98</width>
<height>28</height>
<width>506</width>
<height>383</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_3">
@ -634,49 +634,10 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<widget class="QWidget" name="alertsPalette">
<attribute name="title">
<string>Alerts</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QFrame" name="alertsFrame">
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QGridLayout" name="gridLayout_5">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>239</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>

View file

@ -1,6 +1,7 @@
#include <scwx/qt/util/color.hpp>
#include <fmt/format.h>
#include <re2/re2.h>
#include <QColor>
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

View file

@ -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

@ -1 +1 @@
Subproject commit 20a1ca1752499222d33869e37148321936ca6354
Subproject commit 40a367ca89b5b197353ca58dea547a3e3407c7f3

View file

@ -1,11 +1,16 @@
#pragma once
#include <scwx/awips/phenomenon.hpp>
#include <string>
#include <vector>
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<ThreatCategory> 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

View file

@ -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);

View file

@ -64,9 +64,9 @@ struct Segment
std::optional<CodedLocation> codedLocation_ {};
std::optional<CodedTimeMotionLocation> 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;

View file

@ -4,13 +4,40 @@
#include <unordered_map>
#include <boost/algorithm/string.hpp>
#include <boost/unordered/unordered_flat_map.hpp>
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<Phenomenon, ImpactBasedWarningInfo>
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<ThreatCategory, std::string>
threatCategoryName_ {{ThreatCategory::Base, "Base"},
@ -20,6 +47,16 @@ static const std::unordered_map<ThreatCategory, std::string>
{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

View file

@ -77,64 +77,65 @@ static const PhenomenonCodesBimap phenomenonCodes_ =
(Phenomenon::FreezingSpray, "ZY") //
(Phenomenon::Unknown, "??");
static const std::unordered_map<Phenomenon, std::string> 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<PhenomenonCodesBimap::relation> //
(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

View file

@ -378,7 +378,7 @@ void ParseCodedInformation(std::shared_ptr<Segment> 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> 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;