Merge pull request #321 from AdenKoperczak/location_markers_part2
Location Markers Part 2
|  | @ -10,4 +10,6 @@ Checks: | |||
|   - '-misc-include-cleaner' | ||||
|   - '-misc-non-private-member-variables-in-classes' | ||||
|   - '-modernize-use-trailing-return-type' | ||||
|   - '-bugprone-easily-swappable-parameters' | ||||
|   - '-modernize-return-braced-init-list' | ||||
| 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" | ||||
|         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" | ||||
|         stroke="red"   stroke-width="20" fill="none"/> | ||||
|         stroke="#ffffff" stroke-width="20" fill="none"/> | ||||
| 
 | ||||
|  </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/location_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/media_types.hpp | ||||
|               source/scwx/qt/types/qt_types.hpp | ||||
|               source/scwx/qt/types/radar_product_record.hpp | ||||
|               source/scwx/qt/types/text_event_key.hpp | ||||
|  | @ -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/download_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/gps_info_dialog.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/download_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/gps_info_dialog.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/county_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/imgui_debug_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/streams.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_input_stream.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/network.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_input_stream.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/backward-step-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/discord.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/github.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/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/pause-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-plus-regular.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/tent-solid.svg</file> | ||||
|         <file>res/icons/font-awesome-6/volume-high-solid.svg</file> | ||||
|         <file>res/palettes/wct/CC.pal</file> | ||||
|         <file>res/palettes/wct/Default16.pal</file> | ||||
|  |  | |||
|  | @ -38,7 +38,7 @@ static constexpr std::size_t kIntegersPerVertex_ = 4; | |||
| static constexpr std::size_t kIntegerBufferLength_ = | ||||
|    kNumTriangles * kVerticesPerTriangle * kIntegersPerVertex_; | ||||
| 
 | ||||
| struct GeoIconDrawItem | ||||
| struct GeoIconDrawItem : types::EventHandler | ||||
| { | ||||
|    units::length::nautical_miles<double>       threshold_ {}; | ||||
|    std::chrono::sys_time<std::chrono::seconds> startTime_ {}; | ||||
|  | @ -691,7 +691,7 @@ void GeoIcons::Impl::UpdateSingleBuffer( | |||
|                                hoverIcons.end(), | ||||
|                                [&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; | ||||
| 
 | ||||
|  | @ -903,7 +903,7 @@ bool GeoIcons::RunMousePicking( | |||
|    const QPointF&   mouseGlobalPos, | ||||
|    const glm::vec2& mouseCoords, | ||||
|    const common::Coordinate& /* mouseGeoCoords */, | ||||
|    std::shared_ptr<types::EventHandler>& /* eventHandler */) | ||||
|    std::shared_ptr<types::EventHandler>& eventHandler) | ||||
| { | ||||
|    std::unique_lock lock {p->iconMutex_}; | ||||
| 
 | ||||
|  | @ -993,12 +993,27 @@ bool GeoIcons::RunMousePicking( | |||
|    if (it != p->currentHoverIcons_.crend()) | ||||
|    { | ||||
|       itemPicked = true; | ||||
|       if (!it->di_->hoverText_.empty()) | ||||
|       { | ||||
|          // Show tooltip
 | ||||
|          util::tooltip::Show(it->di_->hoverText_, mouseGlobalPos); | ||||
|       } | ||||
|       if (it->di_->event_ != nullptr) | ||||
|       { | ||||
|          eventHandler = it->di_; | ||||
|       } | ||||
|    } | ||||
| 
 | ||||
|    return itemPicked; | ||||
| } | ||||
| 
 | ||||
| void GeoIcons::RegisterEventHandler( | ||||
|    const std::shared_ptr<GeoIconDrawItem>& di, | ||||
|    const std::function<void(QEvent*)>&     eventHandler) | ||||
| { | ||||
|    di->event_ = eventHandler; | ||||
| } | ||||
| 
 | ||||
| } // namespace draw
 | ||||
| } // namespace gl
 | ||||
| } // namespace qt
 | ||||
|  |  | |||
|  | @ -183,6 +183,16 @@ public: | |||
|     */ | ||||
|    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: | ||||
|    class Impl; | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,13 +1,17 @@ | |||
| #include <scwx/qt/manager/marker_manager.hpp> | ||||
| #include <scwx/qt/types/marker_types.hpp> | ||||
| #include <scwx/qt/util/color.hpp> | ||||
| #include <scwx/qt/util/json.hpp> | ||||
| #include <scwx/qt/util/texture_atlas.hpp> | ||||
| #include <scwx/qt/main/application.hpp> | ||||
| #include <scwx/qt/manager/resource_manager.hpp> | ||||
| #include <scwx/util/logger.hpp> | ||||
| 
 | ||||
| #include <filesystem> | ||||
| #include <shared_mutex> | ||||
| #include <vector> | ||||
| #include <string> | ||||
| #include <unordered_map> | ||||
| 
 | ||||
| #include <QStandardPaths> | ||||
| #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 kLatitudeName_  = "latitude"; | ||||
| 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 | ||||
| { | ||||
|  | @ -39,12 +47,13 @@ public: | |||
|    std::string                                 markerSettingsPath_ {""}; | ||||
|    std::vector<std::shared_ptr<MarkerRecord>>  markerRecords_ {}; | ||||
|    std::unordered_map<types::MarkerId, size_t> idToIndex_ {}; | ||||
| 
 | ||||
|    std::unordered_map<std::string, types::MarkerIconInfo> markerIcons_ {}; | ||||
| 
 | ||||
|    MarkerManager* self_; | ||||
| 
 | ||||
|    boost::asio::thread_pool threadPool_ {1u}; | ||||
|    std::shared_mutex        markerRecordLock_ {}; | ||||
|    std::shared_mutex        markerIconsLock_ {}; | ||||
| 
 | ||||
|    void                          InitializeMarkerSettings(); | ||||
|    void                          ReadMarkerSettings(); | ||||
|  | @ -53,16 +62,12 @@ public: | |||
| 
 | ||||
|    void InitalizeIds(); | ||||
|    types::MarkerId NewId(); | ||||
|    types::MarkerId lastId_; | ||||
|    types::MarkerId lastId_ {0}; | ||||
| }; | ||||
| 
 | ||||
| class MarkerManager::Impl::MarkerRecord | ||||
| { | ||||
| public: | ||||
|    MarkerRecord(const std::string& name, double latitude, double longitude) : | ||||
|       markerInfo_ {types::MarkerInfo(name, latitude, longitude)} | ||||
|    { | ||||
|    } | ||||
|    MarkerRecord(const types::MarkerInfo& info) : | ||||
|       markerInfo_ {info} | ||||
|    { | ||||
|  | @ -81,16 +86,50 @@ public: | |||
|    { | ||||
|       jv = {{kNameName_, record->markerInfo_.name}, | ||||
|             {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>, | ||||
|                                   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<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; | ||||
|    { | ||||
|       std::unique_lock lock(markerRecordLock_); | ||||
|       const std::unique_lock lock(markerRecordLock_); | ||||
| 
 | ||||
|       // Determine if marker settings exists
 | ||||
|       if (std::filesystem::exists(markerSettingsPath_)) | ||||
|  | @ -147,18 +186,16 @@ void MarkerManager::Impl::ReadMarkerSettings() | |||
|          { | ||||
|             try | ||||
|             { | ||||
|                MarkerRecord record = | ||||
|                   boost::json::value_to<MarkerRecord>(markerEntry); | ||||
|                auto record = boost::json::value_to<MarkerRecord>(markerEntry); | ||||
| 
 | ||||
|                if (!record.markerInfo_.name.empty()) | ||||
|                { | ||||
|                   types::MarkerId id    = NewId(); | ||||
|                   size_t          index = markerRecords_.size(); | ||||
|                const types::MarkerId id    = NewId(); | ||||
|                const size_t          index = markerRecords_.size(); | ||||
|                record.markerInfo_.id       = id; | ||||
|                markerRecords_.emplace_back( | ||||
|                   std::make_shared<MarkerRecord>(record.markerInfo_)); | ||||
|                idToIndex_.emplace(id, index); | ||||
|                } | ||||
| 
 | ||||
|                self_->add_icon(record.markerInfo_.iconName, true); | ||||
|             } | ||||
|             catch (const std::exception& ex) | ||||
|             { | ||||
|  | @ -166,6 +203,8 @@ void MarkerManager::Impl::ReadMarkerSettings() | |||
|             } | ||||
|          } | ||||
| 
 | ||||
|          ResourceManager::BuildAtlas(); | ||||
| 
 | ||||
|          logger_->debug("{} location marker entries", markerRecords_.size()); | ||||
|       } | ||||
|    } | ||||
|  | @ -177,7 +216,7 @@ void MarkerManager::Impl::WriteMarkerSettings() | |||
| { | ||||
|    logger_->info("Saving location marker settings"); | ||||
| 
 | ||||
|    std::shared_lock lock(markerRecordLock_); | ||||
|    const std::shared_lock lock(markerRecordLock_); | ||||
|    auto markerJson = boost::json::value_from(markerRecords_); | ||||
|    util::json::WriteJsonFile(markerSettingsPath_, markerJson); | ||||
| } | ||||
|  | @ -198,6 +237,20 @@ MarkerManager::Impl::GetMarkerByName(const std::string& name) | |||
| 
 | ||||
| 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(); | ||||
| 
 | ||||
|    boost::asio::post(p->threadPool_, | ||||
|  | @ -207,8 +260,18 @@ MarkerManager::MarkerManager() : p(std::make_unique<Impl>(this)) | |||
|                         { | ||||
|                            // Read Marker settings on startup
 | ||||
|                            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(); | ||||
| 
 | ||||
|                            Q_EMIT IconsReady(); | ||||
|                            Q_EMIT MarkersInitialized(p->markerRecords_.size()); | ||||
|                         } | ||||
|                         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::shared_lock lock(p->markerRecordLock_); | ||||
|    const std::shared_lock lock(p->markerRecordLock_); | ||||
|    if (!p->idToIndex_.contains(id)) | ||||
|    { | ||||
|       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::shared_lock lock(p->markerRecordLock_); | ||||
|    const std::shared_lock lock(p->markerRecordLock_); | ||||
|    if (!p->idToIndex_.contains(id)) | ||||
|    { | ||||
|       return {}; | ||||
|  | @ -256,10 +319,11 @@ std::optional<size_t> MarkerManager::get_index(types::MarkerId 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)) | ||||
|       { | ||||
|          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!"); | ||||
|          return; | ||||
|       } | ||||
|       std::shared_ptr<MarkerManager::Impl::MarkerRecord>& markerRecord = | ||||
|       const std::shared_ptr<MarkerManager::Impl::MarkerRecord>& markerRecord = | ||||
|          p->markerRecords_[index]; | ||||
|       markerRecord->markerInfo_    = marker; | ||||
|       markerRecord->markerInfo_.id = id; | ||||
| 
 | ||||
|       add_icon(marker.iconName); | ||||
|    } | ||||
|    Q_EMIT MarkerChanged(id); | ||||
|    Q_EMIT MarkersUpdated(); | ||||
| } | ||||
| 
 | ||||
| void MarkerManager::add_marker(const types::MarkerInfo& marker) | ||||
| types::MarkerId MarkerManager::add_marker(const types::MarkerInfo& marker) | ||||
| { | ||||
|    types::MarkerId id; | ||||
|    { | ||||
|       std::unique_lock lock(p->markerRecordLock_); | ||||
|       const std::unique_lock lock(p->markerRecordLock_); | ||||
|       id = p->NewId(); | ||||
|       size_t index = p->markerRecords_.size(); | ||||
|       p->idToIndex_.emplace(id, index); | ||||
|       p->markerRecords_.emplace_back(std::make_shared<Impl::MarkerRecord>(marker)); | ||||
|       p->markerRecords_[index]->markerInfo_.id = id; | ||||
| 
 | ||||
|       add_icon(marker.iconName); | ||||
|    } | ||||
|    Q_EMIT MarkerAdded(id); | ||||
|    Q_EMIT MarkersUpdated(); | ||||
|    return 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)) | ||||
|       { | ||||
|          return; | ||||
|  | @ -327,7 +397,7 @@ void MarkerManager::remove_marker(types::MarkerId id) | |||
| 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()) | ||||
|       { | ||||
|          return; | ||||
|  | @ -358,13 +428,64 @@ void MarkerManager::move_marker(size_t from, size_t to) | |||
| 
 | ||||
| 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_) | ||||
|    { | ||||
|       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
 | ||||
| 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::mutex                   instanceMutex_ {}; | ||||
| 
 | ||||
|    std::unique_lock lock(instanceMutex_); | ||||
|    const std::unique_lock lock(instanceMutex_); | ||||
| 
 | ||||
|    std::shared_ptr<MarkerManager> markerManager = | ||||
|       markerManagerReference_.lock(); | ||||
|  | @ -391,6 +512,11 @@ std::shared_ptr<MarkerManager> MarkerManager::Instance() | |||
|    return markerManager; | ||||
| } | ||||
| 
 | ||||
| const std::string& MarkerManager::getDefaultIconName() | ||||
| { | ||||
|    return defaultIconName; | ||||
| } | ||||
| 
 | ||||
| } // namespace manager
 | ||||
| } // namespace qt
 | ||||
| } // namespace scwx
 | ||||
|  |  | |||
|  | @ -25,16 +25,21 @@ public: | |||
|    std::optional<types::MarkerInfo> get_marker(types::MarkerId id); | ||||
|    std::optional<size_t>            get_index(types::MarkerId id); | ||||
|    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            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); | ||||
| 
 | ||||
|    // Only use for testing
 | ||||
|    void set_marker_settings_path(const std::string& path); | ||||
| 
 | ||||
|    static std::shared_ptr<MarkerManager> Instance(); | ||||
|    static const std::string&             getDefaultIconName(); | ||||
| 
 | ||||
| signals: | ||||
|    void MarkersInitialized(size_t count); | ||||
|  | @ -43,6 +48,9 @@ signals: | |||
|    void MarkerAdded(types::MarkerId id); | ||||
|    void MarkerRemoved(types::MarkerId id); | ||||
| 
 | ||||
|    void IconsReady(); | ||||
|    void IconAdded(std::string name); | ||||
| 
 | ||||
| private: | ||||
|    class Impl; | ||||
|    std::unique_ptr<Impl> p; | ||||
|  |  | |||
|  | @ -22,6 +22,9 @@ namespace ResourceManager | |||
| static const std::string logPrefix_ = "scwx::qt::manager::resource_manager"; | ||||
| 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 LoadTextures(); | ||||
| 
 | ||||
|  | @ -68,8 +71,7 @@ LoadImageResources(const std::vector<std::string>& urlStrings) | |||
| 
 | ||||
|    if (!images.empty()) | ||||
|    { | ||||
|       util::TextureAtlas& textureAtlas = util::TextureAtlas::Instance(); | ||||
|       textureAtlas.BuildAtlas(2048, 2048); | ||||
|       BuildAtlas(); | ||||
|    } | ||||
| 
 | ||||
|    return images; | ||||
|  | @ -103,7 +105,13 @@ static void LoadTextures() | |||
|                                    GetTexturePath(lineTexture)); | ||||
|    } | ||||
| 
 | ||||
|    textureAtlas.BuildAtlas(2048, 2048); | ||||
|    BuildAtlas(); | ||||
| } | ||||
| 
 | ||||
| void BuildAtlas() | ||||
| { | ||||
|    util::TextureAtlas& textureAtlas = util::TextureAtlas::Instance(); | ||||
|    textureAtlas.BuildAtlas(atlasWidth, atlasHeight); | ||||
| } | ||||
| 
 | ||||
| } // namespace ResourceManager
 | ||||
|  |  | |||
|  | @ -22,6 +22,7 @@ std::shared_ptr<boost::gil::rgba8_image_t> | |||
| LoadImageResource(const std::string& urlString); | ||||
| std::vector<std::shared_ptr<boost::gil::rgba8_image_t>> | ||||
| LoadImageResources(const std::vector<std::string>& urlStrings); | ||||
| void BuildAtlas(); | ||||
| 
 | ||||
| } // namespace ResourceManager
 | ||||
| } // namespace manager
 | ||||
|  |  | |||
|  | @ -9,10 +9,10 @@ | |||
| #include <scwx/qt/map/layer_wrapper.hpp> | ||||
| #include <scwx/qt/map/map_provider.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_product_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_range_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/map_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/maplibre.hpp> | ||||
| #include <scwx/qt/util/tooltip.hpp> | ||||
|  | @ -127,8 +128,6 @@ public: | |||
|       ImGui_ImplQt_Init(); | ||||
| 
 | ||||
|       InitializeCustomStyles(); | ||||
| 
 | ||||
|       ConnectSignals(); | ||||
|    } | ||||
| 
 | ||||
|    ~MapWidgetImpl() | ||||
|  | @ -219,6 +218,8 @@ public: | |||
|    std::shared_ptr<model::LayerModel> layerModel_ { | ||||
|       model::LayerModel::Instance()}; | ||||
| 
 | ||||
|    ui::EditMarkerDialog* editMarkerDialog_ {nullptr}; | ||||
| 
 | ||||
|    std::shared_ptr<manager::HotkeyManager> hotkeyManager_ { | ||||
|       manager::HotkeyManager::Instance()}; | ||||
|    std::shared_ptr<manager::PlacefileManager> placefileManager_ { | ||||
|  | @ -283,6 +284,12 @@ MapWidget::MapWidget(std::size_t id, const QMapLibre::Settings& settings) : | |||
|    setFocusPolicy(Qt::StrongFocus); | ||||
| 
 | ||||
|    ImGui_ImplQt_RegisterWidget(this); | ||||
| 
 | ||||
|    // Qt parent deals with memory management
 | ||||
|    // NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
 | ||||
|    p->editMarkerDialog_ = new ui::EditMarkerDialog(this); | ||||
| 
 | ||||
|    p->ConnectSignals(); | ||||
| } | ||||
| 
 | ||||
| MapWidget::~MapWidget() | ||||
|  | @ -429,6 +436,16 @@ void MapWidgetImpl::HandleHotkeyPressed(types::Hotkey hotkey, bool isAutoRepeat) | |||
| 
 | ||||
|    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: | ||||
|       if (context_->settings().isActive_) | ||||
|       { | ||||
|  |  | |||
|  | @ -1,9 +1,14 @@ | |||
| #include <scwx/qt/map/marker_layer.hpp> | ||||
| #include <scwx/qt/manager/marker_manager.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/types/marker_types.hpp> | ||||
| #include <scwx/qt/ui/edit_marker_dialog.hpp> | ||||
| 
 | ||||
| #include <QGeoPositionInfo> | ||||
| #include <QMouseEvent> | ||||
| 
 | ||||
| #include <string> | ||||
| 
 | ||||
| namespace scwx | ||||
| { | ||||
|  | @ -19,48 +24,104 @@ class MarkerLayer::Impl | |||
| { | ||||
| public: | ||||
|    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(); | ||||
|    } | ||||
|    ~Impl() {} | ||||
|    ~Impl() = default; | ||||
| 
 | ||||
|    void ReloadMarkers(); | ||||
|    void ConnectSignals(); | ||||
| 
 | ||||
|    std::shared_ptr<manager::MarkerManager> markerManager_ { | ||||
|       manager::MarkerManager::Instance()}; | ||||
| 
 | ||||
|    void set_icon_sheets(); | ||||
| 
 | ||||
|    MarkerLayer* self_; | ||||
|    const std::string& markerIconName_ { | ||||
|       types::GetTextureName(types::ImageTexture::LocationMarker)}; | ||||
| 
 | ||||
|    std::shared_ptr<gl::draw::GeoIcons> geoIcons_; | ||||
|    std::shared_ptr<ui::EditMarkerDialog> editMarkerDialog_; | ||||
| }; | ||||
| 
 | ||||
| void MarkerLayer::Impl::ConnectSignals() | ||||
| { | ||||
|    auto markerManager = manager::MarkerManager::Instance(); | ||||
| 
 | ||||
|    QObject::connect(markerManager.get(), | ||||
|    QObject::connect(markerManager_.get(), | ||||
|                     &manager::MarkerManager::MarkersUpdated, | ||||
|                     self_, | ||||
|          [this]() | ||||
|          { | ||||
|             this->ReloadMarkers(); | ||||
|          }); | ||||
|                     [this]() { ReloadMarkers(); }); | ||||
|    QObject::connect(markerManager_.get(), | ||||
|                     &manager::MarkerManager::IconsReady, | ||||
|                     self_, | ||||
|                     [this]() { set_icon_sheets(); }); | ||||
|    QObject::connect(markerManager_.get(), | ||||
|                     &manager::MarkerManager::IconAdded, | ||||
|                     self_, | ||||
|                     [this]() { set_icon_sheets(); }); | ||||
| } | ||||
| 
 | ||||
| void MarkerLayer::Impl::ReloadMarkers() | ||||
| { | ||||
|    logger_->debug("ReloadMarkers()"); | ||||
|    auto markerManager = manager::MarkerManager::Instance(); | ||||
| 
 | ||||
|    geoIcons_->StartIcons(); | ||||
| 
 | ||||
|    markerManager->for_each( | ||||
|    markerManager_->for_each( | ||||
|       [this](const types::MarkerInfo& marker) | ||||
|       { | ||||
|          std::shared_ptr<gl::draw::GeoIconDrawItem> icon = geoIcons_->AddIcon(); | ||||
|          geoIcons_->SetIconTexture(icon, markerIconName_, 0); | ||||
|          // must use local ID, instead of reference to marker in event handler
 | ||||
|          // 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_->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(); | ||||
|  | @ -80,17 +141,28 @@ void MarkerLayer::Initialize() | |||
|    logger_->debug("Initialize()"); | ||||
|    DrawLayer::Initialize(); | ||||
| 
 | ||||
|    p->geoIcons_->StartIconSheets(); | ||||
|    p->geoIcons_->AddIconSheet(p->markerIconName_); | ||||
|    p->geoIcons_->FinishIconSheets(); | ||||
| 
 | ||||
|    p->set_icon_sheets(); | ||||
|    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) | ||||
| { | ||||
|    // auto markerManager = manager::MarkerManager::Instance();
 | ||||
|    gl::OpenGLFunctions& gl = context()->gl(); | ||||
|    context()->set_render_parameters(params); | ||||
| 
 | ||||
|    DrawLayer::Render(params); | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ | |||
| #include <scwx/qt/manager/marker_manager.hpp> | ||||
| #include <scwx/qt/types/marker_types.hpp> | ||||
| #include <scwx/qt/types/qt_types.hpp> | ||||
| #include <scwx/qt/util/q_color_modulate.hpp> | ||||
| #include <scwx/util/logger.hpp> | ||||
| 
 | ||||
| #include <vector> | ||||
|  | @ -18,6 +19,7 @@ namespace model | |||
| 
 | ||||
| static const std::string logPrefix_ = "scwx::qt::model::marker_model"; | ||||
| static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | ||||
| static const int         iconSize_  = 30; | ||||
| 
 | ||||
| static constexpr int kFirstColumn = | ||||
|    static_cast<int>(MarkerModel::Column::Latitude); | ||||
|  | @ -38,7 +40,6 @@ public: | |||
| MarkerModel::MarkerModel(QObject* parent) : | ||||
|    QAbstractTableModel(parent), p(std::make_unique<Impl>()) | ||||
| { | ||||
| 
 | ||||
|    connect(p->markerManager_.get(), | ||||
|          &manager::MarkerManager::MarkersInitialized, | ||||
|          this, | ||||
|  | @ -78,26 +79,11 @@ Qt::ItemFlags MarkerModel::flags(const QModelIndex& index) const | |||
| { | ||||
|    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; | ||||
| } | ||||
| 
 | ||||
| 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 || | ||||
|        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): | ||||
|       if (role == Qt::ItemDataRole::DisplayRole || | ||||
|           role == Qt::ItemDataRole::ToolTipRole || | ||||
|           role == Qt::ItemDataRole::EditRole) | ||||
|           role == Qt::ItemDataRole::ToolTipRole) | ||||
|       { | ||||
|          return QString::fromStdString(markerInfo->name); | ||||
|       } | ||||
|  | @ -132,11 +117,6 @@ QVariant MarkerModel::data(const QModelIndex& index, int role) const | |||
|          return QString::fromStdString( | ||||
|             common::GetLatitudeString(markerInfo->latitude)); | ||||
|       } | ||||
|       else if (role == Qt::ItemDataRole::EditRole) | ||||
|       { | ||||
|          return QString::number( | ||||
|             markerInfo->latitude, COORDINATE_FORMAT, COORDINATE_PRECISION); | ||||
|       } | ||||
|       break; | ||||
| 
 | ||||
|    case static_cast<int>(Column::Longitude): | ||||
|  | @ -146,13 +126,41 @@ QVariant MarkerModel::data(const QModelIndex& index, int role) const | |||
|          return QString::fromStdString( | ||||
|             common::GetLongitudeString(markerInfo->longitude)); | ||||
|       } | ||||
|       else if (role == Qt::ItemDataRole::EditRole) | ||||
|       { | ||||
|          return QString::number( | ||||
|             markerInfo->longitude, COORDINATE_FORMAT, COORDINATE_PRECISION); | ||||
|       } | ||||
|       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: | ||||
|       break; | ||||
|  | @ -190,6 +198,9 @@ QVariant MarkerModel::headerData(int             section, | |||
|             case static_cast<int>(Column::Longitude): | ||||
|                return tr("Longitude"); | ||||
| 
 | ||||
|             case static_cast<int>(Column::Icon): | ||||
|                return tr("Icon"); | ||||
| 
 | ||||
|             default: | ||||
|                break; | ||||
|          } | ||||
|  | @ -199,78 +210,9 @@ QVariant MarkerModel::headerData(int             section, | |||
|    return QVariant(); | ||||
| } | ||||
| 
 | ||||
| bool MarkerModel::setData(const QModelIndex& index, | ||||
|                           const QVariant&    value, | ||||
|                           int                role) | ||||
| bool MarkerModel::setData(const QModelIndex&, const QVariant&, int) | ||||
| { | ||||
| 
 | ||||
|    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) | ||||
|  |  | |||
|  | @ -17,7 +17,8 @@ public: | |||
|    { | ||||
|       Latitude  = 0, | ||||
|       Longitude = 1, | ||||
|       Name      = 2, | ||||
|       Icon      = 2, | ||||
|       Name      = 3, | ||||
|    }; | ||||
| 
 | ||||
|    explicit MarkerModel(QObject* parent = nullptr); | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ namespace settings | |||
| static const std::string logPrefix_ = "scwx::qt::settings::hotkey_settings"; | ||||
| 
 | ||||
| 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::CopyCursorCoordinates, | ||||
|     QKeySequence {QKeyCombination {Qt::KeyboardModifier::ControlModifier, | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ namespace types | |||
| { | ||||
| 
 | ||||
| static const std::unordered_map<Hotkey, std::string> hotkeyShortName_ { | ||||
|    {Hotkey::AddLocationMarker, "add_location_marker"}, | ||||
|    {Hotkey::ChangeMapStyle, "change_map_style"}, | ||||
|    {Hotkey::CopyCursorCoordinates, "copy_cursor_coordinates"}, | ||||
|    {Hotkey::CopyMapCoordinates, "copy_map_coordinates"}, | ||||
|  | @ -52,6 +53,7 @@ static const std::unordered_map<Hotkey, std::string> hotkeyShortName_ { | |||
|    {Hotkey::Unknown, "?"}}; | ||||
| 
 | ||||
| static const std::unordered_map<Hotkey, std::string> hotkeyLongName_ { | ||||
|    {Hotkey::AddLocationMarker, "Add Location Marker"}, | ||||
|    {Hotkey::ChangeMapStyle, "Change Map Style"}, | ||||
|    {Hotkey::CopyCursorCoordinates, "Copy Cursor Coordinates"}, | ||||
|    {Hotkey::CopyMapCoordinates, "Copy Map Coordinates"}, | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ namespace types | |||
| 
 | ||||
| enum class Hotkey | ||||
| { | ||||
|    AddLocationMarker, | ||||
|    ChangeMapStyle, | ||||
|    CopyCursorCoordinates, | ||||
|    CopyMapCoordinates, | ||||
|  | @ -52,7 +53,7 @@ enum class Hotkey | |||
|    Unknown | ||||
| }; | ||||
| typedef scwx::util:: | ||||
|    Iterator<Hotkey, Hotkey::ChangeMapStyle, Hotkey::TimelineStepEnd> | ||||
|    Iterator<Hotkey, Hotkey::AddLocationMarker, Hotkey::TimelineStepEnd> | ||||
|       HotkeyIterator; | ||||
| 
 | ||||
| Hotkey             GetHotkeyFromShortName(const std::string& name); | ||||
|  |  | |||
|  | @ -1,29 +1,82 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <string> | ||||
| #include <cstdint> | ||||
| #include <scwx/qt/types/texture_types.hpp> | ||||
| 
 | ||||
| namespace scwx | ||||
| #include <cstdint> | ||||
| #include <string> | ||||
| #include <utility> | ||||
| 
 | ||||
| #include <boost/gil.hpp> | ||||
| #include <QFileInfo> | ||||
| #include <QIcon> | ||||
| 
 | ||||
| namespace scwx::qt::types | ||||
| { | ||||
| namespace qt | ||||
| { | ||||
| namespace types | ||||
| { | ||||
| typedef std::uint64_t MarkerId; | ||||
| using MarkerId = std::uint64_t; | ||||
| 
 | ||||
| struct MarkerInfo | ||||
| { | ||||
|    MarkerInfo(const std::string& name, double latitude, double longitude) : | ||||
|        name {name}, latitude {latitude}, longitude {longitude} | ||||
|    MarkerInfo(std::string                      name, | ||||
|               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; | ||||
|    double                    latitude; | ||||
|    double                    longitude; | ||||
|    std::string               iconName; | ||||
|    boost::gil::rgba8_pixel_t iconColor; | ||||
| }; | ||||
| 
 | ||||
| } // namespace types
 | ||||
| } // namespace qt
 | ||||
| } // namespace scwx
 | ||||
| struct MarkerIconInfo | ||||
| { | ||||
|    // 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, | ||||
|     {"images/cursor-17", ":/res/textures/images/cursor-17.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, | ||||
|     {"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, | ||||
|     {"images/mapbox-logo", ":/res/textures/images/mapbox-logo.svg"}}, | ||||
|    {ImageTexture::MapTilerLogo, | ||||
|  |  | |||
|  | @ -18,7 +18,16 @@ enum class ImageTexture | |||
|    Crosshairs24, | ||||
|    Cursor17, | ||||
|    Dot3, | ||||
|    LocationBriefcase, | ||||
|    LocationBuildingColumns, | ||||
|    LocationBuilding, | ||||
|    LocationCaravan, | ||||
|    LocationCrosshair, | ||||
|    LocationHouse, | ||||
|    LocationMarker, | ||||
|    LocationPin, | ||||
|    LocationStar, | ||||
|    LocationTent, | ||||
|    MapboxLogo, | ||||
|    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/model/marker_model.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 <QSortFilterProxyModel> | ||||
|  | @ -24,16 +24,23 @@ class MarkerSettingsWidgetImpl | |||
| public: | ||||
|    explicit MarkerSettingsWidgetImpl(MarkerSettingsWidget* 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(); | ||||
| 
 | ||||
|    MarkerSettingsWidget*                   self_; | ||||
|    model::MarkerModel*                     markerModel_; | ||||
|    QSortFilterProxyModel*                  proxyModel_; | ||||
|    std::shared_ptr<manager::MarkerManager> markerManager_ { | ||||
|       manager::MarkerManager::Instance()}; | ||||
|    std::shared_ptr<ui::EditMarkerDialog> editMarkerDialog_ {nullptr}; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  | @ -45,8 +52,9 @@ MarkerSettingsWidget::MarkerSettingsWidget(QWidget* parent) : | |||
|    ui->setupUi(this); | ||||
| 
 | ||||
|    ui->removeButton->setEnabled(false); | ||||
|    ui->markerView->setModel(p->proxyModel_); | ||||
| 
 | ||||
|    ui->markerView->setModel(p->markerModel_); | ||||
|    p->editMarkerDialog_ = std::make_shared<ui::EditMarkerDialog>(this); | ||||
| 
 | ||||
|    p->ConnectSignals(); | ||||
| } | ||||
|  | @ -63,7 +71,8 @@ void MarkerSettingsWidgetImpl::ConnectSignals() | |||
|                     self_, | ||||
|                     [this]() | ||||
|                     { | ||||
|                        markerManager_->add_marker(types::MarkerInfo("", 0, 0)); | ||||
|                        editMarkerDialog_->setup(); | ||||
|                        editMarkerDialog_->show(); | ||||
|                     }); | ||||
|    QObject::connect( | ||||
|       self_->ui->removeButton, | ||||
|  | @ -99,9 +108,30 @@ void MarkerSettingsWidgetImpl::ConnectSignals() | |||
|             return; | ||||
|          } | ||||
| 
 | ||||
|          bool itemSelected = selected.size() > 0; | ||||
|          const bool itemSelected = selected.size() > 0; | ||||
|          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
 | ||||
|  |  | |||
							
								
								
									
										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/manager/marker_manager.hpp> | ||||
| #include <scwx/qt/main/application.hpp> | ||||
| #include <scwx/qt/util/color.hpp> | ||||
| 
 | ||||
| #include <filesystem> | ||||
| #include <fstream> | ||||
|  | @ -9,7 +10,6 @@ | |||
| #include <condition_variable> | ||||
| #include <gtest/gtest.h> | ||||
| 
 | ||||
| 
 | ||||
| namespace scwx | ||||
| { | ||||
| namespace qt | ||||
|  | @ -25,11 +25,17 @@ static const std::string ONE_MARKERS_FILE = | |||
|    std::string(SCWX_TEST_DATA_DIR) + "/json/markers/markers-one.json"; | ||||
| static const std::string FIVE_MARKERS_FILE = | ||||
|    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::condition_variable initializedCond {}; | ||||
| 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) | ||||
| { | ||||
|    std::ifstream     ifs1 {file1}; | ||||
|  | @ -49,8 +55,8 @@ void CopyFile(const std::string& from, const std::string& to) | |||
|    CompareFiles(from, to); | ||||
| } | ||||
| 
 | ||||
| typedef void TestFunction(std::shared_ptr<manager::MarkerManager> manager, | ||||
|                           MarkerModel&                            model); | ||||
| using TestFunction = void(std::shared_ptr<manager::MarkerManager>, | ||||
|                           MarkerModel&); | ||||
| 
 | ||||
| void RunTest(const std::string& filename, TestFunction testFunction) | ||||
| { | ||||
|  | @ -65,7 +71,7 @@ void RunTest(const std::string& filename, TestFunction testFunction) | |||
|       initialized = false; | ||||
|       QObject::connect(manager.get(), | ||||
|                        &manager::MarkerManager::MarkersInitialized, | ||||
|                        [](size_t count) | ||||
|                        []() | ||||
|                        { | ||||
|                           std::unique_lock lock(initializedMutex); | ||||
|                           initialized = true; | ||||
|  | @ -119,7 +125,10 @@ TEST(MarkerModelTest, AddRemove) | |||
| 
 | ||||
|    RunTest(ONE_MARKERS_FILE, | ||||
|            [](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( | ||||
|       EMPTY_MARKERS_FILE, | ||||
|       [](std::shared_ptr<manager::MarkerManager> manager, MarkerModel& model) | ||||
|  | @ -143,11 +152,16 @@ TEST(MarkerModelTest, AddFive) | |||
|    RunTest(FIVE_MARKERS_FILE, | ||||
|            [](std::shared_ptr<manager::MarkerManager> manager, MarkerModel&) | ||||
|            { | ||||
|               manager->add_marker(types::MarkerInfo("Null", 0, 0)); | ||||
|               manager->add_marker(types::MarkerInfo("North", 90, 0)); | ||||
|               manager->add_marker(types::MarkerInfo("South", -90, 0)); | ||||
|               manager->add_marker(types::MarkerInfo("East", 0, 90)); | ||||
|               manager->add_marker(types::MarkerInfo("West", 0, -90)); | ||||
|               manager->add_marker(types::MarkerInfo( | ||||
|                  "Null", 0, 0, defaultIconName, defaultIconColor)); | ||||
|               manager->add_marker(types::MarkerInfo( | ||||
|                  "North", 90, 0, defaultIconName, defaultIconColor)); | ||||
|               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); | ||||
|  | @ -161,10 +175,14 @@ TEST(MarkerModelTest, AddFour) | |||
|    RunTest(FIVE_MARKERS_FILE, | ||||
|            [](std::shared_ptr<manager::MarkerManager> manager, MarkerModel&) | ||||
|            { | ||||
|               manager->add_marker(types::MarkerInfo("North", 90, 0)); | ||||
|               manager->add_marker(types::MarkerInfo("South", -90, 0)); | ||||
|               manager->add_marker(types::MarkerInfo("East", 0, 90)); | ||||
|               manager->add_marker(types::MarkerInfo("West", 0, -90)); | ||||
|               manager->add_marker(types::MarkerInfo( | ||||
|                  "North", 90, 0, defaultIconName, defaultIconColor)); | ||||
|               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); | ||||
|  | @ -235,6 +253,17 @@ TEST(MarkerModelTest, RemoveFour) | |||
|    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 qt
 | ||||
| } // namespace scwx
 | ||||
|  |  | |||
 Dan Paulat
						Dan Paulat