Merge pull request #321 from AdenKoperczak/location_markers_part2

Location Markers Part 2
This commit is contained in:
Dan Paulat 2025-01-12 01:05:19 -06:00 committed by GitHub
commit 60b4833eb2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 1234 additions and 218 deletions

View file

@ -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'

View 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

View 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="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

View 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

View 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

View 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

View 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="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

View 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

View 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

View 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

View file

@ -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

Before After
Before After

View file

@ -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

View file

@ -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>

View 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;
util::tooltip::Show(it->di_->hoverText_, mouseGlobalPos);
if (!it->di_->hoverText_.empty())
{
// Show tooltip
util::tooltip::Show(it->di_->hoverText_, mouseGlobalPos);
}
if (it->di_->event_ != nullptr)
{
eventHandler = it->di_;
}
}
return itemPicked;
}
void GeoIcons::RegisterEventHandler(
const std::shared_ptr<GeoIconDrawItem>& di,
const std::function<void(QEvent*)>& eventHandler)
{
di->event_ = eventHandler;
}
} // namespace draw
} // namespace gl
} // namespace qt

View file

@ -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;

View file

@ -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
{
@ -36,15 +44,16 @@ public:
explicit Impl(MarkerManager* self) : self_ {self} {}
~Impl() { threadPool_.join(); }
std::string markerSettingsPath_ {""};
std::vector<std::shared_ptr<MarkerRecord>> markerRecords_ {};
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();
record.markerInfo_.id = id;
markerRecords_.emplace_back(
std::make_shared<MarkerRecord>(record.markerInfo_));
idToIndex_.emplace(id, index);
}
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_ = 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

View file

@ -23,11 +23,15 @@ public:
size_t marker_count();
std::optional<types::MarkerInfo> get_marker(types::MarkerId id);
std::optional<size_t> get_index(types::MarkerId id);
std::optional<size_t> get_index(types::MarkerId id);
void set_marker(types::MarkerId id, const types::MarkerInfo& marker);
void add_marker(const types::MarkerInfo& marker);
void remove_marker(types::MarkerId id);
void move_marker(size_t from, size_t to);
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);
@ -35,6 +39,7 @@ public:
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;

View file

@ -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

View file

@ -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

View file

@ -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_)
{

View file

@ -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(),
&manager::MarkerManager::MarkersUpdated,
self_,
[this]()
{
this->ReloadMarkers();
});
QObject::connect(markerManager_.get(),
&manager::MarkerManager::MarkersUpdated,
self_,
[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);

View file

@ -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;
return false;
}
void MarkerModel::HandleMarkersInitialized(size_t count)

View file

@ -17,7 +17,8 @@ public:
{
Latitude = 0,
Longitude = 1,
Name = 2,
Icon = 2,
Name = 3,
};
explicit MarkerModel(QObject* parent = nullptr);

View file

@ -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,

View file

@ -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"},

View file

@ -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);

View file

@ -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;
std::string name;
double latitude;
double longitude;
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

View file

@ -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,

View file

@ -18,7 +18,16 @@ enum class ImageTexture
Crosshairs24,
Cursor17,
Dot3,
LocationBriefcase,
LocationBuildingColumns,
LocationBuilding,
LocationCaravan,
LocationCrosshair,
LocationHouse,
LocationMarker,
LocationPin,
LocationStar,
LocationTent,
MapboxLogo,
MapTilerLogo
};

View 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

View 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

View 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>

View file

@ -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>
@ -23,17 +23,24 @@ class MarkerSettingsWidgetImpl
{
public:
explicit MarkerSettingsWidgetImpl(MarkerSettingsWidget* self) :
self_ {self},
markerModel_ {new model::MarkerModel(self_)}
self_ {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_;
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

View 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

View 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

View file

@ -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