mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 15:40: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_); | ||||
|    if (id.length() > 3) | ||||
|    { | ||||
|       // SSFNNN
 | ||||
|       char        format = id.at(2); | ||||
|       std::string state  = id.substr(0, 2); | ||||
| 
 | ||||
|    auto it = countyMap_.find(id); | ||||
|    if (it != countyMap_.cend()) | ||||
|       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
	
	 Dan Paulat
						Dan Paulat