mirror of
https://github.com/ciphervance/supercell-wx.git
synced 2025-10-30 21:20:06 +00:00
commit
7b3d78e01a
47 changed files with 2240 additions and 52 deletions
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
|
|
@ -30,7 +30,7 @@ jobs:
|
|||
msvc_version: 2022
|
||||
qt_version: 6.6.1
|
||||
qt_arch: win64_msvc2019_64
|
||||
qt_modules: qtimageformats qtpositioning
|
||||
qt_modules: qtimageformats qtmultimedia qtpositioning
|
||||
qt_tools: ''
|
||||
conan_arch: x86_64
|
||||
conan_compiler: Visual Studio
|
||||
|
|
@ -46,7 +46,7 @@ jobs:
|
|||
compiler: gcc
|
||||
qt_version: 6.6.1
|
||||
qt_arch: gcc_64
|
||||
qt_modules: qtimageformats qtpositioning
|
||||
qt_modules: qtimageformats qtmultimedia qtpositioning
|
||||
qt_tools: ''
|
||||
conan_arch: x86_64
|
||||
conan_compiler: gcc
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ Supercell Wx uses code from the following dependencies:
|
|||
| [FreeType](https://freetype.org/) | [Freetype Project License](https://spdx.org/licenses/FTL.html) |
|
||||
| [FreeType GL](https://github.com/rougier/freetype-gl) | [BSD 2-Clause with views sentence](https://spdx.org/licenses/BSD-2-Clause-Views.html) |
|
||||
| [GeographicLib](https://geographiclib.sourceforge.io/) | [MIT License](https://spdx.org/licenses/MIT.html) |
|
||||
| [geos](https://libgeos.org/) | [GNU Lesser General Public License v2.1 or later](https://spdx.org/licenses/LGPL-2.1-or-later.html) |
|
||||
| [GLEW](https://www.opengl.org/sdk/libs/GLEW/) | [MIT License](https://spdx.org/licenses/MIT.html) |
|
||||
| [GLM](https://github.com/g-truc/glm) | [MIT License](https://spdx.org/licenses/MIT.html) |
|
||||
| [GoogleTest](https://google.github.io/googletest/) | [BSD 3-Clause "New" or "Revised" License](https://spdx.org/licenses/BSD-3-Clause.html) |
|
||||
|
|
@ -33,7 +34,7 @@ Supercell Wx uses code from the following dependencies:
|
|||
| [MapLibre Native](https://maplibre.org/projects/maplibre-native/) | [BSD 2-Clause "Simplified" License](https://spdx.org/licenses/BSD-2-Clause.html) |
|
||||
| [nunicode](https://bitbucket.org/alekseyt/nunicode/src/master/) | [MIT License](https://spdx.org/licenses/MIT.html) | Modified for MapLibre Native |
|
||||
| [OpenSSL](https://www.openssl.org/) | [OpenSSL License](https://spdx.org/licenses/OpenSSL.html) |
|
||||
| [Qt](https://www.qt.io/) | [GNU Lesser General Public License v3.0 only](https://spdx.org/licenses/LGPL-3.0-only.html) | Qt Core, Qt GUI, Qt Network, Qt OpenGL, Qt SQL, Qt SVG, Qt Widgets<br/>Additional Licenses: https://doc.qt.io/qt-6/licenses-used-in-qt.html |
|
||||
| [Qt](https://www.qt.io/) | [GNU Lesser General Public License v3.0 only](https://spdx.org/licenses/LGPL-3.0-only.html) | Qt Core, Qt GUI, Qt Multimedia, Qt Network, Qt OpenGL, Qt Positioning, Qt SQL, Qt SVG, Qt Widgets<br/>Additional Licenses: https://doc.qt.io/qt-6/licenses-used-in-qt.html |
|
||||
| [spdlog](https://github.com/gabime/spdlog) | [MIT License](https://spdx.org/licenses/MIT.html) |
|
||||
| [SQLite](https://www.sqlite.org/) | Public Domain |
|
||||
| [stb](https://github.com/nothings/stb) | Public Domain |
|
||||
|
|
@ -59,6 +60,7 @@ Supercell Wx uses assets from the following sources:
|
|||
| Source | License | Notes |
|
||||
| ------ | ------- | ----- |
|
||||
| Alte DIN 1451 Mittelschrift | SIL Open Font License |
|
||||
| [EAS Attention Signal](https://en.wikipedia.org/wiki/File:Emergency_Alert_System_Attention_Signal_20s.ogg) | Public Domain |
|
||||
| [Font Awesome Free](https://fontawesome.com/) | CC BY 4.0 License |
|
||||
| [Inconsolata](https://fonts.google.com/specimen/Inconsolata) | SIL Open Font License |
|
||||
| [NOAA's Weather and Climate Toolkit](https://www.ncdc.noaa.gov/wct/) | Public Domain | Default Color Tables |
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ class SupercellWxConan(ConanFile):
|
|||
"cpr/1.10.5",
|
||||
"fontconfig/2.14.2",
|
||||
"geographiclib/2.3",
|
||||
"geos/3.12.0",
|
||||
"glew/2.2.0",
|
||||
"glm/cci.20230113",
|
||||
"gtest/1.14.0",
|
||||
|
|
@ -19,7 +20,8 @@ class SupercellWxConan(ConanFile):
|
|||
generators = ("cmake",
|
||||
"cmake_find_package",
|
||||
"cmake_paths")
|
||||
default_options = {"libiconv:shared" : True,
|
||||
default_options = {"geos:shared" : True,
|
||||
"libiconv:shared" : True,
|
||||
"openssl:no_module": True,
|
||||
"openssl:shared" : True}
|
||||
|
||||
|
|
|
|||
2
data
2
data
|
|
@ -1 +1 @@
|
|||
Subproject commit 9b6c72f847193bc29d3ff183b206f26a9b5c007e
|
||||
Subproject commit db52049ea651fea92b06e5024cbff3a3d3d26bc8
|
||||
Binary file not shown.
1
scwx-qt/res/icons/font-awesome-6/stop-solid.svg
Normal file
1
scwx-qt/res/icons/font-awesome-6/stop-solid.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="12" viewBox="0 0 384 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path opacity="1" fill="#000000" d="M0 128C0 92.7 28.7 64 64 64H320c35.3 0 64 28.7 64 64V384c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V128z"/></svg>
|
||||
|
After Width: | Height: | Size: 388 B |
1
scwx-qt/res/icons/font-awesome-6/volume-high-solid.svg
Normal file
1
scwx-qt/res/icons/font-awesome-6/volume-high-solid.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--! Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M533.6 32.5C598.5 85.3 640 165.8 640 256s-41.5 170.8-106.4 223.5c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C557.5 398.2 592 331.2 592 256s-34.5-142.2-88.7-186.3c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zM473.1 107c43.2 35.2 70.9 88.9 70.9 149s-27.7 113.8-70.9 149c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C475.3 341.3 496 301.1 496 256s-20.7-85.3-53.2-111.8c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zm-60.5 74.5C434.1 199.1 448 225.9 448 256s-13.9 56.9-35.4 74.5c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C393.1 284.4 400 271 400 256s-6.9-28.4-17.7-37.3c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zM301.1 34.8C312.6 40 320 51.4 320 64V448c0 12.6-7.4 24-18.9 29.2s-25 3.1-34.4-5.3L131.8 352H64c-35.3 0-64-28.7-64-64V224c0-35.3 28.7-64 64-64h67.8L266.7 40.1c9.4-8.4 22.9-10.4 34.4-5.3z"/></svg>
|
||||
|
After Width: | Height: | Size: 1 KiB |
|
|
@ -14,6 +14,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|||
find_package(Boost)
|
||||
find_package(Fontconfig)
|
||||
find_package(geographiclib)
|
||||
find_package(geos)
|
||||
find_package(GLEW)
|
||||
find_package(glm)
|
||||
find_package(Python COMPONENTS Interpreter)
|
||||
|
|
@ -22,6 +23,7 @@ find_package(SQLite3)
|
|||
find_package(QT NAMES Qt6
|
||||
COMPONENTS Gui
|
||||
LinguistTools
|
||||
Multimedia
|
||||
Network
|
||||
OpenGL
|
||||
OpenGLWidgets
|
||||
|
|
@ -31,6 +33,7 @@ find_package(QT NAMES Qt6
|
|||
find_package(Qt${QT_VERSION_MAJOR}
|
||||
COMPONENTS Gui
|
||||
LinguistTools
|
||||
Multimedia
|
||||
Network
|
||||
OpenGL
|
||||
OpenGLWidgets
|
||||
|
|
@ -76,7 +79,9 @@ set(SRC_GL_DRAW source/scwx/qt/gl/draw/draw_item.cpp
|
|||
source/scwx/qt/gl/draw/placefile_text.cpp
|
||||
source/scwx/qt/gl/draw/placefile_triangles.cpp
|
||||
source/scwx/qt/gl/draw/rectangle.cpp)
|
||||
set(HDR_MANAGER source/scwx/qt/manager/font_manager.hpp
|
||||
set(HDR_MANAGER source/scwx/qt/manager/alert_manager.hpp
|
||||
source/scwx/qt/manager/font_manager.hpp
|
||||
source/scwx/qt/manager/media_manager.hpp
|
||||
source/scwx/qt/manager/placefile_manager.hpp
|
||||
source/scwx/qt/manager/position_manager.hpp
|
||||
source/scwx/qt/manager/radar_product_manager.hpp
|
||||
|
|
@ -86,7 +91,9 @@ set(HDR_MANAGER source/scwx/qt/manager/font_manager.hpp
|
|||
source/scwx/qt/manager/text_event_manager.hpp
|
||||
source/scwx/qt/manager/timeline_manager.hpp
|
||||
source/scwx/qt/manager/update_manager.hpp)
|
||||
set(SRC_MANAGER source/scwx/qt/manager/font_manager.cpp
|
||||
set(SRC_MANAGER source/scwx/qt/manager/alert_manager.cpp
|
||||
source/scwx/qt/manager/font_manager.cpp
|
||||
source/scwx/qt/manager/media_manager.cpp
|
||||
source/scwx/qt/manager/placefile_manager.cpp
|
||||
source/scwx/qt/manager/position_manager.cpp
|
||||
source/scwx/qt/manager/radar_product_manager.cpp
|
||||
|
|
@ -141,18 +148,21 @@ set(SRC_MODEL source/scwx/qt/model/alert_model.cpp
|
|||
source/scwx/qt/model/tree_model.cpp)
|
||||
set(HDR_REQUEST source/scwx/qt/request/nexrad_file_request.hpp)
|
||||
set(SRC_REQUEST source/scwx/qt/request/nexrad_file_request.cpp)
|
||||
set(HDR_SETTINGS source/scwx/qt/settings/general_settings.hpp
|
||||
set(HDR_SETTINGS source/scwx/qt/settings/audio_settings.hpp
|
||||
source/scwx/qt/settings/general_settings.hpp
|
||||
source/scwx/qt/settings/map_settings.hpp
|
||||
source/scwx/qt/settings/palette_settings.hpp
|
||||
source/scwx/qt/settings/settings_category.hpp
|
||||
source/scwx/qt/settings/settings_container.hpp
|
||||
source/scwx/qt/settings/settings_definitions.hpp
|
||||
source/scwx/qt/settings/settings_interface.hpp
|
||||
source/scwx/qt/settings/settings_interface_base.hpp
|
||||
source/scwx/qt/settings/settings_variable.hpp
|
||||
source/scwx/qt/settings/settings_variable_base.hpp
|
||||
source/scwx/qt/settings/text_settings.hpp
|
||||
source/scwx/qt/settings/ui_settings.hpp)
|
||||
set(SRC_SETTINGS source/scwx/qt/settings/general_settings.cpp
|
||||
set(SRC_SETTINGS source/scwx/qt/settings/audio_settings.cpp
|
||||
source/scwx/qt/settings/general_settings.cpp
|
||||
source/scwx/qt/settings/map_settings.cpp
|
||||
source/scwx/qt/settings/palette_settings.cpp
|
||||
source/scwx/qt/settings/settings_category.cpp
|
||||
|
|
@ -168,7 +178,9 @@ set(HDR_TYPES source/scwx/qt/types/alert_types.hpp
|
|||
source/scwx/qt/types/github_types.hpp
|
||||
source/scwx/qt/types/imgui_font.hpp
|
||||
source/scwx/qt/types/layer_types.hpp
|
||||
source/scwx/qt/types/location_types.hpp
|
||||
source/scwx/qt/types/map_types.hpp
|
||||
source/scwx/qt/types/media_types.hpp
|
||||
source/scwx/qt/types/qt_types.hpp
|
||||
source/scwx/qt/types/radar_product_record.hpp
|
||||
source/scwx/qt/types/text_event_key.hpp
|
||||
|
|
@ -178,7 +190,9 @@ set(SRC_TYPES source/scwx/qt/types/alert_types.cpp
|
|||
source/scwx/qt/types/github_types.cpp
|
||||
source/scwx/qt/types/imgui_font.cpp
|
||||
source/scwx/qt/types/layer_types.cpp
|
||||
source/scwx/qt/types/location_types.cpp
|
||||
source/scwx/qt/types/map_types.cpp
|
||||
source/scwx/qt/types/media_types.cpp
|
||||
source/scwx/qt/types/qt_types.cpp
|
||||
source/scwx/qt/types/radar_product_record.cpp
|
||||
source/scwx/qt/types/text_event_key.cpp
|
||||
|
|
@ -189,6 +203,7 @@ set(HDR_UI source/scwx/qt/ui/about_dialog.hpp
|
|||
source/scwx/qt/ui/alert_dock_widget.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/flow_layout.hpp
|
||||
source/scwx/qt/ui/imgui_debug_dialog.hpp
|
||||
source/scwx/qt/ui/imgui_debug_widget.hpp
|
||||
|
|
@ -208,6 +223,7 @@ set(SRC_UI source/scwx/qt/ui/about_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/flow_layout.cpp
|
||||
source/scwx/qt/ui/imgui_debug_dialog.cpp
|
||||
source/scwx/qt/ui/imgui_debug_widget.cpp
|
||||
|
|
@ -227,6 +243,7 @@ set(UI_UI source/scwx/qt/ui/about_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/imgui_debug_dialog.ui
|
||||
source/scwx/qt/ui/layer_dialog.ui
|
||||
source/scwx/qt/ui/open_url_dialog.ui
|
||||
|
|
@ -235,12 +252,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/update_dialog.ui)
|
||||
set(HDR_UI_SETUP source/scwx/qt/ui/setup/finish_page.hpp
|
||||
set(HDR_UI_SETUP source/scwx/qt/ui/setup/audio_codec_page.hpp
|
||||
source/scwx/qt/ui/setup/finish_page.hpp
|
||||
source/scwx/qt/ui/setup/map_layout_page.hpp
|
||||
source/scwx/qt/ui/setup/map_provider_page.hpp
|
||||
source/scwx/qt/ui/setup/setup_wizard.hpp
|
||||
source/scwx/qt/ui/setup/welcome_page.hpp)
|
||||
set(SRC_UI_SETUP source/scwx/qt/ui/setup/finish_page.cpp
|
||||
set(SRC_UI_SETUP source/scwx/qt/ui/setup/audio_codec_page.cpp
|
||||
source/scwx/qt/ui/setup/finish_page.cpp
|
||||
source/scwx/qt/ui/setup/map_layout_page.cpp
|
||||
source/scwx/qt/ui/setup/map_provider_page.cpp
|
||||
source/scwx/qt/ui/setup/setup_wizard.cpp
|
||||
|
|
@ -309,6 +328,7 @@ set(ZONE_DBF_FILES ${SCWX_DIR}/data/db/fz19se23.dbf
|
|||
${SCWX_DIR}/data/db/mz19se23.dbf
|
||||
${SCWX_DIR}/data/db/oz08mr23.dbf
|
||||
${SCWX_DIR}/data/db/z_19se23.dbf)
|
||||
set(STATE_DBF_FILES ${SCWX_DIR}/data/db/s_08mr23.dbf)
|
||||
set(COUNTIES_SQLITE_DB ${scwx-qt_BINARY_DIR}/res/db/counties.db)
|
||||
|
||||
set(VERSIONS_INPUT ${scwx-qt_SOURCE_DIR}/source/scwx/qt/main/versions.hpp.in)
|
||||
|
|
@ -397,8 +417,12 @@ add_custom_command(OUTPUT ${COUNTIES_SQLITE_DB}
|
|||
${scwx-qt_SOURCE_DIR}/tools/generate_counties_db.py
|
||||
-c ${COUNTY_DBF_FILES}
|
||||
-z ${ZONE_DBF_FILES}
|
||||
-s ${STATE_DBF_FILES}
|
||||
-o ${COUNTIES_SQLITE_DB}
|
||||
DEPENDS ${COUNTY_DB_FILES} ${ZONE_DBF_FILES})
|
||||
DEPENDS ${scwx-qt_SOURCE_DIR}/tools/generate_counties_db.py
|
||||
${COUNTY_DB_FILES}
|
||||
${STATE_DBF_FILES}
|
||||
${ZONE_DBF_FILES})
|
||||
|
||||
add_custom_target(scwx-qt_generate_counties_db ALL
|
||||
DEPENDS ${COUNTIES_SQLITE_DB})
|
||||
|
|
@ -512,6 +536,7 @@ endif()
|
|||
|
||||
target_link_libraries(scwx-qt PUBLIC Qt${QT_VERSION_MAJOR}::Widgets
|
||||
Qt${QT_VERSION_MAJOR}::OpenGLWidgets
|
||||
Qt${QT_VERSION_MAJOR}::Multimedia
|
||||
Qt${QT_VERSION_MAJOR}::Positioning
|
||||
Boost::json
|
||||
Boost::timer
|
||||
|
|
@ -519,6 +544,8 @@ target_link_libraries(scwx-qt PUBLIC Qt${QT_VERSION_MAJOR}::Widgets
|
|||
$<$<CXX_COMPILER_ID:MSVC>:opengl32>
|
||||
Fontconfig::Fontconfig
|
||||
GeographicLib::GeographicLib
|
||||
GEOS::geos
|
||||
GEOS::geos_cxx_flags
|
||||
GLEW::GLEW
|
||||
glm::glm
|
||||
imgui
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
<file>gl/texture2d.frag</file>
|
||||
<file>gl/texture2d_array.frag</file>
|
||||
<file>gl/threshold.geom</file>
|
||||
<file>res/audio/wikimedia/Emergency_Alert_System_Attention_Signal_20s.ogg</file>
|
||||
<file>res/config/radar_sites.json</file>
|
||||
<file>res/fonts/din1451alt.ttf</file>
|
||||
<file>res/fonts/din1451alt_g.ttf</file>
|
||||
|
|
@ -43,6 +44,8 @@
|
|||
<file>res/icons/font-awesome-6/square-caret-right-regular.svg</file>
|
||||
<file>res/icons/font-awesome-6/square-minus-regular.svg</file>
|
||||
<file>res/icons/font-awesome-6/square-plus-regular.svg</file>
|
||||
<file>res/icons/font-awesome-6/stop-solid.svg</file>
|
||||
<file>res/icons/font-awesome-6/volume-high-solid.svg</file>
|
||||
<file>res/palettes/wct/CC.pal</file>
|
||||
<file>res/palettes/wct/Default16.pal</file>
|
||||
<file>res/palettes/wct/DOD_DSD.pal</file>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
#include <scwx/qt/config/county_database.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
|
||||
#include <shared_mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <boost/uuid/uuid.hpp>
|
||||
|
|
@ -25,9 +24,13 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_);
|
|||
|
||||
static const std::string countyDatabaseFilename_ = ":/res/db/counties.db";
|
||||
|
||||
typedef std::unordered_map<std::string, std::string> CountyMap;
|
||||
typedef std::unordered_map<std::string, CountyMap> StateMap;
|
||||
typedef std::unordered_map<char, StateMap> FormatMap;
|
||||
|
||||
static bool initialized_ {false};
|
||||
static std::unordered_map<std::string, std::string> countyMap_;
|
||||
static std::shared_mutex countyMutex_;
|
||||
static FormatMap countyDatabase_;
|
||||
static std::unordered_map<std::string, std::string> stateMap_;
|
||||
|
||||
void Initialize()
|
||||
{
|
||||
|
|
@ -87,8 +90,8 @@ void Initialize()
|
|||
return;
|
||||
}
|
||||
|
||||
// Database is open, acquire lock
|
||||
std::unique_lock lock(countyMutex_);
|
||||
// Ensure counties exists
|
||||
countyDatabase_.emplace('C', StateMap {});
|
||||
|
||||
// Query database for counties
|
||||
rc = sqlite3_exec(
|
||||
|
|
@ -101,14 +104,24 @@ void Initialize()
|
|||
{
|
||||
int status = 0;
|
||||
|
||||
if (columns == 2)
|
||||
if (columns == 2 && std::strlen(columnText[0]) == 6)
|
||||
{
|
||||
countyMap_.emplace(columnText[0], columnText[1]);
|
||||
std::string fipsId = columnText[0];
|
||||
std::string state = fipsId.substr(0, 2);
|
||||
char type = fipsId.at(2);
|
||||
|
||||
countyDatabase_[type][state].emplace(fipsId, columnText[1]);
|
||||
}
|
||||
else if (columns != 2)
|
||||
{
|
||||
logger_->error(
|
||||
"County database format error, invalid number of columns: {}",
|
||||
columns);
|
||||
status = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger_->error(
|
||||
"Database format error, invalid number of columns: {}", columns);
|
||||
logger_->error("Invalid FIPS ID: {}", columnText[0]);
|
||||
status = -1;
|
||||
}
|
||||
|
||||
|
|
@ -122,20 +135,48 @@ void Initialize()
|
|||
sqlite3_free(errorMessage);
|
||||
}
|
||||
|
||||
// Finished populating county map, release lock
|
||||
lock.unlock();
|
||||
// Query database for states
|
||||
rc = sqlite3_exec(
|
||||
db,
|
||||
"SELECT * FROM states",
|
||||
[](void* /* param */,
|
||||
int columns,
|
||||
char** columnText,
|
||||
char** /* columnName */) -> int
|
||||
{
|
||||
int status = 0;
|
||||
|
||||
if (columns == 2)
|
||||
{
|
||||
stateMap_.emplace(columnText[0], columnText[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger_->error(
|
||||
"State database format error, invalid number of columns: {}",
|
||||
columns);
|
||||
status = -1;
|
||||
}
|
||||
|
||||
return status;
|
||||
},
|
||||
nullptr,
|
||||
&errorMessage);
|
||||
if (rc != SQLITE_OK)
|
||||
{
|
||||
logger_->error("SQL error: {}", errorMessage);
|
||||
sqlite3_free(errorMessage);
|
||||
}
|
||||
|
||||
// Close database
|
||||
sqlite3_close(db);
|
||||
|
||||
// Remove temporary file
|
||||
std::error_code err;
|
||||
|
||||
if (!std::filesystem::remove(countyDatabaseCache, err)) {
|
||||
logger_->warn(
|
||||
"Unable to remove cached copy of database, error code: {} error category: {}",
|
||||
err.value(),
|
||||
err.category().name());
|
||||
std::error_code error;
|
||||
if (!std::filesystem::remove(countyDatabaseCache, error))
|
||||
{
|
||||
logger_->warn("Unable to remove cached copy of database: {}",
|
||||
error.message());
|
||||
}
|
||||
|
||||
initialized_ = true;
|
||||
|
|
@ -143,17 +184,52 @@ void Initialize()
|
|||
|
||||
std::string GetCountyName(const std::string& id)
|
||||
{
|
||||
std::shared_lock lock(countyMutex_);
|
||||
|
||||
auto it = countyMap_.find(id);
|
||||
if (it != countyMap_.cend())
|
||||
if (id.length() > 3)
|
||||
{
|
||||
return it->second;
|
||||
// SSFNNN
|
||||
char format = id.at(2);
|
||||
std::string state = id.substr(0, 2);
|
||||
|
||||
auto stateIt = countyDatabase_.find(format);
|
||||
if (stateIt != countyDatabase_.cend())
|
||||
{
|
||||
StateMap& states = stateIt->second;
|
||||
auto countyIt = states.find(state);
|
||||
if (countyIt != states.cend())
|
||||
{
|
||||
CountyMap& counties = countyIt->second;
|
||||
auto it = counties.find(id);
|
||||
if (it != counties.cend())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::string>
|
||||
GetCounties(const std::string& state)
|
||||
{
|
||||
std::unordered_map<std::string, std::string> counties {};
|
||||
|
||||
StateMap& states = countyDatabase_.at('C');
|
||||
auto it = states.find(state);
|
||||
if (it != states.cend())
|
||||
{
|
||||
counties = it->second;
|
||||
}
|
||||
|
||||
return counties;
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, std::string>& GetStates()
|
||||
{
|
||||
return stateMap_;
|
||||
}
|
||||
|
||||
} // namespace CountyDatabase
|
||||
} // namespace config
|
||||
} // namespace qt
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace scwx
|
||||
|
|
@ -15,6 +16,9 @@ namespace CountyDatabase
|
|||
|
||||
void Initialize();
|
||||
std::string GetCountyName(const std::string& id);
|
||||
std::unordered_map<std::string, std::string>
|
||||
GetCounties(const std::string& state);
|
||||
const std::unordered_map<std::string, std::string>& GetStates();
|
||||
|
||||
} // namespace CountyDatabase
|
||||
} // namespace config
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#define _SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING
|
||||
|
||||
#include <scwx/qt/config/county_database.hpp>
|
||||
#include <scwx/qt/config/radar_site.hpp>
|
||||
#include <scwx/qt/main/main_window.hpp>
|
||||
#include <scwx/qt/main/versions.hpp>
|
||||
|
|
@ -72,6 +73,7 @@ int main(int argc, char* argv[])
|
|||
|
||||
// Initialize application
|
||||
scwx::qt::config::RadarSite::Initialize();
|
||||
scwx::qt::config::CountyDatabase::Initialize();
|
||||
scwx::qt::manager::SettingsManager::Instance().Initialize();
|
||||
|
||||
// Theme
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <scwx/qt/main/application.hpp>
|
||||
#include <scwx/qt/main/versions.hpp>
|
||||
#include <scwx/qt/manager/alert_manager.hpp>
|
||||
#include <scwx/qt/manager/placefile_manager.hpp>
|
||||
#include <scwx/qt/manager/position_manager.hpp>
|
||||
#include <scwx/qt/manager/radar_product_manager.hpp>
|
||||
|
|
@ -82,6 +83,7 @@ public:
|
|||
radarSiteDialog_ {nullptr},
|
||||
settingsDialog_ {nullptr},
|
||||
updateDialog_ {nullptr},
|
||||
alertManager_ {manager::AlertManager::Instance()},
|
||||
placefileManager_ {manager::PlacefileManager::Instance()},
|
||||
positionManager_ {manager::PositionManager::Instance()},
|
||||
textEventManager_ {manager::TextEventManager::Instance()},
|
||||
|
|
@ -178,6 +180,7 @@ public:
|
|||
ui::SettingsDialog* settingsDialog_;
|
||||
ui::UpdateDialog* updateDialog_;
|
||||
|
||||
std::shared_ptr<manager::AlertManager> alertManager_;
|
||||
std::shared_ptr<manager::PlacefileManager> placefileManager_;
|
||||
std::shared_ptr<manager::PositionManager> positionManager_;
|
||||
std::shared_ptr<manager::TextEventManager> textEventManager_;
|
||||
|
|
|
|||
197
scwx-qt/source/scwx/qt/manager/alert_manager.cpp
Normal file
197
scwx-qt/source/scwx/qt/manager/alert_manager.cpp
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
#include <scwx/qt/manager/alert_manager.hpp>
|
||||
#include <scwx/qt/manager/media_manager.hpp>
|
||||
#include <scwx/qt/manager/position_manager.hpp>
|
||||
#include <scwx/qt/manager/text_event_manager.hpp>
|
||||
#include <scwx/qt/settings/audio_settings.hpp>
|
||||
#include <scwx/qt/types/location_types.hpp>
|
||||
#include <scwx/qt/util/geographic_lib.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/thread_pool.hpp>
|
||||
#include <boost/uuid/random_generator.hpp>
|
||||
#include <QGeoPositionInfo>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
{
|
||||
namespace manager
|
||||
{
|
||||
|
||||
static const std::string logPrefix_ = "scwx::qt::manager::alert_manager";
|
||||
static const auto logger_ = scwx::util::Logger::Create(logPrefix_);
|
||||
|
||||
class AlertManager::Impl
|
||||
{
|
||||
public:
|
||||
explicit Impl(AlertManager* self) : self_ {self}
|
||||
{
|
||||
settings::AudioSettings& audioSettings =
|
||||
settings::AudioSettings::Instance();
|
||||
|
||||
UpdateLocationTracking(audioSettings.alert_location_method().GetValue());
|
||||
|
||||
audioSettings.alert_location_method().RegisterValueChangedCallback(
|
||||
[this](const std::string& value) { UpdateLocationTracking(value); });
|
||||
|
||||
QObject::connect(
|
||||
textEventManager_.get(),
|
||||
&manager::TextEventManager::AlertUpdated,
|
||||
self_,
|
||||
[this](const types::TextEventKey& key, size_t messageIndex)
|
||||
{
|
||||
boost::asio::post(threadPool_,
|
||||
[=, this]() { HandleAlert(key, messageIndex); });
|
||||
});
|
||||
}
|
||||
|
||||
~Impl() { threadPool_.join(); }
|
||||
|
||||
common::Coordinate
|
||||
CurrentCoordinate(types::LocationMethod locationMethod) const;
|
||||
void HandleAlert(const types::TextEventKey& key, size_t messageIndex) const;
|
||||
void UpdateLocationTracking(const std::string& value) const;
|
||||
|
||||
boost::asio::thread_pool threadPool_ {1u};
|
||||
|
||||
AlertManager* self_;
|
||||
|
||||
boost::uuids::uuid uuid_ {boost::uuids::random_generator()()};
|
||||
|
||||
std::shared_ptr<MediaManager> mediaManager_ {MediaManager::Instance()};
|
||||
std::shared_ptr<PositionManager> positionManager_ {
|
||||
PositionManager::Instance()};
|
||||
std::shared_ptr<TextEventManager> textEventManager_ {
|
||||
TextEventManager::Instance()};
|
||||
};
|
||||
|
||||
AlertManager::AlertManager() : p(std::make_unique<Impl>(this)) {}
|
||||
AlertManager::~AlertManager() = default;
|
||||
|
||||
common::Coordinate AlertManager::Impl::CurrentCoordinate(
|
||||
types::LocationMethod locationMethod) const
|
||||
{
|
||||
settings::AudioSettings& audioSettings = settings::AudioSettings::Instance();
|
||||
common::Coordinate coordinate {};
|
||||
|
||||
if (locationMethod == types::LocationMethod::Fixed)
|
||||
{
|
||||
coordinate.latitude_ = audioSettings.alert_latitude().GetValue();
|
||||
coordinate.longitude_ = audioSettings.alert_longitude().GetValue();
|
||||
}
|
||||
else if (locationMethod == types::LocationMethod::Track)
|
||||
{
|
||||
QGeoPositionInfo position = positionManager_->position();
|
||||
if (position.isValid())
|
||||
{
|
||||
QGeoCoordinate trackedCoordinate = position.coordinate();
|
||||
coordinate.latitude_ = trackedCoordinate.latitude();
|
||||
coordinate.longitude_ = trackedCoordinate.longitude();
|
||||
}
|
||||
}
|
||||
|
||||
return coordinate;
|
||||
}
|
||||
|
||||
void AlertManager::Impl::HandleAlert(const types::TextEventKey& key,
|
||||
size_t messageIndex) const
|
||||
{
|
||||
// Skip alert if there are more messages to be processed
|
||||
if (messageIndex + 1 < textEventManager_->message_count(key))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
settings::AudioSettings& audioSettings = settings::AudioSettings::Instance();
|
||||
types::LocationMethod locationMethod = types::GetLocationMethod(
|
||||
audioSettings.alert_location_method().GetValue());
|
||||
common::Coordinate currentCoordinate = CurrentCoordinate(locationMethod);
|
||||
std::string alertCounty = audioSettings.alert_county().GetValue();
|
||||
|
||||
auto message = textEventManager_->message_list(key).at(messageIndex);
|
||||
|
||||
for (auto& segment : message->segments())
|
||||
{
|
||||
if (!segment->codedLocation_.has_value())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& vtec = segment->header_->vtecString_.front();
|
||||
auto action = vtec.pVtec_.action();
|
||||
awips::Phenomenon phenomenon = vtec.pVtec_.phenomenon();
|
||||
auto eventEnd = vtec.pVtec_.event_end();
|
||||
bool alertActive = (action != awips::PVtec::Action::Canceled);
|
||||
|
||||
// If the event has ended or is inactive, or if the alert is not enabled,
|
||||
// skip it
|
||||
if (eventEnd < std::chrono::system_clock::now() || !alertActive ||
|
||||
!audioSettings.alert_enabled(phenomenon).GetValue())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bool activeAtLocation = false;
|
||||
|
||||
if (locationMethod == types::LocationMethod::Fixed ||
|
||||
locationMethod == types::LocationMethod::Track)
|
||||
{
|
||||
|
||||
// Determine if the alert is active at the current coordinte
|
||||
auto alertCoordinates = segment->codedLocation_->coordinates();
|
||||
|
||||
activeAtLocation = util::GeographicLib::AreaContainsPoint(
|
||||
alertCoordinates, currentCoordinate);
|
||||
}
|
||||
else if (locationMethod == types::LocationMethod::County)
|
||||
{
|
||||
// Determine if the alert contains the current county
|
||||
auto fipsIds = segment->header_->ugc_.fips_ids();
|
||||
auto it = std::find(fipsIds.cbegin(), fipsIds.cend(), alertCounty);
|
||||
activeAtLocation = it != fipsIds.cend();
|
||||
}
|
||||
|
||||
if (activeAtLocation)
|
||||
{
|
||||
logger_->info("Alert active at current location: {} {}.{} {}",
|
||||
vtec.pVtec_.office_id(),
|
||||
awips::GetPhenomenonCode(vtec.pVtec_.phenomenon()),
|
||||
awips::PVtec::GetActionCode(vtec.pVtec_.action()),
|
||||
vtec.pVtec_.event_tracking_number());
|
||||
|
||||
mediaManager_->Play(audioSettings.alert_sound_file().GetValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AlertManager::Impl::UpdateLocationTracking(
|
||||
const std::string& locationMethodName) const
|
||||
{
|
||||
types::LocationMethod locationMethod =
|
||||
types::GetLocationMethod(locationMethodName);
|
||||
bool locationEnabled = locationMethod == types::LocationMethod::Track;
|
||||
positionManager_->EnablePositionUpdates(uuid_, locationEnabled);
|
||||
}
|
||||
|
||||
std::shared_ptr<AlertManager> AlertManager::Instance()
|
||||
{
|
||||
static std::weak_ptr<AlertManager> alertManagerReference_ {};
|
||||
static std::mutex instanceMutex_ {};
|
||||
|
||||
std::unique_lock lock(instanceMutex_);
|
||||
|
||||
std::shared_ptr<AlertManager> alertManager = alertManagerReference_.lock();
|
||||
|
||||
if (alertManager == nullptr)
|
||||
{
|
||||
alertManager = std::make_shared<AlertManager>();
|
||||
alertManagerReference_ = alertManager;
|
||||
}
|
||||
|
||||
return alertManager;
|
||||
}
|
||||
|
||||
} // namespace manager
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
32
scwx-qt/source/scwx/qt/manager/alert_manager.hpp
Normal file
32
scwx-qt/source/scwx/qt/manager/alert_manager.hpp
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
{
|
||||
namespace manager
|
||||
{
|
||||
|
||||
class AlertManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(AlertManager)
|
||||
|
||||
public:
|
||||
explicit AlertManager();
|
||||
~AlertManager();
|
||||
|
||||
static std::shared_ptr<AlertManager> Instance();
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> p;
|
||||
};
|
||||
|
||||
} // namespace manager
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
131
scwx-qt/source/scwx/qt/manager/media_manager.cpp
Normal file
131
scwx-qt/source/scwx/qt/manager/media_manager.cpp
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
#include <scwx/qt/manager/media_manager.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
|
||||
#include <QAudioDevice>
|
||||
#include <QAudioOutput>
|
||||
#include <QMediaDevices>
|
||||
#include <QMediaPlayer>
|
||||
#include <QUrl>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
{
|
||||
namespace manager
|
||||
{
|
||||
|
||||
static const std::string logPrefix_ = "scwx::qt::manager::media_manager";
|
||||
static const auto logger_ = scwx::util::Logger::Create(logPrefix_);
|
||||
|
||||
class MediaManager::Impl
|
||||
{
|
||||
public:
|
||||
explicit Impl(MediaManager* self) :
|
||||
self_ {self},
|
||||
mediaDevices_ {new QMediaDevices(self)},
|
||||
mediaPlayer_ {new QMediaPlayer(self)},
|
||||
audioOutput_ {new QAudioOutput(self)}
|
||||
{
|
||||
logger_->debug("Audio device: {}",
|
||||
audioOutput_->device().description().toStdString());
|
||||
|
||||
mediaPlayer_->setAudioOutput(audioOutput_);
|
||||
|
||||
ConnectSignals();
|
||||
}
|
||||
|
||||
~Impl() {}
|
||||
|
||||
void ConnectSignals();
|
||||
|
||||
MediaManager* self_;
|
||||
|
||||
QMediaDevices* mediaDevices_;
|
||||
QMediaPlayer* mediaPlayer_;
|
||||
QAudioOutput* audioOutput_;
|
||||
};
|
||||
|
||||
MediaManager::MediaManager() : p(std::make_unique<Impl>(this)) {}
|
||||
MediaManager::~MediaManager() = default;
|
||||
|
||||
void MediaManager::Impl::ConnectSignals()
|
||||
{
|
||||
QObject::connect(
|
||||
mediaDevices_,
|
||||
&QMediaDevices::audioOutputsChanged,
|
||||
self_,
|
||||
[this]()
|
||||
{ audioOutput_->setDevice(QMediaDevices::defaultAudioOutput()); });
|
||||
|
||||
QObject::connect(audioOutput_,
|
||||
&QAudioOutput::deviceChanged,
|
||||
self_,
|
||||
[this]()
|
||||
{
|
||||
logger_->debug(
|
||||
"Audio device changed: {}",
|
||||
audioOutput_->device().description().toStdString());
|
||||
});
|
||||
|
||||
QObject::connect(mediaPlayer_,
|
||||
&QMediaPlayer::errorOccurred,
|
||||
self_,
|
||||
[](QMediaPlayer::Error error, const QString& errorString)
|
||||
{
|
||||
logger_->error("Error {}: {}",
|
||||
static_cast<int>(error),
|
||||
errorString.toStdString());
|
||||
});
|
||||
}
|
||||
|
||||
void MediaManager::Play(types::AudioFile media)
|
||||
{
|
||||
const std::string path = types::GetMediaPath(media);
|
||||
}
|
||||
|
||||
void MediaManager::Play(const std::string& mediaPath)
|
||||
{
|
||||
logger_->debug("Playing audio: {}", mediaPath);
|
||||
|
||||
if (mediaPath.starts_with(':'))
|
||||
{
|
||||
p->mediaPlayer_->setSource(
|
||||
QUrl(QString("qrc%1").arg(QString::fromStdString(mediaPath))));
|
||||
}
|
||||
else
|
||||
{
|
||||
p->mediaPlayer_->setSource(
|
||||
QUrl::fromLocalFile(QString::fromStdString(mediaPath)));
|
||||
}
|
||||
|
||||
p->mediaPlayer_->setPosition(0);
|
||||
|
||||
QMetaObject::invokeMethod(p->mediaPlayer_, &QMediaPlayer::play);
|
||||
}
|
||||
|
||||
void MediaManager::Stop()
|
||||
{
|
||||
QMetaObject::invokeMethod(p->mediaPlayer_, &QMediaPlayer::stop);
|
||||
}
|
||||
|
||||
std::shared_ptr<MediaManager> MediaManager::Instance()
|
||||
{
|
||||
static std::weak_ptr<MediaManager> mediaManagerReference_ {};
|
||||
static std::mutex instanceMutex_ {};
|
||||
|
||||
std::unique_lock lock(instanceMutex_);
|
||||
|
||||
std::shared_ptr<MediaManager> mediaManager = mediaManagerReference_.lock();
|
||||
|
||||
if (mediaManager == nullptr)
|
||||
{
|
||||
mediaManager = std::make_shared<MediaManager>();
|
||||
mediaManagerReference_ = mediaManager;
|
||||
}
|
||||
|
||||
return mediaManager;
|
||||
}
|
||||
|
||||
} // namespace manager
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
38
scwx-qt/source/scwx/qt/manager/media_manager.hpp
Normal file
38
scwx-qt/source/scwx/qt/manager/media_manager.hpp
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
#include <scwx/qt/types/media_types.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
{
|
||||
namespace manager
|
||||
{
|
||||
|
||||
class MediaManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(MediaManager)
|
||||
|
||||
public:
|
||||
explicit MediaManager();
|
||||
~MediaManager();
|
||||
|
||||
void Play(types::AudioFile media);
|
||||
void Play(const std::string& mediaPath);
|
||||
void Stop();
|
||||
|
||||
static std::shared_ptr<MediaManager> Instance();
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> p;
|
||||
};
|
||||
|
||||
} // namespace manager
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
#include <scwx/qt/manager/resource_manager.hpp>
|
||||
#include <scwx/qt/manager/font_manager.hpp>
|
||||
#include <scwx/qt/config/county_database.hpp>
|
||||
#include <scwx/qt/model/imgui_context_model.hpp>
|
||||
#include <scwx/qt/types/texture_types.hpp>
|
||||
#include <scwx/qt/util/texture_atlas.hpp>
|
||||
|
|
@ -33,8 +32,6 @@ static const std::vector<std::pair<types::Font, std::string>> fontNames_ {
|
|||
|
||||
void Initialize()
|
||||
{
|
||||
config::CountyDatabase::Initialize();
|
||||
|
||||
LoadFonts();
|
||||
LoadTextures();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include <scwx/qt/manager/settings_manager.hpp>
|
||||
#include <scwx/qt/map/map_provider.hpp>
|
||||
#include <scwx/qt/settings/audio_settings.hpp>
|
||||
#include <scwx/qt/settings/general_settings.hpp>
|
||||
#include <scwx/qt/settings/map_settings.hpp>
|
||||
#include <scwx/qt/settings/palette_settings.hpp>
|
||||
|
|
@ -128,6 +129,7 @@ boost::json::value SettingsManager::Impl::ConvertSettingsToJson()
|
|||
boost::json::object settingsJson;
|
||||
|
||||
settings::GeneralSettings::Instance().WriteJson(settingsJson);
|
||||
settings::AudioSettings::Instance().WriteJson(settingsJson);
|
||||
settings::MapSettings::Instance().WriteJson(settingsJson);
|
||||
settings::PaletteSettings::Instance().WriteJson(settingsJson);
|
||||
settings::TextSettings::Instance().WriteJson(settingsJson);
|
||||
|
|
@ -141,6 +143,7 @@ void SettingsManager::Impl::GenerateDefaultSettings()
|
|||
logger_->info("Generating default settings");
|
||||
|
||||
settings::GeneralSettings::Instance().SetDefaults();
|
||||
settings::AudioSettings::Instance().SetDefaults();
|
||||
settings::MapSettings::Instance().SetDefaults();
|
||||
settings::PaletteSettings::Instance().SetDefaults();
|
||||
settings::TextSettings::Instance().SetDefaults();
|
||||
|
|
@ -155,6 +158,7 @@ bool SettingsManager::Impl::LoadSettings(
|
|||
bool jsonDirty = false;
|
||||
|
||||
jsonDirty |= !settings::GeneralSettings::Instance().ReadJson(settingsJson);
|
||||
jsonDirty |= !settings::AudioSettings::Instance().ReadJson(settingsJson);
|
||||
jsonDirty |= !settings::MapSettings::Instance().ReadJson(settingsJson);
|
||||
jsonDirty |= !settings::PaletteSettings::Instance().ReadJson(settingsJson);
|
||||
jsonDirty |= !settings::TextSettings::Instance().ReadJson(settingsJson);
|
||||
|
|
|
|||
172
scwx-qt/source/scwx/qt/settings/audio_settings.cpp
Normal file
172
scwx-qt/source/scwx/qt/settings/audio_settings.cpp
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
#include <scwx/qt/config/county_database.hpp>
|
||||
#include <scwx/qt/settings/audio_settings.hpp>
|
||||
#include <scwx/qt/settings/settings_definitions.hpp>
|
||||
#include <scwx/qt/settings/settings_variable.hpp>
|
||||
#include <scwx/qt/types/alert_types.hpp>
|
||||
#include <scwx/qt/types/location_types.hpp>
|
||||
#include <scwx/qt/types/media_types.hpp>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
{
|
||||
namespace settings
|
||||
{
|
||||
|
||||
static const std::string logPrefix_ = "scwx::qt::settings::audio_settings";
|
||||
|
||||
static const bool kDefaultAlertEnabled_ {false};
|
||||
static const awips::Phenomenon kDefaultPhenomenon_ {awips::Phenomenon::Unknown};
|
||||
|
||||
class AudioSettings::Impl
|
||||
{
|
||||
public:
|
||||
explicit Impl()
|
||||
{
|
||||
std::string defaultAlertSoundFileValue =
|
||||
types::GetMediaPath(types::AudioFile::EasAttentionSignal);
|
||||
std::string defaultAlertLocationMethodValue =
|
||||
types::GetLocationMethodName(types::LocationMethod::Fixed);
|
||||
|
||||
boost::to_lower(defaultAlertLocationMethodValue);
|
||||
|
||||
alertSoundFile_.SetDefault(defaultAlertSoundFileValue);
|
||||
alertLocationMethod_.SetDefault(defaultAlertLocationMethodValue);
|
||||
alertLatitude_.SetDefault(0.0);
|
||||
alertLongitude_.SetDefault(0.0);
|
||||
ignoreMissingCodecs_.SetDefault(false);
|
||||
|
||||
alertLatitude_.SetMinimum(-90.0);
|
||||
alertLatitude_.SetMaximum(90.0);
|
||||
alertLongitude_.SetMinimum(-180.0);
|
||||
alertLongitude_.SetMaximum(180.0);
|
||||
|
||||
alertLocationMethod_.SetValidator(
|
||||
SCWX_SETTINGS_ENUM_VALIDATOR(types::LocationMethod,
|
||||
types::LocationMethodIterator(),
|
||||
types::GetLocationMethodName));
|
||||
|
||||
alertCounty_.SetValidator(
|
||||
[](const std::string& value)
|
||||
{
|
||||
// Empty, or county exists in the database
|
||||
return value.empty() ||
|
||||
config::CountyDatabase::GetCountyName(value) != value;
|
||||
});
|
||||
|
||||
for (auto& phenomenon : types::GetAlertAudioPhenomena())
|
||||
{
|
||||
std::string phenomenonCode = awips::GetPhenomenonCode(phenomenon);
|
||||
std::string name = fmt::format("{}_enabled", phenomenonCode);
|
||||
|
||||
auto result =
|
||||
alertEnabled_.emplace(phenomenon, SettingsVariable<bool> {name});
|
||||
|
||||
SettingsVariable<bool>& variable = result.first->second;
|
||||
|
||||
variable.SetDefault(kDefaultAlertEnabled_);
|
||||
|
||||
variables_.push_back(&variable);
|
||||
}
|
||||
|
||||
// Create a default disabled alert, not stored in the settings file
|
||||
alertEnabled_.emplace(kDefaultPhenomenon_,
|
||||
SettingsVariable<bool> {"alert_disabled"});
|
||||
}
|
||||
|
||||
~Impl() {}
|
||||
|
||||
SettingsVariable<std::string> alertSoundFile_ {"alert_sound_file"};
|
||||
SettingsVariable<std::string> alertLocationMethod_ {"alert_location_method"};
|
||||
SettingsVariable<double> alertLatitude_ {"alert_latitude"};
|
||||
SettingsVariable<double> alertLongitude_ {"alert_longitude"};
|
||||
SettingsVariable<std::string> alertCounty_ {"alert_county"};
|
||||
SettingsVariable<bool> ignoreMissingCodecs_ {"ignore_missing_codecs"};
|
||||
|
||||
std::unordered_map<awips::Phenomenon, SettingsVariable<bool>>
|
||||
alertEnabled_ {};
|
||||
std::vector<SettingsVariableBase*> variables_ {};
|
||||
};
|
||||
|
||||
AudioSettings::AudioSettings() :
|
||||
SettingsCategory("audio"), p(std::make_unique<Impl>())
|
||||
{
|
||||
RegisterVariables({&p->alertSoundFile_,
|
||||
&p->alertLocationMethod_,
|
||||
&p->alertLatitude_,
|
||||
&p->alertLongitude_,
|
||||
&p->alertCounty_,
|
||||
&p->ignoreMissingCodecs_});
|
||||
RegisterVariables(p->variables_);
|
||||
SetDefaults();
|
||||
|
||||
p->variables_.clear();
|
||||
}
|
||||
AudioSettings::~AudioSettings() = default;
|
||||
|
||||
AudioSettings::AudioSettings(AudioSettings&&) noexcept = default;
|
||||
AudioSettings& AudioSettings::operator=(AudioSettings&&) noexcept = default;
|
||||
|
||||
SettingsVariable<std::string>& AudioSettings::alert_sound_file() const
|
||||
{
|
||||
return p->alertSoundFile_;
|
||||
}
|
||||
|
||||
SettingsVariable<std::string>& AudioSettings::alert_location_method() const
|
||||
{
|
||||
return p->alertLocationMethod_;
|
||||
}
|
||||
|
||||
SettingsVariable<double>& AudioSettings::alert_latitude() const
|
||||
{
|
||||
return p->alertLatitude_;
|
||||
}
|
||||
|
||||
SettingsVariable<double>& AudioSettings::alert_longitude() const
|
||||
{
|
||||
return p->alertLongitude_;
|
||||
}
|
||||
|
||||
SettingsVariable<std::string>& AudioSettings::alert_county() const
|
||||
{
|
||||
return p->alertCounty_;
|
||||
}
|
||||
|
||||
SettingsVariable<bool>&
|
||||
AudioSettings::alert_enabled(awips::Phenomenon phenomenon) const
|
||||
{
|
||||
auto alert = p->alertEnabled_.find(phenomenon);
|
||||
if (alert == p->alertEnabled_.cend())
|
||||
{
|
||||
alert = p->alertEnabled_.find(kDefaultPhenomenon_);
|
||||
}
|
||||
return alert->second;
|
||||
}
|
||||
|
||||
SettingsVariable<bool>& AudioSettings::ignore_missing_codecs() const
|
||||
{
|
||||
return p->ignoreMissingCodecs_;
|
||||
}
|
||||
|
||||
AudioSettings& AudioSettings::Instance()
|
||||
{
|
||||
static AudioSettings audioSettings_;
|
||||
return audioSettings_;
|
||||
}
|
||||
|
||||
bool operator==(const AudioSettings& lhs, const AudioSettings& rhs)
|
||||
{
|
||||
return (lhs.p->alertSoundFile_ == rhs.p->alertSoundFile_ &&
|
||||
lhs.p->alertLocationMethod_ == rhs.p->alertLocationMethod_ &&
|
||||
lhs.p->alertLatitude_ == rhs.p->alertLatitude_ &&
|
||||
lhs.p->alertLongitude_ == rhs.p->alertLongitude_ &&
|
||||
lhs.p->alertCounty_ == rhs.p->alertCounty_ &&
|
||||
lhs.p->alertEnabled_ == rhs.p->alertEnabled_);
|
||||
}
|
||||
|
||||
} // namespace settings
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
48
scwx-qt/source/scwx/qt/settings/audio_settings.hpp
Normal file
48
scwx-qt/source/scwx/qt/settings/audio_settings.hpp
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#pragma once
|
||||
|
||||
#include <scwx/qt/settings/settings_category.hpp>
|
||||
#include <scwx/qt/settings/settings_variable.hpp>
|
||||
#include <scwx/awips/phenomenon.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
{
|
||||
namespace settings
|
||||
{
|
||||
|
||||
class AudioSettings : public SettingsCategory
|
||||
{
|
||||
public:
|
||||
explicit AudioSettings();
|
||||
~AudioSettings();
|
||||
|
||||
AudioSettings(const AudioSettings&) = delete;
|
||||
AudioSettings& operator=(const AudioSettings&) = delete;
|
||||
|
||||
AudioSettings(AudioSettings&&) noexcept;
|
||||
AudioSettings& operator=(AudioSettings&&) noexcept;
|
||||
|
||||
SettingsVariable<std::string>& alert_sound_file() const;
|
||||
SettingsVariable<std::string>& alert_location_method() const;
|
||||
SettingsVariable<double>& alert_latitude() const;
|
||||
SettingsVariable<double>& alert_longitude() const;
|
||||
SettingsVariable<std::string>& alert_county() const;
|
||||
SettingsVariable<bool>& alert_enabled(awips::Phenomenon phenomenon) const;
|
||||
SettingsVariable<bool>& ignore_missing_codecs() const;
|
||||
|
||||
static AudioSettings& Instance();
|
||||
|
||||
friend bool operator==(const AudioSettings& lhs, const AudioSettings& rhs);
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> p;
|
||||
};
|
||||
|
||||
} // namespace settings
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
20
scwx-qt/source/scwx/qt/settings/settings_definitions.hpp
Normal file
20
scwx-qt/source/scwx/qt/settings/settings_definitions.hpp
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#define SCWX_SETTINGS_ENUM_VALIDATOR(Type, Iterator, ToName) \
|
||||
[](const std::string& value) \
|
||||
{ \
|
||||
for (Type enumValue : Iterator) \
|
||||
{ \
|
||||
/* If the value is equal to a lower case name */ \
|
||||
std::string enumName = ToName(enumValue); \
|
||||
boost::to_lower(enumName); \
|
||||
if (value == enumName) \
|
||||
{ \
|
||||
/* Regard as a match, valid */ \
|
||||
return true; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
/* No match found, invalid */ \
|
||||
return false; \
|
||||
}
|
||||
|
|
@ -180,6 +180,32 @@ void SettingsInterface<T>::SetEditWidget(QWidget* widget)
|
|||
// TODO: Display invalid status
|
||||
});
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, double>)
|
||||
{
|
||||
// If the line is edited (not programatically changed), stage the new
|
||||
// value
|
||||
QObject::connect(lineEdit,
|
||||
&QLineEdit::textEdited,
|
||||
p->context_.get(),
|
||||
[this](const QString& text)
|
||||
{
|
||||
// Convert to a double
|
||||
bool ok;
|
||||
double value = text.toDouble(&ok);
|
||||
if (ok)
|
||||
{
|
||||
// Attempt to stage the value
|
||||
p->stagedValid_ =
|
||||
p->variable_->StageValue(value);
|
||||
p->UpdateResetButton();
|
||||
}
|
||||
else
|
||||
{
|
||||
p->stagedValid_ = false;
|
||||
p->UpdateResetButton();
|
||||
}
|
||||
});
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, std::vector<std::int64_t>>)
|
||||
{
|
||||
// If the line is edited (not programatically changed), stage the new
|
||||
|
|
@ -310,6 +336,52 @@ void SettingsInterface<T>::SetEditWidget(QWidget* widget)
|
|||
});
|
||||
}
|
||||
}
|
||||
else if (QDoubleSpinBox* doubleSpinBox =
|
||||
dynamic_cast<QDoubleSpinBox*>(widget))
|
||||
{
|
||||
if constexpr (std::is_floating_point_v<T>)
|
||||
{
|
||||
const std::optional<T> minimum = p->variable_->GetMinimum();
|
||||
const std::optional<T> maximum = p->variable_->GetMaximum();
|
||||
|
||||
if (minimum.has_value())
|
||||
{
|
||||
doubleSpinBox->setMinimum(static_cast<double>(*minimum));
|
||||
}
|
||||
if (maximum.has_value())
|
||||
{
|
||||
doubleSpinBox->setMaximum(static_cast<double>(*maximum));
|
||||
}
|
||||
|
||||
// If the spin box is edited, stage a changed value
|
||||
QObject::connect(
|
||||
doubleSpinBox,
|
||||
&QDoubleSpinBox::valueChanged,
|
||||
p->context_.get(),
|
||||
[this](double d)
|
||||
{
|
||||
const T value = p->variable_->GetValue();
|
||||
const std::optional<T> staged = p->variable_->GetStaged();
|
||||
|
||||
// If there is a value staged, and the new value is the same as
|
||||
// the current value, reset the staged value
|
||||
if (staged.has_value() && static_cast<T>(d) == value)
|
||||
{
|
||||
p->variable_->Reset();
|
||||
p->stagedValid_ = true;
|
||||
p->UpdateResetButton();
|
||||
}
|
||||
// If there is no staged value, or if the new value is different
|
||||
// than what is staged, attempt to stage the value
|
||||
else if (!staged.has_value() || static_cast<T>(d) != *staged)
|
||||
{
|
||||
p->stagedValid_ = p->variable_->StageValue(static_cast<T>(d));
|
||||
p->UpdateResetButton();
|
||||
}
|
||||
// Otherwise, don't process an unchanged value
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
p->UpdateEditWidget();
|
||||
}
|
||||
|
|
@ -378,6 +450,10 @@ void SettingsInterface<T>::Impl::SetWidgetText(U* widget, const T& currentValue)
|
|||
{
|
||||
widget->setText(QString::number(currentValue));
|
||||
}
|
||||
else if constexpr (std::is_floating_point_v<T>)
|
||||
{
|
||||
widget->setText(QString::number(currentValue));
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, std::string>)
|
||||
{
|
||||
if (mapFromValue_ != nullptr)
|
||||
|
|
@ -448,6 +524,14 @@ void SettingsInterface<T>::Impl::UpdateEditWidget()
|
|||
spinBox->setValue(static_cast<int>(currentValue));
|
||||
}
|
||||
}
|
||||
else if (QDoubleSpinBox* doubleSpinBox =
|
||||
dynamic_cast<QDoubleSpinBox*>(editWidget_))
|
||||
{
|
||||
if constexpr (std::is_floating_point_v<T>)
|
||||
{
|
||||
doubleSpinBox->setValue(static_cast<double>(currentValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
|
|
|
|||
|
|
@ -37,6 +37,17 @@ std::string GetAlertActionName(AlertAction alertAction)
|
|||
return alertActionName_.at(alertAction);
|
||||
}
|
||||
|
||||
const std::vector<awips::Phenomenon>& GetAlertAudioPhenomena()
|
||||
{
|
||||
static const std::vector<awips::Phenomenon> phenomena_ {
|
||||
awips::Phenomenon::FlashFlood,
|
||||
awips::Phenomenon::SevereThunderstorm,
|
||||
awips::Phenomenon::SnowSquall,
|
||||
awips::Phenomenon::Tornado};
|
||||
|
||||
return phenomena_;
|
||||
}
|
||||
|
||||
} // namespace types
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include <scwx/awips/phenomenon.hpp>
|
||||
#include <scwx/util/iterator.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
|
|
@ -23,6 +25,8 @@ typedef scwx::util::Iterator<AlertAction, AlertAction::Go, AlertAction::View>
|
|||
AlertAction GetAlertAction(const std::string& name);
|
||||
std::string GetAlertActionName(AlertAction alertAction);
|
||||
|
||||
const std::vector<awips::Phenomenon>& GetAlertAudioPhenomena();
|
||||
|
||||
} // namespace types
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
|
|
|
|||
30
scwx-qt/source/scwx/qt/types/location_types.cpp
Normal file
30
scwx-qt/source/scwx/qt/types/location_types.cpp
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#include <scwx/qt/types/location_types.hpp>
|
||||
#include <scwx/util/enum.hpp>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
{
|
||||
namespace types
|
||||
{
|
||||
|
||||
static const std::unordered_map<LocationMethod, std::string>
|
||||
locationMethodName_ {{LocationMethod::Fixed, "Fixed"},
|
||||
{LocationMethod::Track, "Track"},
|
||||
{LocationMethod::County, "County"},
|
||||
{LocationMethod::Unknown, "?"}};
|
||||
|
||||
SCWX_GET_ENUM(LocationMethod, GetLocationMethod, locationMethodName_)
|
||||
|
||||
const std::string& GetLocationMethodName(LocationMethod locationMethod)
|
||||
{
|
||||
return locationMethodName_.at(locationMethod);
|
||||
}
|
||||
|
||||
} // namespace types
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
30
scwx-qt/source/scwx/qt/types/location_types.hpp
Normal file
30
scwx-qt/source/scwx/qt/types/location_types.hpp
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#pragma once
|
||||
|
||||
#include <scwx/util/iterator.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
{
|
||||
namespace types
|
||||
{
|
||||
|
||||
enum class LocationMethod
|
||||
{
|
||||
Fixed,
|
||||
Track,
|
||||
County,
|
||||
Unknown
|
||||
};
|
||||
typedef scwx::util::
|
||||
Iterator<LocationMethod, LocationMethod::Fixed, LocationMethod::County>
|
||||
LocationMethodIterator;
|
||||
|
||||
LocationMethod GetLocationMethod(const std::string& name);
|
||||
const std::string& GetLocationMethodName(LocationMethod locationMethod);
|
||||
|
||||
} // namespace types
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
24
scwx-qt/source/scwx/qt/types/media_types.cpp
Normal file
24
scwx-qt/source/scwx/qt/types/media_types.cpp
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#include <scwx/qt/types/media_types.hpp>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
{
|
||||
namespace types
|
||||
{
|
||||
|
||||
static const std::unordered_map<AudioFile, std::string> audioFileInfo_ {
|
||||
{AudioFile::EasAttentionSignal,
|
||||
":/res/audio/wikimedia/"
|
||||
"Emergency_Alert_System_Attention_Signal_20s.ogg"}};
|
||||
|
||||
const std::string& GetMediaPath(AudioFile audioFile)
|
||||
{
|
||||
return audioFileInfo_.at(audioFile);
|
||||
}
|
||||
|
||||
} // namespace types
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
27
scwx-qt/source/scwx/qt/types/media_types.hpp
Normal file
27
scwx-qt/source/scwx/qt/types/media_types.hpp
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include <scwx/util/iterator.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
{
|
||||
namespace types
|
||||
{
|
||||
|
||||
enum class AudioFile
|
||||
{
|
||||
EasAttentionSignal
|
||||
};
|
||||
typedef scwx::util::Iterator<AudioFile,
|
||||
AudioFile::EasAttentionSignal,
|
||||
AudioFile::EasAttentionSignal>
|
||||
AudioFileIterator;
|
||||
|
||||
const std::string& GetMediaPath(AudioFile audioFile);
|
||||
|
||||
} // namespace types
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
175
scwx-qt/source/scwx/qt/ui/county_dialog.cpp
Normal file
175
scwx-qt/source/scwx/qt/ui/county_dialog.cpp
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
#include "county_dialog.hpp"
|
||||
#include "ui_county_dialog.h"
|
||||
|
||||
#include <scwx/qt/config/county_database.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QStandardItemModel>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
{
|
||||
namespace ui
|
||||
{
|
||||
|
||||
static const std::string logPrefix_ = "scwx::qt::ui::county_dialog";
|
||||
static const auto logger_ = scwx::util::Logger::Create(logPrefix_);
|
||||
|
||||
class CountyDialog::Impl
|
||||
{
|
||||
public:
|
||||
explicit Impl(CountyDialog* self) :
|
||||
self_ {self},
|
||||
model_ {new QStandardItemModel(self)},
|
||||
proxyModel_ {new QSortFilterProxyModel(self)},
|
||||
states_ {config::CountyDatabase::GetStates()}
|
||||
{
|
||||
}
|
||||
~Impl() = default;
|
||||
|
||||
void UpdateModel(const std::string& stateName);
|
||||
|
||||
CountyDialog* self_;
|
||||
QStandardItemModel* model_;
|
||||
QSortFilterProxyModel* proxyModel_;
|
||||
|
||||
std::string selectedCounty_ {"?"};
|
||||
|
||||
const std::unordered_map<std::string, std::string>& states_;
|
||||
};
|
||||
|
||||
CountyDialog::CountyDialog(QWidget* parent) :
|
||||
QDialog(parent), p {std::make_unique<Impl>(this)}, ui(new Ui::CountyDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
for (auto& state : p->states_)
|
||||
{
|
||||
ui->stateComboBox->addItem(QString::fromStdString(state.second));
|
||||
}
|
||||
ui->stateComboBox->model()->sort(0);
|
||||
ui->stateComboBox->setCurrentIndex(0);
|
||||
|
||||
p->proxyModel_->setSourceModel(p->model_);
|
||||
ui->countyView->setModel(p->proxyModel_);
|
||||
ui->countyView->setEditTriggers(
|
||||
QAbstractItemView::EditTrigger::NoEditTriggers);
|
||||
ui->countyView->sortByColumn(0, Qt::SortOrder::AscendingOrder);
|
||||
ui->countyView->header()->setSectionResizeMode(
|
||||
QHeaderView::ResizeMode::Stretch);
|
||||
|
||||
connect(ui->stateComboBox,
|
||||
&QComboBox::currentTextChanged,
|
||||
this,
|
||||
[this](const QString& text) { p->UpdateModel(text.toStdString()); });
|
||||
p->UpdateModel(ui->stateComboBox->currentText().toStdString());
|
||||
|
||||
// Button Box
|
||||
ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok)
|
||||
->setEnabled(false);
|
||||
|
||||
connect(ui->countyView,
|
||||
&QTreeView::doubleClicked,
|
||||
this,
|
||||
[this]() { Q_EMIT accept(); });
|
||||
connect(
|
||||
ui->countyView->selectionModel(),
|
||||
&QItemSelectionModel::selectionChanged,
|
||||
this,
|
||||
[this](const QItemSelection& selected, const QItemSelection& deselected)
|
||||
{
|
||||
if (selected.size() == 0 && deselected.size() == 0)
|
||||
{
|
||||
// Items which stay selected but change their index are not
|
||||
// included in selected and deselected. Thus, this signal might
|
||||
// be emitted with both selected and deselected empty, if only
|
||||
// the indices of selected items change.
|
||||
return;
|
||||
}
|
||||
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)
|
||||
->setEnabled(selected.size() > 0);
|
||||
|
||||
if (selected.size() > 0)
|
||||
{
|
||||
QModelIndex selectedIndex =
|
||||
p->proxyModel_->mapToSource(selected[0].indexes()[0]);
|
||||
selectedIndex = p->model_->index(selectedIndex.row(), 1);
|
||||
QVariant variantData = p->model_->data(selectedIndex);
|
||||
if (variantData.typeId() == QMetaType::QString)
|
||||
{
|
||||
p->selectedCounty_ = variantData.toString().toStdString();
|
||||
}
|
||||
else
|
||||
{
|
||||
logger_->warn("Unexpected selection data type");
|
||||
p->selectedCounty_ = std::string {"?"};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
p->selectedCounty_ = std::string {"?"};
|
||||
}
|
||||
|
||||
logger_->debug("Selected: {}", p->selectedCounty_);
|
||||
});
|
||||
}
|
||||
|
||||
CountyDialog::~CountyDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
std::string CountyDialog::county_fips_id()
|
||||
{
|
||||
return p->selectedCounty_;
|
||||
}
|
||||
|
||||
void CountyDialog::SelectState(const std::string& state)
|
||||
{
|
||||
auto it = p->states_.find(state);
|
||||
if (it != p->states_.cend())
|
||||
{
|
||||
ui->stateComboBox->setCurrentText(QString::fromStdString(it->second));
|
||||
}
|
||||
}
|
||||
|
||||
void CountyDialog::Impl::UpdateModel(const std::string& stateName)
|
||||
{
|
||||
// Clear existing counties
|
||||
model_->clear();
|
||||
|
||||
// Reset selected county and disable OK button
|
||||
selectedCounty_ = std::string {"?"};
|
||||
self_->ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok)
|
||||
->setEnabled(false);
|
||||
|
||||
// Reset headers
|
||||
model_->setHorizontalHeaderLabels({tr("County / Area"), tr("FIPS ID")});
|
||||
|
||||
// Find the state ID from the statename
|
||||
auto it = std::find_if(states_.cbegin(),
|
||||
states_.cend(),
|
||||
[&](const std::pair<std::string, std::string>& record)
|
||||
{ return record.second == stateName; });
|
||||
|
||||
if (it != states_.cend())
|
||||
{
|
||||
QStandardItem* root = model_->invisibleRootItem();
|
||||
|
||||
// Add each county to the model
|
||||
for (auto& county : config::CountyDatabase::GetCounties(it->first))
|
||||
{
|
||||
root->appendRow(
|
||||
{new QStandardItem(QString::fromStdString(county.second)),
|
||||
new QStandardItem(QString::fromStdString(county.first))});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
37
scwx-qt/source/scwx/qt/ui/county_dialog.hpp
Normal file
37
scwx-qt/source/scwx/qt/ui/county_dialog.hpp
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class CountyDialog;
|
||||
}
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
{
|
||||
namespace ui
|
||||
{
|
||||
class CountyDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(CountyDialog)
|
||||
|
||||
public:
|
||||
explicit CountyDialog(QWidget* parent = nullptr);
|
||||
~CountyDialog();
|
||||
|
||||
std::string county_fips_id();
|
||||
|
||||
void SelectState(const std::string& state);
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> p;
|
||||
Ui::CountyDialog* ui;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
104
scwx-qt/source/scwx/qt/ui/county_dialog.ui
Normal file
104
scwx-qt/source/scwx/qt/ui/county_dialog.ui
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>CountyDialog</class>
|
||||
<widget class="QDialog" name="CountyDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>400</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Select County</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTreeView" name="countyView">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="indentation">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="frame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</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>
|
||||
<widget class="QComboBox" name="stateComboBox"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>CountyDialog</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>CountyDialog</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>
|
||||
|
|
@ -3,17 +3,23 @@
|
|||
|
||||
#include <scwx/awips/phenomenon.hpp>
|
||||
#include <scwx/common/color_table.hpp>
|
||||
#include <scwx/qt/config/county_database.hpp>
|
||||
#include <scwx/qt/config/radar_site.hpp>
|
||||
#include <scwx/qt/manager/media_manager.hpp>
|
||||
#include <scwx/qt/manager/position_manager.hpp>
|
||||
#include <scwx/qt/manager/settings_manager.hpp>
|
||||
#include <scwx/qt/map/map_provider.hpp>
|
||||
#include <scwx/qt/settings/audio_settings.hpp>
|
||||
#include <scwx/qt/settings/general_settings.hpp>
|
||||
#include <scwx/qt/settings/palette_settings.hpp>
|
||||
#include <scwx/qt/settings/settings_interface.hpp>
|
||||
#include <scwx/qt/settings/text_settings.hpp>
|
||||
#include <scwx/qt/types/alert_types.hpp>
|
||||
#include <scwx/qt/types/font_types.hpp>
|
||||
#include <scwx/qt/types/location_types.hpp>
|
||||
#include <scwx/qt/types/qt_types.hpp>
|
||||
#include <scwx/qt/types/text_types.hpp>
|
||||
#include <scwx/qt/ui/county_dialog.hpp>
|
||||
#include <scwx/qt/ui/radar_site_dialog.hpp>
|
||||
#include <scwx/qt/util/color.hpp>
|
||||
#include <scwx/qt/util/file.hpp>
|
||||
|
|
@ -26,6 +32,7 @@
|
|||
#include <QFileDialog>
|
||||
#include <QFontDatabase>
|
||||
#include <QFontDialog>
|
||||
#include <QGeoPositionInfo>
|
||||
#include <QStandardItemModel>
|
||||
#include <QToolButton>
|
||||
|
||||
|
|
@ -84,12 +91,31 @@ static const std::unordered_map<std::string, ColorTableConversions>
|
|||
{"VIL", {0u, 255u, 1.0f, 2.5f}},
|
||||
{"???", {0u, 15u, 0.0f, 1.0f}}};
|
||||
|
||||
#define SCWX_ENUM_MAP_FROM_VALUE(Type, Iterator, ToName) \
|
||||
[](const std::string& text) -> std::string \
|
||||
{ \
|
||||
for (Type enumValue : Iterator) \
|
||||
{ \
|
||||
const std::string enumName = ToName(enumValue); \
|
||||
\
|
||||
if (boost::iequals(text, enumName)) \
|
||||
{ \
|
||||
/* Return label */ \
|
||||
return enumName; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
/* Label not found, return unknown */ \
|
||||
return "?"; \
|
||||
}
|
||||
|
||||
class SettingsDialogImpl
|
||||
{
|
||||
public:
|
||||
explicit SettingsDialogImpl(SettingsDialog* self) :
|
||||
self_ {self},
|
||||
radarSiteDialog_ {new RadarSiteDialog(self)},
|
||||
countyDialog_ {new CountyDialog(self)},
|
||||
fontDialog_ {new QFontDialog(self)},
|
||||
fontCategoryModel_ {new QStandardItemModel(self)},
|
||||
settings_ {std::initializer_list<settings::SettingsInterfaceBase*> {
|
||||
|
|
@ -104,6 +130,11 @@ public:
|
|||
&antiAliasingEnabled_,
|
||||
&updateNotificationsEnabled_,
|
||||
&debugEnabled_,
|
||||
&alertAudioSoundFile_,
|
||||
&alertAudioLocationMethod_,
|
||||
&alertAudioLatitude_,
|
||||
&alertAudioLongitude_,
|
||||
&alertAudioCounty_,
|
||||
&hoverTextWrap_,
|
||||
&tooltipMethod_,
|
||||
&placefileTextDropShadowEnabled_}}
|
||||
|
|
@ -136,6 +167,7 @@ public:
|
|||
void SetupGeneralTab();
|
||||
void SetupPalettesColorTablesTab();
|
||||
void SetupPalettesAlertsTab();
|
||||
void SetupAudioTab();
|
||||
void SetupTextTab();
|
||||
|
||||
void ShowColorDialog(QLineEdit* lineEdit, QFrame* frame = nullptr);
|
||||
|
|
@ -164,12 +196,18 @@ public:
|
|||
|
||||
SettingsDialog* self_;
|
||||
RadarSiteDialog* radarSiteDialog_;
|
||||
CountyDialog* countyDialog_;
|
||||
QFontDialog* fontDialog_;
|
||||
|
||||
QStandardItemModel* fontCategoryModel_;
|
||||
|
||||
types::FontCategory selectedFontCategory_ {types::FontCategory::Unknown};
|
||||
|
||||
std::shared_ptr<manager::MediaManager> mediaManager_ {
|
||||
manager::MediaManager::Instance()};
|
||||
std::shared_ptr<manager::PositionManager> positionManager_ {
|
||||
manager::PositionManager::Instance()};
|
||||
|
||||
settings::SettingsInterface<std::string> defaultRadarSite_ {};
|
||||
settings::SettingsInterface<std::int64_t> gridWidth_ {};
|
||||
settings::SettingsInterface<std::int64_t> gridHeight_ {};
|
||||
|
|
@ -191,6 +229,15 @@ public:
|
|||
settings::SettingsInterface<std::string>>
|
||||
inactiveAlertColors_ {};
|
||||
|
||||
settings::SettingsInterface<std::string> alertAudioSoundFile_ {};
|
||||
settings::SettingsInterface<std::string> alertAudioLocationMethod_ {};
|
||||
settings::SettingsInterface<double> alertAudioLatitude_ {};
|
||||
settings::SettingsInterface<double> alertAudioLongitude_ {};
|
||||
settings::SettingsInterface<std::string> alertAudioCounty_ {};
|
||||
|
||||
std::unordered_map<awips::Phenomenon, settings::SettingsInterface<bool>>
|
||||
alertAudioEnabled_ {};
|
||||
|
||||
std::unordered_map<types::FontCategory,
|
||||
settings::SettingsInterface<std::string>>
|
||||
fontFamilies_ {};
|
||||
|
|
@ -223,6 +270,9 @@ SettingsDialog::SettingsDialog(QWidget* parent) :
|
|||
// Palettes > Alerts
|
||||
p->SetupPalettesAlertsTab();
|
||||
|
||||
// Audio
|
||||
p->SetupAudioTab();
|
||||
|
||||
// Text
|
||||
p->SetupTextTab();
|
||||
|
||||
|
|
@ -270,6 +320,20 @@ void SettingsDialogImpl::ConnectSignals()
|
|||
[this](const std::string& newValue)
|
||||
{ UpdateRadarDialogLocation(newValue); });
|
||||
|
||||
QObject::connect(
|
||||
self_->ui->alertAudioSoundTestButton,
|
||||
&QAbstractButton::clicked,
|
||||
self_,
|
||||
[this]()
|
||||
{
|
||||
mediaManager_->Play(
|
||||
self_->ui->alertAudioSoundLineEdit->text().toStdString());
|
||||
});
|
||||
QObject::connect(self_->ui->alertAudioSoundStopButton,
|
||||
&QAbstractButton::clicked,
|
||||
self_,
|
||||
[this]() { mediaManager_->Stop(); });
|
||||
|
||||
QObject::connect(
|
||||
self_->ui->fontListView->selectionModel(),
|
||||
&QItemSelectionModel::selectionChanged,
|
||||
|
|
@ -766,6 +830,211 @@ void SettingsDialogImpl::SetupPalettesAlertsTab()
|
|||
}
|
||||
}
|
||||
|
||||
void SettingsDialogImpl::SetupAudioTab()
|
||||
{
|
||||
QObject::connect(
|
||||
self_->ui->alertAudioLocationMethodComboBox,
|
||||
&QComboBox::currentTextChanged,
|
||||
self_,
|
||||
[this](const QString& text)
|
||||
{
|
||||
types::LocationMethod locationMethod =
|
||||
types::GetLocationMethod(text.toStdString());
|
||||
|
||||
bool coordinateEntryEnabled =
|
||||
locationMethod == types::LocationMethod::Fixed;
|
||||
bool countyEntryEnabled =
|
||||
locationMethod == types::LocationMethod::County;
|
||||
|
||||
self_->ui->alertAudioLatitudeSpinBox->setEnabled(
|
||||
coordinateEntryEnabled);
|
||||
self_->ui->alertAudioLongitudeSpinBox->setEnabled(
|
||||
coordinateEntryEnabled);
|
||||
self_->ui->resetAlertAudioLatitudeButton->setEnabled(
|
||||
coordinateEntryEnabled);
|
||||
self_->ui->resetAlertAudioLongitudeButton->setEnabled(
|
||||
coordinateEntryEnabled);
|
||||
|
||||
self_->ui->alertAudioCountyLineEdit->setEnabled(countyEntryEnabled);
|
||||
self_->ui->alertAudioCountySelectButton->setEnabled(
|
||||
countyEntryEnabled);
|
||||
self_->ui->resetAlertAudioCountyButton->setEnabled(countyEntryEnabled);
|
||||
});
|
||||
|
||||
settings::AudioSettings& audioSettings = settings::AudioSettings::Instance();
|
||||
|
||||
alertAudioSoundFile_.SetSettingsVariable(audioSettings.alert_sound_file());
|
||||
alertAudioSoundFile_.SetEditWidget(self_->ui->alertAudioSoundLineEdit);
|
||||
alertAudioSoundFile_.SetResetButton(self_->ui->resetAlertAudioSoundButton);
|
||||
|
||||
QObject::connect(
|
||||
self_->ui->alertAudioSoundSelectButton,
|
||||
&QAbstractButton::clicked,
|
||||
self_,
|
||||
[this]()
|
||||
{
|
||||
static const std::string audioFilter =
|
||||
"Audio Files (*.3ga *.669 *.a52 *.aac *.ac3 *.adt *.adts *.aif "
|
||||
"*.aifc *.aiff *.amb *.amr *.aob *.ape *.au *.awb *.caf *.dts "
|
||||
"*.flac *.it *.kar *.m4a *.m4b *.m4p *.m5p *.mid *.mka *.mlp *.mod "
|
||||
"*.mpa *.mp1 *.mp2 *.mp3 *.mpc *.mpga *.mus *.oga *.ogg *.oma "
|
||||
"*.opus *.qcp *.ra *.rmi *.s3m *.sid *.spx *.tak *.thd *.tta *.voc "
|
||||
"*.vqf *.w64 *.wav *.wma *.wv *.xa *.xm)";
|
||||
static const std::string allFilter = "All Files (*)";
|
||||
|
||||
QFileDialog* dialog = new QFileDialog(self_);
|
||||
|
||||
dialog->setFileMode(QFileDialog::ExistingFile);
|
||||
dialog->setNameFilters(
|
||||
{QObject::tr(audioFilter.c_str()), QObject::tr(allFilter.c_str())});
|
||||
dialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
QObject::connect(
|
||||
dialog,
|
||||
&QFileDialog::fileSelected,
|
||||
self_,
|
||||
[this](const QString& file)
|
||||
{
|
||||
QString path = QDir::toNativeSeparators(file);
|
||||
|
||||
logger_->info("Selected alert sound file: {}",
|
||||
path.toStdString());
|
||||
self_->ui->alertAudioSoundLineEdit->setText(path);
|
||||
|
||||
// setText does not emit the textEdited signal
|
||||
Q_EMIT self_->ui->alertAudioSoundLineEdit->textEdited(path);
|
||||
});
|
||||
|
||||
dialog->open();
|
||||
});
|
||||
|
||||
for (const auto& locationMethod : types::LocationMethodIterator())
|
||||
{
|
||||
self_->ui->alertAudioLocationMethodComboBox->addItem(
|
||||
QString::fromStdString(types::GetLocationMethodName(locationMethod)));
|
||||
}
|
||||
|
||||
alertAudioLocationMethod_.SetSettingsVariable(
|
||||
audioSettings.alert_location_method());
|
||||
alertAudioLocationMethod_.SetMapFromValueFunction(
|
||||
SCWX_ENUM_MAP_FROM_VALUE(types::LocationMethod,
|
||||
types::LocationMethodIterator(),
|
||||
types::GetLocationMethodName));
|
||||
alertAudioLocationMethod_.SetMapToValueFunction(
|
||||
[](std::string text) -> std::string
|
||||
{
|
||||
// Convert label to lower case and return
|
||||
boost::to_lower(text);
|
||||
return text;
|
||||
});
|
||||
alertAudioLocationMethod_.SetEditWidget(
|
||||
self_->ui->alertAudioLocationMethodComboBox);
|
||||
alertAudioLocationMethod_.SetResetButton(
|
||||
self_->ui->resetAlertAudioLocationMethodButton);
|
||||
|
||||
alertAudioLatitude_.SetSettingsVariable(audioSettings.alert_latitude());
|
||||
alertAudioLatitude_.SetEditWidget(self_->ui->alertAudioLatitudeSpinBox);
|
||||
alertAudioLatitude_.SetResetButton(self_->ui->resetAlertAudioLatitudeButton);
|
||||
|
||||
alertAudioLongitude_.SetSettingsVariable(audioSettings.alert_longitude());
|
||||
alertAudioLongitude_.SetEditWidget(self_->ui->alertAudioLongitudeSpinBox);
|
||||
alertAudioLongitude_.SetResetButton(
|
||||
self_->ui->resetAlertAudioLongitudeButton);
|
||||
|
||||
auto alertAudioLayout =
|
||||
static_cast<QGridLayout*>(self_->ui->alertAudioGroupBox->layout());
|
||||
|
||||
for (const auto& phenomenon : types::GetAlertAudioPhenomena())
|
||||
{
|
||||
QCheckBox* alertAudioCheckbox = new QCheckBox(self_);
|
||||
alertAudioCheckbox->setText(
|
||||
QString::fromStdString(awips::GetPhenomenonText(phenomenon)));
|
||||
|
||||
static_cast<QGridLayout*>(self_->ui->alertAudioGroupBox->layout())
|
||||
->addWidget(
|
||||
alertAudioCheckbox, alertAudioLayout->rowCount(), 0, 1, -1);
|
||||
|
||||
// Create settings interface
|
||||
auto result = alertAudioEnabled_.emplace(
|
||||
phenomenon, settings::SettingsInterface<bool> {});
|
||||
auto& alertAudioEnabled = result.first->second;
|
||||
|
||||
// Add to settings list
|
||||
settings_.push_back(&alertAudioEnabled);
|
||||
|
||||
alertAudioEnabled.SetSettingsVariable(
|
||||
audioSettings.alert_enabled(phenomenon));
|
||||
alertAudioEnabled.SetEditWidget(alertAudioCheckbox);
|
||||
}
|
||||
|
||||
QObject::connect(
|
||||
positionManager_.get(),
|
||||
&manager::PositionManager::PositionUpdated,
|
||||
self_,
|
||||
[this](const QGeoPositionInfo& info)
|
||||
{
|
||||
settings::AudioSettings& audioSettings =
|
||||
settings::AudioSettings::Instance();
|
||||
|
||||
if (info.isValid() &&
|
||||
types::GetLocationMethod(
|
||||
audioSettings.alert_location_method().GetValue()) ==
|
||||
types::LocationMethod::Track)
|
||||
{
|
||||
QGeoCoordinate coordinate = info.coordinate();
|
||||
self_->ui->alertAudioLatitudeSpinBox->setValue(
|
||||
coordinate.latitude());
|
||||
self_->ui->alertAudioLongitudeSpinBox->setValue(
|
||||
coordinate.longitude());
|
||||
}
|
||||
});
|
||||
|
||||
QObject::connect(
|
||||
self_->ui->alertAudioCountySelectButton,
|
||||
&QAbstractButton::clicked,
|
||||
self_,
|
||||
[this]()
|
||||
{
|
||||
std::string countyId =
|
||||
self_->ui->alertAudioCountyLineEdit->text().toStdString();
|
||||
|
||||
if (countyId.length() >= 2)
|
||||
{
|
||||
countyDialog_->SelectState(countyId.substr(0, 2));
|
||||
}
|
||||
|
||||
countyDialog_->show();
|
||||
});
|
||||
QObject::connect(countyDialog_,
|
||||
&CountyDialog::accepted,
|
||||
self_,
|
||||
[this]()
|
||||
{
|
||||
std::string countyId = countyDialog_->county_fips_id();
|
||||
QString qCountyId = QString::fromStdString(countyId);
|
||||
self_->ui->alertAudioCountyLineEdit->setText(qCountyId);
|
||||
|
||||
// setText does not emit the textEdited signal
|
||||
Q_EMIT self_->ui->alertAudioCountyLineEdit->textEdited(
|
||||
qCountyId);
|
||||
});
|
||||
QObject::connect(self_->ui->alertAudioCountyLineEdit,
|
||||
&QLineEdit::textChanged,
|
||||
self_,
|
||||
[this](const QString& text)
|
||||
{
|
||||
std::string countyName =
|
||||
config::CountyDatabase::GetCountyName(
|
||||
text.toStdString());
|
||||
self_->ui->alertAudioCountyLabel->setText(
|
||||
QString::fromStdString(countyName));
|
||||
});
|
||||
|
||||
alertAudioCounty_.SetSettingsVariable(audioSettings.alert_county());
|
||||
alertAudioCounty_.SetEditWidget(self_->ui->alertAudioCountyLineEdit);
|
||||
alertAudioCounty_.SetResetButton(self_->ui->resetAlertAudioCountyButton);
|
||||
}
|
||||
|
||||
void SettingsDialogImpl::SetupTextTab()
|
||||
{
|
||||
settings::TextSettings& textSettings = settings::TextSettings::Instance();
|
||||
|
|
|
|||
|
|
@ -77,6 +77,15 @@
|
|||
<normaloff>:/res/icons/font-awesome-6/palette-solid.svg</normaloff>:/res/icons/font-awesome-6/palette-solid.svg</iconset>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Audio</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../scwx-qt.qrc">
|
||||
<normaloff>:/res/icons/font-awesome-6/volume-high-solid.svg</normaloff>:/res/icons/font-awesome-6/volume-high-solid.svg</iconset>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Text</string>
|
||||
|
|
@ -364,8 +373,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>63</width>
|
||||
<height>18</height>
|
||||
<width>508</width>
|
||||
<height>383</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
|
|
@ -436,6 +445,223 @@
|
|||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="page">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="alertAudioGroupBox">
|
||||
<property name="title">
|
||||
<string>Alerts</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_10">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_14">
|
||||
<property name="text">
|
||||
<string>Latitude</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="6">
|
||||
<widget class="QToolButton" name="resetAlertAudioLocationMethodButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../scwx-qt.qrc">
|
||||
<normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_16">
|
||||
<property name="text">
|
||||
<string>Longitude</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<widget class="QToolButton" name="alertAudioSoundSelectButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_12">
|
||||
<property name="text">
|
||||
<string>Location Method</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="6">
|
||||
<widget class="QToolButton" name="resetAlertAudioLongitudeButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../scwx-qt.qrc">
|
||||
<normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="5">
|
||||
<widget class="QToolButton" name="alertAudioSoundStopButton">
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../scwx-qt.qrc">
|
||||
<normaloff>:/res/icons/font-awesome-6/stop-solid.svg</normaloff>:/res/icons/font-awesome-6/stop-solid.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="6">
|
||||
<widget class="QToolButton" name="resetAlertAudioLatitudeButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../scwx-qt.qrc">
|
||||
<normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="6">
|
||||
<widget class="QToolButton" name="resetAlertAudioSoundButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../scwx-qt.qrc">
|
||||
<normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="4">
|
||||
<widget class="QToolButton" name="alertAudioSoundTestButton">
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../scwx-qt.qrc">
|
||||
<normaloff>:/res/icons/font-awesome-6/play-solid.svg</normaloff>:/res/icons/font-awesome-6/play-solid.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_17">
|
||||
<property name="text">
|
||||
<string>Sound</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_19">
|
||||
<property name="text">
|
||||
<string>County</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1" colspan="2">
|
||||
<widget class="QDoubleSpinBox" name="alertAudioLongitudeSpinBox">
|
||||
<property name="decimals">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>-180.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>180.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.000100000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" colspan="2">
|
||||
<widget class="QDoubleSpinBox" name="alertAudioLatitudeSpinBox">
|
||||
<property name="decimals">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>-90.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>90.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.000100000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" colspan="2">
|
||||
<widget class="QComboBox" name="alertAudioLocationMethodComboBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="2">
|
||||
<widget class="QLineEdit" name="alertAudioSoundLineEdit"/>
|
||||
</item>
|
||||
<item row="4" column="6">
|
||||
<widget class="QToolButton" name="resetAlertAudioCountyButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../scwx-qt.qrc">
|
||||
<normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="3">
|
||||
<widget class="QToolButton" name="alertAudioCountySelectButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
<widget class="QLabel" name="alertAudioCountyLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="alertAudioCountyLineEdit">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_6">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>253</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="text">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
|
|
|
|||
176
scwx-qt/source/scwx/qt/ui/setup/audio_codec_page.cpp
Normal file
176
scwx-qt/source/scwx/qt/ui/setup/audio_codec_page.cpp
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
#include <scwx/qt/ui/setup/audio_codec_page.hpp>
|
||||
#include <scwx/qt/manager/settings_manager.hpp>
|
||||
#include <scwx/qt/settings/audio_settings.hpp>
|
||||
#include <scwx/qt/settings/settings_interface.hpp>
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QDesktopServices>
|
||||
#include <QLabel>
|
||||
#include <QMediaFormat>
|
||||
#include <QScrollArea>
|
||||
#include <QSpacerItem>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
{
|
||||
namespace ui
|
||||
{
|
||||
namespace setup
|
||||
{
|
||||
|
||||
class AudioCodecPage::Impl
|
||||
{
|
||||
public:
|
||||
explicit Impl(AudioCodecPage* self) : self_ {self} {};
|
||||
~Impl() = default;
|
||||
|
||||
void SetupSettingsInterface();
|
||||
void SetInstructionsLabelText();
|
||||
|
||||
AudioCodecPage* self_;
|
||||
|
||||
QLayout* layout_ {};
|
||||
QLayout* topLayout_ {};
|
||||
|
||||
QScrollArea* scrollArea_ {};
|
||||
QWidget* contents_ {};
|
||||
QLabel* descriptionLabel_ {};
|
||||
QLabel* instructionsLabel_ {};
|
||||
QCheckBox* ignoreMissingCodecsCheckBox_ {};
|
||||
QSpacerItem* spacer_ {};
|
||||
|
||||
settings::SettingsInterface<bool> ignoreMissingCodecs_ {};
|
||||
};
|
||||
|
||||
AudioCodecPage::AudioCodecPage(QWidget* parent) :
|
||||
QWizardPage(parent), p {std::make_shared<Impl>(this)}
|
||||
{
|
||||
setTitle(tr("Media Codecs"));
|
||||
setSubTitle(tr("Configure system media settings for Supercell Wx."));
|
||||
|
||||
p->descriptionLabel_ = new QLabel(this);
|
||||
p->instructionsLabel_ = new QLabel(this);
|
||||
p->ignoreMissingCodecsCheckBox_ = new QCheckBox(this);
|
||||
|
||||
// Description
|
||||
p->descriptionLabel_->setText(
|
||||
tr("Your system does not have the proper codecs installed in order to "
|
||||
"play the default audio. You may either install the proper codecs, or "
|
||||
"update Supercell Wx audio settings to change from the default audio "
|
||||
"files. After installing the proper codecs, you must restart "
|
||||
"Supercell Wx."));
|
||||
p->descriptionLabel_->setWordWrap(true);
|
||||
p->SetInstructionsLabelText();
|
||||
p->instructionsLabel_->setWordWrap(true);
|
||||
|
||||
p->ignoreMissingCodecsCheckBox_->setText(tr("Ignore missing codecs"));
|
||||
|
||||
p->spacer_ =
|
||||
new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
|
||||
// Overall layout
|
||||
p->layout_ = new QVBoxLayout(this);
|
||||
p->layout_->addWidget(p->descriptionLabel_);
|
||||
p->layout_->addWidget(p->instructionsLabel_);
|
||||
p->layout_->addWidget(p->ignoreMissingCodecsCheckBox_);
|
||||
p->layout_->addItem(p->spacer_);
|
||||
|
||||
p->contents_ = new QWidget(this);
|
||||
p->contents_->setLayout(p->layout_);
|
||||
|
||||
p->scrollArea_ = new QScrollArea(this);
|
||||
p->scrollArea_->setHorizontalScrollBarPolicy(
|
||||
Qt::ScrollBarPolicy::ScrollBarAlwaysOff);
|
||||
p->scrollArea_->setFrameShape(QFrame::Shape::NoFrame);
|
||||
p->scrollArea_->setWidgetResizable(true);
|
||||
p->scrollArea_->setWidget(p->contents_);
|
||||
|
||||
p->topLayout_ = new QVBoxLayout(this);
|
||||
p->topLayout_->setContentsMargins(0, 0, 0, 0);
|
||||
p->topLayout_->addWidget(p->scrollArea_);
|
||||
|
||||
setLayout(p->topLayout_);
|
||||
|
||||
// Configure settings interface
|
||||
p->SetupSettingsInterface();
|
||||
}
|
||||
|
||||
AudioCodecPage::~AudioCodecPage() = default;
|
||||
|
||||
void AudioCodecPage::Impl::SetInstructionsLabelText()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
instructionsLabel_->setText(tr(
|
||||
"<p><b>Option 1</b></p>" //
|
||||
"<p>Update your Windows installation. The required media codecs may "
|
||||
"be available with the latest operating system updates.</p>" //
|
||||
"<p><b>Option 2</b></p>" //
|
||||
"<p>Install the <a "
|
||||
"href=\"https://www.microsoft.com/store/productId/9N5TDP8VCMHS\">Web "
|
||||
"Media Extensions</a> package from the Windows Store.</p>" //
|
||||
"<p><b>Option 3</b></p>" //
|
||||
"<p>Install <a "
|
||||
"href=\"https://www.codecguide.com/"
|
||||
"download_k-lite_codec_pack_basic.htm\">K-Lite Codec Pack "
|
||||
"Basic</a>. This is a 3rd party application, and no support or warranty "
|
||||
"is provided.</p>"));
|
||||
instructionsLabel_->setTextInteractionFlags(
|
||||
Qt::TextInteractionFlag::TextBrowserInteraction);
|
||||
|
||||
QObject::connect(instructionsLabel_,
|
||||
&QLabel::linkActivated,
|
||||
self_,
|
||||
[](const QString& link)
|
||||
{ QDesktopServices::openUrl(QUrl {link}); });
|
||||
#else
|
||||
instructionsLabel_->setText(
|
||||
tr("Please see the instructions for your Linux distribution for "
|
||||
"installing media codecs."));
|
||||
#endif
|
||||
}
|
||||
|
||||
void AudioCodecPage::Impl::SetupSettingsInterface()
|
||||
{
|
||||
auto& audioSettings = settings::AudioSettings::Instance();
|
||||
|
||||
ignoreMissingCodecs_.SetSettingsVariable(
|
||||
audioSettings.ignore_missing_codecs());
|
||||
ignoreMissingCodecs_.SetEditWidget(ignoreMissingCodecsCheckBox_);
|
||||
}
|
||||
|
||||
bool AudioCodecPage::validatePage()
|
||||
{
|
||||
bool committed = false;
|
||||
|
||||
committed |= p->ignoreMissingCodecs_.Commit();
|
||||
|
||||
if (committed)
|
||||
{
|
||||
manager::SettingsManager::Instance().SaveSettings();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioCodecPage::IsRequired()
|
||||
{
|
||||
auto& audioSettings = settings::AudioSettings::Instance();
|
||||
|
||||
bool ignoreCodecErrors = audioSettings.ignore_missing_codecs().GetValue();
|
||||
|
||||
QMediaFormat oggFormat {QMediaFormat::FileFormat::Ogg};
|
||||
auto oggCodecs =
|
||||
oggFormat.supportedAudioCodecs(QMediaFormat::ConversionMode::Decode);
|
||||
|
||||
// Setup is required if codec errors are not ignored, and the default codecs
|
||||
// are not supported
|
||||
return (!ignoreCodecErrors &&
|
||||
oggCodecs.contains(QMediaFormat::AudioCodec::Vorbis));
|
||||
}
|
||||
|
||||
} // namespace setup
|
||||
} // namespace ui
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
34
scwx-qt/source/scwx/qt/ui/setup/audio_codec_page.hpp
Normal file
34
scwx-qt/source/scwx/qt/ui/setup/audio_codec_page.hpp
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
|
||||
#include <QWizardPage>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
{
|
||||
namespace ui
|
||||
{
|
||||
namespace setup
|
||||
{
|
||||
|
||||
class AudioCodecPage : public QWizardPage
|
||||
{
|
||||
Q_DISABLE_COPY_MOVE(AudioCodecPage)
|
||||
|
||||
public:
|
||||
explicit AudioCodecPage(QWidget* parent = nullptr);
|
||||
~AudioCodecPage();
|
||||
|
||||
bool validatePage() override;
|
||||
|
||||
static bool IsRequired();
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::shared_ptr<Impl> p;
|
||||
};
|
||||
|
||||
} // namespace setup
|
||||
} // namespace ui
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
|
|
@ -278,6 +278,18 @@ bool MapProviderPage::validatePage()
|
|||
return true;
|
||||
}
|
||||
|
||||
bool MapProviderPage::IsRequired()
|
||||
{
|
||||
auto& generalSettings = settings::GeneralSettings::Instance();
|
||||
|
||||
std::string mapboxApiKey = generalSettings.mapbox_api_key().GetValue();
|
||||
std::string maptilerApiKey = generalSettings.maptiler_api_key().GetValue();
|
||||
|
||||
// Setup is required if either API key is empty, or contains a single
|
||||
// character ("?")
|
||||
return (mapboxApiKey.size() <= 1 && maptilerApiKey.size() <= 1);
|
||||
}
|
||||
|
||||
} // namespace setup
|
||||
} // namespace ui
|
||||
} // namespace qt
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ public:
|
|||
bool isComplete() const override;
|
||||
bool validatePage() override;
|
||||
|
||||
static bool IsRequired();
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::shared_ptr<Impl> p;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
#include <scwx/qt/ui/setup/setup_wizard.hpp>
|
||||
#include <scwx/qt/ui/setup/audio_codec_page.hpp>
|
||||
#include <scwx/qt/ui/setup/finish_page.hpp>
|
||||
#include <scwx/qt/ui/setup/map_layout_page.hpp>
|
||||
#include <scwx/qt/ui/setup/map_provider_page.hpp>
|
||||
#include <scwx/qt/ui/setup/welcome_page.hpp>
|
||||
#include <scwx/qt/settings/general_settings.hpp>
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QUrl>
|
||||
|
|
@ -38,6 +38,7 @@ SetupWizard::SetupWizard(QWidget* parent) :
|
|||
setPage(static_cast<int>(Page::Welcome), new WelcomePage(this));
|
||||
setPage(static_cast<int>(Page::MapProvider), new MapProviderPage(this));
|
||||
setPage(static_cast<int>(Page::MapLayout), new MapLayoutPage(this));
|
||||
setPage(static_cast<int>(Page::AudioCodec), new AudioCodecPage(this));
|
||||
setPage(static_cast<int>(Page::Finish), new FinishPage(this));
|
||||
|
||||
#if !defined(Q_OS_MAC)
|
||||
|
|
@ -55,16 +56,43 @@ SetupWizard::SetupWizard(QWidget* parent) :
|
|||
|
||||
SetupWizard::~SetupWizard() = default;
|
||||
|
||||
int SetupWizard::nextId() const
|
||||
{
|
||||
int nextId = currentId();
|
||||
|
||||
while (true)
|
||||
{
|
||||
switch (++nextId)
|
||||
{
|
||||
case static_cast<int>(Page::MapProvider):
|
||||
case static_cast<int>(Page::MapLayout):
|
||||
if (MapProviderPage::IsRequired())
|
||||
{
|
||||
return nextId;
|
||||
}
|
||||
break;
|
||||
|
||||
case static_cast<int>(Page::AudioCodec):
|
||||
if (AudioCodecPage::IsRequired())
|
||||
{
|
||||
return nextId;
|
||||
}
|
||||
break;
|
||||
|
||||
case static_cast<int>(Page::Finish):
|
||||
return nextId;
|
||||
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool SetupWizard::IsSetupRequired()
|
||||
{
|
||||
auto& generalSettings = settings::GeneralSettings::Instance();
|
||||
|
||||
std::string mapboxApiKey = generalSettings.mapbox_api_key().GetValue();
|
||||
std::string maptilerApiKey = generalSettings.maptiler_api_key().GetValue();
|
||||
|
||||
// Setup is required if either API key is empty, or contains a single
|
||||
// character ("?")
|
||||
return (mapboxApiKey.size() <= 1 && maptilerApiKey.size() <= 1);
|
||||
return (MapProviderPage::IsRequired() || AudioCodecPage::IsRequired());
|
||||
}
|
||||
|
||||
} // namespace setup
|
||||
|
|
|
|||
|
|
@ -19,12 +19,15 @@ public:
|
|||
Welcome = 0,
|
||||
MapProvider,
|
||||
MapLayout,
|
||||
AudioCodec,
|
||||
Finish
|
||||
};
|
||||
|
||||
explicit SetupWizard(QWidget* parent = nullptr);
|
||||
~SetupWizard();
|
||||
|
||||
int nextId() const override;
|
||||
|
||||
static bool IsSetupRequired();
|
||||
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
#include <scwx/qt/util/geographic_lib.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
|
||||
#include <GeographicLib/Gnomonic.hpp>
|
||||
#include <geos/algorithm/PointLocation.h>
|
||||
#include <geos/geom/CoordinateSequence.h>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
|
|
@ -9,6 +14,9 @@ namespace util
|
|||
namespace GeographicLib
|
||||
{
|
||||
|
||||
static const std::string logPrefix_ = "scwx::qt::util::geographic_lib";
|
||||
static const auto logger_ = scwx::util::Logger::Create(logPrefix_);
|
||||
|
||||
const ::GeographicLib::Geodesic& DefaultGeodesic()
|
||||
{
|
||||
static const ::GeographicLib::Geodesic geodesic_ {
|
||||
|
|
@ -18,6 +26,60 @@ const ::GeographicLib::Geodesic& DefaultGeodesic()
|
|||
return geodesic_;
|
||||
}
|
||||
|
||||
bool AreaContainsPoint(const std::vector<common::Coordinate>& area,
|
||||
const common::Coordinate& point)
|
||||
{
|
||||
// Cannot have an area with just two points
|
||||
if (area.size() <= 2 || (area.size() == 3 && area.front() == area.back()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
::GeographicLib::Gnomonic gnomonic {};
|
||||
geos::geom::CoordinateSequence sequence {};
|
||||
double x;
|
||||
double y;
|
||||
bool areaContainsPoint = false;
|
||||
|
||||
// Using a gnomonic projection with the test point as the center
|
||||
// latitude/longitude, the projected test point will be at (0, 0)
|
||||
geos::geom::CoordinateXY zero {};
|
||||
|
||||
// Create the area coordinate sequence using a gnomonic projection
|
||||
for (auto& areaCoordinate : area)
|
||||
{
|
||||
gnomonic.Forward(point.latitude_,
|
||||
point.longitude_,
|
||||
areaCoordinate.latitude_,
|
||||
areaCoordinate.longitude_,
|
||||
x,
|
||||
y);
|
||||
sequence.add(x, y);
|
||||
}
|
||||
|
||||
// If the sequence is not a ring, add the first point again for closure
|
||||
if (!sequence.isRing())
|
||||
{
|
||||
sequence.add(sequence.front(), false);
|
||||
}
|
||||
|
||||
// The sequence should be a ring at this point, but make sure
|
||||
if (sequence.isRing())
|
||||
{
|
||||
try
|
||||
{
|
||||
areaContainsPoint =
|
||||
geos::algorithm::PointLocation::isInRing(zero, &sequence);
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
logger_->trace("Invalid area sequence");
|
||||
}
|
||||
}
|
||||
|
||||
return areaContainsPoint;
|
||||
}
|
||||
|
||||
units::angle::degrees<double>
|
||||
GetAngle(double lat1, double lon1, double lat2, double lon2)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include <scwx/common/geographic.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <GeographicLib/Geodesic.hpp>
|
||||
#include <units/angle.h>
|
||||
#include <units/length.h>
|
||||
|
|
@ -20,6 +24,18 @@ namespace GeographicLib
|
|||
*/
|
||||
const ::GeographicLib::Geodesic& DefaultGeodesic();
|
||||
|
||||
/**
|
||||
* Determine if an area/ring, oriented in either direction, contains a point. A
|
||||
* point lying on the area boundary is considered to be inside the area.
|
||||
*
|
||||
* @param [in] area A vector of Coordinates representing the area
|
||||
* @param [in] point The point to check against the area
|
||||
*
|
||||
* @return true if point is inside the area
|
||||
*/
|
||||
bool AreaContainsPoint(const std::vector<common::Coordinate>& area,
|
||||
const common::Coordinate& point);
|
||||
|
||||
/**
|
||||
* Get the angle between two points.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -26,6 +26,14 @@ def ParseArguments():
|
|||
nargs = "+",
|
||||
default = [],
|
||||
type = pathlib.Path)
|
||||
parser.add_argument("-s", "--state_dbf",
|
||||
metavar = "filename",
|
||||
help = "input state database",
|
||||
dest = "inputStateDbs_",
|
||||
action = "extend",
|
||||
nargs = "+",
|
||||
default = [],
|
||||
type = pathlib.Path)
|
||||
parser.add_argument("-o", "--output_db",
|
||||
metavar = "filename",
|
||||
help = "output sqlite database",
|
||||
|
|
@ -47,10 +55,13 @@ def Prepare(dbInfo, outputDb):
|
|||
|
||||
dbInfo.sqlCursor_ = dbInfo.sqlConnection_.cursor()
|
||||
|
||||
# Create database table
|
||||
# Create database tables
|
||||
dbInfo.sqlCursor_.execute("""CREATE TABLE counties(
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
name TEXT)""")
|
||||
dbInfo.sqlCursor_.execute("""CREATE TABLE states(
|
||||
state TEXT NOT NULL PRIMARY KEY,
|
||||
name TEXT NOT NULL)""")
|
||||
|
||||
def ProcessCountiesDbf(dbInfo, dbfFilename):
|
||||
# County area type
|
||||
|
|
@ -72,6 +83,22 @@ def ProcessCountiesDbf(dbInfo, dbfFilename):
|
|||
except:
|
||||
print("Skipping duplicate county:", fipsId, row.COUNTYNAME)
|
||||
|
||||
def ProcessStateDbf(dbInfo, dbfFilename):
|
||||
print("Processing states and territories file:", dbfFilename)
|
||||
|
||||
# Read dataframe
|
||||
dbfTable = gpd.read_file(filename = dbfFilename,
|
||||
include_fields = ["STATE", "NAME"],
|
||||
ignore_geometry = True)
|
||||
dbfTable.drop_duplicates(inplace=True)
|
||||
|
||||
for row in dbfTable.itertuples():
|
||||
# Insert data into database
|
||||
try:
|
||||
dbInfo.sqlCursor_.execute("INSERT INTO states VALUES (?, ?)", (row.STATE, row.NAME))
|
||||
except:
|
||||
print("Error inserting row:", row.STATE, row.NAME)
|
||||
|
||||
def ProcessZoneDbf(dbInfo, dbfFilename):
|
||||
print("Processing zone file:", dbfFilename)
|
||||
# Zone area type
|
||||
|
|
@ -118,4 +145,7 @@ for countyDb in args.inputCountyDbs_:
|
|||
for zoneDb in args.inputZoneDbs_:
|
||||
ProcessZoneDbf(dbInfo, zoneDb)
|
||||
|
||||
for stateDb in args.inputStateDbs_:
|
||||
ProcessStateDbf(dbInfo, stateDb)
|
||||
|
||||
PostProcess(dbInfo)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit cd36a74a9c678d90d10ec397eae65b389a9640fc
|
||||
Subproject commit 65bdc55e4afa29c24010a398238f2036060bbd0c
|
||||
|
|
@ -15,6 +15,12 @@ class CountyDatabaseTest :
|
|||
virtual void SetUp() { scwx::qt::config::CountyDatabase::Initialize(); }
|
||||
};
|
||||
|
||||
class CountyCountTest :
|
||||
public testing::TestWithParam<std::pair<std::string, std::size_t>>
|
||||
{
|
||||
virtual void SetUp() { scwx::qt::config::CountyDatabase::Initialize(); }
|
||||
};
|
||||
|
||||
TEST_P(CountyDatabaseTest, CountyName)
|
||||
{
|
||||
auto& [id, name] = GetParam();
|
||||
|
|
@ -24,6 +30,15 @@ TEST_P(CountyDatabaseTest, CountyName)
|
|||
EXPECT_EQ(actualName, name);
|
||||
}
|
||||
|
||||
TEST_P(CountyCountTest, State)
|
||||
{
|
||||
auto& [state, size] = GetParam();
|
||||
|
||||
auto counties = CountyDatabase::GetCounties(state);
|
||||
|
||||
EXPECT_EQ(counties.size(), size);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
CountyDatabase,
|
||||
CountyDatabaseTest,
|
||||
|
|
@ -33,6 +48,14 @@ INSTANTIATE_TEST_SUITE_P(
|
|||
std::make_pair("GMZ335", "Galveston Bay"),
|
||||
std::make_pair("ANZ338", "New York Harbor")));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(CountyDatabase,
|
||||
CountyCountTest,
|
||||
testing::Values(std::make_pair("AZ", 15),
|
||||
std::make_pair("MO", 115),
|
||||
std::make_pair("TX", 254),
|
||||
std::make_pair("GM", 0),
|
||||
std::make_pair("AN", 0)));
|
||||
|
||||
} // namespace config
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
|
|
|
|||
20
wxdata/include/scwx/util/enum.hpp
Normal file
20
wxdata/include/scwx/util/enum.hpp
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#define SCWX_GET_ENUM(Type, FunctionName, nameMap) \
|
||||
Type FunctionName(const std::string& name) \
|
||||
{ \
|
||||
auto result = \
|
||||
std::find_if(nameMap.cbegin(), \
|
||||
nameMap.cend(), \
|
||||
[&](const std::pair<Type, std::string>& pair) -> bool \
|
||||
{ return boost::iequals(pair.second, name); }); \
|
||||
\
|
||||
if (result != nameMap.cend()) \
|
||||
{ \
|
||||
return result->first; \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
return Type::Unknown; \
|
||||
} \
|
||||
}
|
||||
|
|
@ -66,7 +66,8 @@ set(SRC_PROVIDER source/scwx/provider/aws_level2_data_provider.cpp
|
|||
source/scwx/provider/nexrad_data_provider.cpp
|
||||
source/scwx/provider/nexrad_data_provider_factory.cpp
|
||||
source/scwx/provider/warnings_provider.cpp)
|
||||
set(HDR_UTIL include/scwx/util/environment.hpp
|
||||
set(HDR_UTIL include/scwx/util/enum.hpp
|
||||
include/scwx/util/environment.hpp
|
||||
include/scwx/util/float.hpp
|
||||
include/scwx/util/hash.hpp
|
||||
include/scwx/util/iterator.hpp
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue