Merge pull request #321 from AdenKoperczak/location_markers_part2
Location Markers Part 2
|  | @ -10,4 +10,6 @@ Checks: | ||||||
|   - '-misc-include-cleaner' |   - '-misc-include-cleaner' | ||||||
|   - '-misc-non-private-member-variables-in-classes' |   - '-misc-non-private-member-variables-in-classes' | ||||||
|   - '-modernize-use-trailing-return-type' |   - '-modernize-use-trailing-return-type' | ||||||
|  |   - '-bugprone-easily-swappable-parameters' | ||||||
|  |   - '-modernize-return-braced-init-list' | ||||||
| FormatStyle: 'file' | FormatStyle: 'file' | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								scwx-qt/res/icons/font-awesome-6/briefcase-solid.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1 @@ | ||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path fill="#ffffff" d="M184 48l144 0c4.4 0 8 3.6 8 8l0 40L176 96l0-40c0-4.4 3.6-8 8-8zm-56 8l0 40L64 96C28.7 96 0 124.7 0 160l0 96 192 0 128 0 192 0 0-96c0-35.3-28.7-64-64-64l-64 0 0-40c0-30.9-25.1-56-56-56L184 0c-30.9 0-56 25.1-56 56zM512 288l-192 0 0 32c0 17.7-14.3 32-32 32l-64 0c-17.7 0-32-14.3-32-32l0-32L0 288 0 416c0 35.3 28.7 64 64 64l384 0c35.3 0 64-28.7 64-64l0-128z"/></svg> | ||||||
| After Width: | Height: | Size: 623 B | 
|  | @ -0,0 +1 @@ | ||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path fill="#ffffff" d="M243.4 2.6l-224 96c-14 6-21.8 21-18.7 35.8S16.8 160 32 160l0 8c0 13.3 10.7 24 24 24l400 0c13.3 0 24-10.7 24-24l0-8c15.2 0 28.3-10.7 31.3-25.6s-4.8-29.9-18.7-35.8l-224-96c-8-3.4-17.2-3.4-25.2 0zM128 224l-64 0 0 196.3c-.6 .3-1.2 .7-1.8 1.1l-48 32c-11.7 7.8-17 22.4-12.9 35.9S17.9 512 32 512l448 0c14.1 0 26.5-9.2 30.6-22.7s-1.1-28.1-12.9-35.9l-48-32c-.6-.4-1.2-.7-1.8-1.1L448 224l-64 0 0 192-40 0 0-192-64 0 0 192-48 0 0-192-64 0 0 192-40 0 0-192zM256 64a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg> | ||||||
| After Width: | Height: | Size: 757 B | 
							
								
								
									
										1
									
								
								scwx-qt/res/icons/font-awesome-6/building-solid.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1 @@ | ||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="12" height="16" viewBox="0 0 384 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path fill="#ffffff" d="M48 0C21.5 0 0 21.5 0 48L0 464c0 26.5 21.5 48 48 48l96 0 0-80c0-26.5 21.5-48 48-48s48 21.5 48 48l0 80 96 0c26.5 0 48-21.5 48-48l0-416c0-26.5-21.5-48-48-48L48 0zM64 240c0-8.8 7.2-16 16-16l32 0c8.8 0 16 7.2 16 16l0 32c0 8.8-7.2 16-16 16l-32 0c-8.8 0-16-7.2-16-16l0-32zm112-16l32 0c8.8 0 16 7.2 16 16l0 32c0 8.8-7.2 16-16 16l-32 0c-8.8 0-16-7.2-16-16l0-32c0-8.8 7.2-16 16-16zm80 16c0-8.8 7.2-16 16-16l32 0c8.8 0 16 7.2 16 16l0 32c0 8.8-7.2 16-16 16l-32 0c-8.8 0-16-7.2-16-16l0-32zM80 96l32 0c8.8 0 16 7.2 16 16l0 32c0 8.8-7.2 16-16 16l-32 0c-8.8 0-16-7.2-16-16l0-32c0-8.8 7.2-16 16-16zm80 16c0-8.8 7.2-16 16-16l32 0c8.8 0 16 7.2 16 16l0 32c0 8.8-7.2 16-16 16l-32 0c-8.8 0-16-7.2-16-16l0-32zM272 96l32 0c8.8 0 16 7.2 16 16l0 32c0 8.8-7.2 16-16 16l-32 0c-8.8 0-16-7.2-16-16l0-32c0-8.8 7.2-16 16-16z"/></svg> | ||||||
| After Width: | Height: | Size: 1 KiB | 
							
								
								
									
										1
									
								
								scwx-qt/res/icons/font-awesome-6/caravan-solid.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1 @@ | ||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="20" height="16" viewBox="0 0 640 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path fill="#ffffff" d="M0 112C0 67.8 35.8 32 80 32l336 0c88.4 0 160 71.6 160 160l0 160 32 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-32 0-288 0c0 53-43 96-96 96s-96-43-96-96l-16 0c-44.2 0-80-35.8-80-80L0 112zM320 352l128 0 0-96-32 0c-8.8 0-16-7.2-16-16s7.2-16 16-16l32 0 0-64c0-17.7-14.3-32-32-32l-64 0c-17.7 0-32 14.3-32 32l0 192zM96 128c-17.7 0-32 14.3-32 32l0 64c0 17.7 14.3 32 32 32l128 0c17.7 0 32-14.3 32-32l0-64c0-17.7-14.3-32-32-32L96 128zm96 336a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg> | ||||||
| After Width: | Height: | Size: 732 B | 
							
								
								
									
										1
									
								
								scwx-qt/res/icons/font-awesome-6/house-solid-white.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1 @@ | ||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" height="16" width="18" viewBox="0 0 576 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="#ffffff" d="M575.8 255.5c0 18-15 32.1-32 32.1h-32l.7 160.2c0 2.7-.2 5.4-.5 8.1V472c0 22.1-17.9 40-40 40H456c-1.1 0-2.2 0-3.3-.1c-1.4 .1-2.8 .1-4.2 .1H416 392c-22.1 0-40-17.9-40-40V448 384c0-17.7-14.3-32-32-32H256c-17.7 0-32 14.3-32 32v64 24c0 22.1-17.9 40-40 40H160 128.1c-1.5 0-3-.1-4.5-.2c-1.2 .1-2.4 .2-3.6 .2H104c-22.1 0-40-17.9-40-40V360c0-.9 0-1.9 .1-2.8V287.6H32c-18 0-32-14-32-32.1c0-9 3-17 10-24L266.4 8c7-7 15-8 22-8s15 2 21 7L564.8 231.5c8 7 12 15 11 24z"/></svg> | ||||||
| After Width: | Height: | Size: 735 B | 
|  | @ -0,0 +1 @@ | ||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path fill="#ffffff" d="M256 0c17.7 0 32 14.3 32 32l0 34.7C368.4 80.1 431.9 143.6 445.3 224l34.7 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-34.7 0C431.9 368.4 368.4 431.9 288 445.3l0 34.7c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-34.7C143.6 431.9 80.1 368.4 66.7 288L32 288c-17.7 0-32-14.3-32-32s14.3-32 32-32l34.7 0C80.1 143.6 143.6 80.1 224 66.7L224 32c0-17.7 14.3-32 32-32zM128 256a128 128 0 1 0 256 0 128 128 0 1 0 -256 0zm128-80a80 80 0 1 1 0 160 80 80 0 1 1 0-160z"/></svg> | ||||||
| After Width: | Height: | Size: 708 B | 
							
								
								
									
										4
									
								
								scwx-qt/res/icons/font-awesome-6/location-pin.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,4 @@ | ||||||
|  | <svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="12" height="16" viewBox="0 0 384 512"> | ||||||
|  |     <!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--> | ||||||
|  |     <path fill="#ffffff" d="M215.7 499.2C267 435 384 279.4 384 192C384 86 298 0 192 0S0 86 0 192c0 87.4 117 243 168.3 307.2c12.3 15.3 35.1 15.3 47.4 0zM192 128a64 64 0 1 1 0 128 64 64 0 1 1 0-128z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 463 B | 
							
								
								
									
										1
									
								
								scwx-qt/res/icons/font-awesome-6/star-solid-white.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1 @@ | ||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" height="16" width="18" viewBox="0 0 576 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="#ffffff" d="M316.9 18C311.6 7 300.4 0 288.1 0s-23.4 7-28.8 18L195 150.3 51.4 171.5c-12 1.8-22 10.2-25.7 21.7s-.7 24.2 7.9 32.7L137.8 329 113.2 474.7c-2 12 3 24.2 12.9 31.3s23 8 33.8 2.3l128.3-68.5 128.3 68.5c10.8 5.7 23.9 4.9 33.8-2.3s14.9-19.3 12.9-31.3L438.5 329 542.7 225.9c8.6-8.5 11.7-21.2 7.9-32.7s-13.7-19.9-25.7-21.7L381.2 150.3 316.9 18z"/></svg> | ||||||
| After Width: | Height: | Size: 616 B | 
							
								
								
									
										1
									
								
								scwx-qt/res/icons/font-awesome-6/tent-solid.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1 @@ | ||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="18" height="16" viewBox="0 0 576 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path fill="#ffffff" d="M269.4 6C280.5-2 295.5-2 306.6 6l224 160c7.4 5.3 12.2 13.5 13.2 22.5l32 288c1 9-1.9 18.1-8 24.9s-14.7 10.7-23.8 10.7l-80 0-28.2 0c-12.1 0-23.2-6.8-28.6-17.7L306.7 293.5c-1.7-3.4-5.1-5.5-8.8-5.5c-5.5 0-9.9 4.4-9.9 9.9L288 480c0 17.7-14.3 32-32 32l-16 0L32 512c-9.1 0-17.8-3.9-23.8-10.7s-9-15.8-8-24.9l32-288c1-9 5.8-17.2 13.2-22.5L269.4 6z"/></svg> | ||||||
| After Width: | Height: | Size: 608 B | 
|  | @ -6,6 +6,6 @@ | ||||||
|     <path d="M 40,118 L 85,40 L 130,118 L 40,118 L 85,40 Z" |     <path d="M 40,118 L 85,40 L 130,118 L 40,118 L 85,40 Z" | ||||||
|         stroke="black" stroke-width="40" fill="none"/> |         stroke="black" stroke-width="40" fill="none"/> | ||||||
|     <path d="M 40,118 L 85,40 L 130,118 L 40,118 L 85,40 Z" |     <path d="M 40,118 L 85,40 L 130,118 L 40,118 L 85,40 Z" | ||||||
|         stroke="red"   stroke-width="20" fill="none"/> |         stroke="#ffffff" stroke-width="20" fill="none"/> | ||||||
| 
 | 
 | ||||||
|  </svg> |  </svg> | ||||||
|  |  | ||||||
| Before Width: | Height: | Size: 355 B After Width: | Height: | Size: 357 B | 
|  | @ -222,8 +222,8 @@ set(HDR_TYPES source/scwx/qt/types/alert_types.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/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/marker_types.hpp |               source/scwx/qt/types/marker_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 | ||||||
|  | @ -255,6 +255,7 @@ set(HDR_UI source/scwx/qt/ui/about_dialog.hpp | ||||||
|            source/scwx/qt/ui/county_dialog.hpp |            source/scwx/qt/ui/county_dialog.hpp | ||||||
|            source/scwx/qt/ui/download_dialog.hpp |            source/scwx/qt/ui/download_dialog.hpp | ||||||
|            source/scwx/qt/ui/edit_line_dialog.hpp |            source/scwx/qt/ui/edit_line_dialog.hpp | ||||||
|  |            source/scwx/qt/ui/edit_marker_dialog.hpp | ||||||
|            source/scwx/qt/ui/flow_layout.hpp |            source/scwx/qt/ui/flow_layout.hpp | ||||||
|            source/scwx/qt/ui/gps_info_dialog.hpp |            source/scwx/qt/ui/gps_info_dialog.hpp | ||||||
|            source/scwx/qt/ui/hotkey_edit.hpp |            source/scwx/qt/ui/hotkey_edit.hpp | ||||||
|  | @ -285,6 +286,7 @@ set(SRC_UI source/scwx/qt/ui/about_dialog.cpp | ||||||
|            source/scwx/qt/ui/county_dialog.cpp |            source/scwx/qt/ui/county_dialog.cpp | ||||||
|            source/scwx/qt/ui/download_dialog.cpp |            source/scwx/qt/ui/download_dialog.cpp | ||||||
|            source/scwx/qt/ui/edit_line_dialog.cpp |            source/scwx/qt/ui/edit_line_dialog.cpp | ||||||
|  |            source/scwx/qt/ui/edit_marker_dialog.cpp | ||||||
|            source/scwx/qt/ui/flow_layout.cpp |            source/scwx/qt/ui/flow_layout.cpp | ||||||
|            source/scwx/qt/ui/gps_info_dialog.cpp |            source/scwx/qt/ui/gps_info_dialog.cpp | ||||||
|            source/scwx/qt/ui/hotkey_edit.cpp |            source/scwx/qt/ui/hotkey_edit.cpp | ||||||
|  | @ -314,6 +316,7 @@ set(UI_UI  source/scwx/qt/ui/about_dialog.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/county_dialog.ui | ||||||
|            source/scwx/qt/ui/edit_line_dialog.ui |            source/scwx/qt/ui/edit_line_dialog.ui | ||||||
|  |            source/scwx/qt/ui/edit_marker_dialog.ui | ||||||
|            source/scwx/qt/ui/gps_info_dialog.ui |            source/scwx/qt/ui/gps_info_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 | ||||||
|  | @ -357,6 +360,7 @@ set(HDR_UTIL source/scwx/qt/util/color.hpp | ||||||
|              source/scwx/qt/util/network.hpp |              source/scwx/qt/util/network.hpp | ||||||
|              source/scwx/qt/util/streams.hpp |              source/scwx/qt/util/streams.hpp | ||||||
|              source/scwx/qt/util/texture_atlas.hpp |              source/scwx/qt/util/texture_atlas.hpp | ||||||
|  |              source/scwx/qt/util/q_color_modulate.hpp | ||||||
|              source/scwx/qt/util/q_file_buffer.hpp |              source/scwx/qt/util/q_file_buffer.hpp | ||||||
|              source/scwx/qt/util/q_file_input_stream.hpp |              source/scwx/qt/util/q_file_input_stream.hpp | ||||||
|              source/scwx/qt/util/time.hpp |              source/scwx/qt/util/time.hpp | ||||||
|  | @ -369,6 +373,7 @@ set(SRC_UTIL source/scwx/qt/util/color.cpp | ||||||
|              source/scwx/qt/util/maplibre.cpp |              source/scwx/qt/util/maplibre.cpp | ||||||
|              source/scwx/qt/util/network.cpp |              source/scwx/qt/util/network.cpp | ||||||
|              source/scwx/qt/util/texture_atlas.cpp |              source/scwx/qt/util/texture_atlas.cpp | ||||||
|  |              source/scwx/qt/util/q_color_modulate.cpp | ||||||
|              source/scwx/qt/util/q_file_buffer.cpp |              source/scwx/qt/util/q_file_buffer.cpp | ||||||
|              source/scwx/qt/util/q_file_input_stream.cpp |              source/scwx/qt/util/q_file_input_stream.cpp | ||||||
|              source/scwx/qt/util/time.cpp |              source/scwx/qt/util/time.cpp | ||||||
|  |  | ||||||
|  | @ -32,6 +32,10 @@ | ||||||
|         <file>res/icons/font-awesome-6/angles-up-solid.svg</file> |         <file>res/icons/font-awesome-6/angles-up-solid.svg</file> | ||||||
|         <file>res/icons/font-awesome-6/backward-step-solid.svg</file> |         <file>res/icons/font-awesome-6/backward-step-solid.svg</file> | ||||||
|         <file>res/icons/font-awesome-6/book-solid.svg</file> |         <file>res/icons/font-awesome-6/book-solid.svg</file> | ||||||
|  |         <file>res/icons/font-awesome-6/briefcase-solid.svg</file> | ||||||
|  |         <file>res/icons/font-awesome-6/building-columns-solid.svg</file> | ||||||
|  |         <file>res/icons/font-awesome-6/building-solid.svg</file> | ||||||
|  |         <file>res/icons/font-awesome-6/caravan-solid.svg</file> | ||||||
|         <file>res/icons/font-awesome-6/copy-regular.svg</file> |         <file>res/icons/font-awesome-6/copy-regular.svg</file> | ||||||
|         <file>res/icons/font-awesome-6/discord.svg</file> |         <file>res/icons/font-awesome-6/discord.svg</file> | ||||||
|         <file>res/icons/font-awesome-6/earth-americas-solid.svg</file> |         <file>res/icons/font-awesome-6/earth-americas-solid.svg</file> | ||||||
|  | @ -40,8 +44,11 @@ | ||||||
|         <file>res/icons/font-awesome-6/gears-solid.svg</file> |         <file>res/icons/font-awesome-6/gears-solid.svg</file> | ||||||
|         <file>res/icons/font-awesome-6/github.svg</file> |         <file>res/icons/font-awesome-6/github.svg</file> | ||||||
|         <file>res/icons/font-awesome-6/house-solid.svg</file> |         <file>res/icons/font-awesome-6/house-solid.svg</file> | ||||||
|  |         <file>res/icons/font-awesome-6/house-solid-white.svg</file> | ||||||
|         <file>res/icons/font-awesome-6/keyboard-regular.svg</file> |         <file>res/icons/font-awesome-6/keyboard-regular.svg</file> | ||||||
|         <file>res/icons/font-awesome-6/layer-group-solid.svg</file> |         <file>res/icons/font-awesome-6/layer-group-solid.svg</file> | ||||||
|  |         <file>res/icons/font-awesome-6/location-crosshairs-solid.svg</file> | ||||||
|  |         <file>res/icons/font-awesome-6/location-pin.svg</file> | ||||||
|         <file>res/icons/font-awesome-6/palette-solid.svg</file> |         <file>res/icons/font-awesome-6/palette-solid.svg</file> | ||||||
|         <file>res/icons/font-awesome-6/pause-solid.svg</file> |         <file>res/icons/font-awesome-6/pause-solid.svg</file> | ||||||
|         <file>res/icons/font-awesome-6/play-solid.svg</file> |         <file>res/icons/font-awesome-6/play-solid.svg</file> | ||||||
|  | @ -53,7 +60,9 @@ | ||||||
|         <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/star-solid.svg</file> |         <file>res/icons/font-awesome-6/star-solid.svg</file> | ||||||
|  |         <file>res/icons/font-awesome-6/star-solid-white.svg</file> | ||||||
|         <file>res/icons/font-awesome-6/stop-solid.svg</file> |         <file>res/icons/font-awesome-6/stop-solid.svg</file> | ||||||
|  |         <file>res/icons/font-awesome-6/tent-solid.svg</file> | ||||||
|         <file>res/icons/font-awesome-6/volume-high-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> | ||||||
|  |  | ||||||
|  | @ -38,7 +38,7 @@ static constexpr std::size_t kIntegersPerVertex_ = 4; | ||||||
| static constexpr std::size_t kIntegerBufferLength_ = | static constexpr std::size_t kIntegerBufferLength_ = | ||||||
|    kNumTriangles * kVerticesPerTriangle * kIntegersPerVertex_; |    kNumTriangles * kVerticesPerTriangle * kIntegersPerVertex_; | ||||||
| 
 | 
 | ||||||
| struct GeoIconDrawItem | struct GeoIconDrawItem : types::EventHandler | ||||||
| { | { | ||||||
|    units::length::nautical_miles<double>       threshold_ {}; |    units::length::nautical_miles<double>       threshold_ {}; | ||||||
|    std::chrono::sys_time<std::chrono::seconds> startTime_ {}; |    std::chrono::sys_time<std::chrono::seconds> startTime_ {}; | ||||||
|  | @ -691,7 +691,7 @@ void GeoIcons::Impl::UpdateSingleBuffer( | ||||||
|                                hoverIcons.end(), |                                hoverIcons.end(), | ||||||
|                                [&di](auto& entry) { return entry.di_ == di; }); |                                [&di](auto& entry) { return entry.di_ == di; }); | ||||||
| 
 | 
 | ||||||
|    if (di->visible_ && !di->hoverText_.empty()) |    if (di->visible_ && (!di->hoverText_.empty() || di->event_ != nullptr)) | ||||||
|    { |    { | ||||||
|       const units::angle::radians<double> radians = angle; |       const units::angle::radians<double> radians = angle; | ||||||
| 
 | 
 | ||||||
|  | @ -903,7 +903,7 @@ bool GeoIcons::RunMousePicking( | ||||||
|    const QPointF&   mouseGlobalPos, |    const QPointF&   mouseGlobalPos, | ||||||
|    const glm::vec2& mouseCoords, |    const glm::vec2& mouseCoords, | ||||||
|    const common::Coordinate& /* mouseGeoCoords */, |    const common::Coordinate& /* mouseGeoCoords */, | ||||||
|    std::shared_ptr<types::EventHandler>& /* eventHandler */) |    std::shared_ptr<types::EventHandler>& eventHandler) | ||||||
| { | { | ||||||
|    std::unique_lock lock {p->iconMutex_}; |    std::unique_lock lock {p->iconMutex_}; | ||||||
| 
 | 
 | ||||||
|  | @ -993,12 +993,27 @@ bool GeoIcons::RunMousePicking( | ||||||
|    if (it != p->currentHoverIcons_.crend()) |    if (it != p->currentHoverIcons_.crend()) | ||||||
|    { |    { | ||||||
|       itemPicked = true; |       itemPicked = true; | ||||||
|       util::tooltip::Show(it->di_->hoverText_, mouseGlobalPos); |       if (!it->di_->hoverText_.empty()) | ||||||
|  |       { | ||||||
|  |          // Show tooltip
 | ||||||
|  |          util::tooltip::Show(it->di_->hoverText_, mouseGlobalPos); | ||||||
|  |       } | ||||||
|  |       if (it->di_->event_ != nullptr) | ||||||
|  |       { | ||||||
|  |          eventHandler = it->di_; | ||||||
|  |       } | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    return itemPicked; |    return itemPicked; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void GeoIcons::RegisterEventHandler( | ||||||
|  |    const std::shared_ptr<GeoIconDrawItem>& di, | ||||||
|  |    const std::function<void(QEvent*)>&     eventHandler) | ||||||
|  | { | ||||||
|  |    di->event_ = eventHandler; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| } // namespace draw
 | } // namespace draw
 | ||||||
| } // namespace gl
 | } // namespace gl
 | ||||||
| } // namespace qt
 | } // namespace qt
 | ||||||
|  |  | ||||||
|  | @ -183,6 +183,16 @@ public: | ||||||
|     */ |     */ | ||||||
|    void FinishIcons(); |    void FinishIcons(); | ||||||
| 
 | 
 | ||||||
|  |    /**
 | ||||||
|  |     * Registers an event handler for an icon. | ||||||
|  |     * | ||||||
|  |     * @param [in] di Icon draw item | ||||||
|  |     * @param [in] eventHandler Event handler function | ||||||
|  |     */ | ||||||
|  |    static void | ||||||
|  |    RegisterEventHandler(const std::shared_ptr<GeoIconDrawItem>& di, | ||||||
|  |                         const std::function<void(QEvent*)>&     eventHandler); | ||||||
|  | 
 | ||||||
| private: | private: | ||||||
|    class Impl; |    class Impl; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,13 +1,17 @@ | ||||||
| #include <scwx/qt/manager/marker_manager.hpp> | #include <scwx/qt/manager/marker_manager.hpp> | ||||||
| #include <scwx/qt/types/marker_types.hpp> | #include <scwx/qt/types/marker_types.hpp> | ||||||
|  | #include <scwx/qt/util/color.hpp> | ||||||
| #include <scwx/qt/util/json.hpp> | #include <scwx/qt/util/json.hpp> | ||||||
|  | #include <scwx/qt/util/texture_atlas.hpp> | ||||||
| #include <scwx/qt/main/application.hpp> | #include <scwx/qt/main/application.hpp> | ||||||
|  | #include <scwx/qt/manager/resource_manager.hpp> | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
| 
 | 
 | ||||||
| #include <filesystem> | #include <filesystem> | ||||||
| #include <shared_mutex> | #include <shared_mutex> | ||||||
| #include <vector> | #include <vector> | ||||||
| #include <string> | #include <string> | ||||||
|  | #include <unordered_map> | ||||||
| 
 | 
 | ||||||
| #include <QStandardPaths> | #include <QStandardPaths> | ||||||
| #include <boost/json.hpp> | #include <boost/json.hpp> | ||||||
|  | @ -27,6 +31,10 @@ static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | ||||||
| static const std::string kNameName_      = "name"; | static const std::string kNameName_      = "name"; | ||||||
| static const std::string kLatitudeName_  = "latitude"; | static const std::string kLatitudeName_  = "latitude"; | ||||||
| static const std::string kLongitudeName_ = "longitude"; | static const std::string kLongitudeName_ = "longitude"; | ||||||
|  | static const std::string kIconName_      = "icon"; | ||||||
|  | static const std::string kIconColorName_ = "icon-color"; | ||||||
|  | 
 | ||||||
|  | static const std::string defaultIconName = "images/location-marker"; | ||||||
| 
 | 
 | ||||||
| class MarkerManager::Impl | class MarkerManager::Impl | ||||||
| { | { | ||||||
|  | @ -36,15 +44,16 @@ public: | ||||||
|    explicit Impl(MarkerManager* self) : self_ {self} {} |    explicit Impl(MarkerManager* self) : self_ {self} {} | ||||||
|    ~Impl() { threadPool_.join(); } |    ~Impl() { threadPool_.join(); } | ||||||
| 
 | 
 | ||||||
|    std::string                                markerSettingsPath_ {""}; |    std::string                                 markerSettingsPath_ {""}; | ||||||
|    std::vector<std::shared_ptr<MarkerRecord>> markerRecords_ {}; |    std::vector<std::shared_ptr<MarkerRecord>>  markerRecords_ {}; | ||||||
|    std::unordered_map<types::MarkerId, size_t> idToIndex_ {}; |    std::unordered_map<types::MarkerId, size_t> idToIndex_ {}; | ||||||
| 
 |    std::unordered_map<std::string, types::MarkerIconInfo> markerIcons_ {}; | ||||||
| 
 | 
 | ||||||
|    MarkerManager* self_; |    MarkerManager* self_; | ||||||
| 
 | 
 | ||||||
|    boost::asio::thread_pool threadPool_ {1u}; |    boost::asio::thread_pool threadPool_ {1u}; | ||||||
|    std::shared_mutex        markerRecordLock_ {}; |    std::shared_mutex        markerRecordLock_ {}; | ||||||
|  |    std::shared_mutex        markerIconsLock_ {}; | ||||||
| 
 | 
 | ||||||
|    void                          InitializeMarkerSettings(); |    void                          InitializeMarkerSettings(); | ||||||
|    void                          ReadMarkerSettings(); |    void                          ReadMarkerSettings(); | ||||||
|  | @ -53,16 +62,12 @@ public: | ||||||
| 
 | 
 | ||||||
|    void InitalizeIds(); |    void InitalizeIds(); | ||||||
|    types::MarkerId NewId(); |    types::MarkerId NewId(); | ||||||
|    types::MarkerId lastId_; |    types::MarkerId lastId_ {0}; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class MarkerManager::Impl::MarkerRecord | class MarkerManager::Impl::MarkerRecord | ||||||
| { | { | ||||||
| public: | public: | ||||||
|    MarkerRecord(const std::string& name, double latitude, double longitude) : |  | ||||||
|       markerInfo_ {types::MarkerInfo(name, latitude, longitude)} |  | ||||||
|    { |  | ||||||
|    } |  | ||||||
|    MarkerRecord(const types::MarkerInfo& info) : |    MarkerRecord(const types::MarkerInfo& info) : | ||||||
|       markerInfo_ {info} |       markerInfo_ {info} | ||||||
|    { |    { | ||||||
|  | @ -81,16 +86,50 @@ public: | ||||||
|    { |    { | ||||||
|       jv = {{kNameName_, record->markerInfo_.name}, |       jv = {{kNameName_, record->markerInfo_.name}, | ||||||
|             {kLatitudeName_, record->markerInfo_.latitude}, |             {kLatitudeName_, record->markerInfo_.latitude}, | ||||||
|             {kLongitudeName_, record->markerInfo_.longitude}}; |             {kLongitudeName_, record->markerInfo_.longitude}, | ||||||
|  |             {kIconName_, record->markerInfo_.iconName}, | ||||||
|  |             {kIconColorName_, | ||||||
|  |              util::color::ToArgbString(record->markerInfo_.iconColor)}}; | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    friend MarkerRecord tag_invoke(boost::json::value_to_tag<MarkerRecord>, |    friend MarkerRecord tag_invoke(boost::json::value_to_tag<MarkerRecord>, | ||||||
|                                   const boost::json::value& jv) |                                   const boost::json::value& jv) | ||||||
|    { |    { | ||||||
|       return MarkerRecord( |       static const boost::gil::rgba8_pixel_t defaultIconColor = | ||||||
|  |          util::color::ToRgba8PixelT("#ffff0000"); | ||||||
|  | 
 | ||||||
|  |       const boost::json::object& jo = jv.as_object(); | ||||||
|  | 
 | ||||||
|  |       std::string               iconName  = defaultIconName; | ||||||
|  |       boost::gil::rgba8_pixel_t iconColor = defaultIconColor; | ||||||
|  | 
 | ||||||
|  |       if (jo.contains(kIconName_) && jo.at(kIconName_).is_string()) | ||||||
|  |       { | ||||||
|  |          iconName = boost::json::value_to<std::string>(jv.at(kIconName_)); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if (jo.contains(kIconColorName_) && jo.at(kIconName_).is_string()) | ||||||
|  |       { | ||||||
|  |          try | ||||||
|  |          { | ||||||
|  |             iconColor = util::color::ToRgba8PixelT( | ||||||
|  |                boost::json::value_to<std::string>(jv.at(kIconColorName_))); | ||||||
|  |          } | ||||||
|  |          catch (const std::exception& ex) | ||||||
|  |          { | ||||||
|  |             logger_->warn( | ||||||
|  |                "Could not parse color value in location-markers.json with the " | ||||||
|  |                "following exception: {}", | ||||||
|  |                ex.what()); | ||||||
|  |          } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       return {types::MarkerInfo( | ||||||
|          boost::json::value_to<std::string>(jv.at(kNameName_)), |          boost::json::value_to<std::string>(jv.at(kNameName_)), | ||||||
|          boost::json::value_to<double>(jv.at(kLatitudeName_)), |          boost::json::value_to<double>(jv.at(kLatitudeName_)), | ||||||
|          boost::json::value_to<double>(jv.at(kLongitudeName_))); |          boost::json::value_to<double>(jv.at(kLongitudeName_)), | ||||||
|  |          iconName, | ||||||
|  |          iconColor)}; | ||||||
|    } |    } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | @ -129,7 +168,7 @@ void MarkerManager::Impl::ReadMarkerSettings() | ||||||
| 
 | 
 | ||||||
|    boost::json::value markerJson = nullptr; |    boost::json::value markerJson = nullptr; | ||||||
|    { |    { | ||||||
|       std::unique_lock lock(markerRecordLock_); |       const std::unique_lock lock(markerRecordLock_); | ||||||
| 
 | 
 | ||||||
|       // Determine if marker settings exists
 |       // Determine if marker settings exists
 | ||||||
|       if (std::filesystem::exists(markerSettingsPath_)) |       if (std::filesystem::exists(markerSettingsPath_)) | ||||||
|  | @ -147,18 +186,16 @@ void MarkerManager::Impl::ReadMarkerSettings() | ||||||
|          { |          { | ||||||
|             try |             try | ||||||
|             { |             { | ||||||
|                MarkerRecord record = |                auto record = boost::json::value_to<MarkerRecord>(markerEntry); | ||||||
|                   boost::json::value_to<MarkerRecord>(markerEntry); |  | ||||||
| 
 | 
 | ||||||
|                if (!record.markerInfo_.name.empty()) |                const types::MarkerId id    = NewId(); | ||||||
|                { |                const size_t          index = markerRecords_.size(); | ||||||
|                   types::MarkerId id    = NewId(); |                record.markerInfo_.id       = id; | ||||||
|                   size_t          index = markerRecords_.size(); |                markerRecords_.emplace_back( | ||||||
|                   record.markerInfo_.id = id; |                   std::make_shared<MarkerRecord>(record.markerInfo_)); | ||||||
|                   markerRecords_.emplace_back( |                idToIndex_.emplace(id, index); | ||||||
|                      std::make_shared<MarkerRecord>(record.markerInfo_)); | 
 | ||||||
|                   idToIndex_.emplace(id, index); |                self_->add_icon(record.markerInfo_.iconName, true); | ||||||
|                } |  | ||||||
|             } |             } | ||||||
|             catch (const std::exception& ex) |             catch (const std::exception& ex) | ||||||
|             { |             { | ||||||
|  | @ -166,6 +203,8 @@ void MarkerManager::Impl::ReadMarkerSettings() | ||||||
|             } |             } | ||||||
|          } |          } | ||||||
| 
 | 
 | ||||||
|  |          ResourceManager::BuildAtlas(); | ||||||
|  | 
 | ||||||
|          logger_->debug("{} location marker entries", markerRecords_.size()); |          logger_->debug("{} location marker entries", markerRecords_.size()); | ||||||
|       } |       } | ||||||
|    } |    } | ||||||
|  | @ -177,7 +216,7 @@ void MarkerManager::Impl::WriteMarkerSettings() | ||||||
| { | { | ||||||
|    logger_->info("Saving location marker settings"); |    logger_->info("Saving location marker settings"); | ||||||
| 
 | 
 | ||||||
|    std::shared_lock lock(markerRecordLock_); |    const std::shared_lock lock(markerRecordLock_); | ||||||
|    auto markerJson = boost::json::value_from(markerRecords_); |    auto markerJson = boost::json::value_from(markerRecords_); | ||||||
|    util::json::WriteJsonFile(markerSettingsPath_, markerJson); |    util::json::WriteJsonFile(markerSettingsPath_, markerJson); | ||||||
| } | } | ||||||
|  | @ -198,6 +237,20 @@ MarkerManager::Impl::GetMarkerByName(const std::string& name) | ||||||
| 
 | 
 | ||||||
| MarkerManager::MarkerManager() : p(std::make_unique<Impl>(this)) | MarkerManager::MarkerManager() : p(std::make_unique<Impl>(this)) | ||||||
| { | { | ||||||
|  |    static const std::vector<types::MarkerIconInfo> defaultMarkerIcons_ { | ||||||
|  |       types::MarkerIconInfo(types::ImageTexture::LocationMarker, -1, -1), | ||||||
|  |       types::MarkerIconInfo(types::ImageTexture::LocationPin, 6, 16), | ||||||
|  |       types::MarkerIconInfo(types::ImageTexture::LocationCrosshair, -1, -1), | ||||||
|  |       types::MarkerIconInfo(types::ImageTexture::LocationStar, -1, -1), | ||||||
|  |       types::MarkerIconInfo(types::ImageTexture::LocationBriefcase, -1, -1), | ||||||
|  |       types::MarkerIconInfo( | ||||||
|  |          types::ImageTexture::LocationBuildingColumns, -1, -1), | ||||||
|  |       types::MarkerIconInfo(types::ImageTexture::LocationBuilding, -1, -1), | ||||||
|  |       types::MarkerIconInfo(types::ImageTexture::LocationCaravan, -1, -1), | ||||||
|  |       types::MarkerIconInfo(types::ImageTexture::LocationHouse, -1, -1), | ||||||
|  |       types::MarkerIconInfo(types::ImageTexture::LocationTent, -1, -1), | ||||||
|  |    }; | ||||||
|  | 
 | ||||||
|    p->InitializeMarkerSettings(); |    p->InitializeMarkerSettings(); | ||||||
| 
 | 
 | ||||||
|    boost::asio::post(p->threadPool_, |    boost::asio::post(p->threadPool_, | ||||||
|  | @ -207,8 +260,18 @@ MarkerManager::MarkerManager() : p(std::make_unique<Impl>(this)) | ||||||
|                         { |                         { | ||||||
|                            // Read Marker settings on startup
 |                            // Read Marker settings on startup
 | ||||||
|                            main::Application::WaitForInitialization(); |                            main::Application::WaitForInitialization(); | ||||||
|  |                            { | ||||||
|  |                               const std::unique_lock lock(p->markerIconsLock_); | ||||||
|  |                               p->markerIcons_.reserve( | ||||||
|  |                                  defaultMarkerIcons_.size()); | ||||||
|  |                               for (auto& icon : defaultMarkerIcons_) | ||||||
|  |                               { | ||||||
|  |                                  p->markerIcons_.emplace(icon.name, icon); | ||||||
|  |                               } | ||||||
|  |                            } | ||||||
|                            p->ReadMarkerSettings(); |                            p->ReadMarkerSettings(); | ||||||
| 
 | 
 | ||||||
|  |                            Q_EMIT IconsReady(); | ||||||
|                            Q_EMIT MarkersInitialized(p->markerRecords_.size()); |                            Q_EMIT MarkersInitialized(p->markerRecords_.size()); | ||||||
|                         } |                         } | ||||||
|                         catch (const std::exception& ex) |                         catch (const std::exception& ex) | ||||||
|  | @ -230,7 +293,7 @@ size_t MarkerManager::marker_count() | ||||||
| 
 | 
 | ||||||
| std::optional<types::MarkerInfo> MarkerManager::get_marker(types::MarkerId id) | std::optional<types::MarkerInfo> MarkerManager::get_marker(types::MarkerId id) | ||||||
| { | { | ||||||
|    std::shared_lock lock(p->markerRecordLock_); |    const std::shared_lock lock(p->markerRecordLock_); | ||||||
|    if (!p->idToIndex_.contains(id)) |    if (!p->idToIndex_.contains(id)) | ||||||
|    { |    { | ||||||
|       return {}; |       return {}; | ||||||
|  | @ -248,7 +311,7 @@ std::optional<types::MarkerInfo> MarkerManager::get_marker(types::MarkerId id) | ||||||
| 
 | 
 | ||||||
| std::optional<size_t> MarkerManager::get_index(types::MarkerId id) | std::optional<size_t> MarkerManager::get_index(types::MarkerId id) | ||||||
| { | { | ||||||
|    std::shared_lock lock(p->markerRecordLock_); |    const std::shared_lock lock(p->markerRecordLock_); | ||||||
|    if (!p->idToIndex_.contains(id)) |    if (!p->idToIndex_.contains(id)) | ||||||
|    { |    { | ||||||
|       return {}; |       return {}; | ||||||
|  | @ -256,10 +319,11 @@ std::optional<size_t> MarkerManager::get_index(types::MarkerId id) | ||||||
|    return p->idToIndex_[id]; |    return p->idToIndex_[id]; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void MarkerManager::set_marker(types::MarkerId id, const types::MarkerInfo& marker) | void MarkerManager::set_marker(types::MarkerId          id, | ||||||
|  |                                const types::MarkerInfo& marker) | ||||||
| { | { | ||||||
|    { |    { | ||||||
|       std::unique_lock lock(p->markerRecordLock_); |       const std::unique_lock lock(p->markerRecordLock_); | ||||||
|       if (!p->idToIndex_.contains(id)) |       if (!p->idToIndex_.contains(id)) | ||||||
|       { |       { | ||||||
|          return; |          return; | ||||||
|  | @ -270,33 +334,39 @@ void MarkerManager::set_marker(types::MarkerId id, const types::MarkerInfo& mark | ||||||
|          logger_->warn("id in idToIndex_ but out of range!"); |          logger_->warn("id in idToIndex_ but out of range!"); | ||||||
|          return; |          return; | ||||||
|       } |       } | ||||||
|       std::shared_ptr<MarkerManager::Impl::MarkerRecord>& markerRecord = |       const std::shared_ptr<MarkerManager::Impl::MarkerRecord>& markerRecord = | ||||||
|          p->markerRecords_[index]; |          p->markerRecords_[index]; | ||||||
|       markerRecord->markerInfo_ = marker; |       markerRecord->markerInfo_    = marker; | ||||||
|  |       markerRecord->markerInfo_.id = id; | ||||||
|  | 
 | ||||||
|  |       add_icon(marker.iconName); | ||||||
|    } |    } | ||||||
|    Q_EMIT MarkerChanged(id); |    Q_EMIT MarkerChanged(id); | ||||||
|    Q_EMIT MarkersUpdated(); |    Q_EMIT MarkersUpdated(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void MarkerManager::add_marker(const types::MarkerInfo& marker) | types::MarkerId MarkerManager::add_marker(const types::MarkerInfo& marker) | ||||||
| { | { | ||||||
|    types::MarkerId id; |    types::MarkerId id; | ||||||
|    { |    { | ||||||
|       std::unique_lock lock(p->markerRecordLock_); |       const std::unique_lock lock(p->markerRecordLock_); | ||||||
|       id = p->NewId(); |       id = p->NewId(); | ||||||
|       size_t index = p->markerRecords_.size(); |       size_t index = p->markerRecords_.size(); | ||||||
|       p->idToIndex_.emplace(id, index); |       p->idToIndex_.emplace(id, index); | ||||||
|       p->markerRecords_.emplace_back(std::make_shared<Impl::MarkerRecord>(marker)); |       p->markerRecords_.emplace_back(std::make_shared<Impl::MarkerRecord>(marker)); | ||||||
|       p->markerRecords_[index]->markerInfo_.id = id; |       p->markerRecords_[index]->markerInfo_.id = id; | ||||||
|  | 
 | ||||||
|  |       add_icon(marker.iconName); | ||||||
|    } |    } | ||||||
|    Q_EMIT MarkerAdded(id); |    Q_EMIT MarkerAdded(id); | ||||||
|    Q_EMIT MarkersUpdated(); |    Q_EMIT MarkersUpdated(); | ||||||
|  |    return id; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void MarkerManager::remove_marker(types::MarkerId id) | void MarkerManager::remove_marker(types::MarkerId id) | ||||||
| { | { | ||||||
|    { |    { | ||||||
|       std::unique_lock lock(p->markerRecordLock_); |       const std::unique_lock lock(p->markerRecordLock_); | ||||||
|       if (!p->idToIndex_.contains(id)) |       if (!p->idToIndex_.contains(id)) | ||||||
|       { |       { | ||||||
|          return; |          return; | ||||||
|  | @ -327,7 +397,7 @@ void MarkerManager::remove_marker(types::MarkerId id) | ||||||
| void MarkerManager::move_marker(size_t from, size_t to) | void MarkerManager::move_marker(size_t from, size_t to) | ||||||
| { | { | ||||||
|    { |    { | ||||||
|       std::unique_lock lock(p->markerRecordLock_); |       const std::unique_lock lock(p->markerRecordLock_); | ||||||
|       if (from >= p->markerRecords_.size() || to >= p->markerRecords_.size()) |       if (from >= p->markerRecords_.size() || to >= p->markerRecords_.size()) | ||||||
|       { |       { | ||||||
|          return; |          return; | ||||||
|  | @ -358,13 +428,64 @@ void MarkerManager::move_marker(size_t from, size_t to) | ||||||
| 
 | 
 | ||||||
| void MarkerManager::for_each(std::function<MarkerForEachFunc> func) | void MarkerManager::for_each(std::function<MarkerForEachFunc> func) | ||||||
| { | { | ||||||
|    std::shared_lock lock(p->markerRecordLock_); |    const std::shared_lock lock(p->markerRecordLock_); | ||||||
|    for (auto marker : p->markerRecords_) |    for (auto marker : p->markerRecords_) | ||||||
|    { |    { | ||||||
|       func(marker->markerInfo_); |       func(marker->markerInfo_); | ||||||
|    } |    } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void MarkerManager::add_icon(const std::string& name, bool startup) | ||||||
|  | { | ||||||
|  |    { | ||||||
|  |       const std::unique_lock lock(p->markerIconsLock_); | ||||||
|  |       if (p->markerIcons_.contains(name)) | ||||||
|  |       { | ||||||
|  |          return; | ||||||
|  |       } | ||||||
|  |       const std::shared_ptr<boost::gil::rgba8_image_t> image = | ||||||
|  |          ResourceManager::LoadImageResource(name); | ||||||
|  | 
 | ||||||
|  |       if (image) | ||||||
|  |       { | ||||||
|  |          auto icon = types::MarkerIconInfo(name, -1, -1, image); | ||||||
|  |          p->markerIcons_.emplace(name, icon); | ||||||
|  |       } | ||||||
|  |       else | ||||||
|  |       { | ||||||
|  |          // defaultIconName should always be in markerIcons, so at is fine
 | ||||||
|  |          auto icon = p->markerIcons_.at(defaultIconName); | ||||||
|  |          p->markerIcons_.emplace(name, icon); | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    if (!startup) | ||||||
|  |    { | ||||||
|  |       ResourceManager::BuildAtlas(); | ||||||
|  |       Q_EMIT IconAdded(name); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::optional<types::MarkerIconInfo> | ||||||
|  | MarkerManager::get_icon(const std::string& name) | ||||||
|  | { | ||||||
|  |    const std::shared_lock lock(p->markerIconsLock_); | ||||||
|  |    auto                   it = p->markerIcons_.find(name); | ||||||
|  |    if (it != p->markerIcons_.end()) | ||||||
|  |    { | ||||||
|  |       return it->second; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return {}; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const std::unordered_map<std::string, types::MarkerIconInfo> | ||||||
|  | MarkerManager::get_icons() | ||||||
|  | { | ||||||
|  |    const std::shared_lock lock(p->markerIconsLock_); | ||||||
|  |    return p->markerIcons_; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // Only use for testing
 | // Only use for testing
 | ||||||
| void MarkerManager::set_marker_settings_path(const std::string& path) | void MarkerManager::set_marker_settings_path(const std::string& path) | ||||||
| { | { | ||||||
|  | @ -377,7 +498,7 @@ std::shared_ptr<MarkerManager> MarkerManager::Instance() | ||||||
|    static std::weak_ptr<MarkerManager> markerManagerReference_ {}; |    static std::weak_ptr<MarkerManager> markerManagerReference_ {}; | ||||||
|    static std::mutex                   instanceMutex_ {}; |    static std::mutex                   instanceMutex_ {}; | ||||||
| 
 | 
 | ||||||
|    std::unique_lock lock(instanceMutex_); |    const std::unique_lock lock(instanceMutex_); | ||||||
| 
 | 
 | ||||||
|    std::shared_ptr<MarkerManager> markerManager = |    std::shared_ptr<MarkerManager> markerManager = | ||||||
|       markerManagerReference_.lock(); |       markerManagerReference_.lock(); | ||||||
|  | @ -391,6 +512,11 @@ std::shared_ptr<MarkerManager> MarkerManager::Instance() | ||||||
|    return markerManager; |    return markerManager; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | const std::string& MarkerManager::getDefaultIconName() | ||||||
|  | { | ||||||
|  |    return defaultIconName; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| } // namespace manager
 | } // namespace manager
 | ||||||
| } // namespace qt
 | } // namespace qt
 | ||||||
| } // namespace scwx
 | } // namespace scwx
 | ||||||
|  |  | ||||||
|  | @ -23,11 +23,15 @@ public: | ||||||
| 
 | 
 | ||||||
|    size_t                           marker_count(); |    size_t                           marker_count(); | ||||||
|    std::optional<types::MarkerInfo> get_marker(types::MarkerId id); |    std::optional<types::MarkerInfo> get_marker(types::MarkerId id); | ||||||
|    std::optional<size_t> get_index(types::MarkerId id); |    std::optional<size_t>            get_index(types::MarkerId id); | ||||||
|    void set_marker(types::MarkerId id, const types::MarkerInfo& marker); |    void set_marker(types::MarkerId id, const types::MarkerInfo& marker); | ||||||
|    void add_marker(const types::MarkerInfo& marker); |    types::MarkerId add_marker(const types::MarkerInfo& marker); | ||||||
|    void remove_marker(types::MarkerId id); |    void            remove_marker(types::MarkerId id); | ||||||
|    void move_marker(size_t from, size_t to); |    void            move_marker(size_t from, size_t to); | ||||||
|  | 
 | ||||||
|  |    void add_icon(const std::string& name, bool startup = false); | ||||||
|  |    std::optional<types::MarkerIconInfo> get_icon(const std::string& name); | ||||||
|  |    const std::unordered_map<std::string, types::MarkerIconInfo> get_icons(); | ||||||
| 
 | 
 | ||||||
|    void for_each(std::function<MarkerForEachFunc> func); |    void for_each(std::function<MarkerForEachFunc> func); | ||||||
| 
 | 
 | ||||||
|  | @ -35,6 +39,7 @@ public: | ||||||
|    void set_marker_settings_path(const std::string& path); |    void set_marker_settings_path(const std::string& path); | ||||||
| 
 | 
 | ||||||
|    static std::shared_ptr<MarkerManager> Instance(); |    static std::shared_ptr<MarkerManager> Instance(); | ||||||
|  |    static const std::string&             getDefaultIconName(); | ||||||
| 
 | 
 | ||||||
| signals: | signals: | ||||||
|    void MarkersInitialized(size_t count); |    void MarkersInitialized(size_t count); | ||||||
|  | @ -43,6 +48,9 @@ signals: | ||||||
|    void MarkerAdded(types::MarkerId id); |    void MarkerAdded(types::MarkerId id); | ||||||
|    void MarkerRemoved(types::MarkerId id); |    void MarkerRemoved(types::MarkerId id); | ||||||
| 
 | 
 | ||||||
|  |    void IconsReady(); | ||||||
|  |    void IconAdded(std::string name); | ||||||
|  | 
 | ||||||
| private: | private: | ||||||
|    class Impl; |    class Impl; | ||||||
|    std::unique_ptr<Impl> p; |    std::unique_ptr<Impl> p; | ||||||
|  |  | ||||||
|  | @ -22,6 +22,9 @@ namespace ResourceManager | ||||||
| static const std::string logPrefix_ = "scwx::qt::manager::resource_manager"; | static const std::string logPrefix_ = "scwx::qt::manager::resource_manager"; | ||||||
| static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | ||||||
| 
 | 
 | ||||||
|  | static const size_t atlasWidth  = 2048; | ||||||
|  | static const size_t atlasHeight = 2048; | ||||||
|  | 
 | ||||||
| static void LoadFonts(); | static void LoadFonts(); | ||||||
| static void LoadTextures(); | static void LoadTextures(); | ||||||
| 
 | 
 | ||||||
|  | @ -68,8 +71,7 @@ LoadImageResources(const std::vector<std::string>& urlStrings) | ||||||
| 
 | 
 | ||||||
|    if (!images.empty()) |    if (!images.empty()) | ||||||
|    { |    { | ||||||
|       util::TextureAtlas& textureAtlas = util::TextureAtlas::Instance(); |       BuildAtlas(); | ||||||
|       textureAtlas.BuildAtlas(2048, 2048); |  | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    return images; |    return images; | ||||||
|  | @ -103,7 +105,13 @@ static void LoadTextures() | ||||||
|                                    GetTexturePath(lineTexture)); |                                    GetTexturePath(lineTexture)); | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    textureAtlas.BuildAtlas(2048, 2048); |    BuildAtlas(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void BuildAtlas() | ||||||
|  | { | ||||||
|  |    util::TextureAtlas& textureAtlas = util::TextureAtlas::Instance(); | ||||||
|  |    textureAtlas.BuildAtlas(atlasWidth, atlasHeight); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } // namespace ResourceManager
 | } // namespace ResourceManager
 | ||||||
|  |  | ||||||
|  | @ -22,6 +22,7 @@ std::shared_ptr<boost::gil::rgba8_image_t> | ||||||
| LoadImageResource(const std::string& urlString); | LoadImageResource(const std::string& urlString); | ||||||
| std::vector<std::shared_ptr<boost::gil::rgba8_image_t>> | std::vector<std::shared_ptr<boost::gil::rgba8_image_t>> | ||||||
| LoadImageResources(const std::vector<std::string>& urlStrings); | LoadImageResources(const std::vector<std::string>& urlStrings); | ||||||
|  | void BuildAtlas(); | ||||||
| 
 | 
 | ||||||
| } // namespace ResourceManager
 | } // namespace ResourceManager
 | ||||||
| } // namespace manager
 | } // namespace manager
 | ||||||
|  |  | ||||||
|  | @ -9,10 +9,10 @@ | ||||||
| #include <scwx/qt/map/layer_wrapper.hpp> | #include <scwx/qt/map/layer_wrapper.hpp> | ||||||
| #include <scwx/qt/map/map_provider.hpp> | #include <scwx/qt/map/map_provider.hpp> | ||||||
| #include <scwx/qt/map/map_settings.hpp> | #include <scwx/qt/map/map_settings.hpp> | ||||||
|  | #include <scwx/qt/map/marker_layer.hpp> | ||||||
| #include <scwx/qt/map/overlay_layer.hpp> | #include <scwx/qt/map/overlay_layer.hpp> | ||||||
| #include <scwx/qt/map/overlay_product_layer.hpp> | #include <scwx/qt/map/overlay_product_layer.hpp> | ||||||
| #include <scwx/qt/map/placefile_layer.hpp> | #include <scwx/qt/map/placefile_layer.hpp> | ||||||
| #include <scwx/qt/map/marker_layer.hpp> |  | ||||||
| #include <scwx/qt/map/radar_product_layer.hpp> | #include <scwx/qt/map/radar_product_layer.hpp> | ||||||
| #include <scwx/qt/map/radar_range_layer.hpp> | #include <scwx/qt/map/radar_range_layer.hpp> | ||||||
| #include <scwx/qt/map/radar_site_layer.hpp> | #include <scwx/qt/map/radar_site_layer.hpp> | ||||||
|  | @ -21,6 +21,7 @@ | ||||||
| #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> | ||||||
|  | #include <scwx/qt/ui/edit_marker_dialog.hpp> | ||||||
| #include <scwx/qt/util/file.hpp> | #include <scwx/qt/util/file.hpp> | ||||||
| #include <scwx/qt/util/maplibre.hpp> | #include <scwx/qt/util/maplibre.hpp> | ||||||
| #include <scwx/qt/util/tooltip.hpp> | #include <scwx/qt/util/tooltip.hpp> | ||||||
|  | @ -127,8 +128,6 @@ public: | ||||||
|       ImGui_ImplQt_Init(); |       ImGui_ImplQt_Init(); | ||||||
| 
 | 
 | ||||||
|       InitializeCustomStyles(); |       InitializeCustomStyles(); | ||||||
| 
 |  | ||||||
|       ConnectSignals(); |  | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    ~MapWidgetImpl() |    ~MapWidgetImpl() | ||||||
|  | @ -219,6 +218,8 @@ public: | ||||||
|    std::shared_ptr<model::LayerModel> layerModel_ { |    std::shared_ptr<model::LayerModel> layerModel_ { | ||||||
|       model::LayerModel::Instance()}; |       model::LayerModel::Instance()}; | ||||||
| 
 | 
 | ||||||
|  |    ui::EditMarkerDialog* editMarkerDialog_ {nullptr}; | ||||||
|  | 
 | ||||||
|    std::shared_ptr<manager::HotkeyManager> hotkeyManager_ { |    std::shared_ptr<manager::HotkeyManager> hotkeyManager_ { | ||||||
|       manager::HotkeyManager::Instance()}; |       manager::HotkeyManager::Instance()}; | ||||||
|    std::shared_ptr<manager::PlacefileManager> placefileManager_ { |    std::shared_ptr<manager::PlacefileManager> placefileManager_ { | ||||||
|  | @ -283,6 +284,12 @@ MapWidget::MapWidget(std::size_t id, const QMapLibre::Settings& settings) : | ||||||
|    setFocusPolicy(Qt::StrongFocus); |    setFocusPolicy(Qt::StrongFocus); | ||||||
| 
 | 
 | ||||||
|    ImGui_ImplQt_RegisterWidget(this); |    ImGui_ImplQt_RegisterWidget(this); | ||||||
|  | 
 | ||||||
|  |    // Qt parent deals with memory management
 | ||||||
|  |    // NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
 | ||||||
|  |    p->editMarkerDialog_ = new ui::EditMarkerDialog(this); | ||||||
|  | 
 | ||||||
|  |    p->ConnectSignals(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| MapWidget::~MapWidget() | MapWidget::~MapWidget() | ||||||
|  | @ -429,6 +436,16 @@ void MapWidgetImpl::HandleHotkeyPressed(types::Hotkey hotkey, bool isAutoRepeat) | ||||||
| 
 | 
 | ||||||
|    switch (hotkey) |    switch (hotkey) | ||||||
|    { |    { | ||||||
|  |    case types::Hotkey::AddLocationMarker: | ||||||
|  |       if (hasMouse_) | ||||||
|  |       { | ||||||
|  |          auto coordinate = map_->coordinateForPixel(lastPos_); | ||||||
|  | 
 | ||||||
|  |          editMarkerDialog_->setup(coordinate.first, coordinate.second); | ||||||
|  |          editMarkerDialog_->show(); | ||||||
|  |       } | ||||||
|  |       break; | ||||||
|  | 
 | ||||||
|    case types::Hotkey::ChangeMapStyle: |    case types::Hotkey::ChangeMapStyle: | ||||||
|       if (context_->settings().isActive_) |       if (context_->settings().isActive_) | ||||||
|       { |       { | ||||||
|  |  | ||||||
|  | @ -1,9 +1,14 @@ | ||||||
| #include <scwx/qt/map/marker_layer.hpp> | #include <scwx/qt/map/marker_layer.hpp> | ||||||
| #include <scwx/qt/manager/marker_manager.hpp> | #include <scwx/qt/manager/marker_manager.hpp> | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
| #include <scwx/qt/types/marker_types.hpp> |  | ||||||
| #include <scwx/qt/types/texture_types.hpp> |  | ||||||
| #include <scwx/qt/gl/draw/geo_icons.hpp> | #include <scwx/qt/gl/draw/geo_icons.hpp> | ||||||
|  | #include <scwx/qt/types/marker_types.hpp> | ||||||
|  | #include <scwx/qt/ui/edit_marker_dialog.hpp> | ||||||
|  | 
 | ||||||
|  | #include <QGeoPositionInfo> | ||||||
|  | #include <QMouseEvent> | ||||||
|  | 
 | ||||||
|  | #include <string> | ||||||
| 
 | 
 | ||||||
| namespace scwx | namespace scwx | ||||||
| { | { | ||||||
|  | @ -19,48 +24,104 @@ class MarkerLayer::Impl | ||||||
| { | { | ||||||
| public: | public: | ||||||
|    explicit Impl(MarkerLayer* self, std::shared_ptr<MapContext> context) : |    explicit Impl(MarkerLayer* self, std::shared_ptr<MapContext> context) : | ||||||
|        self_ {self}, geoIcons_ {std::make_shared<gl::draw::GeoIcons>(context)} |        self_ {self}, | ||||||
|  |        geoIcons_ {std::make_shared<gl::draw::GeoIcons>(context)}, | ||||||
|  |        editMarkerDialog_ {std::make_shared<ui::EditMarkerDialog>()} | ||||||
|    { |    { | ||||||
|       ConnectSignals(); |       ConnectSignals(); | ||||||
|    } |    } | ||||||
|    ~Impl() {} |    ~Impl() = default; | ||||||
| 
 | 
 | ||||||
|    void ReloadMarkers(); |    void ReloadMarkers(); | ||||||
|    void ConnectSignals(); |    void ConnectSignals(); | ||||||
| 
 | 
 | ||||||
|  |    std::shared_ptr<manager::MarkerManager> markerManager_ { | ||||||
|  |       manager::MarkerManager::Instance()}; | ||||||
|  | 
 | ||||||
|  |    void set_icon_sheets(); | ||||||
|  | 
 | ||||||
|    MarkerLayer* self_; |    MarkerLayer* self_; | ||||||
|    const std::string& markerIconName_ { |  | ||||||
|       types::GetTextureName(types::ImageTexture::LocationMarker)}; |  | ||||||
| 
 | 
 | ||||||
|    std::shared_ptr<gl::draw::GeoIcons> geoIcons_; |    std::shared_ptr<gl::draw::GeoIcons> geoIcons_; | ||||||
|  |    std::shared_ptr<ui::EditMarkerDialog> editMarkerDialog_; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| void MarkerLayer::Impl::ConnectSignals() | void MarkerLayer::Impl::ConnectSignals() | ||||||
| { | { | ||||||
|    auto markerManager = manager::MarkerManager::Instance(); |    QObject::connect(markerManager_.get(), | ||||||
| 
 |                     &manager::MarkerManager::MarkersUpdated, | ||||||
|    QObject::connect(markerManager.get(), |                     self_, | ||||||
|          &manager::MarkerManager::MarkersUpdated, |                     [this]() { ReloadMarkers(); }); | ||||||
|          self_, |    QObject::connect(markerManager_.get(), | ||||||
|          [this]() |                     &manager::MarkerManager::IconsReady, | ||||||
|          { |                     self_, | ||||||
|             this->ReloadMarkers(); |                     [this]() { set_icon_sheets(); }); | ||||||
|          }); |    QObject::connect(markerManager_.get(), | ||||||
|  |                     &manager::MarkerManager::IconAdded, | ||||||
|  |                     self_, | ||||||
|  |                     [this]() { set_icon_sheets(); }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void MarkerLayer::Impl::ReloadMarkers() | void MarkerLayer::Impl::ReloadMarkers() | ||||||
| { | { | ||||||
|    logger_->debug("ReloadMarkers()"); |    logger_->debug("ReloadMarkers()"); | ||||||
|    auto markerManager = manager::MarkerManager::Instance(); |  | ||||||
| 
 | 
 | ||||||
|    geoIcons_->StartIcons(); |    geoIcons_->StartIcons(); | ||||||
| 
 |    markerManager_->for_each( | ||||||
|    markerManager->for_each( |  | ||||||
|       [this](const types::MarkerInfo& marker) |       [this](const types::MarkerInfo& marker) | ||||||
|       { |       { | ||||||
|          std::shared_ptr<gl::draw::GeoIconDrawItem> icon = geoIcons_->AddIcon(); |          // must use local ID, instead of reference to marker in event handler
 | ||||||
|          geoIcons_->SetIconTexture(icon, markerIconName_, 0); |          // callback.
 | ||||||
|  |          const types::MarkerId id = marker.id; | ||||||
|  | 
 | ||||||
|  |          const std::shared_ptr<gl::draw::GeoIconDrawItem> icon = | ||||||
|  |             geoIcons_->AddIcon(); | ||||||
|  | 
 | ||||||
|  |          const std::string latitudeString = | ||||||
|  |             common::GetLatitudeString(marker.latitude); | ||||||
|  |          const std::string longitudeString = | ||||||
|  |             common::GetLongitudeString(marker.longitude); | ||||||
|  | 
 | ||||||
|  |          const std::string hoverText = | ||||||
|  |             marker.name != "" ? | ||||||
|  |                fmt::format( | ||||||
|  |                   "{}\n{}, {}", marker.name, latitudeString, longitudeString) : | ||||||
|  |                fmt::format("{}, {}", latitudeString, longitudeString); | ||||||
|  | 
 | ||||||
|  |          auto iconInfo = markerManager_->get_icon(marker.iconName); | ||||||
|  |          if (iconInfo) | ||||||
|  |          { | ||||||
|  |             geoIcons_->SetIconTexture(icon, iconInfo->name, 0); | ||||||
|  |          } | ||||||
|  |          else | ||||||
|  |          { | ||||||
|  |             geoIcons_->SetIconTexture(icon, marker.iconName, 0); | ||||||
|  |          } | ||||||
|  | 
 | ||||||
|          geoIcons_->SetIconLocation(icon, marker.latitude, marker.longitude); |          geoIcons_->SetIconLocation(icon, marker.latitude, marker.longitude); | ||||||
|  |          geoIcons_->SetIconHoverText(icon, hoverText); | ||||||
|  |          geoIcons_->SetIconModulate(icon, marker.iconColor); | ||||||
|  |          geoIcons_->RegisterEventHandler( | ||||||
|  |             icon, | ||||||
|  |             [this, id](QEvent* ev) | ||||||
|  |             { | ||||||
|  |                switch (ev->type()) | ||||||
|  |                { | ||||||
|  |                case QEvent::Type::MouseButtonPress: | ||||||
|  |                { | ||||||
|  |                   auto* mouseEvent = reinterpret_cast<QMouseEvent*>(ev); | ||||||
|  |                   if (mouseEvent->buttons() == Qt::MouseButton::RightButton) | ||||||
|  |                   { | ||||||
|  |                      editMarkerDialog_->setup(id); | ||||||
|  |                      editMarkerDialog_->show(); | ||||||
|  |                   } | ||||||
|  |                } | ||||||
|  |                break; | ||||||
|  | 
 | ||||||
|  |                default: | ||||||
|  |                   break; | ||||||
|  |                } | ||||||
|  |             }); | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|    geoIcons_->FinishIcons(); |    geoIcons_->FinishIcons(); | ||||||
|  | @ -80,17 +141,28 @@ void MarkerLayer::Initialize() | ||||||
|    logger_->debug("Initialize()"); |    logger_->debug("Initialize()"); | ||||||
|    DrawLayer::Initialize(); |    DrawLayer::Initialize(); | ||||||
| 
 | 
 | ||||||
|    p->geoIcons_->StartIconSheets(); |    p->set_icon_sheets(); | ||||||
|    p->geoIcons_->AddIconSheet(p->markerIconName_); |  | ||||||
|    p->geoIcons_->FinishIconSheets(); |  | ||||||
| 
 |  | ||||||
|    p->ReloadMarkers(); |    p->ReloadMarkers(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void MarkerLayer::Impl::set_icon_sheets() | ||||||
|  | { | ||||||
|  |    geoIcons_->StartIconSheets(); | ||||||
|  |    for (auto& markerIcon : markerManager_->get_icons()) | ||||||
|  |    { | ||||||
|  |       geoIcons_->AddIconSheet(markerIcon.second.name, | ||||||
|  |                               0, | ||||||
|  |                               0, | ||||||
|  |                               markerIcon.second.hotX, | ||||||
|  |                               markerIcon.second.hotY); | ||||||
|  |    } | ||||||
|  |    geoIcons_->FinishIconSheets(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void MarkerLayer::Render(const QMapLibre::CustomLayerRenderParameters& params) | void MarkerLayer::Render(const QMapLibre::CustomLayerRenderParameters& params) | ||||||
| { | { | ||||||
|    // auto markerManager = manager::MarkerManager::Instance();
 |  | ||||||
|    gl::OpenGLFunctions& gl = context()->gl(); |    gl::OpenGLFunctions& gl = context()->gl(); | ||||||
|  |    context()->set_render_parameters(params); | ||||||
| 
 | 
 | ||||||
|    DrawLayer::Render(params); |    DrawLayer::Render(params); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ | ||||||
| #include <scwx/qt/manager/marker_manager.hpp> | #include <scwx/qt/manager/marker_manager.hpp> | ||||||
| #include <scwx/qt/types/marker_types.hpp> | #include <scwx/qt/types/marker_types.hpp> | ||||||
| #include <scwx/qt/types/qt_types.hpp> | #include <scwx/qt/types/qt_types.hpp> | ||||||
|  | #include <scwx/qt/util/q_color_modulate.hpp> | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
| 
 | 
 | ||||||
| #include <vector> | #include <vector> | ||||||
|  | @ -18,6 +19,7 @@ namespace model | ||||||
| 
 | 
 | ||||||
| static const std::string logPrefix_ = "scwx::qt::model::marker_model"; | static const std::string logPrefix_ = "scwx::qt::model::marker_model"; | ||||||
| static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | ||||||
|  | static const int         iconSize_  = 30; | ||||||
| 
 | 
 | ||||||
| static constexpr int kFirstColumn = | static constexpr int kFirstColumn = | ||||||
|    static_cast<int>(MarkerModel::Column::Latitude); |    static_cast<int>(MarkerModel::Column::Latitude); | ||||||
|  | @ -38,7 +40,6 @@ public: | ||||||
| MarkerModel::MarkerModel(QObject* parent) : | MarkerModel::MarkerModel(QObject* parent) : | ||||||
|    QAbstractTableModel(parent), p(std::make_unique<Impl>()) |    QAbstractTableModel(parent), p(std::make_unique<Impl>()) | ||||||
| { | { | ||||||
| 
 |  | ||||||
|    connect(p->markerManager_.get(), |    connect(p->markerManager_.get(), | ||||||
|          &manager::MarkerManager::MarkersInitialized, |          &manager::MarkerManager::MarkersInitialized, | ||||||
|          this, |          this, | ||||||
|  | @ -78,26 +79,11 @@ Qt::ItemFlags MarkerModel::flags(const QModelIndex& index) const | ||||||
| { | { | ||||||
|    Qt::ItemFlags flags = QAbstractTableModel::flags(index); |    Qt::ItemFlags flags = QAbstractTableModel::flags(index); | ||||||
| 
 | 
 | ||||||
|    switch (index.column()) |  | ||||||
|    { |  | ||||||
|    case static_cast<int>(Column::Name): |  | ||||||
|    case static_cast<int>(Column::Latitude): |  | ||||||
|    case static_cast<int>(Column::Longitude): |  | ||||||
|       flags |= Qt::ItemFlag::ItemIsEditable; |  | ||||||
|       break; |  | ||||||
|    default: |  | ||||||
|       break; |  | ||||||
|    } |  | ||||||
| 
 |  | ||||||
|    return flags; |    return flags; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| QVariant MarkerModel::data(const QModelIndex& index, int role) const | QVariant MarkerModel::data(const QModelIndex& index, int role) const | ||||||
| { | { | ||||||
| 
 |  | ||||||
|    static const char COORDINATE_FORMAT    = 'g'; |  | ||||||
|    static const int  COORDINATE_PRECISION = 10; |  | ||||||
| 
 |  | ||||||
|    if (!index.isValid() || index.row() < 0 || |    if (!index.isValid() || index.row() < 0 || | ||||||
|        static_cast<size_t>(index.row()) >= p->markerIds_.size()) |        static_cast<size_t>(index.row()) >= p->markerIds_.size()) | ||||||
|    { |    { | ||||||
|  | @ -118,8 +104,7 @@ QVariant MarkerModel::data(const QModelIndex& index, int role) const | ||||||
|    { |    { | ||||||
|    case static_cast<int>(Column::Name): |    case static_cast<int>(Column::Name): | ||||||
|       if (role == Qt::ItemDataRole::DisplayRole || |       if (role == Qt::ItemDataRole::DisplayRole || | ||||||
|           role == Qt::ItemDataRole::ToolTipRole || |           role == Qt::ItemDataRole::ToolTipRole) | ||||||
|           role == Qt::ItemDataRole::EditRole) |  | ||||||
|       { |       { | ||||||
|          return QString::fromStdString(markerInfo->name); |          return QString::fromStdString(markerInfo->name); | ||||||
|       } |       } | ||||||
|  | @ -132,11 +117,6 @@ QVariant MarkerModel::data(const QModelIndex& index, int role) const | ||||||
|          return QString::fromStdString( |          return QString::fromStdString( | ||||||
|             common::GetLatitudeString(markerInfo->latitude)); |             common::GetLatitudeString(markerInfo->latitude)); | ||||||
|       } |       } | ||||||
|       else if (role == Qt::ItemDataRole::EditRole) |  | ||||||
|       { |  | ||||||
|          return QString::number( |  | ||||||
|             markerInfo->latitude, COORDINATE_FORMAT, COORDINATE_PRECISION); |  | ||||||
|       } |  | ||||||
|       break; |       break; | ||||||
| 
 | 
 | ||||||
|    case static_cast<int>(Column::Longitude): |    case static_cast<int>(Column::Longitude): | ||||||
|  | @ -146,13 +126,41 @@ QVariant MarkerModel::data(const QModelIndex& index, int role) const | ||||||
|          return QString::fromStdString( |          return QString::fromStdString( | ||||||
|             common::GetLongitudeString(markerInfo->longitude)); |             common::GetLongitudeString(markerInfo->longitude)); | ||||||
|       } |       } | ||||||
|       else if (role == Qt::ItemDataRole::EditRole) |  | ||||||
|       { |  | ||||||
|          return QString::number( |  | ||||||
|             markerInfo->longitude, COORDINATE_FORMAT, COORDINATE_PRECISION); |  | ||||||
|       } |  | ||||||
|       break; |       break; | ||||||
|       break; |       break; | ||||||
|  |    case static_cast<int>(Column::Icon): | ||||||
|  |       if (role == Qt::ItemDataRole::DisplayRole) | ||||||
|  |       { | ||||||
|  |          std::optional<types::MarkerIconInfo> icon = | ||||||
|  |             p->markerManager_->get_icon(markerInfo->iconName); | ||||||
|  |          if (icon) | ||||||
|  |          { | ||||||
|  |             return QString::fromStdString(icon->shortName); | ||||||
|  |          } | ||||||
|  |          else | ||||||
|  |          { | ||||||
|  |             return {}; | ||||||
|  |          } | ||||||
|  |       } | ||||||
|  |       else if (role == Qt::ItemDataRole::DecorationRole) | ||||||
|  |       { | ||||||
|  |          std::optional<types::MarkerIconInfo> icon = | ||||||
|  |             p->markerManager_->get_icon(markerInfo->iconName); | ||||||
|  |          if (icon) | ||||||
|  |          { | ||||||
|  |             return util::modulateColors(icon->qIcon, | ||||||
|  |                                         QSize(iconSize_, iconSize_), | ||||||
|  |                                         QColor(markerInfo->iconColor[0], | ||||||
|  |                                                markerInfo->iconColor[1], | ||||||
|  |                                                markerInfo->iconColor[2], | ||||||
|  |                                                markerInfo->iconColor[3])); | ||||||
|  |          } | ||||||
|  |          else | ||||||
|  |          { | ||||||
|  |             return {}; | ||||||
|  |          } | ||||||
|  |       } | ||||||
|  |       break; | ||||||
| 
 | 
 | ||||||
|    default: |    default: | ||||||
|       break; |       break; | ||||||
|  | @ -190,6 +198,9 @@ QVariant MarkerModel::headerData(int             section, | ||||||
|             case static_cast<int>(Column::Longitude): |             case static_cast<int>(Column::Longitude): | ||||||
|                return tr("Longitude"); |                return tr("Longitude"); | ||||||
| 
 | 
 | ||||||
|  |             case static_cast<int>(Column::Icon): | ||||||
|  |                return tr("Icon"); | ||||||
|  | 
 | ||||||
|             default: |             default: | ||||||
|                break; |                break; | ||||||
|          } |          } | ||||||
|  | @ -199,78 +210,9 @@ QVariant MarkerModel::headerData(int             section, | ||||||
|    return QVariant(); |    return QVariant(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool MarkerModel::setData(const QModelIndex& index, | bool MarkerModel::setData(const QModelIndex&, const QVariant&, int) | ||||||
|                           const QVariant&    value, |  | ||||||
|                           int                role) |  | ||||||
| { | { | ||||||
| 
 |    return false; | ||||||
|    if (!index.isValid() || index.row() < 0 || |  | ||||||
|        static_cast<size_t>(index.row()) >= p->markerIds_.size()) |  | ||||||
|    { |  | ||||||
|       return false; |  | ||||||
|    } |  | ||||||
| 
 |  | ||||||
|    types::MarkerId id = p->markerIds_[index.row()]; |  | ||||||
|    std::optional<types::MarkerInfo> markerInfo = |  | ||||||
|       p->markerManager_->get_marker(id); |  | ||||||
|    if (!markerInfo) |  | ||||||
|    { |  | ||||||
|       return false; |  | ||||||
|    } |  | ||||||
|    bool result = false; |  | ||||||
| 
 |  | ||||||
|    switch(index.column()) |  | ||||||
|    { |  | ||||||
|    case static_cast<int>(Column::Name): |  | ||||||
|       if (role == Qt::ItemDataRole::EditRole) |  | ||||||
|       { |  | ||||||
|          QString str = value.toString(); |  | ||||||
|          markerInfo->name = str.toStdString(); |  | ||||||
|          p->markerManager_->set_marker(id, *markerInfo); |  | ||||||
|          result = true; |  | ||||||
|       } |  | ||||||
|       break; |  | ||||||
| 
 |  | ||||||
|    case static_cast<int>(Column::Latitude): |  | ||||||
|       if (role == Qt::ItemDataRole::EditRole) |  | ||||||
|       { |  | ||||||
|          QString str = value.toString(); |  | ||||||
|          bool ok; |  | ||||||
|          double latitude = str.toDouble(&ok); |  | ||||||
|          if (!str.isEmpty() && ok && -90 <= latitude && latitude <= 90) |  | ||||||
|          { |  | ||||||
|             markerInfo->latitude = latitude; |  | ||||||
|             p->markerManager_->set_marker(id, *markerInfo); |  | ||||||
|             result = true; |  | ||||||
|          } |  | ||||||
|       } |  | ||||||
|       break; |  | ||||||
| 
 |  | ||||||
|    case static_cast<int>(Column::Longitude): |  | ||||||
|       if (role == Qt::ItemDataRole::EditRole) |  | ||||||
|       { |  | ||||||
|          QString str = value.toString(); |  | ||||||
|          bool ok; |  | ||||||
|          double longitude = str.toDouble(&ok); |  | ||||||
|          if (!str.isEmpty() && ok && -180 <= longitude && longitude <= 180) |  | ||||||
|          { |  | ||||||
|             markerInfo->longitude = longitude; |  | ||||||
|             p->markerManager_->set_marker(id, *markerInfo); |  | ||||||
|             result = true; |  | ||||||
|          } |  | ||||||
|       } |  | ||||||
|       break; |  | ||||||
| 
 |  | ||||||
|    default: |  | ||||||
|       break; |  | ||||||
|    } |  | ||||||
| 
 |  | ||||||
|    if (result) |  | ||||||
|    { |  | ||||||
|       Q_EMIT dataChanged(index, index); |  | ||||||
|    } |  | ||||||
| 
 |  | ||||||
|    return result; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void MarkerModel::HandleMarkersInitialized(size_t count) | void MarkerModel::HandleMarkersInitialized(size_t count) | ||||||
|  |  | ||||||
|  | @ -17,7 +17,8 @@ public: | ||||||
|    { |    { | ||||||
|       Latitude  = 0, |       Latitude  = 0, | ||||||
|       Longitude = 1, |       Longitude = 1, | ||||||
|       Name      = 2, |       Icon      = 2, | ||||||
|  |       Name      = 3, | ||||||
|    }; |    }; | ||||||
| 
 | 
 | ||||||
|    explicit MarkerModel(QObject* parent = nullptr); |    explicit MarkerModel(QObject* parent = nullptr); | ||||||
|  |  | ||||||
|  | @ -12,6 +12,7 @@ namespace settings | ||||||
| static const std::string logPrefix_ = "scwx::qt::settings::hotkey_settings"; | static const std::string logPrefix_ = "scwx::qt::settings::hotkey_settings"; | ||||||
| 
 | 
 | ||||||
| static const std::unordered_map<types::Hotkey, QKeySequence> kDefaultHotkeys_ { | static const std::unordered_map<types::Hotkey, QKeySequence> kDefaultHotkeys_ { | ||||||
|  |    {types::Hotkey::AddLocationMarker, QKeySequence {Qt::Key::Key_M}}, | ||||||
|    {types::Hotkey::ChangeMapStyle, QKeySequence {Qt::Key::Key_Z}}, |    {types::Hotkey::ChangeMapStyle, QKeySequence {Qt::Key::Key_Z}}, | ||||||
|    {types::Hotkey::CopyCursorCoordinates, |    {types::Hotkey::CopyCursorCoordinates, | ||||||
|     QKeySequence {QKeyCombination {Qt::KeyboardModifier::ControlModifier, |     QKeySequence {QKeyCombination {Qt::KeyboardModifier::ControlModifier, | ||||||
|  |  | ||||||
|  | @ -13,6 +13,7 @@ namespace types | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
| static const std::unordered_map<Hotkey, std::string> hotkeyShortName_ { | static const std::unordered_map<Hotkey, std::string> hotkeyShortName_ { | ||||||
|  |    {Hotkey::AddLocationMarker, "add_location_marker"}, | ||||||
|    {Hotkey::ChangeMapStyle, "change_map_style"}, |    {Hotkey::ChangeMapStyle, "change_map_style"}, | ||||||
|    {Hotkey::CopyCursorCoordinates, "copy_cursor_coordinates"}, |    {Hotkey::CopyCursorCoordinates, "copy_cursor_coordinates"}, | ||||||
|    {Hotkey::CopyMapCoordinates, "copy_map_coordinates"}, |    {Hotkey::CopyMapCoordinates, "copy_map_coordinates"}, | ||||||
|  | @ -52,6 +53,7 @@ static const std::unordered_map<Hotkey, std::string> hotkeyShortName_ { | ||||||
|    {Hotkey::Unknown, "?"}}; |    {Hotkey::Unknown, "?"}}; | ||||||
| 
 | 
 | ||||||
| static const std::unordered_map<Hotkey, std::string> hotkeyLongName_ { | static const std::unordered_map<Hotkey, std::string> hotkeyLongName_ { | ||||||
|  |    {Hotkey::AddLocationMarker, "Add Location Marker"}, | ||||||
|    {Hotkey::ChangeMapStyle, "Change Map Style"}, |    {Hotkey::ChangeMapStyle, "Change Map Style"}, | ||||||
|    {Hotkey::CopyCursorCoordinates, "Copy Cursor Coordinates"}, |    {Hotkey::CopyCursorCoordinates, "Copy Cursor Coordinates"}, | ||||||
|    {Hotkey::CopyMapCoordinates, "Copy Map Coordinates"}, |    {Hotkey::CopyMapCoordinates, "Copy Map Coordinates"}, | ||||||
|  |  | ||||||
|  | @ -13,6 +13,7 @@ namespace types | ||||||
| 
 | 
 | ||||||
| enum class Hotkey | enum class Hotkey | ||||||
| { | { | ||||||
|  |    AddLocationMarker, | ||||||
|    ChangeMapStyle, |    ChangeMapStyle, | ||||||
|    CopyCursorCoordinates, |    CopyCursorCoordinates, | ||||||
|    CopyMapCoordinates, |    CopyMapCoordinates, | ||||||
|  | @ -52,7 +53,7 @@ enum class Hotkey | ||||||
|    Unknown |    Unknown | ||||||
| }; | }; | ||||||
| typedef scwx::util:: | typedef scwx::util:: | ||||||
|    Iterator<Hotkey, Hotkey::ChangeMapStyle, Hotkey::TimelineStepEnd> |    Iterator<Hotkey, Hotkey::AddLocationMarker, Hotkey::TimelineStepEnd> | ||||||
|       HotkeyIterator; |       HotkeyIterator; | ||||||
| 
 | 
 | ||||||
| Hotkey             GetHotkeyFromShortName(const std::string& name); | Hotkey             GetHotkeyFromShortName(const std::string& name); | ||||||
|  |  | ||||||
|  | @ -1,29 +1,82 @@ | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <string> | #include <scwx/qt/types/texture_types.hpp> | ||||||
| #include <cstdint> |  | ||||||
| 
 | 
 | ||||||
| namespace scwx | #include <cstdint> | ||||||
|  | #include <string> | ||||||
|  | #include <utility> | ||||||
|  | 
 | ||||||
|  | #include <boost/gil.hpp> | ||||||
|  | #include <QFileInfo> | ||||||
|  | #include <QIcon> | ||||||
|  | 
 | ||||||
|  | namespace scwx::qt::types | ||||||
| { | { | ||||||
| namespace qt | using MarkerId = std::uint64_t; | ||||||
| { |  | ||||||
| namespace types |  | ||||||
| { |  | ||||||
| typedef std::uint64_t MarkerId; |  | ||||||
| 
 | 
 | ||||||
| struct MarkerInfo | struct MarkerInfo | ||||||
| { | { | ||||||
|    MarkerInfo(const std::string& name, double latitude, double longitude) : |    MarkerInfo(std::string                      name, | ||||||
|        name {name}, latitude {latitude}, longitude {longitude} |               double                           latitude, | ||||||
|  |               double                           longitude, | ||||||
|  |               std::string                      iconName, | ||||||
|  |               const boost::gil::rgba8_pixel_t& iconColor) : | ||||||
|  |        name {std::move(name)}, | ||||||
|  |        latitude {latitude}, | ||||||
|  |        longitude {longitude}, | ||||||
|  |        iconName {std::move(iconName)}, | ||||||
|  |        iconColor {iconColor} | ||||||
|    { |    { | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    MarkerId    id; |    MarkerId                  id {0}; | ||||||
|    std::string name; |    std::string               name; | ||||||
|    double      latitude; |    double                    latitude; | ||||||
|    double      longitude; |    double                    longitude; | ||||||
|  |    std::string               iconName; | ||||||
|  |    boost::gil::rgba8_pixel_t iconColor; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace types
 | struct MarkerIconInfo | ||||||
| } // namespace qt
 | { | ||||||
| } // namespace scwx
 |    // Initializer for default icons (which use a texture)
 | ||||||
|  |    explicit MarkerIconInfo(types::ImageTexture texture, | ||||||
|  |                            std::int32_t        hotX, | ||||||
|  |                            std::int32_t        hotY) : | ||||||
|  |        name {types::GetTextureName(texture)}, | ||||||
|  |        path {types::GetTexturePath(texture)}, | ||||||
|  |        hotX {hotX}, | ||||||
|  |        hotY {hotY}, | ||||||
|  |        qIcon {QIcon(QString::fromStdString(path))}, | ||||||
|  |        image {} | ||||||
|  |    { | ||||||
|  |       auto        qName = QString::fromStdString(name); | ||||||
|  |       QStringList parts = qName.split("location-"); | ||||||
|  |       shortName         = parts.last().toStdString(); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // Initializer for custom icons (which use a file path)
 | ||||||
|  |    explicit MarkerIconInfo(const std::string&                         path, | ||||||
|  |                            std::int32_t                               hotX, | ||||||
|  |                            std::int32_t                               hotY, | ||||||
|  |                            std::shared_ptr<boost::gil::rgba8_image_t> image) : | ||||||
|  |        name {path}, | ||||||
|  |        path {path}, | ||||||
|  |        shortName {QFileInfo(path.c_str()).fileName().toStdString()}, | ||||||
|  |        hotX {hotX}, | ||||||
|  |        hotY {hotY}, | ||||||
|  |        qIcon {QIcon(QString::fromStdString(path))}, | ||||||
|  |        image {image} | ||||||
|  |    { | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    std::string                                               name; | ||||||
|  |    std::string                                               path; | ||||||
|  |    std::string                                               shortName; | ||||||
|  |    std::int32_t                                              hotX; | ||||||
|  |    std::int32_t                                              hotY; | ||||||
|  |    QIcon                                                     qIcon; | ||||||
|  |    std::optional<std::shared_ptr<boost::gil::rgba8_image_t>> image; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace scwx::qt::types
 | ||||||
|  |  | ||||||
|  | @ -25,8 +25,33 @@ static const std::unordered_map<ImageTexture, TextureInfo> imageTextureInfo_ { | ||||||
|    {ImageTexture::Cursor17, |    {ImageTexture::Cursor17, | ||||||
|     {"images/cursor-17", ":/res/textures/images/cursor-17.png"}}, |     {"images/cursor-17", ":/res/textures/images/cursor-17.png"}}, | ||||||
|    {ImageTexture::Dot3, {"images/dot-3", ":/res/textures/images/dot-3.png"}}, |    {ImageTexture::Dot3, {"images/dot-3", ":/res/textures/images/dot-3.png"}}, | ||||||
|  |    {ImageTexture::LocationBriefcase, | ||||||
|  |     {"images/location-briefcase", | ||||||
|  |      ":/res/icons/font-awesome-6/briefcase-solid.svg"}}, | ||||||
|  |    {ImageTexture::LocationBuildingColumns, | ||||||
|  |     {"images/location-building-columns", | ||||||
|  |      ":/res/icons/font-awesome-6/building-columns-solid.svg"}}, | ||||||
|  |    {ImageTexture::LocationBuilding, | ||||||
|  |     {"images/location-building", | ||||||
|  |      ":/res/icons/font-awesome-6/building-solid.svg"}}, | ||||||
|  |    {ImageTexture::LocationCaravan, | ||||||
|  |     {"images/location-caravan", | ||||||
|  |      ":/res/icons/font-awesome-6/caravan-solid.svg"}}, | ||||||
|  |    {ImageTexture::LocationCrosshair, | ||||||
|  |     {"images/location-crosshair", | ||||||
|  |      ":/res/icons/font-awesome-6/location-crosshairs-solid.svg"}}, | ||||||
|  |    {ImageTexture::LocationHouse, | ||||||
|  |     {"images/location-house", | ||||||
|  |      ":/res/icons/font-awesome-6/house-solid-white.svg"}}, | ||||||
|    {ImageTexture::LocationMarker, |    {ImageTexture::LocationMarker, | ||||||
|     {"images/location-marker", ":/res/textures/images/location-marker.svg"}}, |     {"images/location-marker", ":/res/textures/images/location-marker.svg"}}, | ||||||
|  |    {ImageTexture::LocationPin, | ||||||
|  |     {"images/location-pin", ":/res/icons/font-awesome-6/location-pin.svg"}}, | ||||||
|  |    {ImageTexture::LocationStar, | ||||||
|  |     {"images/location-star", | ||||||
|  |      ":/res/icons/font-awesome-6/star-solid-white.svg"}}, | ||||||
|  |    {ImageTexture::LocationTent, | ||||||
|  |     {"images/location-tent", ":/res/icons/font-awesome-6/tent-solid.svg"}}, | ||||||
|    {ImageTexture::MapboxLogo, |    {ImageTexture::MapboxLogo, | ||||||
|     {"images/mapbox-logo", ":/res/textures/images/mapbox-logo.svg"}}, |     {"images/mapbox-logo", ":/res/textures/images/mapbox-logo.svg"}}, | ||||||
|    {ImageTexture::MapTilerLogo, |    {ImageTexture::MapTilerLogo, | ||||||
|  |  | ||||||
|  | @ -18,7 +18,16 @@ enum class ImageTexture | ||||||
|    Crosshairs24, |    Crosshairs24, | ||||||
|    Cursor17, |    Cursor17, | ||||||
|    Dot3, |    Dot3, | ||||||
|  |    LocationBriefcase, | ||||||
|  |    LocationBuildingColumns, | ||||||
|  |    LocationBuilding, | ||||||
|  |    LocationCaravan, | ||||||
|  |    LocationCrosshair, | ||||||
|  |    LocationHouse, | ||||||
|    LocationMarker, |    LocationMarker, | ||||||
|  |    LocationPin, | ||||||
|  |    LocationStar, | ||||||
|  |    LocationTent, | ||||||
|    MapboxLogo, |    MapboxLogo, | ||||||
|    MapTilerLogo |    MapTilerLogo | ||||||
| }; | }; | ||||||
|  |  | ||||||
							
								
								
									
										314
									
								
								scwx-qt/source/scwx/qt/ui/edit_marker_dialog.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,314 @@ | ||||||
|  | #include "edit_marker_dialog.hpp" | ||||||
|  | #include "ui_edit_marker_dialog.h" | ||||||
|  | 
 | ||||||
|  | #include <scwx/qt/manager/marker_manager.hpp> | ||||||
|  | #include <scwx/qt/types/marker_types.hpp> | ||||||
|  | #include <scwx/qt/util/color.hpp> | ||||||
|  | #include <scwx/qt/util/q_color_modulate.hpp> | ||||||
|  | #include <scwx/util/logger.hpp> | ||||||
|  | 
 | ||||||
|  | #include <string> | ||||||
|  | 
 | ||||||
|  | #include <QObject> | ||||||
|  | #include <QString> | ||||||
|  | #include <QIcon> | ||||||
|  | #include <QPixmap> | ||||||
|  | #include <QColorDialog> | ||||||
|  | #include <QPushButton> | ||||||
|  | #include <QFileDialog> | ||||||
|  | 
 | ||||||
|  | namespace scwx::qt::ui | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | static const std::string logPrefix_ = "scwx::qt::ui::edit_marker_dialog"; | ||||||
|  | static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | ||||||
|  | 
 | ||||||
|  | class EditMarkerDialog::Impl | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |    explicit Impl(EditMarkerDialog* self) : self_ {self} {} | ||||||
|  | 
 | ||||||
|  |    void show_color_dialog(); | ||||||
|  |    void show_icon_file_dialog(); | ||||||
|  | 
 | ||||||
|  |    void set_icon_color(const std::string& color); | ||||||
|  | 
 | ||||||
|  |    void connect_signals(); | ||||||
|  | 
 | ||||||
|  |    void handle_accepted(); | ||||||
|  |    void handle_rejected(); | ||||||
|  | 
 | ||||||
|  |    EditMarkerDialog* self_; | ||||||
|  |    QPushButton*      deleteButton_ {nullptr}; | ||||||
|  |    QIcon             get_colored_icon(const types::MarkerIconInfo& marker, | ||||||
|  |                                       const std::string&           color); | ||||||
|  | 
 | ||||||
|  |    std::shared_ptr<manager::MarkerManager> markerManager_ = | ||||||
|  |       manager::MarkerManager::Instance(); | ||||||
|  |    types::MarkerId editId_ {0}; | ||||||
|  |    bool            adding_ {false}; | ||||||
|  |    std::string     setIconOnAdded_ {""}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | QIcon EditMarkerDialog::Impl::get_colored_icon( | ||||||
|  |    const types::MarkerIconInfo& marker, const std::string& color) | ||||||
|  | { | ||||||
|  |    return util::modulateColors(marker.qIcon, | ||||||
|  |                                self_->ui->iconComboBox->iconSize(), | ||||||
|  |                                QColor(QString::fromStdString(color))); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | EditMarkerDialog::EditMarkerDialog(QWidget* parent) : | ||||||
|  |     QDialog(parent), | ||||||
|  |     p {std::make_unique<Impl>(this)}, | ||||||
|  |     ui(new Ui::EditMarkerDialog) | ||||||
|  | { | ||||||
|  |    ui->setupUi(this); | ||||||
|  | 
 | ||||||
|  |    for (auto& markerIcon : p->markerManager_->get_icons()) | ||||||
|  |    { | ||||||
|  |       ui->iconComboBox->addItem( | ||||||
|  |          markerIcon.second.qIcon, | ||||||
|  |          QString::fromStdString(markerIcon.second.shortName), | ||||||
|  |          QString::fromStdString(markerIcon.second.name)); | ||||||
|  |    } | ||||||
|  |    p->deleteButton_ = | ||||||
|  |       ui->buttonBox->addButton("Delete", QDialogButtonBox::DestructiveRole); | ||||||
|  |    p->connect_signals(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | EditMarkerDialog::~EditMarkerDialog() | ||||||
|  | { | ||||||
|  |    delete ui; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void EditMarkerDialog::setup() | ||||||
|  | { | ||||||
|  |    setup(0, 0); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void EditMarkerDialog::setup(double latitude, double longitude) | ||||||
|  | { | ||||||
|  |    // By default use foreground color as marker color, mainly so the icons
 | ||||||
|  |    // are vissable in the dropdown menu.
 | ||||||
|  |    const QColor color = QWidget::palette().color(QWidget::foregroundRole()); | ||||||
|  |    p->editId_         = p->markerManager_->add_marker(types::MarkerInfo( | ||||||
|  |       "", | ||||||
|  |       latitude, | ||||||
|  |       longitude, | ||||||
|  |       manager::MarkerManager::getDefaultIconName(), | ||||||
|  |       boost::gil::rgba8_pixel_t {static_cast<uint8_t>(color.red()), | ||||||
|  |                                  static_cast<uint8_t>(color.green()), | ||||||
|  |                                  static_cast<uint8_t>(color.blue()), | ||||||
|  |                                  static_cast<uint8_t>(color.alpha())})); | ||||||
|  | 
 | ||||||
|  |    setup(p->editId_); | ||||||
|  |    p->adding_ = true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void EditMarkerDialog::setup(types::MarkerId id) | ||||||
|  | { | ||||||
|  |    std::optional<types::MarkerInfo> marker = p->markerManager_->get_marker(id); | ||||||
|  |    if (!marker) | ||||||
|  |    { | ||||||
|  |       return; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    p->editId_ = id; | ||||||
|  |    p->adding_ = false; | ||||||
|  | 
 | ||||||
|  |    const std::string iconColorStr = | ||||||
|  |       util::color::ToArgbString(marker->iconColor); | ||||||
|  |    p->set_icon_color(iconColorStr); | ||||||
|  | 
 | ||||||
|  |    int iconIndex = | ||||||
|  |       ui->iconComboBox->findData(QString::fromStdString(marker->iconName)); | ||||||
|  |    if (iconIndex < 0 || marker->iconName == "") | ||||||
|  |    { | ||||||
|  |       iconIndex = 0; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    ui->nameLineEdit->setText(QString::fromStdString(marker->name)); | ||||||
|  |    ui->iconComboBox->setCurrentIndex(iconIndex); | ||||||
|  |    ui->latitudeDoubleSpinBox->setValue(marker->latitude); | ||||||
|  |    ui->longitudeDoubleSpinBox->setValue(marker->longitude); | ||||||
|  |    ui->iconColorLineEdit->setText(QString::fromStdString(iconColorStr)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | types::MarkerInfo EditMarkerDialog::get_marker_info() const | ||||||
|  | { | ||||||
|  |    const QString                   colorName = ui->iconColorLineEdit->text(); | ||||||
|  |    const boost::gil::rgba8_pixel_t color = | ||||||
|  |       util::color::ToRgba8PixelT(colorName.toStdString()); | ||||||
|  | 
 | ||||||
|  |    return types::MarkerInfo( | ||||||
|  |       ui->nameLineEdit->text().toStdString(), | ||||||
|  |       ui->latitudeDoubleSpinBox->value(), | ||||||
|  |       ui->longitudeDoubleSpinBox->value(), | ||||||
|  |       ui->iconComboBox->currentData().toString().toStdString(), | ||||||
|  |       color); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void EditMarkerDialog::Impl::show_color_dialog() | ||||||
|  | { | ||||||
|  |    // WA_DeleteOnClose manages memory
 | ||||||
|  |    // NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
 | ||||||
|  |    auto* dialog = new QColorDialog(self_); | ||||||
|  | 
 | ||||||
|  |    dialog->setAttribute(Qt::WA_DeleteOnClose); | ||||||
|  |    dialog->setOption(QColorDialog::ColorDialogOption::ShowAlphaChannel); | ||||||
|  | 
 | ||||||
|  |    const QColor initialColor(self_->ui->iconColorLineEdit->text()); | ||||||
|  |    if (initialColor.isValid()) | ||||||
|  |    { | ||||||
|  |       dialog->setCurrentColor(initialColor); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    QObject::connect(dialog, | ||||||
|  |                     &QColorDialog::colorSelected, | ||||||
|  |                     self_, | ||||||
|  |                     [this](const QColor& qColor) | ||||||
|  |                     { | ||||||
|  |                        const QString colorName = | ||||||
|  |                           qColor.name(QColor::NameFormat::HexArgb); | ||||||
|  |                        self_->ui->iconColorLineEdit->setText(colorName); | ||||||
|  |                        set_icon_color(colorName.toStdString()); | ||||||
|  |                     }); | ||||||
|  |    dialog->open(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void EditMarkerDialog::Impl::show_icon_file_dialog() | ||||||
|  | { | ||||||
|  |    auto* dialog = new QFileDialog(self_); | ||||||
|  | 
 | ||||||
|  |    dialog->setFileMode(QFileDialog::ExistingFile); | ||||||
|  |    dialog->setNameFilters({"Icon (*.png *.svg)", "All Files (*)"}); | ||||||
|  |    dialog->setAttribute(Qt::WA_DeleteOnClose); | ||||||
|  | 
 | ||||||
|  |    QObject::connect(dialog, | ||||||
|  |                     &QFileDialog::fileSelected, | ||||||
|  |                     self_, | ||||||
|  |                     [this](const QString& file) | ||||||
|  |                     { | ||||||
|  |                        const std::string path = | ||||||
|  |                           QDir::toNativeSeparators(file).toStdString(); | ||||||
|  |                        setIconOnAdded_ = path; | ||||||
|  |                        markerManager_->add_icon(path); | ||||||
|  |                     }); | ||||||
|  |    dialog->open(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void EditMarkerDialog::Impl::connect_signals() | ||||||
|  | { | ||||||
|  |    connect(self_, | ||||||
|  |            &EditMarkerDialog::accepted, | ||||||
|  |            self_, | ||||||
|  |            [this]() { handle_accepted(); }); | ||||||
|  | 
 | ||||||
|  |    connect(self_, | ||||||
|  |            &EditMarkerDialog::rejected, | ||||||
|  |            self_, | ||||||
|  |            [this]() { handle_rejected(); }); | ||||||
|  | 
 | ||||||
|  |    connect(deleteButton_, | ||||||
|  |            &QPushButton::clicked, | ||||||
|  |            self_, | ||||||
|  |            [this]() | ||||||
|  |            { | ||||||
|  |               markerManager_->remove_marker(editId_); | ||||||
|  |               self_->done(0); | ||||||
|  |            }); | ||||||
|  | 
 | ||||||
|  |    connect(self_->ui->iconColorLineEdit, | ||||||
|  |            &QLineEdit::textEdited, | ||||||
|  |            self_, | ||||||
|  |            [this](const QString& text) { set_icon_color(text.toStdString()); }); | ||||||
|  | 
 | ||||||
|  |    connect(self_->ui->iconColorButton, | ||||||
|  |            &QAbstractButton::clicked, | ||||||
|  |            self_, | ||||||
|  |            [this]() { show_color_dialog(); }); | ||||||
|  | 
 | ||||||
|  |    connect(self_->ui->iconFileOpenButton, | ||||||
|  |            &QPushButton::clicked, | ||||||
|  |            self_, | ||||||
|  |            [this]() { show_icon_file_dialog(); }); | ||||||
|  | 
 | ||||||
|  |    connect(markerManager_.get(), | ||||||
|  |            &manager::MarkerManager::IconAdded, | ||||||
|  |            self_, | ||||||
|  |            [this]() | ||||||
|  |            { | ||||||
|  |               const std::string color = | ||||||
|  |                  self_->ui->iconColorLineEdit->text().toStdString(); | ||||||
|  |               set_icon_color(color); | ||||||
|  | 
 | ||||||
|  |               if (setIconOnAdded_ != "") | ||||||
|  |               { | ||||||
|  |                  const int i = self_->ui->iconComboBox->findData( | ||||||
|  |                     QString::fromStdString(setIconOnAdded_)); | ||||||
|  |                  if (i >= 0) | ||||||
|  |                  { | ||||||
|  |                     self_->ui->iconComboBox->setCurrentIndex(i); | ||||||
|  |                     setIconOnAdded_ = ""; | ||||||
|  |                  } | ||||||
|  |               } | ||||||
|  |            }); | ||||||
|  | 
 | ||||||
|  |    connect(self_->ui->buttonBox->button(QDialogButtonBox::Apply), | ||||||
|  |            &QAbstractButton::clicked, | ||||||
|  |            self_, | ||||||
|  |            [this]() { handle_accepted(); }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void EditMarkerDialog::Impl::set_icon_color(const std::string& color) | ||||||
|  | { | ||||||
|  |    self_->ui->iconColorFrame->setStyleSheet( | ||||||
|  |       QString::fromStdString(fmt::format("background-color: {}", color))); | ||||||
|  | 
 | ||||||
|  |    auto* iconComboBox = self_->ui->iconComboBox; | ||||||
|  | 
 | ||||||
|  |    const QVariant currentIcon = iconComboBox->currentData(); | ||||||
|  | 
 | ||||||
|  |    self_->ui->iconComboBox->clear(); | ||||||
|  |    for (auto& markerIcon : markerManager_->get_icons()) | ||||||
|  |    { | ||||||
|  |       const int i = | ||||||
|  |          iconComboBox->findData(QString::fromStdString(markerIcon.second.name)); | ||||||
|  |       const QIcon icon = get_colored_icon(markerIcon.second, color); | ||||||
|  |       if (i < 0) | ||||||
|  |       { | ||||||
|  |          iconComboBox->addItem( | ||||||
|  |             icon, | ||||||
|  |             QString::fromStdString(markerIcon.second.shortName), | ||||||
|  |             QString::fromStdString(markerIcon.second.name)); | ||||||
|  |       } | ||||||
|  |       else | ||||||
|  |       { | ||||||
|  |          self_->ui->iconComboBox->setItemIcon(i, icon); | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    const int i = iconComboBox->findData(currentIcon); | ||||||
|  |    if (i < 0) | ||||||
|  |    { | ||||||
|  |       return; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    iconComboBox->setCurrentIndex(i); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void EditMarkerDialog::Impl::handle_accepted() | ||||||
|  | { | ||||||
|  |    markerManager_->set_marker(editId_, self_->get_marker_info()); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void EditMarkerDialog::Impl::handle_rejected() | ||||||
|  | { | ||||||
|  |    if (adding_) | ||||||
|  |    { | ||||||
|  |       markerManager_->remove_marker(editId_); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace scwx::qt::ui
 | ||||||
							
								
								
									
										34
									
								
								scwx-qt/source/scwx/qt/ui/edit_marker_dialog.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,34 @@ | ||||||
|  | #pragma once | ||||||
|  | #include <scwx/qt/types/marker_types.hpp> | ||||||
|  | 
 | ||||||
|  | #include <QDialog> | ||||||
|  | 
 | ||||||
|  | namespace Ui | ||||||
|  | { | ||||||
|  | class EditMarkerDialog; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | namespace scwx::qt::ui | ||||||
|  | { | ||||||
|  | class EditMarkerDialog : public QDialog | ||||||
|  | { | ||||||
|  |    Q_OBJECT | ||||||
|  |    Q_DISABLE_COPY_MOVE(EditMarkerDialog) | ||||||
|  | 
 | ||||||
|  | public: | ||||||
|  |    explicit EditMarkerDialog(QWidget* parent = nullptr); | ||||||
|  |    ~EditMarkerDialog() override; | ||||||
|  | 
 | ||||||
|  |    void setup(); | ||||||
|  |    void setup(double latitude, double longitude); | ||||||
|  |    void setup(types::MarkerId id); | ||||||
|  | 
 | ||||||
|  |    [[nodiscard]] types::MarkerInfo get_marker_info() const; | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |    class Impl; | ||||||
|  |    std::unique_ptr<Impl> p; | ||||||
|  |    Ui::EditMarkerDialog* ui; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace scwx::qt::ui
 | ||||||
							
								
								
									
										210
									
								
								scwx-qt/source/scwx/qt/ui/edit_marker_dialog.ui
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,210 @@ | ||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <ui version="4.0"> | ||||||
|  |  <class>EditMarkerDialog</class> | ||||||
|  |  <widget class="QDialog" name="EditMarkerDialog"> | ||||||
|  |   <property name="geometry"> | ||||||
|  |    <rect> | ||||||
|  |     <x>0</x> | ||||||
|  |     <y>0</y> | ||||||
|  |     <width>400</width> | ||||||
|  |     <height>249</height> | ||||||
|  |    </rect> | ||||||
|  |   </property> | ||||||
|  |   <property name="windowTitle"> | ||||||
|  |    <string>Edit Location Marker</string> | ||||||
|  |   </property> | ||||||
|  |   <layout class="QGridLayout" name="gridLayout"> | ||||||
|  |    <item row="9" column="0"> | ||||||
|  |     <spacer name="verticalSpacer"> | ||||||
|  |      <property name="orientation"> | ||||||
|  |       <enum>Qt::Orientation::Vertical</enum> | ||||||
|  |      </property> | ||||||
|  |      <property name="sizeHint" stdset="0"> | ||||||
|  |       <size> | ||||||
|  |        <width>20</width> | ||||||
|  |        <height>40</height> | ||||||
|  |       </size> | ||||||
|  |      </property> | ||||||
|  |     </spacer> | ||||||
|  |    </item> | ||||||
|  |    <item row="4" column="2"> | ||||||
|  |     <layout class="QHBoxLayout" name="horizontalLayout"> | ||||||
|  |      <item> | ||||||
|  |       <widget class="QFrame" name="iconColorFrame"> | ||||||
|  |        <property name="minimumSize"> | ||||||
|  |         <size> | ||||||
|  |          <width>24</width> | ||||||
|  |          <height>24</height> | ||||||
|  |         </size> | ||||||
|  |        </property> | ||||||
|  |        <property name="frameShape"> | ||||||
|  |         <enum>QFrame::Shape::Box</enum> | ||||||
|  |        </property> | ||||||
|  |        <property name="frameShadow"> | ||||||
|  |         <enum>QFrame::Shadow::Plain</enum> | ||||||
|  |        </property> | ||||||
|  |       </widget> | ||||||
|  |      </item> | ||||||
|  |      <item> | ||||||
|  |       <widget class="QLineEdit" name="iconColorLineEdit"> | ||||||
|  |        <property name="text"> | ||||||
|  |         <string>#ffffffff</string> | ||||||
|  |        </property> | ||||||
|  |       </widget> | ||||||
|  |      </item> | ||||||
|  |      <item> | ||||||
|  |       <widget class="QToolButton" name="iconColorButton"> | ||||||
|  |        <property name="text"> | ||||||
|  |         <string>...</string> | ||||||
|  |        </property> | ||||||
|  |        <property name="icon"> | ||||||
|  |         <iconset resource="../../../../scwx-qt.qrc"> | ||||||
|  |          <normaloff>:/res/icons/font-awesome-6/palette-solid.svg</normaloff>:/res/icons/font-awesome-6/palette-solid.svg</iconset> | ||||||
|  |        </property> | ||||||
|  |       </widget> | ||||||
|  |      </item> | ||||||
|  |     </layout> | ||||||
|  |    </item> | ||||||
|  |    <item row="0" column="2"> | ||||||
|  |     <widget class="QDoubleSpinBox" name="latitudeDoubleSpinBox"> | ||||||
|  |      <property name="correctionMode"> | ||||||
|  |       <enum>QAbstractSpinBox::CorrectionMode::CorrectToNearestValue</enum> | ||||||
|  |      </property> | ||||||
|  |      <property name="decimals"> | ||||||
|  |       <number>5</number> | ||||||
|  |      </property> | ||||||
|  |      <property name="minimum"> | ||||||
|  |       <double>-90.000000000000000</double> | ||||||
|  |      </property> | ||||||
|  |      <property name="maximum"> | ||||||
|  |       <double>90.000000000000000</double> | ||||||
|  |      </property> | ||||||
|  |     </widget> | ||||||
|  |    </item> | ||||||
|  |    <item row="5" column="0"> | ||||||
|  |     <widget class="QLabel" name="label"> | ||||||
|  |      <property name="text"> | ||||||
|  |       <string>Name</string> | ||||||
|  |      </property> | ||||||
|  |     </widget> | ||||||
|  |    </item> | ||||||
|  |    <item row="3" column="0"> | ||||||
|  |     <widget class="QLabel" name="label_4"> | ||||||
|  |      <property name="text"> | ||||||
|  |       <string>Icon</string> | ||||||
|  |      </property> | ||||||
|  |     </widget> | ||||||
|  |    </item> | ||||||
|  |    <item row="10" column="0" colspan="3"> | ||||||
|  |     <widget class="QDialogButtonBox" name="buttonBox"> | ||||||
|  |      <property name="orientation"> | ||||||
|  |       <enum>Qt::Orientation::Horizontal</enum> | ||||||
|  |      </property> | ||||||
|  |      <property name="standardButtons"> | ||||||
|  |       <set>QDialogButtonBox::StandardButton::Apply|QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set> | ||||||
|  |      </property> | ||||||
|  |     </widget> | ||||||
|  |    </item> | ||||||
|  |    <item row="1" column="0"> | ||||||
|  |     <widget class="QLabel" name="label_3"> | ||||||
|  |      <property name="text"> | ||||||
|  |       <string>Longitude</string> | ||||||
|  |      </property> | ||||||
|  |     </widget> | ||||||
|  |    </item> | ||||||
|  |    <item row="4" column="0"> | ||||||
|  |     <widget class="QLabel" name="label_5"> | ||||||
|  |      <property name="text"> | ||||||
|  |       <string>Icon Color</string> | ||||||
|  |      </property> | ||||||
|  |     </widget> | ||||||
|  |    </item> | ||||||
|  |    <item row="0" column="0"> | ||||||
|  |     <widget class="QLabel" name="label_2"> | ||||||
|  |      <property name="text"> | ||||||
|  |       <string>Latitude</string> | ||||||
|  |      </property> | ||||||
|  |     </widget> | ||||||
|  |    </item> | ||||||
|  |    <item row="3" column="3"> | ||||||
|  |     <widget class="QToolButton" name="iconFileOpenButton"> | ||||||
|  |      <property name="toolTip"> | ||||||
|  |       <string>Add Custom Icon</string> | ||||||
|  |      </property> | ||||||
|  |      <property name="text"> | ||||||
|  |       <string>...</string> | ||||||
|  |      </property> | ||||||
|  |     </widget> | ||||||
|  |    </item> | ||||||
|  |    <item row="1" column="2"> | ||||||
|  |     <widget class="QDoubleSpinBox" name="longitudeDoubleSpinBox"> | ||||||
|  |      <property name="correctionMode"> | ||||||
|  |       <enum>QAbstractSpinBox::CorrectionMode::CorrectToNearestValue</enum> | ||||||
|  |      </property> | ||||||
|  |      <property name="decimals"> | ||||||
|  |       <number>5</number> | ||||||
|  |      </property> | ||||||
|  |      <property name="minimum"> | ||||||
|  |       <double>-180.000000000000000</double> | ||||||
|  |      </property> | ||||||
|  |      <property name="maximum"> | ||||||
|  |       <double>180.000000000000000</double> | ||||||
|  |      </property> | ||||||
|  |     </widget> | ||||||
|  |    </item> | ||||||
|  |    <item row="5" column="2"> | ||||||
|  |     <widget class="QLineEdit" name="nameLineEdit"/> | ||||||
|  |    </item> | ||||||
|  |    <item row="3" column="2"> | ||||||
|  |     <widget class="QComboBox" name="iconComboBox"> | ||||||
|  |      <property name="sizePolicy"> | ||||||
|  |       <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> | ||||||
|  |        <horstretch>0</horstretch> | ||||||
|  |        <verstretch>0</verstretch> | ||||||
|  |       </sizepolicy> | ||||||
|  |      </property> | ||||||
|  |      <property name="autoFillBackground"> | ||||||
|  |       <bool>true</bool> | ||||||
|  |      </property> | ||||||
|  |     </widget> | ||||||
|  |    </item> | ||||||
|  |   </layout> | ||||||
|  |  </widget> | ||||||
|  |  <resources> | ||||||
|  |   <include location="../../../../scwx-qt.qrc"/> | ||||||
|  |  </resources> | ||||||
|  |  <connections> | ||||||
|  |   <connection> | ||||||
|  |    <sender>buttonBox</sender> | ||||||
|  |    <signal>accepted()</signal> | ||||||
|  |    <receiver>EditMarkerDialog</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>EditMarkerDialog</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> | ||||||
|  | @ -4,7 +4,7 @@ | ||||||
| #include <scwx/qt/manager/marker_manager.hpp> | #include <scwx/qt/manager/marker_manager.hpp> | ||||||
| #include <scwx/qt/model/marker_model.hpp> | #include <scwx/qt/model/marker_model.hpp> | ||||||
| #include <scwx/qt/types/qt_types.hpp> | #include <scwx/qt/types/qt_types.hpp> | ||||||
| #include <scwx/qt/ui/open_url_dialog.hpp> | #include <scwx/qt/ui/edit_marker_dialog.hpp> | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
| 
 | 
 | ||||||
| #include <QSortFilterProxyModel> | #include <QSortFilterProxyModel> | ||||||
|  | @ -23,17 +23,24 @@ class MarkerSettingsWidgetImpl | ||||||
| { | { | ||||||
| public: | public: | ||||||
|    explicit MarkerSettingsWidgetImpl(MarkerSettingsWidget* self) : |    explicit MarkerSettingsWidgetImpl(MarkerSettingsWidget* self) : | ||||||
|       self_ {self}, |        self_ {self}, | ||||||
|       markerModel_ {new model::MarkerModel(self_)} |        markerModel_ {new model::MarkerModel(self_)}, | ||||||
|  |        proxyModel_ {new QSortFilterProxyModel(self_)} | ||||||
|    { |    { | ||||||
|  |       proxyModel_->setSourceModel(markerModel_); | ||||||
|  |       proxyModel_->setSortRole(Qt::DisplayRole); // TODO types::SortRole
 | ||||||
|  |       proxyModel_->setFilterCaseSensitivity(Qt::CaseInsensitive); | ||||||
|  |       proxyModel_->setFilterKeyColumn(-1); | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    void ConnectSignals(); |    void ConnectSignals(); | ||||||
| 
 | 
 | ||||||
|    MarkerSettingsWidget* self_; |    MarkerSettingsWidget*                   self_; | ||||||
|    model::MarkerModel* markerModel_; |    model::MarkerModel*                     markerModel_; | ||||||
|  |    QSortFilterProxyModel*                  proxyModel_; | ||||||
|    std::shared_ptr<manager::MarkerManager> markerManager_ { |    std::shared_ptr<manager::MarkerManager> markerManager_ { | ||||||
|       manager::MarkerManager::Instance()}; |       manager::MarkerManager::Instance()}; | ||||||
|  |    std::shared_ptr<ui::EditMarkerDialog> editMarkerDialog_ {nullptr}; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -45,8 +52,9 @@ MarkerSettingsWidget::MarkerSettingsWidget(QWidget* parent) : | ||||||
|    ui->setupUi(this); |    ui->setupUi(this); | ||||||
| 
 | 
 | ||||||
|    ui->removeButton->setEnabled(false); |    ui->removeButton->setEnabled(false); | ||||||
|  |    ui->markerView->setModel(p->proxyModel_); | ||||||
| 
 | 
 | ||||||
|    ui->markerView->setModel(p->markerModel_); |    p->editMarkerDialog_ = std::make_shared<ui::EditMarkerDialog>(this); | ||||||
| 
 | 
 | ||||||
|    p->ConnectSignals(); |    p->ConnectSignals(); | ||||||
| } | } | ||||||
|  | @ -63,7 +71,8 @@ void MarkerSettingsWidgetImpl::ConnectSignals() | ||||||
|                     self_, |                     self_, | ||||||
|                     [this]() |                     [this]() | ||||||
|                     { |                     { | ||||||
|                        markerManager_->add_marker(types::MarkerInfo("", 0, 0)); |                        editMarkerDialog_->setup(); | ||||||
|  |                        editMarkerDialog_->show(); | ||||||
|                     }); |                     }); | ||||||
|    QObject::connect( |    QObject::connect( | ||||||
|       self_->ui->removeButton, |       self_->ui->removeButton, | ||||||
|  | @ -99,9 +108,30 @@ void MarkerSettingsWidgetImpl::ConnectSignals() | ||||||
|             return; |             return; | ||||||
|          } |          } | ||||||
| 
 | 
 | ||||||
|          bool itemSelected = selected.size() > 0; |          const bool itemSelected = selected.size() > 0; | ||||||
|          self_->ui->removeButton->setEnabled(itemSelected); |          self_->ui->removeButton->setEnabled(itemSelected); | ||||||
|       }); |       }); | ||||||
|  |    QObject::connect(self_->ui->markerView, | ||||||
|  |                     &QAbstractItemView::doubleClicked, | ||||||
|  |                     self_, | ||||||
|  |                     [this](const QModelIndex& index) | ||||||
|  |                     { | ||||||
|  |                        const int row = index.row(); | ||||||
|  |                        if (row < 0) | ||||||
|  |                        { | ||||||
|  |                           return; | ||||||
|  |                        } | ||||||
|  | 
 | ||||||
|  |                        std::optional<types::MarkerId> id = | ||||||
|  |                           markerModel_->getId(row); | ||||||
|  |                        if (!id) | ||||||
|  |                        { | ||||||
|  |                           return; | ||||||
|  |                        } | ||||||
|  | 
 | ||||||
|  |                        editMarkerDialog_->setup(*id); | ||||||
|  |                        editMarkerDialog_->show(); | ||||||
|  |                     }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } // namespace ui
 | } // namespace ui
 | ||||||
|  |  | ||||||
							
								
								
									
										64
									
								
								scwx-qt/source/scwx/qt/util/q_color_modulate.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,64 @@ | ||||||
|  | #include <scwx/qt/util/q_color_modulate.hpp> | ||||||
|  | 
 | ||||||
|  | #include <QColor> | ||||||
|  | #include <QImage> | ||||||
|  | #include <QIcon> | ||||||
|  | #include <QPixmap> | ||||||
|  | #include <QSize> | ||||||
|  | 
 | ||||||
|  | namespace scwx::qt::util | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | void modulateColors_(QImage& image, const QColor& color) | ||||||
|  | { | ||||||
|  |    for (int y = 0; y < image.height(); ++y) | ||||||
|  |    { | ||||||
|  |       QRgb* line = reinterpret_cast<QRgb*>(image.scanLine(y)); | ||||||
|  |       for (int x = 0; x < image.width(); ++x) | ||||||
|  |       { | ||||||
|  |          // This is pulled from Qt Documentation
 | ||||||
|  |          // https://doc.qt.io/qt-6/qimage.html#scanLine
 | ||||||
|  |          // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
 | ||||||
|  |          QRgb& rgb = line[x]; | ||||||
|  |          /* clang-format off
 | ||||||
|  |           * NOLINTBEGIN(cppcoreguidelines-narrowing-conversions, bugprone-narrowing-conversions) | ||||||
|  |           * qRed/qGreen/qBlue/qAlpha return values 0-255, handlable by float | ||||||
|  |           * redF/greenF/blueF/alphaF are all 0-1, so output is 0-255 | ||||||
|  |           * Rounding is fine for this. | ||||||
|  |           * clang-format on | ||||||
|  |           */ | ||||||
|  |          const int red   = qRed(rgb) * color.redF(); | ||||||
|  |          const int green = qGreen(rgb) * color.greenF(); | ||||||
|  |          const int blue  = qBlue(rgb) * color.blueF(); | ||||||
|  |          const int alpha = qAlpha(rgb) * color.alphaF(); | ||||||
|  |          /* clang-format off
 | ||||||
|  |           * NOLINTEND(cppcoreguidelines-narrowing-conversions, bugprone-narrowing-conversions) | ||||||
|  |           * clang-format on | ||||||
|  |           */ | ||||||
|  | 
 | ||||||
|  |          rgb = qRgba(red, green, blue, alpha); | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | QImage modulateColors(const QImage& image, const QColor& color) | ||||||
|  | { | ||||||
|  |    QImage copy = image.copy(); | ||||||
|  |    modulateColors_(copy, color); | ||||||
|  |    return copy; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | QPixmap modulateColors(const QPixmap& pixmap, const QColor& color) | ||||||
|  | { | ||||||
|  |    QImage image = pixmap.toImage(); | ||||||
|  |    modulateColors_(image, color); | ||||||
|  |    return QPixmap::fromImage(image); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | QIcon modulateColors(const QIcon& icon, const QSize& size, const QColor& color) | ||||||
|  | { | ||||||
|  |    const QPixmap pixmap = modulateColors(icon.pixmap(size), color); | ||||||
|  |    return QIcon(pixmap); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace scwx::qt::util
 | ||||||
							
								
								
									
										16
									
								
								scwx-qt/source/scwx/qt/util/q_color_modulate.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,16 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <QColor> | ||||||
|  | #include <QImage> | ||||||
|  | #include <QIcon> | ||||||
|  | #include <QPixmap> | ||||||
|  | #include <QSize> | ||||||
|  | 
 | ||||||
|  | namespace scwx::qt::util | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | QImage  modulateColors(const QImage& image, const QColor& color); | ||||||
|  | QPixmap modulateColors(const QPixmap& pixmap, const QColor& color); | ||||||
|  | QIcon modulateColors(const QIcon& icon, const QSize& size, const QColor& color); | ||||||
|  | 
 | ||||||
|  | } // namespace scwx::qt::util
 | ||||||
|  | @ -1 +1 @@ | ||||||
| Subproject commit 4b4d9c54b8218aa2297dbd457e3747091570f0d2 | Subproject commit 0d085b1df59045e14ca996982b4907b1a0da4fdb | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| #include <scwx/qt/model/marker_model.hpp> | #include <scwx/qt/model/marker_model.hpp> | ||||||
| #include <scwx/qt/manager/marker_manager.hpp> | #include <scwx/qt/manager/marker_manager.hpp> | ||||||
| #include <scwx/qt/main/application.hpp> | #include <scwx/qt/main/application.hpp> | ||||||
|  | #include <scwx/qt/util/color.hpp> | ||||||
| 
 | 
 | ||||||
| #include <filesystem> | #include <filesystem> | ||||||
| #include <fstream> | #include <fstream> | ||||||
|  | @ -9,7 +10,6 @@ | ||||||
| #include <condition_variable> | #include <condition_variable> | ||||||
| #include <gtest/gtest.h> | #include <gtest/gtest.h> | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| namespace scwx | namespace scwx | ||||||
| { | { | ||||||
| namespace qt | namespace qt | ||||||
|  | @ -25,11 +25,17 @@ static const std::string ONE_MARKERS_FILE = | ||||||
|    std::string(SCWX_TEST_DATA_DIR) + "/json/markers/markers-one.json"; |    std::string(SCWX_TEST_DATA_DIR) + "/json/markers/markers-one.json"; | ||||||
| static const std::string FIVE_MARKERS_FILE = | static const std::string FIVE_MARKERS_FILE = | ||||||
|    std::string(SCWX_TEST_DATA_DIR) + "/json/markers/markers-five.json"; |    std::string(SCWX_TEST_DATA_DIR) + "/json/markers/markers-five.json"; | ||||||
|  | static const std::string PART1_MARKER_FILE = | ||||||
|  |    std::string(SCWX_TEST_DATA_DIR) + "/json/markers/markers-part1.json"; | ||||||
| 
 | 
 | ||||||
| static std::mutex              initializedMutex {}; | static std::mutex              initializedMutex {}; | ||||||
| static std::condition_variable initializedCond {}; | static std::condition_variable initializedCond {}; | ||||||
| static bool                    initialized; | static bool                    initialized; | ||||||
| 
 | 
 | ||||||
|  | static const boost::gil::rgba8_pixel_t defaultIconColor = | ||||||
|  |    util::color::ToRgba8PixelT("#ffff0000"); | ||||||
|  | static const std::string defaultIconName = "images/location-marker"; | ||||||
|  | 
 | ||||||
| void CompareFiles(const std::string& file1, const std::string& file2) | void CompareFiles(const std::string& file1, const std::string& file2) | ||||||
| { | { | ||||||
|    std::ifstream     ifs1 {file1}; |    std::ifstream     ifs1 {file1}; | ||||||
|  | @ -49,8 +55,8 @@ void CopyFile(const std::string& from, const std::string& to) | ||||||
|    CompareFiles(from, to); |    CompareFiles(from, to); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| typedef void TestFunction(std::shared_ptr<manager::MarkerManager> manager, | using TestFunction = void(std::shared_ptr<manager::MarkerManager>, | ||||||
|                           MarkerModel&                            model); |                           MarkerModel&); | ||||||
| 
 | 
 | ||||||
| void RunTest(const std::string& filename, TestFunction testFunction) | void RunTest(const std::string& filename, TestFunction testFunction) | ||||||
| { | { | ||||||
|  | @ -65,7 +71,7 @@ void RunTest(const std::string& filename, TestFunction testFunction) | ||||||
|       initialized = false; |       initialized = false; | ||||||
|       QObject::connect(manager.get(), |       QObject::connect(manager.get(), | ||||||
|                        &manager::MarkerManager::MarkersInitialized, |                        &manager::MarkerManager::MarkersInitialized, | ||||||
|                        [](size_t count) |                        []() | ||||||
|                        { |                        { | ||||||
|                           std::unique_lock lock(initializedMutex); |                           std::unique_lock lock(initializedMutex); | ||||||
|                           initialized = true; |                           initialized = true; | ||||||
|  | @ -119,7 +125,10 @@ TEST(MarkerModelTest, AddRemove) | ||||||
| 
 | 
 | ||||||
|    RunTest(ONE_MARKERS_FILE, |    RunTest(ONE_MARKERS_FILE, | ||||||
|            [](std::shared_ptr<manager::MarkerManager> manager, MarkerModel&) |            [](std::shared_ptr<manager::MarkerManager> manager, MarkerModel&) | ||||||
|            { manager->add_marker(types::MarkerInfo("Null", 0, 0)); }); |            { | ||||||
|  |               manager->add_marker(types::MarkerInfo( | ||||||
|  |                  "Null", 0, 0, defaultIconName, defaultIconColor)); | ||||||
|  |            }); | ||||||
|    RunTest( |    RunTest( | ||||||
|       EMPTY_MARKERS_FILE, |       EMPTY_MARKERS_FILE, | ||||||
|       [](std::shared_ptr<manager::MarkerManager> manager, MarkerModel& model) |       [](std::shared_ptr<manager::MarkerManager> manager, MarkerModel& model) | ||||||
|  | @ -143,11 +152,16 @@ TEST(MarkerModelTest, AddFive) | ||||||
|    RunTest(FIVE_MARKERS_FILE, |    RunTest(FIVE_MARKERS_FILE, | ||||||
|            [](std::shared_ptr<manager::MarkerManager> manager, MarkerModel&) |            [](std::shared_ptr<manager::MarkerManager> manager, MarkerModel&) | ||||||
|            { |            { | ||||||
|               manager->add_marker(types::MarkerInfo("Null", 0, 0)); |               manager->add_marker(types::MarkerInfo( | ||||||
|               manager->add_marker(types::MarkerInfo("North", 90, 0)); |                  "Null", 0, 0, defaultIconName, defaultIconColor)); | ||||||
|               manager->add_marker(types::MarkerInfo("South", -90, 0)); |               manager->add_marker(types::MarkerInfo( | ||||||
|               manager->add_marker(types::MarkerInfo("East", 0, 90)); |                  "North", 90, 0, defaultIconName, defaultIconColor)); | ||||||
|               manager->add_marker(types::MarkerInfo("West", 0, -90)); |               manager->add_marker(types::MarkerInfo( | ||||||
|  |                  "South", -90, 0, defaultIconName, defaultIconColor)); | ||||||
|  |               manager->add_marker(types::MarkerInfo( | ||||||
|  |                  "East", 0, 90, defaultIconName, defaultIconColor)); | ||||||
|  |               manager->add_marker(types::MarkerInfo( | ||||||
|  |                  "West", 0, -90, defaultIconName, defaultIconColor)); | ||||||
|            }); |            }); | ||||||
| 
 | 
 | ||||||
|    std::filesystem::remove(TEMP_MARKERS_FILE); |    std::filesystem::remove(TEMP_MARKERS_FILE); | ||||||
|  | @ -161,10 +175,14 @@ TEST(MarkerModelTest, AddFour) | ||||||
|    RunTest(FIVE_MARKERS_FILE, |    RunTest(FIVE_MARKERS_FILE, | ||||||
|            [](std::shared_ptr<manager::MarkerManager> manager, MarkerModel&) |            [](std::shared_ptr<manager::MarkerManager> manager, MarkerModel&) | ||||||
|            { |            { | ||||||
|               manager->add_marker(types::MarkerInfo("North", 90, 0)); |               manager->add_marker(types::MarkerInfo( | ||||||
|               manager->add_marker(types::MarkerInfo("South", -90, 0)); |                  "North", 90, 0, defaultIconName, defaultIconColor)); | ||||||
|               manager->add_marker(types::MarkerInfo("East", 0, 90)); |               manager->add_marker(types::MarkerInfo( | ||||||
|               manager->add_marker(types::MarkerInfo("West", 0, -90)); |                  "South", -90, 0, defaultIconName, defaultIconColor)); | ||||||
|  |               manager->add_marker(types::MarkerInfo( | ||||||
|  |                  "East", 0, 90, defaultIconName, defaultIconColor)); | ||||||
|  |               manager->add_marker(types::MarkerInfo( | ||||||
|  |                  "West", 0, -90, defaultIconName, defaultIconColor)); | ||||||
|            }); |            }); | ||||||
| 
 | 
 | ||||||
|    std::filesystem::remove(TEMP_MARKERS_FILE); |    std::filesystem::remove(TEMP_MARKERS_FILE); | ||||||
|  | @ -235,6 +253,17 @@ TEST(MarkerModelTest, RemoveFour) | ||||||
|    EXPECT_EQ(std::filesystem::exists(TEMP_MARKERS_FILE), false); |    EXPECT_EQ(std::filesystem::exists(TEMP_MARKERS_FILE), false); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | TEST(MarkerModelTest, UpdateFromPart1) | ||||||
|  | { | ||||||
|  |    CopyFile(PART1_MARKER_FILE, TEMP_MARKERS_FILE); | ||||||
|  | 
 | ||||||
|  |    RunTest(ONE_MARKERS_FILE, | ||||||
|  |            [](std::shared_ptr<manager::MarkerManager>, MarkerModel&) {}); | ||||||
|  | 
 | ||||||
|  |    std::filesystem::remove(TEMP_MARKERS_FILE); | ||||||
|  |    EXPECT_EQ(std::filesystem::exists(TEMP_MARKERS_FILE), false); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| } // namespace model
 | } // namespace model
 | ||||||
| } // namespace qt
 | } // namespace qt
 | ||||||
| } // namespace scwx
 | } // namespace scwx
 | ||||||
|  |  | ||||||
 Dan Paulat
						Dan Paulat