mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 13:10:04 +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 |             msvc_version: 2022 | ||||||
|             qt_version: 6.6.1 |             qt_version: 6.6.1 | ||||||
|             qt_arch: win64_msvc2019_64 |             qt_arch: win64_msvc2019_64 | ||||||
|             qt_modules: qtimageformats qtpositioning |             qt_modules: qtimageformats qtmultimedia qtpositioning | ||||||
|             qt_tools: '' |             qt_tools: '' | ||||||
|             conan_arch: x86_64 |             conan_arch: x86_64 | ||||||
|             conan_compiler: Visual Studio |             conan_compiler: Visual Studio | ||||||
|  | @ -46,7 +46,7 @@ jobs: | ||||||
|             compiler: gcc |             compiler: gcc | ||||||
|             qt_version: 6.6.1 |             qt_version: 6.6.1 | ||||||
|             qt_arch: gcc_64 |             qt_arch: gcc_64 | ||||||
|             qt_modules: qtimageformats qtpositioning |             qt_modules: qtimageformats qtmultimedia qtpositioning | ||||||
|             qt_tools: '' |             qt_tools: '' | ||||||
|             conan_arch: x86_64 |             conan_arch: x86_64 | ||||||
|             conan_compiler: gcc |             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](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) | | | [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) | | | [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) | | | [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) | | | [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) | | | [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) | | | [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 | | | [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) | | | [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) | | | [spdlog](https://github.com/gabime/spdlog) | [MIT License](https://spdx.org/licenses/MIT.html) | | ||||||
| | [SQLite](https://www.sqlite.org/) | Public Domain | | | [SQLite](https://www.sqlite.org/) | Public Domain | | ||||||
| | [stb](https://github.com/nothings/stb) | 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 | | | Source | License | Notes | | ||||||
| | ------ | ------- | ----- | | | ------ | ------- | ----- | | ||||||
| | Alte DIN 1451 Mittelschrift | SIL Open Font License | | | 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 | | | [Font Awesome Free](https://fontawesome.com/) | CC BY 4.0 License | | ||||||
| | [Inconsolata](https://fonts.google.com/specimen/Inconsolata) | SIL Open Font 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 | | | [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", |                   "cpr/1.10.5", | ||||||
|                   "fontconfig/2.14.2", |                   "fontconfig/2.14.2", | ||||||
|                   "geographiclib/2.3", |                   "geographiclib/2.3", | ||||||
|  |                   "geos/3.12.0", | ||||||
|                   "glew/2.2.0", |                   "glew/2.2.0", | ||||||
|                   "glm/cci.20230113", |                   "glm/cci.20230113", | ||||||
|                   "gtest/1.14.0", |                   "gtest/1.14.0", | ||||||
|  | @ -19,7 +20,8 @@ class SupercellWxConan(ConanFile): | ||||||
|     generators = ("cmake", |     generators = ("cmake", | ||||||
|                   "cmake_find_package", |                   "cmake_find_package", | ||||||
|                   "cmake_paths") |                   "cmake_paths") | ||||||
|     default_options = {"libiconv:shared"  : True, |     default_options = {"geos:shared"      : True, | ||||||
|  |                        "libiconv:shared"  : True, | ||||||
|                        "openssl:no_module": True, |                        "openssl:no_module": True, | ||||||
|                        "openssl:shared"   : 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(Boost) | ||||||
| find_package(Fontconfig) | find_package(Fontconfig) | ||||||
| find_package(geographiclib) | find_package(geographiclib) | ||||||
|  | find_package(geos) | ||||||
| find_package(GLEW) | find_package(GLEW) | ||||||
| find_package(glm) | find_package(glm) | ||||||
| find_package(Python COMPONENTS Interpreter) | find_package(Python COMPONENTS Interpreter) | ||||||
|  | @ -22,6 +23,7 @@ find_package(SQLite3) | ||||||
| find_package(QT NAMES Qt6 | find_package(QT NAMES Qt6 | ||||||
|              COMPONENTS Gui |              COMPONENTS Gui | ||||||
|                         LinguistTools |                         LinguistTools | ||||||
|  |                         Multimedia | ||||||
|                         Network |                         Network | ||||||
|                         OpenGL |                         OpenGL | ||||||
|                         OpenGLWidgets |                         OpenGLWidgets | ||||||
|  | @ -31,6 +33,7 @@ find_package(QT NAMES Qt6 | ||||||
| find_package(Qt${QT_VERSION_MAJOR} | find_package(Qt${QT_VERSION_MAJOR} | ||||||
|              COMPONENTS Gui |              COMPONENTS Gui | ||||||
|                         LinguistTools |                         LinguistTools | ||||||
|  |                         Multimedia | ||||||
|                         Network |                         Network | ||||||
|                         OpenGL |                         OpenGL | ||||||
|                         OpenGLWidgets |                         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_text.cpp | ||||||
|                 source/scwx/qt/gl/draw/placefile_triangles.cpp |                 source/scwx/qt/gl/draw/placefile_triangles.cpp | ||||||
|                 source/scwx/qt/gl/draw/rectangle.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/placefile_manager.hpp | ||||||
|                 source/scwx/qt/manager/position_manager.hpp |                 source/scwx/qt/manager/position_manager.hpp | ||||||
|                 source/scwx/qt/manager/radar_product_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/text_event_manager.hpp | ||||||
|                 source/scwx/qt/manager/timeline_manager.hpp |                 source/scwx/qt/manager/timeline_manager.hpp | ||||||
|                 source/scwx/qt/manager/update_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/placefile_manager.cpp | ||||||
|                 source/scwx/qt/manager/position_manager.cpp |                 source/scwx/qt/manager/position_manager.cpp | ||||||
|                 source/scwx/qt/manager/radar_product_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) |               source/scwx/qt/model/tree_model.cpp) | ||||||
| set(HDR_REQUEST source/scwx/qt/request/nexrad_file_request.hpp) | set(HDR_REQUEST source/scwx/qt/request/nexrad_file_request.hpp) | ||||||
| set(SRC_REQUEST source/scwx/qt/request/nexrad_file_request.cpp) | 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/map_settings.hpp | ||||||
|                  source/scwx/qt/settings/palette_settings.hpp |                  source/scwx/qt/settings/palette_settings.hpp | ||||||
|                  source/scwx/qt/settings/settings_category.hpp |                  source/scwx/qt/settings/settings_category.hpp | ||||||
|                  source/scwx/qt/settings/settings_container.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.hpp | ||||||
|                  source/scwx/qt/settings/settings_interface_base.hpp |                  source/scwx/qt/settings/settings_interface_base.hpp | ||||||
|                  source/scwx/qt/settings/settings_variable.hpp |                  source/scwx/qt/settings/settings_variable.hpp | ||||||
|                  source/scwx/qt/settings/settings_variable_base.hpp |                  source/scwx/qt/settings/settings_variable_base.hpp | ||||||
|                  source/scwx/qt/settings/text_settings.hpp |                  source/scwx/qt/settings/text_settings.hpp | ||||||
|                  source/scwx/qt/settings/ui_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/map_settings.cpp | ||||||
|                  source/scwx/qt/settings/palette_settings.cpp |                  source/scwx/qt/settings/palette_settings.cpp | ||||||
|                  source/scwx/qt/settings/settings_category.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/github_types.hpp | ||||||
|               source/scwx/qt/types/imgui_font.hpp |               source/scwx/qt/types/imgui_font.hpp | ||||||
|               source/scwx/qt/types/layer_types.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/map_types.hpp | ||||||
|  |               source/scwx/qt/types/media_types.hpp | ||||||
|               source/scwx/qt/types/qt_types.hpp |               source/scwx/qt/types/qt_types.hpp | ||||||
|               source/scwx/qt/types/radar_product_record.hpp |               source/scwx/qt/types/radar_product_record.hpp | ||||||
|               source/scwx/qt/types/text_event_key.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/github_types.cpp | ||||||
|               source/scwx/qt/types/imgui_font.cpp |               source/scwx/qt/types/imgui_font.cpp | ||||||
|               source/scwx/qt/types/layer_types.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/map_types.cpp | ||||||
|  |               source/scwx/qt/types/media_types.cpp | ||||||
|               source/scwx/qt/types/qt_types.cpp |               source/scwx/qt/types/qt_types.cpp | ||||||
|               source/scwx/qt/types/radar_product_record.cpp |               source/scwx/qt/types/radar_product_record.cpp | ||||||
|               source/scwx/qt/types/text_event_key.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/alert_dock_widget.hpp | ||||||
|            source/scwx/qt/ui/animation_dock_widget.hpp |            source/scwx/qt/ui/animation_dock_widget.hpp | ||||||
|            source/scwx/qt/ui/collapsible_group.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/flow_layout.hpp | ||||||
|            source/scwx/qt/ui/imgui_debug_dialog.hpp |            source/scwx/qt/ui/imgui_debug_dialog.hpp | ||||||
|            source/scwx/qt/ui/imgui_debug_widget.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/alert_dock_widget.cpp | ||||||
|            source/scwx/qt/ui/animation_dock_widget.cpp |            source/scwx/qt/ui/animation_dock_widget.cpp | ||||||
|            source/scwx/qt/ui/collapsible_group.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/flow_layout.cpp | ||||||
|            source/scwx/qt/ui/imgui_debug_dialog.cpp |            source/scwx/qt/ui/imgui_debug_dialog.cpp | ||||||
|            source/scwx/qt/ui/imgui_debug_widget.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/alert_dock_widget.ui | ||||||
|            source/scwx/qt/ui/animation_dock_widget.ui |            source/scwx/qt/ui/animation_dock_widget.ui | ||||||
|            source/scwx/qt/ui/collapsible_group.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/imgui_debug_dialog.ui | ||||||
|            source/scwx/qt/ui/layer_dialog.ui |            source/scwx/qt/ui/layer_dialog.ui | ||||||
|            source/scwx/qt/ui/open_url_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/radar_site_dialog.ui | ||||||
|            source/scwx/qt/ui/settings_dialog.ui |            source/scwx/qt/ui/settings_dialog.ui | ||||||
|            source/scwx/qt/ui/update_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_layout_page.hpp | ||||||
|                  source/scwx/qt/ui/setup/map_provider_page.hpp |                  source/scwx/qt/ui/setup/map_provider_page.hpp | ||||||
|                  source/scwx/qt/ui/setup/setup_wizard.hpp |                  source/scwx/qt/ui/setup/setup_wizard.hpp | ||||||
|                  source/scwx/qt/ui/setup/welcome_page.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_layout_page.cpp | ||||||
|                  source/scwx/qt/ui/setup/map_provider_page.cpp |                  source/scwx/qt/ui/setup/map_provider_page.cpp | ||||||
|                  source/scwx/qt/ui/setup/setup_wizard.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/mz19se23.dbf | ||||||
|                      ${SCWX_DIR}/data/db/oz08mr23.dbf |                      ${SCWX_DIR}/data/db/oz08mr23.dbf | ||||||
|                      ${SCWX_DIR}/data/db/z_19se23.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(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) | 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 |                            ${scwx-qt_SOURCE_DIR}/tools/generate_counties_db.py | ||||||
|                            -c ${COUNTY_DBF_FILES} |                            -c ${COUNTY_DBF_FILES} | ||||||
|                            -z ${ZONE_DBF_FILES} |                            -z ${ZONE_DBF_FILES} | ||||||
|  |                            -s ${STATE_DBF_FILES} | ||||||
|                            -o ${COUNTIES_SQLITE_DB} |                            -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 | add_custom_target(scwx-qt_generate_counties_db ALL | ||||||
|                   DEPENDS ${COUNTIES_SQLITE_DB}) |                   DEPENDS ${COUNTIES_SQLITE_DB}) | ||||||
|  | @ -512,6 +536,7 @@ endif() | ||||||
| 
 | 
 | ||||||
| target_link_libraries(scwx-qt PUBLIC Qt${QT_VERSION_MAJOR}::Widgets | target_link_libraries(scwx-qt PUBLIC Qt${QT_VERSION_MAJOR}::Widgets | ||||||
|                                      Qt${QT_VERSION_MAJOR}::OpenGLWidgets |                                      Qt${QT_VERSION_MAJOR}::OpenGLWidgets | ||||||
|  |                                      Qt${QT_VERSION_MAJOR}::Multimedia | ||||||
|                                      Qt${QT_VERSION_MAJOR}::Positioning |                                      Qt${QT_VERSION_MAJOR}::Positioning | ||||||
|                                      Boost::json |                                      Boost::json | ||||||
|                                      Boost::timer |                                      Boost::timer | ||||||
|  | @ -519,6 +544,8 @@ target_link_libraries(scwx-qt PUBLIC Qt${QT_VERSION_MAJOR}::Widgets | ||||||
|                                      $<$<CXX_COMPILER_ID:MSVC>:opengl32> |                                      $<$<CXX_COMPILER_ID:MSVC>:opengl32> | ||||||
|                                      Fontconfig::Fontconfig |                                      Fontconfig::Fontconfig | ||||||
|                                      GeographicLib::GeographicLib |                                      GeographicLib::GeographicLib | ||||||
|  |                                      GEOS::geos | ||||||
|  |                                      GEOS::geos_cxx_flags | ||||||
|                                      GLEW::GLEW |                                      GLEW::GLEW | ||||||
|                                      glm::glm |                                      glm::glm | ||||||
|                                      imgui |                                      imgui | ||||||
|  |  | ||||||
|  | @ -12,6 +12,7 @@ | ||||||
|         <file>gl/texture2d.frag</file> |         <file>gl/texture2d.frag</file> | ||||||
|         <file>gl/texture2d_array.frag</file> |         <file>gl/texture2d_array.frag</file> | ||||||
|         <file>gl/threshold.geom</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/config/radar_sites.json</file> | ||||||
|         <file>res/fonts/din1451alt.ttf</file> |         <file>res/fonts/din1451alt.ttf</file> | ||||||
|         <file>res/fonts/din1451alt_g.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-caret-right-regular.svg</file> | ||||||
|         <file>res/icons/font-awesome-6/square-minus-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/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/CC.pal</file> | ||||||
|         <file>res/palettes/wct/Default16.pal</file> |         <file>res/palettes/wct/Default16.pal</file> | ||||||
|         <file>res/palettes/wct/DOD_DSD.pal</file> |         <file>res/palettes/wct/DOD_DSD.pal</file> | ||||||
|  |  | ||||||
|  | @ -1,7 +1,6 @@ | ||||||
| #include <scwx/qt/config/county_database.hpp> | #include <scwx/qt/config/county_database.hpp> | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
| 
 | 
 | ||||||
| #include <shared_mutex> |  | ||||||
| #include <unordered_map> | #include <unordered_map> | ||||||
| 
 | 
 | ||||||
| #include <boost/uuid/uuid.hpp> | #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"; | 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 bool                                         initialized_ {false}; | ||||||
| static std::unordered_map<std::string, std::string> countyMap_; | static FormatMap                                    countyDatabase_; | ||||||
| static std::shared_mutex                            countyMutex_; | static std::unordered_map<std::string, std::string> stateMap_; | ||||||
| 
 | 
 | ||||||
| void Initialize() | void Initialize() | ||||||
| { | { | ||||||
|  | @ -87,8 +90,8 @@ void Initialize() | ||||||
|       return; |       return; | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    // Database is open, acquire lock
 |    // Ensure counties exists
 | ||||||
|    std::unique_lock lock(countyMutex_); |    countyDatabase_.emplace('C', StateMap {}); | ||||||
| 
 | 
 | ||||||
|    // Query database for counties
 |    // Query database for counties
 | ||||||
|    rc = sqlite3_exec( |    rc = sqlite3_exec( | ||||||
|  | @ -101,14 +104,24 @@ void Initialize() | ||||||
|       { |       { | ||||||
|          int status = 0; |          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 |          else | ||||||
|          { |          { | ||||||
|             logger_->error( |             logger_->error("Invalid FIPS ID: {}", columnText[0]); | ||||||
|                "Database format error, invalid number of columns: {}", columns); |  | ||||||
|             status = -1; |             status = -1; | ||||||
|          } |          } | ||||||
| 
 | 
 | ||||||
|  | @ -122,20 +135,48 @@ void Initialize() | ||||||
|       sqlite3_free(errorMessage); |       sqlite3_free(errorMessage); | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    // Finished populating county map, release lock
 |    // Query database for states
 | ||||||
|    lock.unlock(); |    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
 |    // Close database
 | ||||||
|    sqlite3_close(db); |    sqlite3_close(db); | ||||||
| 
 | 
 | ||||||
|    // Remove temporary file
 |    // Remove temporary file
 | ||||||
|    std::error_code err; |    std::error_code error; | ||||||
| 
 |    if (!std::filesystem::remove(countyDatabaseCache, error)) | ||||||
|    if (!std::filesystem::remove(countyDatabaseCache, err)) { |    { | ||||||
|       logger_->warn( |       logger_->warn("Unable to remove cached copy of database: {}", | ||||||
|           "Unable to remove cached copy of database, error code: {} error category: {}", |                     error.message()); | ||||||
|           err.value(), |  | ||||||
|           err.category().name()); |  | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    initialized_ = true; |    initialized_ = true; | ||||||
|  | @ -143,17 +184,52 @@ void Initialize() | ||||||
| 
 | 
 | ||||||
| std::string GetCountyName(const std::string& id) | std::string GetCountyName(const std::string& id) | ||||||
| { | { | ||||||
|    std::shared_lock lock(countyMutex_); |    if (id.length() > 3) | ||||||
| 
 |  | ||||||
|    auto it = countyMap_.find(id); |  | ||||||
|    if (it != countyMap_.cend()) |  | ||||||
|    { |    { | ||||||
|       return it->second; |       // SSFNNN
 | ||||||
|  |       char        format = id.at(2); | ||||||
|  |       std::string state  = id.substr(0, 2); | ||||||
|  | 
 | ||||||
|  |       auto stateIt = countyDatabase_.find(format); | ||||||
|  |       if (stateIt != countyDatabase_.cend()) | ||||||
|  |       { | ||||||
|  |          StateMap& states   = stateIt->second; | ||||||
|  |          auto      countyIt = states.find(state); | ||||||
|  |          if (countyIt != states.cend()) | ||||||
|  |          { | ||||||
|  |             CountyMap& counties = countyIt->second; | ||||||
|  |             auto       it       = counties.find(id); | ||||||
|  |             if (it != counties.cend()) | ||||||
|  |             { | ||||||
|  |                return it->second; | ||||||
|  |             } | ||||||
|  |          } | ||||||
|  |       } | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    return id; |    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 CountyDatabase
 | ||||||
| } // namespace config
 | } // namespace config
 | ||||||
| } // namespace qt
 | } // namespace qt
 | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ | ||||||
| 
 | 
 | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <string> | #include <string> | ||||||
|  | #include <unordered_map> | ||||||
| #include <vector> | #include <vector> | ||||||
| 
 | 
 | ||||||
| namespace scwx | namespace scwx | ||||||
|  | @ -15,6 +16,9 @@ namespace CountyDatabase | ||||||
| 
 | 
 | ||||||
| void        Initialize(); | void        Initialize(); | ||||||
| std::string GetCountyName(const std::string& id); | 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 CountyDatabase
 | ||||||
| } // namespace config
 | } // namespace config
 | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| #define _SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING | #define _SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING | ||||||
| 
 | 
 | ||||||
|  | #include <scwx/qt/config/county_database.hpp> | ||||||
| #include <scwx/qt/config/radar_site.hpp> | #include <scwx/qt/config/radar_site.hpp> | ||||||
| #include <scwx/qt/main/main_window.hpp> | #include <scwx/qt/main/main_window.hpp> | ||||||
| #include <scwx/qt/main/versions.hpp> | #include <scwx/qt/main/versions.hpp> | ||||||
|  | @ -72,6 +73,7 @@ int main(int argc, char* argv[]) | ||||||
| 
 | 
 | ||||||
|    // Initialize application
 |    // Initialize application
 | ||||||
|    scwx::qt::config::RadarSite::Initialize(); |    scwx::qt::config::RadarSite::Initialize(); | ||||||
|  |    scwx::qt::config::CountyDatabase::Initialize(); | ||||||
|    scwx::qt::manager::SettingsManager::Instance().Initialize(); |    scwx::qt::manager::SettingsManager::Instance().Initialize(); | ||||||
| 
 | 
 | ||||||
|    // Theme
 |    // Theme
 | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ | ||||||
| 
 | 
 | ||||||
| #include <scwx/qt/main/application.hpp> | #include <scwx/qt/main/application.hpp> | ||||||
| #include <scwx/qt/main/versions.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/placefile_manager.hpp> | ||||||
| #include <scwx/qt/manager/position_manager.hpp> | #include <scwx/qt/manager/position_manager.hpp> | ||||||
| #include <scwx/qt/manager/radar_product_manager.hpp> | #include <scwx/qt/manager/radar_product_manager.hpp> | ||||||
|  | @ -82,6 +83,7 @@ public: | ||||||
|        radarSiteDialog_ {nullptr}, |        radarSiteDialog_ {nullptr}, | ||||||
|        settingsDialog_ {nullptr}, |        settingsDialog_ {nullptr}, | ||||||
|        updateDialog_ {nullptr}, |        updateDialog_ {nullptr}, | ||||||
|  |        alertManager_ {manager::AlertManager::Instance()}, | ||||||
|        placefileManager_ {manager::PlacefileManager::Instance()}, |        placefileManager_ {manager::PlacefileManager::Instance()}, | ||||||
|        positionManager_ {manager::PositionManager::Instance()}, |        positionManager_ {manager::PositionManager::Instance()}, | ||||||
|        textEventManager_ {manager::TextEventManager::Instance()}, |        textEventManager_ {manager::TextEventManager::Instance()}, | ||||||
|  | @ -178,6 +180,7 @@ public: | ||||||
|    ui::SettingsDialog*      settingsDialog_; |    ui::SettingsDialog*      settingsDialog_; | ||||||
|    ui::UpdateDialog*        updateDialog_; |    ui::UpdateDialog*        updateDialog_; | ||||||
| 
 | 
 | ||||||
|  |    std::shared_ptr<manager::AlertManager>     alertManager_; | ||||||
|    std::shared_ptr<manager::PlacefileManager> placefileManager_; |    std::shared_ptr<manager::PlacefileManager> placefileManager_; | ||||||
|    std::shared_ptr<manager::PositionManager>  positionManager_; |    std::shared_ptr<manager::PositionManager>  positionManager_; | ||||||
|    std::shared_ptr<manager::TextEventManager> textEventManager_; |    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/resource_manager.hpp> | ||||||
| #include <scwx/qt/manager/font_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/model/imgui_context_model.hpp> | ||||||
| #include <scwx/qt/types/texture_types.hpp> | #include <scwx/qt/types/texture_types.hpp> | ||||||
| #include <scwx/qt/util/texture_atlas.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() | void Initialize() | ||||||
| { | { | ||||||
|    config::CountyDatabase::Initialize(); |  | ||||||
| 
 |  | ||||||
|    LoadFonts(); |    LoadFonts(); | ||||||
|    LoadTextures(); |    LoadTextures(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| #include <scwx/qt/manager/settings_manager.hpp> | #include <scwx/qt/manager/settings_manager.hpp> | ||||||
| #include <scwx/qt/map/map_provider.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/general_settings.hpp> | ||||||
| #include <scwx/qt/settings/map_settings.hpp> | #include <scwx/qt/settings/map_settings.hpp> | ||||||
| #include <scwx/qt/settings/palette_settings.hpp> | #include <scwx/qt/settings/palette_settings.hpp> | ||||||
|  | @ -128,6 +129,7 @@ boost::json::value SettingsManager::Impl::ConvertSettingsToJson() | ||||||
|    boost::json::object settingsJson; |    boost::json::object settingsJson; | ||||||
| 
 | 
 | ||||||
|    settings::GeneralSettings::Instance().WriteJson(settingsJson); |    settings::GeneralSettings::Instance().WriteJson(settingsJson); | ||||||
|  |    settings::AudioSettings::Instance().WriteJson(settingsJson); | ||||||
|    settings::MapSettings::Instance().WriteJson(settingsJson); |    settings::MapSettings::Instance().WriteJson(settingsJson); | ||||||
|    settings::PaletteSettings::Instance().WriteJson(settingsJson); |    settings::PaletteSettings::Instance().WriteJson(settingsJson); | ||||||
|    settings::TextSettings::Instance().WriteJson(settingsJson); |    settings::TextSettings::Instance().WriteJson(settingsJson); | ||||||
|  | @ -141,6 +143,7 @@ void SettingsManager::Impl::GenerateDefaultSettings() | ||||||
|    logger_->info("Generating default settings"); |    logger_->info("Generating default settings"); | ||||||
| 
 | 
 | ||||||
|    settings::GeneralSettings::Instance().SetDefaults(); |    settings::GeneralSettings::Instance().SetDefaults(); | ||||||
|  |    settings::AudioSettings::Instance().SetDefaults(); | ||||||
|    settings::MapSettings::Instance().SetDefaults(); |    settings::MapSettings::Instance().SetDefaults(); | ||||||
|    settings::PaletteSettings::Instance().SetDefaults(); |    settings::PaletteSettings::Instance().SetDefaults(); | ||||||
|    settings::TextSettings::Instance().SetDefaults(); |    settings::TextSettings::Instance().SetDefaults(); | ||||||
|  | @ -155,6 +158,7 @@ bool SettingsManager::Impl::LoadSettings( | ||||||
|    bool jsonDirty = false; |    bool jsonDirty = false; | ||||||
| 
 | 
 | ||||||
|    jsonDirty |= !settings::GeneralSettings::Instance().ReadJson(settingsJson); |    jsonDirty |= !settings::GeneralSettings::Instance().ReadJson(settingsJson); | ||||||
|  |    jsonDirty |= !settings::AudioSettings::Instance().ReadJson(settingsJson); | ||||||
|    jsonDirty |= !settings::MapSettings::Instance().ReadJson(settingsJson); |    jsonDirty |= !settings::MapSettings::Instance().ReadJson(settingsJson); | ||||||
|    jsonDirty |= !settings::PaletteSettings::Instance().ReadJson(settingsJson); |    jsonDirty |= !settings::PaletteSettings::Instance().ReadJson(settingsJson); | ||||||
|    jsonDirty |= !settings::TextSettings::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
 |                              // 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>>) |       else if constexpr (std::is_same_v<T, std::vector<std::int64_t>>) | ||||||
|       { |       { | ||||||
|          // If the line is edited (not programatically changed), stage the new
 |          // 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(); |    p->UpdateEditWidget(); | ||||||
| } | } | ||||||
|  | @ -378,6 +450,10 @@ void SettingsInterface<T>::Impl::SetWidgetText(U* widget, const T& currentValue) | ||||||
|    { |    { | ||||||
|       widget->setText(QString::number(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>) |    else if constexpr (std::is_same_v<T, std::string>) | ||||||
|    { |    { | ||||||
|       if (mapFromValue_ != nullptr) |       if (mapFromValue_ != nullptr) | ||||||
|  | @ -448,6 +524,14 @@ void SettingsInterface<T>::Impl::UpdateEditWidget() | ||||||
|          spinBox->setValue(static_cast<int>(currentValue)); |          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> | template<class T> | ||||||
|  |  | ||||||
|  | @ -37,6 +37,17 @@ std::string GetAlertActionName(AlertAction alertAction) | ||||||
|    return alertActionName_.at(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 types
 | ||||||
| } // namespace qt
 | } // namespace qt
 | ||||||
| } // namespace scwx
 | } // namespace scwx
 | ||||||
|  |  | ||||||
|  | @ -1,8 +1,10 @@ | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
|  | #include <scwx/awips/phenomenon.hpp> | ||||||
| #include <scwx/util/iterator.hpp> | #include <scwx/util/iterator.hpp> | ||||||
| 
 | 
 | ||||||
| #include <string> | #include <string> | ||||||
|  | #include <vector> | ||||||
| 
 | 
 | ||||||
| namespace scwx | namespace scwx | ||||||
| { | { | ||||||
|  | @ -23,6 +25,8 @@ typedef scwx::util::Iterator<AlertAction, AlertAction::Go, AlertAction::View> | ||||||
| AlertAction GetAlertAction(const std::string& name); | AlertAction GetAlertAction(const std::string& name); | ||||||
| std::string GetAlertActionName(AlertAction alertAction); | std::string GetAlertActionName(AlertAction alertAction); | ||||||
| 
 | 
 | ||||||
|  | const std::vector<awips::Phenomenon>& GetAlertAudioPhenomena(); | ||||||
|  | 
 | ||||||
| } // namespace types
 | } // namespace types
 | ||||||
| } // namespace qt
 | } // namespace qt
 | ||||||
| } // namespace scwx
 | } // 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/awips/phenomenon.hpp> | ||||||
| #include <scwx/common/color_table.hpp> | #include <scwx/common/color_table.hpp> | ||||||
|  | #include <scwx/qt/config/county_database.hpp> | ||||||
| #include <scwx/qt/config/radar_site.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/manager/settings_manager.hpp> | ||||||
| #include <scwx/qt/map/map_provider.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/general_settings.hpp> | ||||||
| #include <scwx/qt/settings/palette_settings.hpp> | #include <scwx/qt/settings/palette_settings.hpp> | ||||||
| #include <scwx/qt/settings/settings_interface.hpp> | #include <scwx/qt/settings/settings_interface.hpp> | ||||||
| #include <scwx/qt/settings/text_settings.hpp> | #include <scwx/qt/settings/text_settings.hpp> | ||||||
| #include <scwx/qt/types/alert_types.hpp> | #include <scwx/qt/types/alert_types.hpp> | ||||||
| #include <scwx/qt/types/font_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/qt_types.hpp> | ||||||
| #include <scwx/qt/types/text_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/ui/radar_site_dialog.hpp> | ||||||
| #include <scwx/qt/util/color.hpp> | #include <scwx/qt/util/color.hpp> | ||||||
| #include <scwx/qt/util/file.hpp> | #include <scwx/qt/util/file.hpp> | ||||||
|  | @ -26,6 +32,7 @@ | ||||||
| #include <QFileDialog> | #include <QFileDialog> | ||||||
| #include <QFontDatabase> | #include <QFontDatabase> | ||||||
| #include <QFontDialog> | #include <QFontDialog> | ||||||
|  | #include <QGeoPositionInfo> | ||||||
| #include <QStandardItemModel> | #include <QStandardItemModel> | ||||||
| #include <QToolButton> | #include <QToolButton> | ||||||
| 
 | 
 | ||||||
|  | @ -84,12 +91,31 @@ static const std::unordered_map<std::string, ColorTableConversions> | ||||||
|                             {"VIL", {0u, 255u, 1.0f, 2.5f}}, |                             {"VIL", {0u, 255u, 1.0f, 2.5f}}, | ||||||
|                             {"???", {0u, 15u, 0.0f, 1.0f}}}; |                             {"???", {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 | class SettingsDialogImpl | ||||||
| { | { | ||||||
| public: | public: | ||||||
|    explicit SettingsDialogImpl(SettingsDialog* self) : |    explicit SettingsDialogImpl(SettingsDialog* self) : | ||||||
|        self_ {self}, |        self_ {self}, | ||||||
|        radarSiteDialog_ {new RadarSiteDialog(self)}, |        radarSiteDialog_ {new RadarSiteDialog(self)}, | ||||||
|  |        countyDialog_ {new CountyDialog(self)}, | ||||||
|        fontDialog_ {new QFontDialog(self)}, |        fontDialog_ {new QFontDialog(self)}, | ||||||
|        fontCategoryModel_ {new QStandardItemModel(self)}, |        fontCategoryModel_ {new QStandardItemModel(self)}, | ||||||
|        settings_ {std::initializer_list<settings::SettingsInterfaceBase*> { |        settings_ {std::initializer_list<settings::SettingsInterfaceBase*> { | ||||||
|  | @ -104,6 +130,11 @@ public: | ||||||
|           &antiAliasingEnabled_, |           &antiAliasingEnabled_, | ||||||
|           &updateNotificationsEnabled_, |           &updateNotificationsEnabled_, | ||||||
|           &debugEnabled_, |           &debugEnabled_, | ||||||
|  |           &alertAudioSoundFile_, | ||||||
|  |           &alertAudioLocationMethod_, | ||||||
|  |           &alertAudioLatitude_, | ||||||
|  |           &alertAudioLongitude_, | ||||||
|  |           &alertAudioCounty_, | ||||||
|           &hoverTextWrap_, |           &hoverTextWrap_, | ||||||
|           &tooltipMethod_, |           &tooltipMethod_, | ||||||
|           &placefileTextDropShadowEnabled_}} |           &placefileTextDropShadowEnabled_}} | ||||||
|  | @ -136,6 +167,7 @@ public: | ||||||
|    void SetupGeneralTab(); |    void SetupGeneralTab(); | ||||||
|    void SetupPalettesColorTablesTab(); |    void SetupPalettesColorTablesTab(); | ||||||
|    void SetupPalettesAlertsTab(); |    void SetupPalettesAlertsTab(); | ||||||
|  |    void SetupAudioTab(); | ||||||
|    void SetupTextTab(); |    void SetupTextTab(); | ||||||
| 
 | 
 | ||||||
|    void ShowColorDialog(QLineEdit* lineEdit, QFrame* frame = nullptr); |    void ShowColorDialog(QLineEdit* lineEdit, QFrame* frame = nullptr); | ||||||
|  | @ -164,12 +196,18 @@ public: | ||||||
| 
 | 
 | ||||||
|    SettingsDialog*  self_; |    SettingsDialog*  self_; | ||||||
|    RadarSiteDialog* radarSiteDialog_; |    RadarSiteDialog* radarSiteDialog_; | ||||||
|  |    CountyDialog*    countyDialog_; | ||||||
|    QFontDialog*     fontDialog_; |    QFontDialog*     fontDialog_; | ||||||
| 
 | 
 | ||||||
|    QStandardItemModel* fontCategoryModel_; |    QStandardItemModel* fontCategoryModel_; | ||||||
| 
 | 
 | ||||||
|    types::FontCategory selectedFontCategory_ {types::FontCategory::Unknown}; |    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::string>  defaultRadarSite_ {}; | ||||||
|    settings::SettingsInterface<std::int64_t> gridWidth_ {}; |    settings::SettingsInterface<std::int64_t> gridWidth_ {}; | ||||||
|    settings::SettingsInterface<std::int64_t> gridHeight_ {}; |    settings::SettingsInterface<std::int64_t> gridHeight_ {}; | ||||||
|  | @ -191,6 +229,15 @@ public: | ||||||
|                       settings::SettingsInterface<std::string>> |                       settings::SettingsInterface<std::string>> | ||||||
|       inactiveAlertColors_ {}; |       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, |    std::unordered_map<types::FontCategory, | ||||||
|                       settings::SettingsInterface<std::string>> |                       settings::SettingsInterface<std::string>> | ||||||
|       fontFamilies_ {}; |       fontFamilies_ {}; | ||||||
|  | @ -223,6 +270,9 @@ SettingsDialog::SettingsDialog(QWidget* parent) : | ||||||
|    // Palettes > Alerts
 |    // Palettes > Alerts
 | ||||||
|    p->SetupPalettesAlertsTab(); |    p->SetupPalettesAlertsTab(); | ||||||
| 
 | 
 | ||||||
|  |    // Audio
 | ||||||
|  |    p->SetupAudioTab(); | ||||||
|  | 
 | ||||||
|    // Text
 |    // Text
 | ||||||
|    p->SetupTextTab(); |    p->SetupTextTab(); | ||||||
| 
 | 
 | ||||||
|  | @ -270,6 +320,20 @@ void SettingsDialogImpl::ConnectSignals() | ||||||
|       [this](const std::string& newValue) |       [this](const std::string& newValue) | ||||||
|       { UpdateRadarDialogLocation(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( |    QObject::connect( | ||||||
|       self_->ui->fontListView->selectionModel(), |       self_->ui->fontListView->selectionModel(), | ||||||
|       &QItemSelectionModel::selectionChanged, |       &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() | void SettingsDialogImpl::SetupTextTab() | ||||||
| { | { | ||||||
|    settings::TextSettings& textSettings = settings::TextSettings::Instance(); |    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> |             <normaloff>:/res/icons/font-awesome-6/palette-solid.svg</normaloff>:/res/icons/font-awesome-6/palette-solid.svg</iconset> | ||||||
|           </property> |           </property> | ||||||
|          </item> |          </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> |          <item> | ||||||
|           <property name="text"> |           <property name="text"> | ||||||
|            <string>Text</string> |            <string>Text</string> | ||||||
|  | @ -364,8 +373,8 @@ | ||||||
|                    <rect> |                    <rect> | ||||||
|                     <x>0</x> |                     <x>0</x> | ||||||
|                     <y>0</y> |                     <y>0</y> | ||||||
|                     <width>63</width> |                     <width>508</width> | ||||||
|                     <height>18</height> |                     <height>383</height> | ||||||
|                    </rect> |                    </rect> | ||||||
|                   </property> |                   </property> | ||||||
|                   <layout class="QGridLayout" name="gridLayout_3"> |                   <layout class="QGridLayout" name="gridLayout_3"> | ||||||
|  | @ -436,6 +445,223 @@ | ||||||
|            </item> |            </item> | ||||||
|           </layout> |           </layout> | ||||||
|          </widget> |          </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"> |          <widget class="QWidget" name="text"> | ||||||
|           <layout class="QVBoxLayout" name="verticalLayout_5"> |           <layout class="QVBoxLayout" name="verticalLayout_5"> | ||||||
|            <item> |            <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; |    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 setup
 | ||||||
| } // namespace ui
 | } // namespace ui
 | ||||||
| } // namespace qt
 | } // namespace qt
 | ||||||
|  |  | ||||||
|  | @ -22,6 +22,8 @@ public: | ||||||
|    bool isComplete() const override; |    bool isComplete() const override; | ||||||
|    bool validatePage() override; |    bool validatePage() override; | ||||||
| 
 | 
 | ||||||
|  |    static bool IsRequired(); | ||||||
|  | 
 | ||||||
| private: | private: | ||||||
|    class Impl; |    class Impl; | ||||||
|    std::shared_ptr<Impl> p; |    std::shared_ptr<Impl> p; | ||||||
|  |  | ||||||
|  | @ -1,9 +1,9 @@ | ||||||
| #include <scwx/qt/ui/setup/setup_wizard.hpp> | #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/finish_page.hpp> | ||||||
| #include <scwx/qt/ui/setup/map_layout_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/map_provider_page.hpp> | ||||||
| #include <scwx/qt/ui/setup/welcome_page.hpp> | #include <scwx/qt/ui/setup/welcome_page.hpp> | ||||||
| #include <scwx/qt/settings/general_settings.hpp> |  | ||||||
| 
 | 
 | ||||||
| #include <QDesktopServices> | #include <QDesktopServices> | ||||||
| #include <QUrl> | #include <QUrl> | ||||||
|  | @ -38,6 +38,7 @@ SetupWizard::SetupWizard(QWidget* parent) : | ||||||
|    setPage(static_cast<int>(Page::Welcome), new WelcomePage(this)); |    setPage(static_cast<int>(Page::Welcome), new WelcomePage(this)); | ||||||
|    setPage(static_cast<int>(Page::MapProvider), new MapProviderPage(this)); |    setPage(static_cast<int>(Page::MapProvider), new MapProviderPage(this)); | ||||||
|    setPage(static_cast<int>(Page::MapLayout), new MapLayoutPage(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)); |    setPage(static_cast<int>(Page::Finish), new FinishPage(this)); | ||||||
| 
 | 
 | ||||||
| #if !defined(Q_OS_MAC) | #if !defined(Q_OS_MAC) | ||||||
|  | @ -55,16 +56,43 @@ SetupWizard::SetupWizard(QWidget* parent) : | ||||||
| 
 | 
 | ||||||
| SetupWizard::~SetupWizard() = default; | 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() | bool SetupWizard::IsSetupRequired() | ||||||
| { | { | ||||||
|    auto& generalSettings = settings::GeneralSettings::Instance(); |    return (MapProviderPage::IsRequired() || AudioCodecPage::IsRequired()); | ||||||
| 
 |  | ||||||
|    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 setup
 | ||||||
|  |  | ||||||
|  | @ -19,12 +19,15 @@ public: | ||||||
|       Welcome = 0, |       Welcome = 0, | ||||||
|       MapProvider, |       MapProvider, | ||||||
|       MapLayout, |       MapLayout, | ||||||
|  |       AudioCodec, | ||||||
|       Finish |       Finish | ||||||
|    }; |    }; | ||||||
| 
 | 
 | ||||||
|    explicit SetupWizard(QWidget* parent = nullptr); |    explicit SetupWizard(QWidget* parent = nullptr); | ||||||
|    ~SetupWizard(); |    ~SetupWizard(); | ||||||
| 
 | 
 | ||||||
|  |    int nextId() const override; | ||||||
|  | 
 | ||||||
|    static bool IsSetupRequired(); |    static bool IsSetupRequired(); | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|  |  | ||||||
|  | @ -1,4 +1,9 @@ | ||||||
| #include <scwx/qt/util/geographic_lib.hpp> | #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 | namespace scwx | ||||||
| { | { | ||||||
|  | @ -9,6 +14,9 @@ namespace util | ||||||
| namespace GeographicLib | 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() | const ::GeographicLib::Geodesic& DefaultGeodesic() | ||||||
| { | { | ||||||
|    static const ::GeographicLib::Geodesic geodesic_ { |    static const ::GeographicLib::Geodesic geodesic_ { | ||||||
|  | @ -18,6 +26,60 @@ const ::GeographicLib::Geodesic& DefaultGeodesic() | ||||||
|    return geodesic_; |    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> | units::angle::degrees<double> | ||||||
| GetAngle(double lat1, double lon1, double lat2, double lon2) | GetAngle(double lat1, double lon1, double lat2, double lon2) | ||||||
| { | { | ||||||
|  |  | ||||||
|  | @ -1,5 +1,9 @@ | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
|  | #include <scwx/common/geographic.hpp> | ||||||
|  | 
 | ||||||
|  | #include <vector> | ||||||
|  | 
 | ||||||
| #include <GeographicLib/Geodesic.hpp> | #include <GeographicLib/Geodesic.hpp> | ||||||
| #include <units/angle.h> | #include <units/angle.h> | ||||||
| #include <units/length.h> | #include <units/length.h> | ||||||
|  | @ -20,6 +24,18 @@ namespace GeographicLib | ||||||
|  */ |  */ | ||||||
| const ::GeographicLib::Geodesic& DefaultGeodesic(); | 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. |  * Get the angle between two points. | ||||||
|  * |  * | ||||||
|  |  | ||||||
|  | @ -26,6 +26,14 @@ def ParseArguments(): | ||||||
|                         nargs   = "+", |                         nargs   = "+", | ||||||
|                         default = [], |                         default = [], | ||||||
|                         type    = pathlib.Path) |                         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", |     parser.add_argument("-o", "--output_db", | ||||||
|                         metavar  = "filename", |                         metavar  = "filename", | ||||||
|                         help     = "output sqlite database", |                         help     = "output sqlite database", | ||||||
|  | @ -47,10 +55,13 @@ def Prepare(dbInfo, outputDb): | ||||||
| 
 | 
 | ||||||
|     dbInfo.sqlCursor_     = dbInfo.sqlConnection_.cursor() |     dbInfo.sqlCursor_     = dbInfo.sqlConnection_.cursor() | ||||||
| 
 | 
 | ||||||
|     # Create database table |     # Create database tables | ||||||
|     dbInfo.sqlCursor_.execute("""CREATE TABLE counties( |     dbInfo.sqlCursor_.execute("""CREATE TABLE counties( | ||||||
|         id   TEXT NOT NULL PRIMARY KEY, |         id   TEXT NOT NULL PRIMARY KEY, | ||||||
|         name TEXT)""") |         name TEXT)""") | ||||||
|  |     dbInfo.sqlCursor_.execute("""CREATE TABLE states( | ||||||
|  |         state TEXT NOT NULL PRIMARY KEY, | ||||||
|  |         name  TEXT NOT NULL)""") | ||||||
| 
 | 
 | ||||||
| def ProcessCountiesDbf(dbInfo, dbfFilename): | def ProcessCountiesDbf(dbInfo, dbfFilename): | ||||||
|     # County area type |     # County area type | ||||||
|  | @ -72,6 +83,22 @@ def ProcessCountiesDbf(dbInfo, dbfFilename): | ||||||
|         except: |         except: | ||||||
|             print("Skipping duplicate county:", fipsId, row.COUNTYNAME) |             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): | def ProcessZoneDbf(dbInfo, dbfFilename): | ||||||
|     print("Processing zone file:", dbfFilename) |     print("Processing zone file:", dbfFilename) | ||||||
|     # Zone area type |     # Zone area type | ||||||
|  | @ -118,4 +145,7 @@ for countyDb in args.inputCountyDbs_: | ||||||
| for zoneDb in args.inputZoneDbs_: | for zoneDb in args.inputZoneDbs_: | ||||||
|     ProcessZoneDbf(dbInfo, zoneDb) |     ProcessZoneDbf(dbInfo, zoneDb) | ||||||
| 
 | 
 | ||||||
|  | for stateDb in args.inputStateDbs_: | ||||||
|  |     ProcessStateDbf(dbInfo, stateDb) | ||||||
|  | 
 | ||||||
| PostProcess(dbInfo) | PostProcess(dbInfo) | ||||||
|  |  | ||||||
|  | @ -1 +1 @@ | ||||||
| Subproject commit cd36a74a9c678d90d10ec397eae65b389a9640fc | Subproject commit 65bdc55e4afa29c24010a398238f2036060bbd0c | ||||||
|  | @ -15,6 +15,12 @@ class CountyDatabaseTest : | ||||||
|    virtual void SetUp() { scwx::qt::config::CountyDatabase::Initialize(); } |    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) | TEST_P(CountyDatabaseTest, CountyName) | ||||||
| { | { | ||||||
|    auto& [id, name] = GetParam(); |    auto& [id, name] = GetParam(); | ||||||
|  | @ -24,6 +30,15 @@ TEST_P(CountyDatabaseTest, CountyName) | ||||||
|    EXPECT_EQ(actualName, name); |    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( | INSTANTIATE_TEST_SUITE_P( | ||||||
|    CountyDatabase, |    CountyDatabase, | ||||||
|    CountyDatabaseTest, |    CountyDatabaseTest, | ||||||
|  | @ -33,6 +48,14 @@ INSTANTIATE_TEST_SUITE_P( | ||||||
|                    std::make_pair("GMZ335", "Galveston Bay"), |                    std::make_pair("GMZ335", "Galveston Bay"), | ||||||
|                    std::make_pair("ANZ338", "New York Harbor"))); |                    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 config
 | ||||||
| } // namespace qt
 | } // namespace qt
 | ||||||
| } // namespace scwx
 | } // 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.cpp | ||||||
|                  source/scwx/provider/nexrad_data_provider_factory.cpp |                  source/scwx/provider/nexrad_data_provider_factory.cpp | ||||||
|                  source/scwx/provider/warnings_provider.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/float.hpp | ||||||
|              include/scwx/util/hash.hpp |              include/scwx/util/hash.hpp | ||||||
|              include/scwx/util/iterator.hpp |              include/scwx/util/iterator.hpp | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Dan Paulat
						Dan Paulat