From 88475f5b0e69231062de0182dec49812b07593c4 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 16 Jul 2023 01:41:29 -0500 Subject: [PATCH 001/199] Initial placefile parsing --- wxdata/include/scwx/gr/color.hpp | 21 ++ wxdata/include/scwx/gr/gr_types.hpp | 15 + wxdata/include/scwx/gr/placefile.hpp | 47 ++++ wxdata/source/scwx/gr/color.cpp | 86 ++++++ wxdata/source/scwx/gr/placefile.cpp | 391 +++++++++++++++++++++++++++ wxdata/wxdata.cmake | 9 + 6 files changed, 569 insertions(+) create mode 100644 wxdata/include/scwx/gr/color.hpp create mode 100644 wxdata/include/scwx/gr/gr_types.hpp create mode 100644 wxdata/include/scwx/gr/placefile.hpp create mode 100644 wxdata/source/scwx/gr/color.cpp create mode 100644 wxdata/source/scwx/gr/placefile.cpp diff --git a/wxdata/include/scwx/gr/color.hpp b/wxdata/include/scwx/gr/color.hpp new file mode 100644 index 00000000..787a7649 --- /dev/null +++ b/wxdata/include/scwx/gr/color.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include +#include + +#include + +namespace scwx +{ +namespace gr +{ + +boost::gil::rgba8_pixel_t ParseColor(const std::vector& tokenList, + std::size_t startIndex, + ColorMode colorMode, + bool hasAlpha = true); + +} // namespace gr +} // namespace scwx diff --git a/wxdata/include/scwx/gr/gr_types.hpp b/wxdata/include/scwx/gr/gr_types.hpp new file mode 100644 index 00000000..e90c1042 --- /dev/null +++ b/wxdata/include/scwx/gr/gr_types.hpp @@ -0,0 +1,15 @@ +#pragma once + +namespace scwx +{ +namespace gr +{ + +enum class ColorMode +{ + RGBA, + HSLuv +}; + +} // namespace gr +} // namespace scwx diff --git a/wxdata/include/scwx/gr/placefile.hpp b/wxdata/include/scwx/gr/placefile.hpp new file mode 100644 index 00000000..33b7fff5 --- /dev/null +++ b/wxdata/include/scwx/gr/placefile.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include +#include + +#include + +namespace scwx +{ +namespace gr +{ + +/** + * @brief Place File + * + * Implementation based on: + * Place File Specification + * Mike Gibson + * Gibson Ridge Software, LLC. Used with permission. + * http://www.grlevelx.com/manuals/gis/files_places.htm + */ +class Placefile +{ +public: + explicit Placefile(); + ~Placefile(); + + Placefile(const Placefile&) = delete; + Placefile& operator=(const Placefile&) = delete; + + Placefile(Placefile&&) noexcept; + Placefile& operator=(Placefile&&) noexcept; + + bool IsValid() const; + + static std::shared_ptr Load(const std::string& filename); + static std::shared_ptr Load(std::istream& is); + +private: + class Impl; + std::unique_ptr p; +}; + +} // namespace common +} // namespace scwx diff --git a/wxdata/source/scwx/gr/color.cpp b/wxdata/source/scwx/gr/color.cpp new file mode 100644 index 00000000..5f815e6d --- /dev/null +++ b/wxdata/source/scwx/gr/color.cpp @@ -0,0 +1,86 @@ +#include + +#include + +#include + +namespace scwx +{ +namespace gr +{ + +template +T RoundChannel(double value); +template +T StringToDecimal(const std::string& str); + +boost::gil::rgba8_pixel_t ParseColor(const std::vector& tokenList, + std::size_t startIndex, + ColorMode colorMode, + bool hasAlpha) +{ + + std::uint8_t r {}; + std::uint8_t g {}; + std::uint8_t b {}; + std::uint8_t a = 255; + + if (colorMode == ColorMode::RGBA) + { + if (tokenList.size() >= startIndex + 3) + { + r = StringToDecimal(tokenList[startIndex + 0]); + g = StringToDecimal(tokenList[startIndex + 1]); + b = StringToDecimal(tokenList[startIndex + 2]); + } + + if (hasAlpha && tokenList.size() >= startIndex + 4) + { + a = StringToDecimal(tokenList[startIndex + 3]); + } + } + else // if (colorMode == ColorMode::HSLuv) + { + double h {}; + double s {}; + double l {}; + + if (tokenList.size() >= startIndex + 3) + { + h = std::stod(tokenList[startIndex + 0]); + s = std::stod(tokenList[startIndex + 1]); + l = std::stod(tokenList[startIndex + 2]); + } + + double dr; + double dg; + double db; + + hsluv2rgb(h, s, l, &dr, &dg, &db); + + r = RoundChannel(dr * 255.0); + g = RoundChannel(dg * 255.0); + b = RoundChannel(db * 255.0); + } + + return boost::gil::rgba8_pixel_t {r, g, b, a}; +} + +template +T RoundChannel(double value) +{ + return static_cast(std::clamp(std::lround(value), + std::numeric_limits::min(), + std::numeric_limits::max())); +} + +template +T StringToDecimal(const std::string& str) +{ + return static_cast(std::clamp(std::stoi(str), + std::numeric_limits::min(), + std::numeric_limits::max())); +} + +} // namespace gr +} // namespace scwx diff --git a/wxdata/source/scwx/gr/placefile.cpp b/wxdata/source/scwx/gr/placefile.cpp new file mode 100644 index 00000000..3fbe1f08 --- /dev/null +++ b/wxdata/source/scwx/gr/placefile.cpp @@ -0,0 +1,391 @@ +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace scwx +{ +namespace gr +{ + +static const std::string logPrefix_ {"scwx::gr::placefile"}; +static const auto logger_ = util::Logger::Create(logPrefix_); + +enum class DrawingStatement +{ + Standard, + Line, + Triangles, + Image, + Polygon +}; + +class Placefile::Impl +{ +public: + explicit Impl() = default; + ~Impl() = default; + + struct Object + { + double x_ {}; + double y_ {}; + }; + + struct DrawItem + { + }; + + struct PlaceDrawItem : DrawItem + { + boost::units::quantity threshold_ {}; + boost::gil::rgba8_pixel_t color_ {}; + double latitude_ {}; + double longitude_ {}; + double x_ {}; + double y_ {}; + std::string text_ {}; + }; + + void ParseLocation(const std::string& latitudeToken, + const std::string& longitudeToken, + double& latitude, + double& longitude, + double& x, + double& y); + void ProcessLine(const std::string& line, + const std::vector& tokenList); + + std::chrono::seconds refresh_ {-1}; + + // Parsing state + boost::units::quantity threshold_ { + 999.0 * boost::units::metric::nautical_mile_base_unit::unit_type()}; + boost::gil::rgba8_pixel_t color_ {255, 255, 255, 255}; + ColorMode colorMode_ {ColorMode::RGBA}; + std::vector objectStack_ {}; + DrawingStatement currentStatement_ {DrawingStatement::Standard}; + + // References + std::unordered_map iconFiles_ {}; + std::unordered_map fonts_ {}; + + std::vector> drawItems_ {}; +}; + +Placefile::Placefile() : p(std::make_unique()) {} +Placefile::~Placefile() = default; + +Placefile::Placefile(Placefile&&) noexcept = default; +Placefile& Placefile::operator=(Placefile&&) noexcept = default; + +bool Placefile::IsValid() const +{ + return p->drawItems_.size() > 0; +} + +std::shared_ptr Placefile::Load(const std::string& filename) +{ + logger_->debug("Loading placefile: {}", filename); + std::ifstream f(filename, std::ios_base::in); + return Load(f); +} + +std::shared_ptr Placefile::Load(std::istream& is) +{ + std::shared_ptr placefile = std::make_shared(); + + std::string line; + while (scwx::util::getline(is, line)) + { + // Find position of comment (;) + std::size_t lineEnd = line.find(';'); + if (lineEnd == std::string::npos) + { + // Remove comment + line.erase(lineEnd); + } + + // Remove extra spacing from line + boost::trim(line); + + boost::char_separator delimiter(", "); + boost::tokenizer tokens(line, delimiter); + std::vector tokenList; + + for (auto& token : tokens) + { + tokenList.push_back(token); + } + + if (tokenList.size() >= 1) + { + try + { + switch (placefile->p->currentStatement_) + { + case DrawingStatement::Standard: + placefile->p->ProcessLine(line, tokenList); + break; + + case DrawingStatement::Line: + case DrawingStatement::Triangles: + case DrawingStatement::Image: + case DrawingStatement::Polygon: + if (boost::iequals(tokenList[0], "End:")) + { + placefile->p->currentStatement_ = DrawingStatement::Standard; + } + break; + } + } + catch (const std::exception&) + { + logger_->warn("Could not parse line: {}", line); + } + } + } + + return placefile; +} + +void Placefile::Impl::ProcessLine(const std::string& line, + const std::vector& tokenList) +{ + currentStatement_ = DrawingStatement::Standard; + + if (boost::iequals(tokenList[0], "Threshold:")) + { + // Threshold: nautical_miles + if (tokenList.size() >= 2) + { + threshold_ = + static_cast>( + std::stod(tokenList[1]) * + boost::units::metric::nautical_mile_base_unit::unit_type()); + } + } + else if (boost::iequals(tokenList[0], "HSLuv:")) + { + // HSLuv: value + if (tokenList.size() >= 2) + { + if (boost::iequals(tokenList[1], "true")) + { + colorMode_ = ColorMode::HSLuv; + } + else + { + colorMode_ = ColorMode::RGBA; + } + } + } + else if (boost::iequals(tokenList[0], "Color:")) + { + // Color: red green blue + if (tokenList.size() >= 2) + { + color_ = ParseColor(tokenList, 1, colorMode_); + } + } + else if (boost::iequals(tokenList[0], "Refresh:")) + { + // Refresh: minutes + if (tokenList.size() >= 2) + { + refresh_ = std::chrono::minutes {std::stoi(tokenList[1])}; + } + } + else if (boost::iequals(tokenList[0], "RefreshSeconds:")) + { + // RefreshSeconds: seconds + if (tokenList.size() >= 2) + { + refresh_ = std::chrono::seconds {std::stoi(tokenList[1])}; + } + } + else if (boost::iequals(tokenList[0], "Place:")) + { + // Place: latitude, longitude, string with spaces + std::regex re {"Place:\\s*([+\\-0-9\\.]+),\\s*([+\\-0-9\\.]+),\\s*(.+)"}; + std::smatch match; + std::regex_match(line, match, re); + + if (match.size() >= 4) + { + std::shared_ptr di = std::make_shared(); + + di->threshold_ = threshold_; + di->color_ = color_; + + ParseLocation(match[1].str(), + match[2].str(), + di->latitude_, + di->longitude_, + di->x_, + di->y_); + + di->text_ = match[3].str(); + + drawItems_.emplace_back(std::move(di)); + } + else + { + logger_->warn("Place statement malformed: {}", line); + } + } + else if (boost::iequals(tokenList[0], "IconFile:")) + { + // IconFile: fileNumber, iconWidth, iconHeight, hotX, hotY, fileName + + // TODO + } + else if (boost::iequals(tokenList[0], "Icon:")) + { + // Icon: lat, lon, angle, fileNumber, iconNumber, hoverText + + // TODO + } + else if (boost::iequals(tokenList[0], "Font:")) + { + // Font: fontNumber, pixels, flags, "face" + + // TODO + } + else if (boost::iequals(tokenList[0], "Text:")) + { + // Text: lat, lon, fontNumber, "string", "hover" + + // TODO + } + else if (boost::iequals(tokenList[0], "Object:")) + { + // Object: lat, lon + // ... + // End: + std::regex re {"Object:\\s*([+\\-0-9\\.]+),\\s*([+\\-0-9\\.]+)"}; + std::smatch match; + std::regex_match(line, match, re); + + double latitude {}; + double longitude {}; + + if (match.size() >= 3) + { + latitude = std::stod(match[1].str()); + longitude = std::stod(match[2].str()); + } + else + { + logger_->warn("Object statement malformed: {}", line); + } + + objectStack_.emplace_back(Object {latitude, longitude}); + } + else if (boost::iequals(tokenList[0], "End:")) + { + // Object End + if (!objectStack_.empty()) + { + objectStack_.pop_back(); + } + else + { + logger_->warn("End found without Object"); + } + } + else if (boost::iequals(tokenList[0], "Line:")) + { + // Line: width, flags [, hover_text] + // lat, lon + // ... + // End: + currentStatement_ = DrawingStatement::Line; + + // TODO + } + else if (boost::iequals(tokenList[0], "Triangles:")) + { + // Triangles: + // lat, lon [, r, g, b [,a]] + // ... + // End: + currentStatement_ = DrawingStatement::Triangles; + + // TODO + } + else if (boost::iequals(tokenList[0], "Image:")) + { + // Image: image_file + // lat, lon, Tu [, Tv ] + // ... + // End: + currentStatement_ = DrawingStatement::Image; + + // TODO + } + else if (boost::iequals(tokenList[0], "Polygon:")) + { + // Polygon: + // lat1, lon1 [, r, g, b [,a]] ; start of the first contour + // ... + // lat1, lon1 ; repeating the first point closes the + // ; contour + // + // lat2, lon2 ; next point starts a new contour + // ... + // lat2, lon2 ; and repeating it ends the contour + // End: + currentStatement_ = DrawingStatement::Polygon; + + // TODO + } +} + +void Placefile::Impl::ParseLocation(const std::string& latitudeToken, + const std::string& longitudeToken, + double& latitude, + double& longitude, + double& x, + double& y) +{ + if (objectStack_.empty()) + { + // If an Object statement is not currently open, parse latitude and + // longitude tokens as-is + latitude = std::stod(latitudeToken); + longitude = std::stod(longitudeToken); + } + else + { + // If an Object statement is open, the latitude and longitude are from the + // outermost Object + latitude = objectStack_[0].x_; + longitude = objectStack_[0].y_; + + // The latitude and longitude tokens are interpreted as x, y offsets + x = std::stod(latitudeToken); + y = std::stod(longitudeToken); + + // If there are inner Object statements open, treat these as x, y offsets + for (std::size_t i = 1; i < objectStack_.size(); i++) + { + x += objectStack_[i].x_; + y += objectStack_[i].y_; + } + } +} + +} // namespace gr +} // namespace scwx diff --git a/wxdata/wxdata.cmake b/wxdata/wxdata.cmake index b6c4fb1c..3f7ab8d9 100644 --- a/wxdata/wxdata.cmake +++ b/wxdata/wxdata.cmake @@ -45,6 +45,11 @@ set(SRC_COMMON source/scwx/common/characters.cpp source/scwx/common/products.cpp source/scwx/common/sites.cpp source/scwx/common/vcp.cpp) +set(HDR_GR include/scwx/gr/color.hpp + include/scwx/gr/gr_types.hpp + include/scwx/gr/placefile.hpp) +set(SRC_GR source/scwx/gr/color.cpp + source/scwx/gr/placefile.cpp) set(HDR_NETWORK include/scwx/network/dir_list.hpp) set(SRC_NETWORK source/scwx/network/dir_list.cpp) set(HDR_PROVIDER include/scwx/provider/aws_level2_data_provider.hpp @@ -195,6 +200,8 @@ add_library(wxdata OBJECT ${HDR_AWIPS} ${SRC_AWIPS} ${HDR_COMMON} ${SRC_COMMON} + ${HDR_GR} + ${SRC_GR} ${HDR_NETWORK} ${SRC_NETWORK} ${HDR_PROVIDER} @@ -213,6 +220,8 @@ source_group("Header Files\\awips" FILES ${HDR_AWIPS}) source_group("Source Files\\awips" FILES ${SRC_AWIPS}) source_group("Header Files\\common" FILES ${HDR_COMMON}) source_group("Source Files\\common" FILES ${SRC_COMMON}) +source_group("Header Files\\gr" FILES ${HDR_GR}) +source_group("Source Files\\gr" FILES ${SRC_GR}) source_group("Header Files\\network" FILES ${HDR_NETWORK}) source_group("Source Files\\network" FILES ${SRC_NETWORK}) source_group("Header Files\\provider" FILES ${HDR_PROVIDER}) From 6767c0c50a796d4a80f7b3bacd9bf82fbcfe4de7 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 16 Jul 2023 23:59:28 -0500 Subject: [PATCH 002/199] Create custom string tokenizer for use in placefile parsing - Avoids the use of regular expressions, and is expected to be more efficient with large placefiles --- test/source/scwx/util/strings.test.cpp | 62 ++++++++++++++++++++++++ test/test.cmake | 1 + wxdata/include/scwx/util/strings.hpp | 19 ++++++++ wxdata/source/scwx/util/strings.cpp | 66 ++++++++++++++++++++++++++ 4 files changed, 148 insertions(+) create mode 100644 test/source/scwx/util/strings.test.cpp diff --git a/test/source/scwx/util/strings.test.cpp b/test/source/scwx/util/strings.test.cpp new file mode 100644 index 00000000..e91c95d9 --- /dev/null +++ b/test/source/scwx/util/strings.test.cpp @@ -0,0 +1,62 @@ +#include + +#include + +namespace scwx +{ +namespace util +{ + +TEST(StringsTest, ParseTokensColor) +{ + static const std::string line {"Color: red green blue alpha discarded"}; + static const std::vector delimiters {":", " ", " ", " ", " "}; + + std::vector tokens = ParseTokens(line, delimiters); + + ASSERT_EQ(tokens.size(), 6); + EXPECT_EQ(tokens[0], "Color"); + EXPECT_EQ(tokens[1], "red"); + EXPECT_EQ(tokens[2], "green"); + EXPECT_EQ(tokens[3], "blue"); + EXPECT_EQ(tokens[4], "alpha"); + EXPECT_EQ(tokens[5], "discarded"); +} + +TEST(StringsTest, ParseTokensColorOffset) +{ + static const std::string line {"Color: red green blue alpha"}; + static const std::vector delimiters {" ", " ", " ", " "}; + static const std::size_t offset = std::string {"Color:"}.size(); + + std::vector tokens = ParseTokens(line, delimiters, offset); + + ASSERT_EQ(tokens.size(), 4); + EXPECT_EQ(tokens[0], "red"); + EXPECT_EQ(tokens[1], "green"); + EXPECT_EQ(tokens[2], "blue"); + EXPECT_EQ(tokens[3], "alpha"); +} + +TEST(StringsTest, ParseTokensText) +{ + static const std::string line { + "Text: lat, lon, fontNumber, \"string, string\", \"hover, hover\", " + "discarded"}; + static const std::vector delimiters { + ":", ",", ",", ",", ",", ","}; + + std::vector tokens = ParseTokens(line, delimiters); + + ASSERT_EQ(tokens.size(), 7); + EXPECT_EQ(tokens[0], "Text"); + EXPECT_EQ(tokens[1], "lat"); + EXPECT_EQ(tokens[2], "lon"); + EXPECT_EQ(tokens[3], "fontNumber"); + EXPECT_EQ(tokens[4], "\"string, string\""); + EXPECT_EQ(tokens[5], "\"hover, hover\""); + EXPECT_EQ(tokens[6], "discarded"); +} + +} // namespace util +} // namespace scwx diff --git a/test/test.cmake b/test/test.cmake index b0731ddc..16801990 100644 --- a/test/test.cmake +++ b/test/test.cmake @@ -31,6 +31,7 @@ set(SRC_QT_UTIL_TESTS source/scwx/qt/util/q_file_input_stream.test.cpp) set(SRC_UTIL_TESTS source/scwx/util/float.test.cpp source/scwx/util/rangebuf.test.cpp source/scwx/util/streams.test.cpp + source/scwx/util/strings.test.cpp source/scwx/util/vectorbuf.test.cpp) set(SRC_WSR88D_TESTS source/scwx/wsr88d/ar2v_file.test.cpp source/scwx/wsr88d/level3_file.test.cpp diff --git a/wxdata/include/scwx/util/strings.hpp b/wxdata/include/scwx/util/strings.hpp index 0a5cc43f..f3088c9d 100644 --- a/wxdata/include/scwx/util/strings.hpp +++ b/wxdata/include/scwx/util/strings.hpp @@ -8,6 +8,25 @@ namespace scwx namespace util { +/** + * @brief Parse a list of tokens from a string + * + * This function will take an input string, and apply the delimiters vector in + * order to tokenize the string. Each set of delimiters in the delimiters vector + * will be used once. A set of delimiters will be used to match any character, + * rather than a sequence of characters. Tokens are automatically trimmed of any + * whitespace. + * + * @param [in] s Input string to tokenize + * @param [in] delimiters A vector of delimiters to use for each token. + * @param [in] pos Search begin position. Default is 0. + * + * @return Tokenized string + */ +std::vector ParseTokens(const std::string& s, + std::vector delimiters, + std::size_t pos = 0); + std::string ToString(const std::vector& v); } // namespace util diff --git a/wxdata/source/scwx/util/strings.cpp b/wxdata/source/scwx/util/strings.cpp index 3a72c993..e6b46a48 100644 --- a/wxdata/source/scwx/util/strings.cpp +++ b/wxdata/source/scwx/util/strings.cpp @@ -1,10 +1,76 @@ #include +#include + namespace scwx { namespace util { +std::vector ParseTokens(const std::string& s, + std::vector delimiters, + std::size_t pos) +{ + std::vector tokens {}; + std::size_t findPos {}; + + // Iterate through each delimiter + for (std::size_t i = 0; i < delimiters.size() && pos != std::string::npos; + ++i) + { + // Skip leading spaces + while (pos < s.size() && std::isspace(s[pos])) + { + ++pos; + } + + if (pos < s.size() && s[pos] == '"') + { + // Do not search for a delimeter within a quoted string + findPos = s.find('"', pos + 1); + + // Increment search start to one after quotation mark + if (findPos != std::string::npos) + { + ++findPos; + } + } + else + { + // Search starting at the current position + findPos = pos; + } + + // Search for delimiter + std::size_t nextPos = s.find_first_of(delimiters[i], findPos); + + // If the delimiter was not found, stop processing tokens + if (nextPos == std::string::npos) + { + break; + } + + // Add the current substring as a token + auto& newToken = tokens.emplace_back(s.substr(pos, nextPos - pos)); + boost::trim(newToken); + + // Increment nextPos until the next non-space character + while (++nextPos < s.size() && std::isspace(s[nextPos])) {} + + // Store new position value + pos = nextPos; + } + + // Add the remainder of the string as a token + if (pos < s.size()) + { + auto& newToken = tokens.emplace_back(s.substr(pos)); + boost::trim(newToken); + } + + return tokens; +} + std::string ToString(const std::vector& v) { std::string value {}; From c99e24f2c19d800c70d29a9d7fd386a62d9477c5 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 17 Jul 2023 21:57:04 -0500 Subject: [PATCH 003/199] Ignore comment character if it appears in quotes --- wxdata/include/scwx/gr/placefile.hpp | 4 ++-- wxdata/source/scwx/gr/placefile.cpp | 17 +++++++++++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/wxdata/include/scwx/gr/placefile.hpp b/wxdata/include/scwx/gr/placefile.hpp index 33b7fff5..c7f1335a 100644 --- a/wxdata/include/scwx/gr/placefile.hpp +++ b/wxdata/include/scwx/gr/placefile.hpp @@ -33,7 +33,7 @@ public: Placefile(Placefile&&) noexcept; Placefile& operator=(Placefile&&) noexcept; - bool IsValid() const; + bool IsValid() const; static std::shared_ptr Load(const std::string& filename); static std::shared_ptr Load(std::istream& is); @@ -43,5 +43,5 @@ private: std::unique_ptr p; }; -} // namespace common +} // namespace gr } // namespace scwx diff --git a/wxdata/source/scwx/gr/placefile.cpp b/wxdata/source/scwx/gr/placefile.cpp index 3fbe1f08..4c4fdc7f 100644 --- a/wxdata/source/scwx/gr/placefile.cpp +++ b/wxdata/source/scwx/gr/placefile.cpp @@ -110,11 +110,20 @@ std::shared_ptr Placefile::Load(std::istream& is) while (scwx::util::getline(is, line)) { // Find position of comment (;) - std::size_t lineEnd = line.find(';'); - if (lineEnd == std::string::npos) + bool inQuotes = false; + for (std::size_t i = 0; i < line.size(); ++i) { - // Remove comment - line.erase(lineEnd); + if (!inQuotes && line[i] == ';') + { + // Remove comment + line.erase(i); + break; + } + else if (line[i] == '"') + { + // Toggle quote state + inQuotes = !inQuotes; + } } // Remove extra spacing from line From fed94e0b7fc3f77c8ea50a80098fa3ce04bc4791 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 17 Jul 2023 21:59:30 -0500 Subject: [PATCH 004/199] Update placefile parsing to use custom tokenizer instead of regex --- wxdata/source/scwx/gr/placefile.cpp | 155 ++++++++++++++++------------ 1 file changed, 90 insertions(+), 65 deletions(-) diff --git a/wxdata/source/scwx/gr/placefile.cpp b/wxdata/source/scwx/gr/placefile.cpp index 4c4fdc7f..e6fb1a6f 100644 --- a/wxdata/source/scwx/gr/placefile.cpp +++ b/wxdata/source/scwx/gr/placefile.cpp @@ -2,14 +2,13 @@ #include #include #include +#include #include -#include #include #include #include -#include #include #include #include @@ -45,17 +44,17 @@ public: struct DrawItem { + boost::units::quantity threshold_ {}; }; struct PlaceDrawItem : DrawItem { - boost::units::quantity threshold_ {}; - boost::gil::rgba8_pixel_t color_ {}; - double latitude_ {}; - double longitude_ {}; - double x_ {}; - double y_ {}; - std::string text_ {}; + boost::gil::rgba8_pixel_t color_ {}; + double latitude_ {}; + double longitude_ {}; + double x_ {}; + double y_ {}; + std::string text_ {}; }; void ParseLocation(const std::string& latitudeToken, @@ -64,8 +63,7 @@ public: double& longitude, double& x, double& y); - void ProcessLine(const std::string& line, - const std::vector& tokenList); + void ProcessLine(const std::string& line); std::chrono::seconds refresh_ {-1}; @@ -129,30 +127,21 @@ std::shared_ptr Placefile::Load(std::istream& is) // Remove extra spacing from line boost::trim(line); - boost::char_separator delimiter(", "); - boost::tokenizer tokens(line, delimiter); - std::vector tokenList; - - for (auto& token : tokens) - { - tokenList.push_back(token); - } - - if (tokenList.size() >= 1) + if (line.size() >= 1) { try { switch (placefile->p->currentStatement_) { case DrawingStatement::Standard: - placefile->p->ProcessLine(line, tokenList); + placefile->p->ProcessLine(line); break; case DrawingStatement::Line: case DrawingStatement::Triangles: case DrawingStatement::Image: case DrawingStatement::Polygon: - if (boost::iequals(tokenList[0], "End:")) + if (boost::istarts_with(line, "End:")) { placefile->p->currentStatement_ = DrawingStatement::Standard; } @@ -169,28 +158,53 @@ std::shared_ptr Placefile::Load(std::istream& is) return placefile; } -void Placefile::Impl::ProcessLine(const std::string& line, - const std::vector& tokenList) +void Placefile::Impl::ProcessLine(const std::string& line) { + static const std::string thresholdKey_ {"Threshold:"}; + static const std::string hsluvKey_ {"HSLuv:"}; + static const std::string colorKey_ {"Color:"}; + static const std::string refreshKey_ {"Refresh:"}; + static const std::string refreshSecondsKey_ {"RefreshSeconds:"}; + static const std::string placeKey_ {"Place:"}; + static const std::string iconFileKey_ {"IconFile:"}; + static const std::string iconKey_ {"Icon:"}; + static const std::string fontKey_ {"Font:"}; + static const std::string textKey_ {"Text:"}; + static const std::string objectKey_ {"Object:"}; + static const std::string endKey_ {"End:"}; + static const std::string lineKey_ {"Line:"}; + static const std::string trianglesKey_ {"Triangles:"}; + static const std::string imageKey_ {"Image:"}; + static const std::string polygonKey_ {"Polygon:"}; + currentStatement_ = DrawingStatement::Standard; - if (boost::iequals(tokenList[0], "Threshold:")) + // When tokenizing, add one additional delimiter to discard unexpected + // parameters (where appropriate) + + if (boost::istarts_with(line, thresholdKey_)) { // Threshold: nautical_miles - if (tokenList.size() >= 2) + std::vector tokenList = + util::ParseTokens(line, {" "}, thresholdKey_.size()); + + if (tokenList.size() >= 1) { threshold_ = static_cast>( - std::stod(tokenList[1]) * + std::stod(tokenList[0]) * boost::units::metric::nautical_mile_base_unit::unit_type()); } } - else if (boost::iequals(tokenList[0], "HSLuv:")) + else if (boost::istarts_with(line, hsluvKey_)) { // HSLuv: value - if (tokenList.size() >= 2) + std::vector tokenList = + util::ParseTokens(line, {" "}, hsluvKey_.size()); + + if (tokenList.size() >= 1) { - if (boost::iequals(tokenList[1], "true")) + if (boost::iequals(tokenList[0], "true")) { colorMode_ = ColorMode::HSLuv; } @@ -200,52 +214,60 @@ void Placefile::Impl::ProcessLine(const std::string& line, } } } - else if (boost::iequals(tokenList[0], "Color:")) + else if (boost::istarts_with(line, colorKey_)) { - // Color: red green blue - if (tokenList.size() >= 2) + // Color: red green blue [alpha] + std::vector tokenList = + util::ParseTokens(line, {" ", " ", " ", " "}, colorKey_.size()); + + if (tokenList.size() >= 3) { - color_ = ParseColor(tokenList, 1, colorMode_); + color_ = ParseColor(tokenList, 0, colorMode_); } } - else if (boost::iequals(tokenList[0], "Refresh:")) + else if (boost::istarts_with(line, refreshKey_)) { // Refresh: minutes - if (tokenList.size() >= 2) + std::vector tokenList = + util::ParseTokens(line, {" "}, refreshKey_.size()); + + if (tokenList.size() >= 1) { - refresh_ = std::chrono::minutes {std::stoi(tokenList[1])}; + refresh_ = std::chrono::minutes {std::stoi(tokenList[0])}; } } - else if (boost::iequals(tokenList[0], "RefreshSeconds:")) + else if (boost::istarts_with(line, refreshSecondsKey_)) { // RefreshSeconds: seconds - if (tokenList.size() >= 2) + std::vector tokenList = + util::ParseTokens(line, {" "}, refreshSecondsKey_.size()); + + if (tokenList.size() >= 1) { - refresh_ = std::chrono::seconds {std::stoi(tokenList[1])}; + refresh_ = std::chrono::seconds {std::stoi(tokenList[0])}; } } - else if (boost::iequals(tokenList[0], "Place:")) + else if (boost::istarts_with(line, placeKey_)) { // Place: latitude, longitude, string with spaces - std::regex re {"Place:\\s*([+\\-0-9\\.]+),\\s*([+\\-0-9\\.]+),\\s*(.+)"}; - std::smatch match; - std::regex_match(line, match, re); + std::vector tokenList = + util::ParseTokens(line, {",", ","}, placeKey_.size()); - if (match.size() >= 4) + if (tokenList.size() >= 3) { std::shared_ptr di = std::make_shared(); di->threshold_ = threshold_; di->color_ = color_; - ParseLocation(match[1].str(), - match[2].str(), + ParseLocation(tokenList[0], + tokenList[1], di->latitude_, di->longitude_, di->x_, di->y_); - di->text_ = match[3].str(); + di->text_.swap(tokenList[2]); drawItems_.emplace_back(std::move(di)); } @@ -254,46 +276,45 @@ void Placefile::Impl::ProcessLine(const std::string& line, logger_->warn("Place statement malformed: {}", line); } } - else if (boost::iequals(tokenList[0], "IconFile:")) + else if (boost::istarts_with(line, iconFileKey_)) { // IconFile: fileNumber, iconWidth, iconHeight, hotX, hotY, fileName // TODO } - else if (boost::iequals(tokenList[0], "Icon:")) + else if (boost::istarts_with(line, iconKey_)) { // Icon: lat, lon, angle, fileNumber, iconNumber, hoverText // TODO } - else if (boost::iequals(tokenList[0], "Font:")) + else if (boost::istarts_with(line, fontKey_)) { // Font: fontNumber, pixels, flags, "face" // TODO } - else if (boost::iequals(tokenList[0], "Text:")) + else if (boost::istarts_with(line, textKey_)) { // Text: lat, lon, fontNumber, "string", "hover" // TODO } - else if (boost::iequals(tokenList[0], "Object:")) + else if (boost::istarts_with(line, objectKey_)) { // Object: lat, lon // ... // End: - std::regex re {"Object:\\s*([+\\-0-9\\.]+),\\s*([+\\-0-9\\.]+)"}; - std::smatch match; - std::regex_match(line, match, re); + std::vector tokenList = + util::ParseTokens(line, {",", ","}, objectKey_.size()); double latitude {}; double longitude {}; - if (match.size() >= 3) + if (tokenList.size() >= 2) { - latitude = std::stod(match[1].str()); - longitude = std::stod(match[2].str()); + latitude = std::stod(tokenList[0]); + longitude = std::stod(tokenList[1]); } else { @@ -302,7 +323,7 @@ void Placefile::Impl::ProcessLine(const std::string& line, objectStack_.emplace_back(Object {latitude, longitude}); } - else if (boost::iequals(tokenList[0], "End:")) + else if (boost::istarts_with(line, endKey_)) { // Object End if (!objectStack_.empty()) @@ -314,7 +335,7 @@ void Placefile::Impl::ProcessLine(const std::string& line, logger_->warn("End found without Object"); } } - else if (boost::iequals(tokenList[0], "Line:")) + else if (boost::istarts_with(line, lineKey_)) { // Line: width, flags [, hover_text] // lat, lon @@ -324,7 +345,7 @@ void Placefile::Impl::ProcessLine(const std::string& line, // TODO } - else if (boost::iequals(tokenList[0], "Triangles:")) + else if (boost::istarts_with(line, trianglesKey_)) { // Triangles: // lat, lon [, r, g, b [,a]] @@ -334,7 +355,7 @@ void Placefile::Impl::ProcessLine(const std::string& line, // TODO } - else if (boost::iequals(tokenList[0], "Image:")) + else if (boost::istarts_with(line, imageKey_)) { // Image: image_file // lat, lon, Tu [, Tv ] @@ -344,7 +365,7 @@ void Placefile::Impl::ProcessLine(const std::string& line, // TODO } - else if (boost::iequals(tokenList[0], "Polygon:")) + else if (boost::istarts_with(line, polygonKey_)) { // Polygon: // lat1, lon1 [, r, g, b [,a]] ; start of the first contour @@ -360,6 +381,10 @@ void Placefile::Impl::ProcessLine(const std::string& line, // TODO } + else + { + logger_->warn("Unknown statement: {}", line); + } } void Placefile::Impl::ParseLocation(const std::string& latitudeToken, From 207cc1e3024a5495d3988c34ce8fc2d4e93970df Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 17 Jul 2023 22:03:14 -0500 Subject: [PATCH 005/199] Stub for placefile test --- test/data | 2 +- test/source/scwx/gr/placefile.test.cpp | 21 +++++++++++++++++++++ test/test.cmake | 3 +++ 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 test/source/scwx/gr/placefile.test.cpp diff --git a/test/data b/test/data index b2fa7d86..a87219c0 160000 --- a/test/data +++ b/test/data @@ -1 +1 @@ -Subproject commit b2fa7d866800902f4c092c567017c832bcdbe702 +Subproject commit a87219c010a26905fd893e68e17077144394b316 diff --git a/test/source/scwx/gr/placefile.test.cpp b/test/source/scwx/gr/placefile.test.cpp new file mode 100644 index 00000000..98115ba4 --- /dev/null +++ b/test/source/scwx/gr/placefile.test.cpp @@ -0,0 +1,21 @@ +#include + +#include + +namespace scwx +{ +namespace gr +{ + +TEST(PlacefileTest, OldExample) +{ + std::string filename(std::string(SCWX_TEST_DATA_DIR) + + "/gr/placefiles/placefile-old-example.txt"); + + std::shared_ptr ct = Placefile::Load(filename); + + EXPECT_EQ(true, true); +} + +} // namespace gr +} // namespace scwx diff --git a/test/test.cmake b/test/test.cmake index 16801990..b7b5fffa 100644 --- a/test/test.cmake +++ b/test/test.cmake @@ -15,6 +15,7 @@ set(SRC_AWIPS_TESTS source/scwx/awips/coded_location.test.cpp source/scwx/awips/ugc.test.cpp) set(SRC_COMMON_TESTS source/scwx/common/color_table.test.cpp source/scwx/common/products.test.cpp) +set(SRC_GR_TESTS source/scwx/gr/placefile.test.cpp) set(SRC_NETWORK_TESTS source/scwx/network/dir_list.test.cpp) set(SRC_PROVIDER_TESTS source/scwx/provider/aws_level2_data_provider.test.cpp source/scwx/provider/aws_level3_data_provider.test.cpp @@ -42,6 +43,7 @@ set(CMAKE_FILES test.cmake) add_executable(wxtest ${SRC_MAIN} ${SRC_AWIPS_TESTS} ${SRC_COMMON_TESTS} + ${SRC_GR_TESTS} ${SRC_NETWORK_TESTS} ${SRC_PROVIDER_TESTS} ${SRC_QT_CONFIG_TESTS} @@ -57,6 +59,7 @@ add_executable(wxtest ${SRC_MAIN} source_group("Source Files\\main" FILES ${SRC_MAIN}) source_group("Source Files\\awips" FILES ${SRC_AWIPS_TESTS}) source_group("Source Files\\common" FILES ${SRC_COMMON_TESTS}) +source_group("Source Files\\gr" FILES ${SRC_GR_TESTS}) source_group("Source Files\\network" FILES ${SRC_NETWORK_TESTS}) source_group("Source Files\\provider" FILES ${SRC_PROVIDER_TESTS}) source_group("Source Files\\qt\\config" FILES ${SRC_QT_CONFIG_TESTS}) From f70de26a2d05a6e8519805c69b038004bfd1251a Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 18 Jul 2023 23:16:50 -0500 Subject: [PATCH 006/199] Initial placefile manager implementation --- scwx-qt/scwx-qt.cmake | 6 +- .../scwx/qt/manager/placefile_manager.cpp | 135 ++++++++++++++++++ .../scwx/qt/manager/placefile_manager.hpp | 38 +++++ 3 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 scwx-qt/source/scwx/qt/manager/placefile_manager.cpp create mode 100644 scwx-qt/source/scwx/qt/manager/placefile_manager.hpp diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 225d2b19..67b41da6 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -60,14 +60,16 @@ set(HDR_GL_DRAW source/scwx/qt/gl/draw/draw_item.hpp set(SRC_GL_DRAW source/scwx/qt/gl/draw/draw_item.cpp source/scwx/qt/gl/draw/geo_line.cpp source/scwx/qt/gl/draw/rectangle.cpp) -set(HDR_MANAGER source/scwx/qt/manager/radar_product_manager.hpp +set(HDR_MANAGER source/scwx/qt/manager/placefile_manager.hpp + source/scwx/qt/manager/radar_product_manager.hpp source/scwx/qt/manager/radar_product_manager_notifier.hpp source/scwx/qt/manager/resource_manager.hpp source/scwx/qt/manager/settings_manager.hpp source/scwx/qt/manager/text_event_manager.hpp source/scwx/qt/manager/timeline_manager.hpp source/scwx/qt/manager/update_manager.hpp) -set(SRC_MANAGER source/scwx/qt/manager/radar_product_manager.cpp +set(SRC_MANAGER source/scwx/qt/manager/placefile_manager.cpp + source/scwx/qt/manager/radar_product_manager.cpp source/scwx/qt/manager/radar_product_manager_notifier.cpp source/scwx/qt/manager/resource_manager.cpp source/scwx/qt/manager/settings_manager.cpp diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp new file mode 100644 index 00000000..42d59624 --- /dev/null +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -0,0 +1,135 @@ +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace manager +{ + +static const std::string logPrefix_ = "scwx::qt::manager::placefile_manager"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +class PlacefileRecord +{ +public: + explicit PlacefileRecord(const std::string& name, + std::shared_ptr placefile, + bool enabled = true) : + name_ {name}, placefile_ {placefile}, enabled_ {enabled} + { + } + ~PlacefileRecord() + { + std::unique_lock lock(refreshMutex_); + refreshTimer_.cancel(); + } + + void Update(std::shared_ptr placefile); + + std::string name_; + std::shared_ptr placefile_; + bool enabled_; + boost::asio::thread_pool threadPool_ {1u}; + boost::asio::steady_timer refreshTimer_ {threadPool_}; + std::mutex refreshMutex_ {}; +}; + +class PlacefileManager::Impl +{ +public: + explicit Impl(PlacefileManager* self) : self_ {self} {} + ~Impl() {} + + boost::asio::thread_pool threadPool_ {1u}; + + PlacefileManager* self_; + + std::vector> placefileRecords_ {}; + std::shared_mutex placefileRecordLock_ {}; +}; + +PlacefileManager::PlacefileManager() : p(std::make_unique(this)) {} +PlacefileManager::~PlacefileManager() = default; + +void PlacefileManager::LoadFile(const std::string& filename) +{ + logger_->debug("LoadFile: {}", filename); + + boost::asio::post( + p->threadPool_, + [=, this]() + { + // Load file + std::shared_ptr placefile = + gr::Placefile::Load(filename); + + if (placefile == nullptr) + { + return; + } + + std::unique_lock lock(p->placefileRecordLock_); + + // Determine if the placefile has been loaded previously + auto it = std::find_if(p->placefileRecords_.begin(), + p->placefileRecords_.end(), + [&filename](auto& record) + { return record->name_ == filename; }); + if (it != p->placefileRecords_.end()) + { + // If the placefile has been loaded previously, update it + (*it)->Update(placefile); + + Q_EMIT PlacefileUpdated(filename); + } + else + { + // If this is a new placefile, add it + auto& record = p->placefileRecords_.emplace_back( + std::make_unique(filename, placefile)); + + Q_EMIT PlacefileEnabled(filename, record->enabled_); + } + }); +} + +void PlacefileRecord::Update(std::shared_ptr placefile) +{ + // Update placefile + placefile_ = placefile; + + // TODO: Update refresh timer +} + +std::shared_ptr PlacefileManager::Instance() +{ + static std::weak_ptr placefileManagerReference_ {}; + static std::mutex instanceMutex_ {}; + + std::unique_lock lock(instanceMutex_); + + std::shared_ptr placefileManager = + placefileManagerReference_.lock(); + + if (placefileManager == nullptr) + { + placefileManager = std::make_shared(); + placefileManagerReference_ = placefileManager; + } + + return placefileManager; +} + +} // namespace manager +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp new file mode 100644 index 00000000..1a61d65d --- /dev/null +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace manager +{ + +class PlacefileManager : public QObject +{ + Q_OBJECT + +public: + explicit PlacefileManager(); + ~PlacefileManager(); + + void LoadFile(const std::string& filename); + + static std::shared_ptr Instance(); + +signals: + void PlacefileEnabled(const std::string& name, bool enabled); + void PlacefileUpdated(const std::string& name); + +private: + class Impl; + std::unique_ptr p; +}; + +} // namespace manager +} // namespace qt +} // namespace scwx From bfe62301b2edcf5bbaf5b40dcedf1147879f73b9 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 18 Jul 2023 23:32:05 -0500 Subject: [PATCH 007/199] Enable loading of placefiles via the UI --- scwx-qt/source/scwx/qt/main/main_window.cpp | 25 +++++++++++++++++++++ scwx-qt/source/scwx/qt/main/main_window.hpp | 1 + scwx-qt/source/scwx/qt/main/main_window.ui | 6 +++++ 3 files changed, 32 insertions(+) diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp index 225f439b..a92e3791 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -79,6 +80,7 @@ public: settingsDialog_ {nullptr}, updateDialog_ {nullptr}, radarProductModel_ {nullptr}, + placefileManager_ {manager::PlacefileManager::Instance()}, textEventManager_ {manager::TextEventManager::Instance()}, timelineManager_ {manager::TimelineManager::Instance()}, updateManager_ {manager::UpdateManager::Instance()}, @@ -169,6 +171,7 @@ public: ui::UpdateDialog* updateDialog_; std::unique_ptr radarProductModel_; + std::shared_ptr placefileManager_; std::shared_ptr textEventManager_; std::shared_ptr timelineManager_; std::shared_ptr updateManager_; @@ -398,6 +401,28 @@ void MainWindow::on_actionOpenNexrad_triggered() dialog->open(); } +void MainWindow::on_actionOpenPlacefile_triggered() +{ + static const std::string placefileFilter = "Placefiles (*)"; + + QFileDialog* dialog = new QFileDialog(this); + + dialog->setFileMode(QFileDialog::ExistingFile); + dialog->setNameFilter(tr(placefileFilter.c_str())); + dialog->setAttribute(Qt::WA_DeleteOnClose); + + connect(dialog, + &QFileDialog::fileSelected, + this, + [this](const QString& file) + { + logger_->info("Selected: {}", file.toStdString()); + p->placefileManager_->LoadFile(file.toStdString()); + }); + + dialog->open(); +} + void MainWindow::on_actionOpenTextEvent_triggered() { static const std::string textFilter = "Text Event Products (*.txt)"; diff --git a/scwx-qt/source/scwx/qt/main/main_window.hpp b/scwx-qt/source/scwx/qt/main/main_window.hpp index 6ed96aba..3261355e 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.hpp +++ b/scwx-qt/source/scwx/qt/main/main_window.hpp @@ -33,6 +33,7 @@ signals: private slots: void on_actionOpenNexrad_triggered(); + void on_actionOpenPlacefile_triggered(); void on_actionOpenTextEvent_triggered(); void on_actionSettings_triggered(); void on_actionExit_triggered(); diff --git a/scwx-qt/source/scwx/qt/main/main_window.ui b/scwx-qt/source/scwx/qt/main/main_window.ui index 369c8d28..8cf8ce5b 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.ui +++ b/scwx-qt/source/scwx/qt/main/main_window.ui @@ -51,6 +51,7 @@ &Open + @@ -415,6 +416,11 @@ &Check for Updates + + + &Placefile... + + From 1a411af3bc539c48748247fd15b44b5a2772d91e Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 19 Jul 2023 23:27:37 -0500 Subject: [PATCH 008/199] Add placefile layer with text rendering --- scwx-qt/scwx-qt.cmake | 2 + scwx-qt/source/scwx/qt/map/map_widget.cpp | 7 ++ .../source/scwx/qt/map/placefile_layer.cpp | 104 ++++++++++++++++++ .../source/scwx/qt/map/placefile_layer.hpp | 29 +++++ 4 files changed, 142 insertions(+) create mode 100644 scwx-qt/source/scwx/qt/map/placefile_layer.cpp create mode 100644 scwx-qt/source/scwx/qt/map/placefile_layer.hpp diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 67b41da6..01ba5517 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -86,6 +86,7 @@ set(HDR_MAP source/scwx/qt/map/alert_layer.hpp source/scwx/qt/map/map_settings.hpp source/scwx/qt/map/map_widget.hpp source/scwx/qt/map/overlay_layer.hpp + source/scwx/qt/map/placefile_layer.hpp source/scwx/qt/map/radar_product_layer.hpp source/scwx/qt/map/radar_range_layer.hpp) set(SRC_MAP source/scwx/qt/map/alert_layer.cpp @@ -97,6 +98,7 @@ set(SRC_MAP source/scwx/qt/map/alert_layer.cpp source/scwx/qt/map/map_provider.cpp source/scwx/qt/map/map_widget.cpp source/scwx/qt/map/overlay_layer.cpp + source/scwx/qt/map/placefile_layer.cpp source/scwx/qt/map/radar_product_layer.cpp source/scwx/qt/map/radar_range_layer.cpp) set(HDR_MODEL source/scwx/qt/model/alert_model.hpp diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index cc35ef85..a59de3d5 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -61,6 +62,7 @@ public: radarProductLayer_ {nullptr}, alertLayer_ {std::make_shared(context_)}, overlayLayer_ {nullptr}, + placefileLayer_ {nullptr}, colorTableLayer_ {nullptr}, autoRefreshEnabled_ {true}, autoUpdateEnabled_ {true}, @@ -147,6 +149,7 @@ public: std::shared_ptr radarProductLayer_; std::shared_ptr alertLayer_; std::shared_ptr overlayLayer_; + std::shared_ptr placefileLayer_; std::shared_ptr colorTableLayer_; bool autoRefreshEnabled_; @@ -698,6 +701,10 @@ void MapWidget::AddLayers() } p->alertLayer_->AddLayers("colorTable"); + + p->placefileLayer_ = std::make_shared(p->context_); + p->AddLayer("placefile", p->placefileLayer_); + p->overlayLayer_ = std::make_shared(p->context_); p->AddLayer("overlay", p->overlayLayer_); } diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp new file mode 100644 index 00000000..76578e66 --- /dev/null +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp @@ -0,0 +1,104 @@ +#include +#include + +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace map +{ + +static const std::string logPrefix_ = "scwx::qt::map::placefile_layer"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +class PlacefileLayer::Impl +{ +public: + explicit Impl(std::shared_ptr context) {}; + ~Impl() = default; + + void RenderText(const std::string& text, + const std::string& hoverText, + boost::gil::rgba8_pixel_t color, + float x, + float y); + + std::uint32_t textId_ {}; +}; + +PlacefileLayer::PlacefileLayer(std::shared_ptr context) : + DrawLayer(context), p(std::make_unique(context)) +{ +} + +PlacefileLayer::~PlacefileLayer() = default; + +void PlacefileLayer::Initialize() +{ + logger_->debug("Initialize()"); + + DrawLayer::Initialize(); +} + +void PlacefileLayer::Impl::RenderText(const std::string& text, + const std::string& hoverText, + boost::gil::rgba8_pixel_t color, + float x, + float y) +{ + const std::string windowName {fmt::format("PlacefileText-{}", ++textId_)}; + + // Setup "window" to hold text + ImGui::SetNextWindowPos( + ImVec2 {x, y}, ImGuiCond_Always, ImVec2 {0.5f, 0.5f}); + ImGui::Begin(windowName.c_str(), + nullptr, + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoNav | + ImGuiWindowFlags_NoBackground); + + // Render text + ImGui::PushStyleColor(ImGuiCol_Text, + IM_COL32(color[0], color[1], color[2], color[3])); + ImGui::TextUnformatted(text.c_str()); + ImGui::PopStyleColor(); + + // Create tooltip for hover text + if (!hoverText.empty() && ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::TextUnformatted(hoverText.c_str()); + ImGui::EndTooltip(); + } + + // End window + ImGui::End(); +} + +void PlacefileLayer::Render( + const QMapLibreGL::CustomLayerRenderParameters& params) +{ + gl::OpenGLFunctions& gl = context()->gl(); + + DrawLayer::Render(params); + + // Reset text ID per frame + p->textId_ = 0; + + // Render text + + SCWX_GL_CHECK_ERROR(); +} + +void PlacefileLayer::Deinitialize() +{ + logger_->debug("Deinitialize()"); + + DrawLayer::Deinitialize(); +} + +} // namespace map +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.hpp b/scwx-qt/source/scwx/qt/map/placefile_layer.hpp new file mode 100644 index 00000000..9a293d38 --- /dev/null +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include + +namespace scwx +{ +namespace qt +{ +namespace map +{ + +class PlacefileLayer : public DrawLayer +{ +public: + explicit PlacefileLayer(std::shared_ptr context); + ~PlacefileLayer(); + + void Initialize() override final; + void Render(const QMapLibreGL::CustomLayerRenderParameters&) override final; + void Deinitialize() override final; + +private: + class Impl; + std::unique_ptr p; +}; + +} // namespace map +} // namespace qt +} // namespace scwx From 1c39464228aa72b647a3909bbe9aaa57492b835c Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 20 Jul 2023 23:12:36 -0500 Subject: [PATCH 009/199] Extract utility geographic and maplibre functions --- scwx-qt/scwx-qt.cmake | 2 ++ .../scwx/qt/map/radar_product_layer.cpp | 22 ++----------- .../source/scwx/qt/util/geographic_lib.cpp | 11 +++++++ .../source/scwx/qt/util/geographic_lib.hpp | 17 +++++++++- scwx-qt/source/scwx/qt/util/maplibre.cpp | 32 +++++++++++++++++++ scwx-qt/source/scwx/qt/util/maplibre.hpp | 20 ++++++++++++ 6 files changed, 83 insertions(+), 21 deletions(-) create mode 100644 scwx-qt/source/scwx/qt/util/maplibre.cpp create mode 100644 scwx-qt/source/scwx/qt/util/maplibre.hpp diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 01ba5517..3856117f 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -192,6 +192,7 @@ set(HDR_UTIL source/scwx/qt/util/color.hpp source/scwx/qt/util/font_buffer.hpp source/scwx/qt/util/geographic_lib.hpp source/scwx/qt/util/json.hpp + source/scwx/qt/util/maplibre.hpp source/scwx/qt/util/streams.hpp source/scwx/qt/util/texture_atlas.hpp source/scwx/qt/util/q_file_buffer.hpp @@ -203,6 +204,7 @@ set(SRC_UTIL source/scwx/qt/util/color.cpp source/scwx/qt/util/font_buffer.cpp source/scwx/qt/util/geographic_lib.cpp source/scwx/qt/util/json.cpp + source/scwx/qt/util/maplibre.cpp source/scwx/qt/util/texture_atlas.cpp source/scwx/qt/util/q_file_buffer.cpp source/scwx/qt/util/q_file_input_stream.cpp diff --git a/scwx-qt/source/scwx/qt/map/radar_product_layer.cpp b/scwx-qt/source/scwx/qt/map/radar_product_layer.cpp index 710ca3d6..be3218c5 100644 --- a/scwx-qt/source/scwx/qt/map/radar_product_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/radar_product_layer.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -31,9 +32,6 @@ static constexpr uint32_t MAX_DATA_MOMENT_GATES = 1840; static const std::string logPrefix_ = "scwx::qt::map::radar_product_layer"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); -static glm::vec2 -LatLongToScreenCoordinate(const QMapLibreGL::Coordinate& coordinate); - class RadarProductLayerImpl { public: @@ -287,7 +285,7 @@ void RadarProductLayer::Render( gl.glUniform2fv(p->uMapScreenCoordLocation_, 1, - glm::value_ptr(LatLongToScreenCoordinate( + glm::value_ptr(util::maplibre::LatLongToScreenCoordinate( {params.latitude, params.longitude}))); gl.glUniformMatrix4fv( @@ -355,22 +353,6 @@ void RadarProductLayer::UpdateColorTable() gl.glUniform1f(p->uDataMomentScaleLocation_, scale); } -static glm::vec2 -LatLongToScreenCoordinate(const QMapLibreGL::Coordinate& coordinate) -{ - static constexpr double RAD2DEG_D = 180.0 / M_PI; - - double latitude = std::clamp( - coordinate.first, -mbgl::util::LATITUDE_MAX, mbgl::util::LATITUDE_MAX); - glm::vec2 screen { - mbgl::util::LONGITUDE_MAX + coordinate.second, - -(mbgl::util::LONGITUDE_MAX - - RAD2DEG_D * - std::log(std::tan(M_PI / 4.0 + - latitude * M_PI / mbgl::util::DEGREES_MAX)))}; - return screen; -} - } // namespace map } // namespace qt } // namespace scwx diff --git a/scwx-qt/source/scwx/qt/util/geographic_lib.cpp b/scwx-qt/source/scwx/qt/util/geographic_lib.cpp index 5c55b9e2..84ae9e66 100644 --- a/scwx-qt/source/scwx/qt/util/geographic_lib.cpp +++ b/scwx-qt/source/scwx/qt/util/geographic_lib.cpp @@ -18,6 +18,17 @@ const ::GeographicLib::Geodesic& DefaultGeodesic() return geodesic_; } +boost::units::quantity +GetDistance(double lat1, double lon1, double lat2, double lon2) +{ + double distance; + util::GeographicLib::DefaultGeodesic().Inverse( + lat1, lon1, lat2, lon2, distance); + + return static_cast>( + distance * boost::units::si::meter_base_unit::unit_type()); +} + } // namespace GeographicLib } // namespace util } // namespace qt diff --git a/scwx-qt/source/scwx/qt/util/geographic_lib.hpp b/scwx-qt/source/scwx/qt/util/geographic_lib.hpp index 3fe3c187..c3e74ef9 100644 --- a/scwx-qt/source/scwx/qt/util/geographic_lib.hpp +++ b/scwx-qt/source/scwx/qt/util/geographic_lib.hpp @@ -1,6 +1,8 @@ #pragma once #include +#include +#include namespace scwx { @@ -14,10 +16,23 @@ namespace GeographicLib /** * Get the default geodesic for the WGS84 ellipsoid. * - * return WGS84 ellipsoid geodesic + * @return WGS84 ellipsoid geodesic */ const ::GeographicLib::Geodesic& DefaultGeodesic(); +/** + * Get the distance between two points. + * + * @param [in] lat1 latitude of point 1 (degrees) + * @param [in] lon1 longitude of point 1 (degrees) + * @param [in] lat2 latitude of point 2 (degrees) + * @param [in] lon2 longitude of point 2 (degrees) + * + * @return distance between point 1 and point 2 + */ +boost::units::quantity +GetDistance(double lat1, double lon1, double lat2, double lon2); + } // namespace GeographicLib } // namespace util } // namespace qt diff --git a/scwx-qt/source/scwx/qt/util/maplibre.cpp b/scwx-qt/source/scwx/qt/util/maplibre.cpp new file mode 100644 index 00000000..c2170425 --- /dev/null +++ b/scwx-qt/source/scwx/qt/util/maplibre.cpp @@ -0,0 +1,32 @@ +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace util +{ +namespace maplibre +{ + +glm::vec2 LatLongToScreenCoordinate(const QMapLibreGL::Coordinate& coordinate) +{ + static constexpr double RAD2DEG_D = 180.0 / M_PI; + + double latitude = std::clamp( + coordinate.first, -mbgl::util::LATITUDE_MAX, mbgl::util::LATITUDE_MAX); + glm::vec2 screen { + mbgl::util::LONGITUDE_MAX + coordinate.second, + -(mbgl::util::LONGITUDE_MAX - + RAD2DEG_D * + std::log(std::tan(M_PI / 4.0 + + latitude * M_PI / mbgl::util::DEGREES_MAX)))}; + return screen; +} + +} // namespace maplibre +} // namespace util +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/util/maplibre.hpp b/scwx-qt/source/scwx/qt/util/maplibre.hpp new file mode 100644 index 00000000..03d548de --- /dev/null +++ b/scwx-qt/source/scwx/qt/util/maplibre.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace util +{ +namespace maplibre +{ + +glm::vec2 LatLongToScreenCoordinate(const QMapLibreGL::Coordinate& coordinate); + +} // namespace maplibre +} // namespace util +} // namespace qt +} // namespace scwx From 9f5de14f6b9be21c0402f0e957d2282917b4649b Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 20 Jul 2023 23:38:43 -0500 Subject: [PATCH 010/199] Render placefile Place statement --- .../scwx/qt/manager/placefile_manager.cpp | 18 ++++ .../scwx/qt/manager/placefile_manager.hpp | 10 ++- .../source/scwx/qt/map/placefile_layer.cpp | 87 ++++++++++++++++--- wxdata/include/scwx/gr/placefile.hpp | 40 +++++++++ wxdata/source/scwx/gr/placefile.cpp | 22 ++--- 5 files changed, 148 insertions(+), 29 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index 42d59624..88e8f232 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -61,6 +61,24 @@ public: PlacefileManager::PlacefileManager() : p(std::make_unique(this)) {} PlacefileManager::~PlacefileManager() = default; +std::vector> +PlacefileManager::GetActivePlacefiles() +{ + std::vector> placefiles; + + std::shared_lock lock {p->placefileRecordLock_}; + + for (const auto& record : p->placefileRecords_) + { + if (record->enabled_) + { + placefiles.emplace_back(record->placefile_); + } + } + + return placefiles; +} + void PlacefileManager::LoadFile(const std::string& filename) { logger_->debug("LoadFile: {}", filename); diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp index 1a61d65d..55be1e90 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp @@ -1,7 +1,6 @@ #pragma once -#include -#include +#include #include @@ -20,6 +19,13 @@ public: explicit PlacefileManager(); ~PlacefileManager(); + /** + * @brief Gets a list of active placefiles + * + * @return Vector of placefile pointers + */ + std::vector> GetActivePlacefiles(); + void LoadFile(const std::string& filename); static std::shared_ptr Instance(); diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp index 76578e66..9db91381 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp @@ -1,8 +1,12 @@ #include +#include +#include +#include #include #include #include +#include namespace scwx { @@ -20,13 +24,21 @@ public: explicit Impl(std::shared_ptr context) {}; ~Impl() = default; - void RenderText(const std::string& text, - const std::string& hoverText, - boost::gil::rgba8_pixel_t color, - float x, - float y); + void RenderPlace(const QMapLibreGL::CustomLayerRenderParameters& params, + std::shared_ptr place); + + void RenderText(const QMapLibreGL::CustomLayerRenderParameters& params, + const std::string& text, + const std::string& hoverText, + boost::gil::rgba8_pixel_t color, + float x, + float y); std::uint32_t textId_ {}; + glm::vec2 mapScreenCoordLocation_ {}; + float mapScale_ {1.0f}; + float halfWidth_ {}; + float halfHeight_ {}; }; PlacefileLayer::PlacefileLayer(std::shared_ptr context) : @@ -43,14 +55,43 @@ void PlacefileLayer::Initialize() DrawLayer::Initialize(); } -void PlacefileLayer::Impl::RenderText(const std::string& text, - const std::string& hoverText, - boost::gil::rgba8_pixel_t color, - float x, - float y) +void PlacefileLayer::Impl::RenderPlace( + const QMapLibreGL::CustomLayerRenderParameters& params, + std::shared_ptr place) +{ + auto distance = util::GeographicLib::GetDistance( + params.latitude, params.longitude, place->latitude_, place->longitude_); + + if (distance < place->threshold_) + { + const auto screenCoordinates = + (util::maplibre::LatLongToScreenCoordinate( + {place->latitude_, place->longitude_}) - + mapScreenCoordLocation_) * + mapScale_; + + RenderText(params, + place->text_, + "", + place->color_, + screenCoordinates.x + place->x_ + halfWidth_, + screenCoordinates.y + place->y_ + halfHeight_); + } +} + +void PlacefileLayer::Impl::RenderText( + const QMapLibreGL::CustomLayerRenderParameters& params, + const std::string& text, + const std::string& hoverText, + boost::gil::rgba8_pixel_t color, + float x, + float y) { const std::string windowName {fmt::format("PlacefileText-{}", ++textId_)}; + // Convert screen to ImGui coordinates + y = params.height - y; + // Setup "window" to hold text ImGui::SetNextWindowPos( ImVec2 {x, y}, ImGuiCond_Always, ImVec2 {0.5f, 0.5f}); @@ -87,7 +128,33 @@ void PlacefileLayer::Render( // Reset text ID per frame p->textId_ = 0; + // Update map screen coordinate and scale information + p->mapScreenCoordLocation_ = util::maplibre::LatLongToScreenCoordinate( + {params.latitude, params.longitude}); + p->mapScale_ = std::pow(2.0, params.zoom) * mbgl::util::tileSize_D / + mbgl::util::DEGREES_MAX; + p->halfWidth_ = params.width * 0.5f; + p->halfHeight_ = params.height * 0.5f; + + std::shared_ptr placefileManager = + manager::PlacefileManager::Instance(); + // Render text + for (auto& placefile : placefileManager->GetActivePlacefiles()) + { + for (auto& drawItem : placefile->GetDrawItems()) + { + switch (drawItem->itemType_) + { + case gr::Placefile::ItemType::Place: + p->RenderPlace( + params, + std::static_pointer_cast( + drawItem)); + break; + } + } + } SCWX_GL_CHECK_ERROR(); } diff --git a/wxdata/include/scwx/gr/placefile.hpp b/wxdata/include/scwx/gr/placefile.hpp index c7f1335a..91f8cb28 100644 --- a/wxdata/include/scwx/gr/placefile.hpp +++ b/wxdata/include/scwx/gr/placefile.hpp @@ -6,6 +6,8 @@ #include #include +#include +#include namespace scwx { @@ -33,8 +35,46 @@ public: Placefile(Placefile&&) noexcept; Placefile& operator=(Placefile&&) noexcept; + enum class ItemType + { + Place, + Icon, + Font, + Text, + Line, + Triangles, + Image, + Polygon, + Unknown + }; + + struct DrawItem + { + ItemType itemType_ {ItemType::Unknown}; + boost::units::quantity threshold_ {}; + }; + + struct PlaceDrawItem : DrawItem + { + PlaceDrawItem() { itemType_ = ItemType::Place; } + + boost::gil::rgba8_pixel_t color_ {}; + double latitude_ {}; + double longitude_ {}; + double x_ {}; + double y_ {}; + std::string text_ {}; + }; + bool IsValid() const; + /** + * @brief Gets the list of draw items defined in the placefile + * + * @return vector of draw item pointers + */ + std::vector> GetDrawItems(); + static std::shared_ptr Load(const std::string& filename); static std::shared_ptr Load(std::istream& is); diff --git a/wxdata/source/scwx/gr/placefile.cpp b/wxdata/source/scwx/gr/placefile.cpp index e6fb1a6f..2622f143 100644 --- a/wxdata/source/scwx/gr/placefile.cpp +++ b/wxdata/source/scwx/gr/placefile.cpp @@ -10,8 +10,6 @@ #include #include -#include -#include namespace scwx { @@ -42,21 +40,6 @@ public: double y_ {}; }; - struct DrawItem - { - boost::units::quantity threshold_ {}; - }; - - struct PlaceDrawItem : DrawItem - { - boost::gil::rgba8_pixel_t color_ {}; - double latitude_ {}; - double longitude_ {}; - double x_ {}; - double y_ {}; - std::string text_ {}; - }; - void ParseLocation(const std::string& latitudeToken, const std::string& longitudeToken, double& latitude, @@ -93,6 +76,11 @@ bool Placefile::IsValid() const return p->drawItems_.size() > 0; } +std::vector> Placefile::GetDrawItems() +{ + return p->drawItems_; +} + std::shared_ptr Placefile::Load(const std::string& filename) { logger_->debug("Loading placefile: {}", filename); From 48d71cc14d2fd4e78c6592aa3c37138f6987ec50 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 21 Jul 2023 00:26:42 -0500 Subject: [PATCH 011/199] Placefile text statement support - Merged place and text - Todo: Custom fonts not yet supported - Todo: End statements sometimes appear after "Text" or other items - Todo: Support "Title" statement --- .../source/scwx/qt/map/placefile_layer.cpp | 39 ++++++------ wxdata/include/scwx/gr/placefile.hpp | 7 ++- wxdata/source/scwx/gr/placefile.cpp | 59 ++++++++++++++++++- 3 files changed, 80 insertions(+), 25 deletions(-) diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp index 9db91381..09dbd2af 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp @@ -24,8 +24,9 @@ public: explicit Impl(std::shared_ptr context) {}; ~Impl() = default; - void RenderPlace(const QMapLibreGL::CustomLayerRenderParameters& params, - std::shared_ptr place); + void + RenderTextDrawItem(const QMapLibreGL::CustomLayerRenderParameters& params, + std::shared_ptr di); void RenderText(const QMapLibreGL::CustomLayerRenderParameters& params, const std::string& text, @@ -55,27 +56,26 @@ void PlacefileLayer::Initialize() DrawLayer::Initialize(); } -void PlacefileLayer::Impl::RenderPlace( +void PlacefileLayer::Impl::RenderTextDrawItem( const QMapLibreGL::CustomLayerRenderParameters& params, - std::shared_ptr place) + std::shared_ptr di) { auto distance = util::GeographicLib::GetDistance( - params.latitude, params.longitude, place->latitude_, place->longitude_); + params.latitude, params.longitude, di->latitude_, di->longitude_); - if (distance < place->threshold_) + if (distance < di->threshold_) { - const auto screenCoordinates = - (util::maplibre::LatLongToScreenCoordinate( - {place->latitude_, place->longitude_}) - - mapScreenCoordLocation_) * - mapScale_; + const auto screenCoordinates = (util::maplibre::LatLongToScreenCoordinate( + {di->latitude_, di->longitude_}) - + mapScreenCoordLocation_) * + mapScale_; RenderText(params, - place->text_, - "", - place->color_, - screenCoordinates.x + place->x_ + halfWidth_, - screenCoordinates.y + place->y_ + halfHeight_); + di->text_, + di->hoverText_, + di->color_, + screenCoordinates.x + di->x_ + halfWidth_, + screenCoordinates.y + di->y_ + halfHeight_); } } @@ -146,11 +146,10 @@ void PlacefileLayer::Render( { switch (drawItem->itemType_) { - case gr::Placefile::ItemType::Place: - p->RenderPlace( + case gr::Placefile::ItemType::Text: + p->RenderTextDrawItem( params, - std::static_pointer_cast( - drawItem)); + std::static_pointer_cast(drawItem)); break; } } diff --git a/wxdata/include/scwx/gr/placefile.hpp b/wxdata/include/scwx/gr/placefile.hpp index 91f8cb28..71f26cb3 100644 --- a/wxdata/include/scwx/gr/placefile.hpp +++ b/wxdata/include/scwx/gr/placefile.hpp @@ -37,7 +37,6 @@ public: enum class ItemType { - Place, Icon, Font, Text, @@ -54,16 +53,18 @@ public: boost::units::quantity threshold_ {}; }; - struct PlaceDrawItem : DrawItem + struct TextDrawItem : DrawItem { - PlaceDrawItem() { itemType_ = ItemType::Place; } + TextDrawItem() { itemType_ = ItemType::Text; } boost::gil::rgba8_pixel_t color_ {}; double latitude_ {}; double longitude_ {}; double x_ {}; double y_ {}; + std::size_t fontNumber_ {0u}; std::string text_ {}; + std::string hoverText_ {}; }; bool IsValid() const; diff --git a/wxdata/source/scwx/gr/placefile.cpp b/wxdata/source/scwx/gr/placefile.cpp index 2622f143..04dc0ae3 100644 --- a/wxdata/source/scwx/gr/placefile.cpp +++ b/wxdata/source/scwx/gr/placefile.cpp @@ -48,6 +48,8 @@ public: double& y); void ProcessLine(const std::string& line); + static void TrimQuotes(std::string& s); + std::chrono::seconds refresh_ {-1}; // Parsing state @@ -243,7 +245,7 @@ void Placefile::Impl::ProcessLine(const std::string& line) if (tokenList.size() >= 3) { - std::shared_ptr di = std::make_shared(); + std::shared_ptr di = std::make_shared(); di->threshold_ = threshold_; di->color_ = color_; @@ -255,6 +257,7 @@ void Placefile::Impl::ProcessLine(const std::string& line) di->x_, di->y_); + ProcessEscapeCharacters(tokenList[2]); di->text_.swap(tokenList[2]); drawItems_.emplace_back(std::move(di)); @@ -285,8 +288,46 @@ void Placefile::Impl::ProcessLine(const std::string& line) else if (boost::istarts_with(line, textKey_)) { // Text: lat, lon, fontNumber, "string", "hover" + std::vector tokenList = + util::ParseTokens(line, {",", ",", ",", ",", ","}, textKey_.size()); - // TODO + std::shared_ptr di = nullptr; + + if (tokenList.size() >= 4) + { + di = std::make_shared(); + + di->threshold_ = threshold_; + di->color_ = color_; + + ParseLocation(tokenList[0], + tokenList[1], + di->latitude_, + di->longitude_, + di->x_, + di->y_); + + di->fontNumber_ = std::stoul(tokenList[2]); + + ProcessEscapeCharacters(tokenList[3]); + TrimQuotes(tokenList[3]); + di->text_.swap(tokenList[3]); + } + if (tokenList.size() >= 5) + { + ProcessEscapeCharacters(tokenList[4]); + TrimQuotes(tokenList[4]); + di->hoverText_.swap(tokenList[4]); + } + + if (di != nullptr) + { + drawItems_.emplace_back(std::move(di)); + } + else + { + logger_->warn("Text statement malformed: {}", line); + } } else if (boost::istarts_with(line, objectKey_)) { @@ -409,5 +450,19 @@ void Placefile::Impl::ParseLocation(const std::string& latitudeToken, } } +void Placefile::Impl::ProcessEscapeCharacters(std::string& s) +{ + boost::replace_all(s, "\\n", "\n"); +} + +void Placefile::Impl::TrimQuotes(std::string& s) +{ + if (s.size() >= 2 && s.front() == '"' && s.back() == '"') + { + s.erase(s.size() - 1); + s.erase(0, 1); + } +} + } // namespace gr } // namespace scwx From 8be32a8998bdcffb4f7cd54e468a0753c29af32c Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 21 Jul 2023 22:34:19 -0500 Subject: [PATCH 012/199] Render hover text in monospace - TODO: Configure separate font size for monospace --- ACKNOWLEDGEMENTS.md | 2 ++ scwx-qt/res/fonts/Inconsolata-Regular.ttf | Bin 0 -> 97864 bytes scwx-qt/scwx-qt.qrc | 1 + .../scwx/qt/manager/resource_manager.cpp | 19 +++++++++++++++--- .../scwx/qt/manager/resource_manager.hpp | 4 +++- .../source/scwx/qt/map/placefile_layer.cpp | 17 ++++++++++++++++ scwx-qt/source/scwx/qt/types/font_types.hpp | 3 ++- scwx-qt/source/scwx/qt/util/font.cpp | 10 +++++++++ scwx-qt/source/scwx/qt/util/font.hpp | 10 ++++++--- 9 files changed, 58 insertions(+), 8 deletions(-) create mode 100644 scwx-qt/res/fonts/Inconsolata-Regular.ttf diff --git a/ACKNOWLEDGEMENTS.md b/ACKNOWLEDGEMENTS.md index b5a5b93a..87476473 100644 --- a/ACKNOWLEDGEMENTS.md +++ b/ACKNOWLEDGEMENTS.md @@ -55,7 +55,9 @@ Supercell Wx uses assets from the following sources: | Source | License | Notes | | ------ | ------- | ----- | +| Alte DIN 1451 Mittelschrift | SIL Open Font License | | [Font Awesome Free](https://fontawesome.com/) | CC BY 4.0 License | +| [Inconsolata](https://fonts.google.com/specimen/Inconsolata) | SIL Open Font License | | [NOAA's Weather and Climate Toolkit](https://www.ncdc.noaa.gov/wct/) | Public Domain | Default Color Tables | | [Supercell thunderstorm with dramatic clouds](https://www.shutterstock.com/image-photo/supercell-thunderstorm-dramatic-clouds-1354353521) | Shutterstock Standard License | Photo by John Sirlin diff --git a/scwx-qt/res/fonts/Inconsolata-Regular.ttf b/scwx-qt/res/fonts/Inconsolata-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..0d879bf3a474dbd6989e73565cae9bfd752cef12 GIT binary patch literal 97864 zcmb4s34mNxmG-;uRoA}n`}S(>+PbT{x_a+Ur@PY$2}wvu*o6TUMJCum!w#~Dq97>0 zijF8KAg&@E}G72al)&F-S25dMAiEPP;o*!XqeC-~f1=bU%G#s9tM@$Y8=pZWFs zPCuA8Kd86O%xQ|+{hit20mRCeYsonJlaql>>W|7Q9A(LZzx@`W$IMOs}IxRj8w zAc6hNsC?vUNwVE9+f0l__ehnIH5i``b0_Eqj0-UN^)PP{Fj7GQ3}LhFlLB*2B^qRG zWY5|Y8s61&*prHe7tdU4qh|nzb=yHt-M9Kb+EYn9_39%$wZ_xi1;2==fj)l8 zQRU47GiLqps>JE=*%-Td59dFJ?dOz+QRfulB~FDn;v;eDR(*^w0-jdGi!t7L=op+s z+FU`=m;NT8BY-~T2++6dxF6AQ2LXN85ukTy&?euSjbx**$Lu(Z>+R*;=R9qO9Z zzX~W?(W&)Ny6Bt5wL zKYTN+?S+34aF7eYF_ZF<)u*LTN>ONrB8L9@o`at2+eHa|gu5_;wx>Cxp=!VjiTzPN z7vFtSWiH@$4w#+zBl!#lCa+lRc8T;&v(c(FJy1B~jRwB8hKBe1G66Nv5`WvOsnhbP zuCKEACvLp)uHRfSx^SLNK6mEvx&NBE>#kq1?WAG&NL&j=F(&i|cmZnsjIkW&33MH_ zE&HYneIli$s+5>>+A^6megr8&K?ulz=JP`H?f0~rNjcy*8cj(h;CISuvDF+MZI2WR zs@fJm)@4;4x@Yw0Rya^jMk^^#eEhhnvkz3~^XW~~ul|S5T-=o&$)6+d3zo(*iLpVO zC0!Yg7B|!*)1i?{empZ7t;GYm7%YWUUVWbZKt4|@OPM)$A)n7=%9a(6)9nt~6k-wh zBbWs4EN1I+dt^pwHG?6OsW@6Ff~c$$)Fx%3tP?UCRhDQSx3Lf(npwy$oEo08D2mY> zX(x)awNR`vn~O9`&XMHqX>0!I^LK4NXG_(;aV%~ynH|n-NZokq^yH~a1+N;)Hu7dgt(-rA?X4A$ncMW**D_Y0U}k@|9CVLvYi>So`{2an zxkv9fZ|3m{>=`~=g;p%bSM`ox5=Z1D_Dr_NHH}uIQOZlS&qkp+!6E$_O><@>vavtl zYK23cxen?End{d$hq{GBLFOkh>O;^DHYqY^A26FGt`vF zFR^iL`x8YR3zwHcA)9cVAe(l5=j$+}Sq03ULhjsvY4m0)@DMiy%pC&81(=KuvpUHh z;{FC;NH%}R{S82pY?e4W79&T9o)EHmFnzZRyhLA0#|yDgxh)rCKWr8leSq4xhtGw> zY8bHw5A|jYDumZVcwx)sFh|V1oY#F`kD!BWxftV%8r43)fBMidz{`JC$d(J}FAWHa zqM+!M!$Lo#kS!O`k7&3?%3#eNGrLF&>&R^n~e69{7q;?&TbSWWwFEHKql8#B(5FLi>y`cSSDa>s&BQ^>> zqC=6r7f{zgB*>EU2`=eWk^J|h34FLpvaCU!y&mcwflDe1C~l*lm)_6i&W$zMCAiF= z>X*AipSx}KrB{9q{zX5pKEy343*wvZU5J$-+b=kEm*6?=C%!PGP+q6OfHcf7|0tK>U!5}PIXLPjK36V8Z+HeTq zrP?9g=V-t*m_DX)9~=JFCzoukUBl(&PI^RkX2(l~iL~48 zaF~szQ8gz|&d&ZbLOLcLjW3^r=CMejIa^Qj^r_yS-hp=Y9L-+S{n>y2w|wsOSEsw* zrl(cd>c`|a!JmNkt_%)Z)gfW4!`41q?dAGwF^hQ>xUKFPYq$+rCwvMr)-!Ua4fP%% zgB{p%&IC)1%x#F}H%)qm%?4(&xzhPitT~wWgln@UXDXGnzu~jhlN#2euRmqy8@3F( zWA${=WHdV*o}ksGPV64vaQcF3u(+%@O+TB?KoU8vT*fwnbi_tsF4JwqMCG1@2^i9R z0_F|@GXfahdI}iY8v^Eb0W%Dkh>oZGH1S1;c}J48KARpQ+wfG+HV90#Uj?RHL!2VQ zG^=AG4(n9Y&d_!lL7mB=untYk=*!%ngZ(i1e3B<%Ua?3pqLpT)Dfqt&0%bG?0`*55^X^go!?8bgI+q+eBG#(8x|8Mu&TQdp^-gXzS*3xe z_5pkkl{TNJ71(*vd8~cWnobu(5eDENw2_uE3lJZ2biwccY^!is}6H`Yjg0N zZ~HGDlb#b1Fl24SoUcZHi`x+&#+&bPMnI9Z5m474dK1)a@6g!;|{AsS~N86f|1rcd|wYnuyG7T`3!=nbXgZ}eb;#4_0G?lTb*{0cGaoQZt z{oaY3XKs$;J3>J1)=R^mQ-3T#b>%>_^2(=70h6?JDjHI@BelJ8yoF*h2r=#%Q2+S zx_|E8VjzEX5LytUfIFvhO5Oqaf)>mww2OZ(d|z3kWk0NAPD;*sa^9pVlST5<8@9uS z5Th#1Sr9Y}iZp7pfG?mH3dL-eY=|kK`Z~-sNW=~8-L{mrc+HGd3+%8pU7tU0@~GFe zndbyS=*zp$TT00?bdi}^GmCFL{ts-=VE1wZI5Q7QAHZr^CFlJnE7&0j(8nexe4dmT zt9IY6VS8bUwB-EiYuVTEWDM~EB5J#&dVRh`+`N(i3aL@4P!NOQm7-Rxcv40X!=q=4 zY68O^tas7VLYN(Zf?-Lle8Xr^j02&xKV1o4@eNw{mya1ReEhv?cHCgKnT#$Qt^G4t zd(YJFk??y#j$(Gd{LD0X&*#CV;B?OWk9>hk0kVQxo{=c#b3IS;aM`p)K&uzD@-X`` zaD8ZaLs10!?U7MLe3S#wR6DVO?TUvIi1q@3j4$7PqQ?~WfpB2hY4iFW{#ZT{92$9m zTay>2h$=_~xC@C8RK3H=B_ae{){}@H?Q2kS7W{?WctG-ltAU>OpNDSg$EE3({v0PP z*8J)*>|Xhuk{PQqET!h$)j}beGOmOy2x*O4ghg2wq+WQg!_WC|g0q4(QurFp$h0S0 z9z>kJEPOMK7xTwhKHN&Q2EB~|gVEzNyWHkbKJI$Q58}h~rv;LUU?7!}_gP&QtjMRG ziNMSN_KZatP+X~ImR&sA-8Qnh9+JPG3^yhaWNUmk;3Xea$(RcvO1X+V}&Oxh4aKkWCkd_LGjk4Y_h z%ANOruswfG>{5AO_dBfl@(t|YU;1mehB=k7PQZmVKL=Qi6r<5<`jAfR<)Sen+44s* zCns!<%`P*W)4ZZ!iMRuUd>Cy&rh6_7j>`fw&SM{Tx=(M!m`(O=&1re68^ov_AD91q zd{mK(XD(g-br|yY1k4@KPtcTJ=qEkuZ$e^0mky&zYz+C7K8Y2hku4OMZsi$3<^m?& z7Lq5@k4fW0fMe1=_q_I2GCj3_*Y^E$bNjdN+E03gkH}a1{Jzze)8@X6g@7TeFJP!Pf*el{Fx8&TKNyej zC^?>ZAB74c}7{# zK^Y1$Mz7jHV|?N;V}Rd0Lm{Ah)rLBtFFXP?`FjHT5zs=Fj0&K?b_8hhH3^!0&0e*E zm|G{Na4%9&OunYT+N(AY)bv46+Puiu`MR=aG3d`-hxfwA4lv9CSV<08%QN`W2E zVSj-ZdM?Omuj09epq=}Iy{Eu?dB0AR*m3K)eHhu%Y8!`<9r8W}7xdZ&EyzrtWMI5I z^zlCas~h&FhI%_=?nqkyt56a94qD&OYTPa; zfYEsU0lvq^0Q}E;xW44i^z-`egP!^@FxeT)YO(6aghYJTU#%dSYZxo{wCUU56*6}zf>{C>%ztVH5*VQf6(ym*f~ zR?qNIt%w_*5FGBcqMFeN8`SaiDo4TKC-c*Tq2lI|K-v3WcfIqScmBpxaz{%hdm-gR zi7L`C6}F#$%}+bz74W4jKKzmNar*0so))*ara;f|s%k*1-cccM@@Vo4ufV{i-3!-TQ)!1W z?&b16#7~ODa{SPap`oMexBNKKOp37+WY>`SOTw#0&Z+EFR#M>s%L*hUt!aYvLicMG%dgNjFT_foXT&zVE5uVJDS#D);F;5Xk`4v`Q2wv zh0BY_4Q;<*fh1-OEAd^dgbUd##AQ~u+v$`d;E`5x6|2#!bPA`$H!e)uj<@_bKM1WtkjKwaAC(NNOL6rF;B`qXIZJHcv_fP4v&6) z=7S4_Qq(soPZA>v>+MSl#*iz9&-<6@)ABikNjBP|i`95T^_hI>rn=#%RQ=e=>})4c zG#Z)F79PzumAJesF9t&>bn10&xkT?{tPYW&rigQs^^|pbunLVuk8BCW+9mv~v^l_)P{JN>PH;Gm)L^9!D8d% zkZ+EM*TC=P@=d&l9NG}s>3&2CRbSADH5M?}{f@(gz<#|VmV&hO!)Ot+pKAw^^`p@s z3H?w)A2BC~()Cdanx-F0i%DVP`ZY}x@g~L0VMLqGuw}*9t4eSc)9z&r{8UK8Z#-^( z>5MZMkUf~(a@5qu=7}@5181}MQ39d+?yV=Bu${f9d-nD-he0RD&3FCh#jfW)eJo<= z)8+z(B1-`yB1@L=_T}{t#-r6L2#=oE7civD3D24?m-%pOx?I3ftR==1v6g`1K{)rY zDBl3C=ujGmQ>cpfV$H+MPs9)`0@`(JCB~x|LeSNVAy^tvJl-N%u3;7aM}8boNZ(_> zejM_HJP*zA!PW2d_BMy&{QMWe&nHNNWVN54HBc@v37BjDNSNgEddd&RbQ@|_gsGo$ z0Yj7%CPY8Hz7D9tLn!Cou^8{JCpo4fU^qKzc7g|F83oTp^aGv)C`gJ@<2fXecee>n z_3S6-f^I(ruAaOgjs>T9m4bMs*B=DbXT*roTJ-Z_Q1gEBtra-|v)tLkk@LrNDvIk? z{`xk>F)SN~N$Tlzft|YI#C$|o9G?%8tbgOnMO|?Mjv@yEr{#M{wh!VBMGk<0)jKG1 zkcR>M$R55Q_%r0mGbw@^#808?jHK%nNnUIDAzy5-Ldiwt>F;m;@Ou~k%^;uG&9I-o zd>x+i;Njn5x7*>(xr~ZUF&HE>+&QbBoJ1WR81{1vghW-8H=eO!;~Bv-$0tvZosBlM z?uqO`_ZoKR%Qv$3c3(^D%dv8~gg`=iKk^r@*&O{|+rcom3kydu`W_ZAv^NEw52OEs zIyDGrZEqfo={EF-Vuc*YiHco^c~TDc`{0jFiqE;M%!Fwgq1aK@)rvF?51*z_X@xqd z&8S6F`QvvUb>*9vZnRS7F2w6O>r=crsW7m z=a7IQ4hcLx?L|Oq91>9EO$aD0qiX4|F2$4Ovcsx?BVp?g% zVvaaAq#5rgt}LTiS`E9r?kkZBr*dSq?Hq=_rb94;NyAgvT5i zTBBm-FzOs3yu^_Oj`&C%nbgPleBT(?9cGN@*rQ5LhyKEw8@q5oOq-$p<`c6jJMIeH8Hak4hLAaSomNYc$Z&I>_759&1Ccj3~(zNbTJ z{$js897b|4KBDA4qGP(+#U*zZ>>TS~r-$XMbeL-}Pv{vUT+c`fy3f&JNZ)EO|FDK9 zL7G5^`H+B_hpy8j5`pJ)I?NR>tj&MTs}^$p9svbe02D02CFl+LJ@>YD&rv=_@K!w4 zlOe2_{0SZVm7?pG_MYfmM6TEhef0;fuQp0srSZA=;=)4KGBuUSW~I&Xc-Y}Lt&EI~ zm4=3Tx{I3u>UF2+4JwTOa4p8wn21(2l?9Ea1+1v?_BXy$4I51blvfmugdOcUHWS1d$gdVztubi?C2Ni@4L~J_+zI{`^8GZD_i0ZwD;o*dB#aSx( zOsGY#7_F2{U9*_fVOTWjJuB2D((4n~(~VSQgxY7;c(d$|)TwE~mdurWgNw~ry0eh8 zgloBgH&c)0$J0*C!{$i`r{tR{G@lZ&USIG9+7x$ z09kge?pYzrUPz~?w-zOr*{vJ9Urfm z22A!~Gm{u8hr@%D+0bCYJ(%4!WXsL%9ck^I%hgI%6Hd6;{qAsWu2|eK6mvwp!?_&R zozueCeL$=`c!ynGMvsHuhbdt0hHMC!%aa_Y4w#|d&L2ZA0&~(~t`k~jtOU??Fuk#` zrvXEfh7rk1Zs0>Q{Ry$3#n`Y3+Sr%nboy#*cu6jA&|xl0=`eda3}m;4eAJgAyIHAC zvYTwTv$mC}b;TvEIIC5xfbjLSHP|aovhc)HNXuCG{K1fld?@MzonEGjA8tXT*i-7p zPC7fbpr%_z8}&gj#k6U_z?`LK(w3>m9nrCBvXOEpCkCy_sgtH>j~|cu)iByFsI~L| zdR4DI9I}V={@K{@hJ0%$8V05d<9l#0NR~>g&!S`F!;qF~im^N?f7}p>jDX~_P*2G2 zU(92yKFJA&X!8|2b-CDqM(TvL(?VLNU+EpoVN&I2$kD`dAzCZB-GlMVy=S>Hs&8kg z5_rAm*!W<=8Eb8AMjH8`JE|6Z;o)L9SZLbS**&f1aSH`o$Xi%U=7!>qV8HBlBz=i$ zB)w_J-p15FEEZ-0wS}SLL@8nqRyS128;7E_FR9P=r}95Z8EHh?B&lzLG^laZ=nWK|Y78K{ZdcFgAM;@()_c9)lar!F#^gTl!tp@WD z#wLudDtWYPusw|A<7*f}#kFP9c!ZbwO~yb)WsltCKEBAe^L`UPKLsAGHpb`s#<=b< zV?58EAdgmq{sLk|qG$pXoqJel?$K({|EzJO1L(&N58bz7Z-IYFr&jeP(>z+@8_BO& zj|+L0ML?ZOjO&9UEiIrff-%un|JXI|f?w?WIt=(lxyF7D9Y&5WKB64K>(xfPn%gzG z1(xflUxggO>(yYc5wb>;)6c#N7YnZ>XAYib{fZ@I>Wd>;_hQQlX z1#kH?J=r+iQx{`?ul!VX9fv;jI~Q@W4M`s4T;#Uyuk35^Z-a=)QDZe1i;aeqa-q$# zXE4Z(<#|0(-|M`x2h?gdJAq!59t{3|Z0}I87)zvTM-R=OGBWxP8!EGEW_mDH@_R~I zqupq<+f1Qi+?{NhS!uBxuO*G1Mm*O^PoKTC`1+}-!R_NCJ8Isf-y3(cjY!2={BeJz z7|?l1{=3HSE4d#k)Bb;5Z+;#4Pyd4Xk&c63=Y`u|wAxm}61U<#ziwC0-Q?C|w3DgB z>fzWd%z{tV4!Jxj6x`OTP;hLKE4WZ4=Pvn&`V<`d-VrohIOXa-xu)SxT;0yT%)V(b zNgE{pDsEnoW})eRB!5UsQU_tuy29ddIbvM#o0;%IDY8H}B^p|L-4O8kV7saQ#m|S} zgoAq9>8Dq=4p)wf6nHJwosJ~x(f7P(k=-y}m_9n6O1F8f)M*d3s)h0Hx#-3(tnOw% zX8%_jm6i~Bogz)oW%r(R(uw)prcIsgv*k?9VvU7NosM+eiL<7a6HO~V$;Bx`V>C{v zgvKev@s9IvJqs+%LocWiiJH0sy2)yYqfx@c|9f3F9VB-`p@Q)*L)$uua50qh2Ce>l zJ6j)*yPGX_qMmXmB8Y$nA~Wswyy^_pw{9I?E*6)Eo12QoP0d6t8m%Sx*W2QW*@`7I z@kv*rnv7QyCa*1+@OxrjD~@Uh<4&V9kn~4~f-#%NWpVp0p7dZOHe3v`O_j~n>T;#B zT&-@dOvUQ4cr6yI#qq<(GKrWnsaUZSITz)(KpJb@V!fZ|-wdys)r$Efu>yvyk$}1U zm!#JWX8=ae9tjw-0|MqUB$@z2c=UWAhfyjz%v+y-o?y-fjGprpcu0o|JQw{+$Fses zLj??JECF-X+E^!w9mJ%~n9berhLhMo4iCiA6b#z1dn7Fiq1I5nyf_ zn-}3ld51gk@~pZPEQGxUHSS&#f!TBMa;UI-vHLsb8yb(;!)i6weF1#Je>5iOqJjf_ zq19`+FT_$dx{A5%vdWm?-tOF91Hc^FL%wgC!#ko9`1ti0OaYf<* zFC_)28qCil;m5!kZ`}95*%R#8iS8RFXhs76L!edQe~-wmSoI9F4ntm#fYGvxPQdK! z&E|9vawGR81k7bzGlEXW#JvWtbzWaT5EWwON~wmfzB+v&LVt^kOtEzvaPX^nD{JB1N%k z(h5Qbu|Xk&QD2W^kU@fZ17t8lSp$2->ocxINM(zx!TRnUL`33@1p0HnQrQ+Qp;l$I zIrE`pBl___4{aa%bUHl>o8hwCoU!pbKmM?|x1>?>GcIL|gz{KU(*^4-53)ZflE75#)t=F?nHk>u%`pZ?{W@`o^2`pKm1!+k%T>mj(v#@>$E z4vv5Z4a%r!9HH}-y$&?^ev82Hh@g~wKeyEiU3Cz>;solN#)7$q5l6lWf#Bl)bMAkC zD3Z;ELU~o22lHma;Y=nR%F=$@j-A_2ho)6=S%@C3Qa>)F^|PQOg1+2_l%Mk_Q_@N% z7DLKqFh{a$*ZhskKJ9~%5vbB?U{FF~#SFQc$eg0Zqii)E?wvYp2%@6u?pW85b z)x~FO$9*8=;*`&Tu_xqo#^;mTwB&N%S)c8QuKQ=D^|sInt=@SH>_NVon3V^;#%{%F zw4vB-kydaQ!gc_v?`{vGdeuO_ilIgxc^2bNvG3g1MmP-tLuSBpV^(=yb zA)idZT+aPAmIRERAy~$K0<}8KJHotfnn!OyZ$Igyku4LLF5>YYqtW!Jbs600$8>Fg zV@h#MdIff2bqBi}+^Jz-x|2SiYFddKS7KJQ5NMuvFaOKWy72~NttppWF5;U=q4OR% zV{|x}RR-+gK&BFKz=QS`)R=qA7Du)?7)Xv30@3=+-5$?IW_4Jzi>=?}nn|YvzF4&T z0$3Z!h8=<0=8^xLK5i^VvxM~b&)^26t58EmvRapZjTiUv*i7)^N*=+p1bCsxX2sRN zvd0md(P<;tDK|3iYq=-AX7UOLj%HppI{K;^@wGIis#B%nw5m>N`ddLDuMNon(TK%4LGQq+SJ=pY_mvZ~;R;mVgmH7Vn_feEw!2XC^qTu>M8J#^cTG!Ijp?h?E{jN}LplXKx9$srF@%uaZiGMzTkE2x44W*z2j zKmHwMuINn1gR6J<`t1Z1`Lp}`x5@5RH=EuaM52V(-?WTIQZ z2)dP_L+K{(RKSROsZs-ssF&(I`T}6Y{4YhvF=YWsRCqG2PX@@x7Er>+=0JK4SkcE^`HVrV462S1ddy;DW7$8@TQceDI|o+`Sa#p-anRjVL0y zf#%Tln?k3G`s9XIn^scrR;(nK!1XJsoUHQ}FdA>~O)4UK!F3HwYUmj=OeR zJja17+j-fkNN_kF8L7sqn@Y)s$KOm1PNkoeL)CO@$fHK5pS60O=yXIO$uMAYc&(vo zJY2crWA0ocS`69|fcOLLlb4S>s~*CC<(<YcN^(-zC%k4}AJcp)t-MZk z_du^iS)^Nwl7^|xwsI$3SZt>^Ub~K^$N7`^jNKc8M>r$PTxVw1)3@&>5V7df+IoD&C41C5?j4a+SdKPbmX4p5{TYMtdM2ViYjMM>mN&UFA9>y%kC2sEn zw<5@{|CW7As#AQCt-Hv7HU@)8{cFX0WG#6OyhE$le~%0HJ62L$-Zk36^w6SsQU%z~d? zM>yk=0cA4F9j{;KE?GgrxogsbyO^%-WgdXH-9OtKaElE%m*BJ2ZXuLWCl|7zz>~&# zLKvT<$!H9R?WPqZIC|pIKqUvM4fLieN0JhiIX{0yLHXqPI7=K}R>1LbRFfZsd`?Q} zQ}%SC4Z~zLX~iKkM)yd*hGC54XP6G>ms3}Un(q4^UTzMEihTn zx8n9#H9d0lVBsj-l2*-o%5>F^Ei@4;O$5e9#uroN-E&rVI=ldh_Sge4Zzkvp`TbT` zshw3@35z%8ZJGznE}PdEPq-2Zmp4C@Da;qKDtsPX3g0bc9ySOvKZPqLb(!ZdN(n0m z82BCNfP!tj2pNp`k!_Uca5YB=U68$Jgl!a@AJX+IwA-k;@3+Nb(? zF@cHVT!HB#F&bgg;# z6VTG0rq74dVyQv4l1FJmD_X*uXjMyoV-F>X$c&>di6l#N26ZSZQEC~GwE&YhzQCYI z5+fnil?$TV!{;qbl#)4DBI>t$lBIB=9b2ev9~nKi=~E+#TG~@PhE9&z?SVoo85>gl z;}&mJ4MrN(C&^11l>R8eu+mEv1_CVR9m=tF&nQ6s? z+2!J|+2-y8rsV9Y^K8k&OeUx24c^4?E(Al7rZpNA(2ylwLh+zJYG?M# z>7JeB8YLc)6ouw6y&^@^Z1ahKho+y5kN@h(WMTgiXTB$Y1=95)F8lkWPfIFfM^3?_ zJh*ym&sr>@YXcnA;Ec+3{6w2vk21;BljkMKb3crB61T$pVE$_j6V`${@cD>F<-%t! z* ztE-i%oZDBJD*vr}DYcA+!Zu$rU;}^o*j&Hiw3{9uFcsaK+{lvM7ci9n5HMPesR$Uo z#w1`!p9mODpQy<2=ryK;$LiIX2x!fs3nkSl={3iD6I}gi=ulcV&y1C7nnqh&Im@Y zgK-$8C17AHMT|}np>`GyEpxaTe(fQB{6yb+(;e||7Ivl@l_XDZhYJeOA+Ax->ky{} z=Z!gib0Mw>Iz_z8NS@bms8lXGV{VOlU}Bm3AlyKnn^=akam853O;OeHtGxL*L5 zpFDiU=u;$z0qDG1Qv9)&aG}KMI2f^Z3zstBh}`Q>n%#1A^)!daqHMH=V(x|AI~e;` z_eQqRefap5&CQ5w+s*IW%1YfI(%$By@csWKA+zEfKmhpxeg6v>+S>v~+uH&3-{^Z= zz|gK0Fuh$%Kx@19;L)`GxHcN*-2ferV%~HL+NqtFla0uAiwdE3XIZC6r{zo`oR(|c zddv0$D_d^**fEzL`0X$5y6dOE`|M|7Q-i1@eF1plQkpPD((aTY9!Q{m6c<@@f$8A# zXUErD9ypp{uIaLL+n+)e)atbOZLxgZvDXzJST-d+E8(MtcOG?9`qO^jFdR!8E~Jl+ zmYJh|LcM-Uqx;2;9r-}_%HxU4oEFaIuL&+gTR}5qAaOdE1q^Xnz+9#X7_?97Toy3I zWdU=cEb?g?0IhLZK#@l#pf2j_qv=(24#V{)!I+e5r7QV+9<7dW20zAGUcP0;!ktf_w9&dSf* zQBcDxft1rY_hjRVGhe zz+C;0>5Y}+u0SOduKH7vS+%w?zrh+wCU0RUcR#}9u}X0)$z-`9 z%M7h-{)pA9w$e{>L6p8&+f*)Zs@0du<)wO@j!ML0gZTPxPL`p*tJB$47hgv*Q>D^mCNo(oO=Zr+ z`v`iqO#~9CeSya$DRhsG9hyBm=k_`rHkZqddmqvJ$t*a<3%-gsWAKvgfnx9EXq&Y{ zA%2!rJubq|5T{33aHtjS?qmPdeYP10K&S_Y<#A7-GW_ztfd}KZQ#$rq1(`35{Uf#)8; znVbq)R!LPW_7z(M6QR>Mocb9dBF%?SsGNJbl+#=Y(yG8}sRE%>W%6% z!zZNc$YU67o@%Nz5{=a7N<-5byCanG`-^$+V0LGzwOkBTGwNuvvTNFwQ#+Kyu)BQe z@m#K%uo=eu*;<$iwidrgn-8o2@ca8Q#@|Bo1K`Kg3#C;jiG{cU)!a-1^2L-?w?ptJD3lHDiVju0EKKZ`yvn04e zKG?&MZ>m;1)4^aa7q{0OE2%hNWYJ^GJBS5WjN-mzEgU$EkZ@zYXc zba}khKb#9!F@Lol3smy%YJg33SFN6m>M1X@;v>h6*_!o1BMvO#P)VRLmQ_0izccFd zvgz4-ah*$|vu$YZWNbiL+KBwr1Gx7}oQ=Uzl~yWco-R370yyWzrHXo8NV4cO+WHP# z$`(MX`Y04RbN;z(GNN3-24?owI;YPU7ZW%@g?pdPUccR4DyaeH9Bf9_nMQ1AM^gzD zlOA8X=<}-SnAPCOG}G4Qt4=ug{X60bN=P}eAc{RXdhAp<<4uenRi8O!WX6{p%@syf z50&n-%hS{X!n;fE2G1Q-Ifu9!!D~CFA@;_8q%BErfaVV*rZ=eF=hO&8n zG+$i4;a%@OiZtdFc=8Z#ddfimFOttxF-JxW(zJVEC99u&qq_voT$BQE|pE{fj zq5(WU+V-YGj$k#D84MuPVy$hh=jJ;eS8Xg=SsW~FEC;K_jNP8EwPM5DYO{&qWT=u) zS}citIh1Us*swL8R=weJG*SwMP*@=@E$}tp4_$)Z?^LPOv`=8Yn|TtHI?rF46nYSk5!&qU5&0?+}eQFbFAs_lwl8uZPWuQ%gv^UmV9IS5XBo_{ByY!{i4>e6*+=VXHu_&tNYxM6}zlk zn%-TpeTOC2*%g36k(+;V0Y15eQ;4P*{z;E! zBl|TzS)qW8D2NUxDS#j*6LH8Lc@@I;SRBl|u7y+^5$A^CuR z@6l;wf8f8bNPlEk$QQ#H5NAQ9^gK(;7vOjB6~FJt@0aj@C$8doz`u$A4jS?MKjPot z#o>vg;@_{7&&Tt`&sFI{=^1vZdyJc)2RQ5&wR&MT|96yAk(6h(aYdbbK7W4VV#^xQ+BpOF>=S^_Vs=ymXL5^F@ zr(rUZ^2boH1O;ff4VX%) zc-o;&A;k6pY4+*yhFAAf z&cy3yd@b}%;*yV6n`Q5Nq&hbCsW&};a-Ojl>L*?MA#MJUNOb;(@Ggg>A6Di-A_`2c zCo%IDs)5?fHSeT%P87NxzG*-;(ZXm`ezCOZFT*>r*lh`KARj~HmiBXlLN!!1OXgm+V`H&8f`vEZ$e!1L1l_dZg@AD7VLihb8I#l&NKkiZhEhZEhCA-@rXwbEIO+8!-1l38F{d-+bOs&XC@#h8X({X}?Cm%_@B{v;AfKb2VvCJ(U^_@+EVa-PUX+=*h)!J8B^752jgU<7Ar`^Vi*6-pQ?1f-JQv)tx^HE=36X2cPyR?lwy%exfU(QIpH9OD1VAm z?m>(lmNv{K5e}FfR-e!9x0%f*o6Qssg^e9cwCFW*lCtIVVcpWz%J=gM?QI(v^HFzxcz_%ubg`#eDC)=(j!?jR zSkl>t{4S^8?{o(4KGGzZbRXjHqVyW%&#m_wMGcXA;22bB%|C>*S)9no+Oi>h#b46l zx33<@ns~}5SwAa1VwW1x$4DoLWJT{$r~Hj zD~%S`>t2jMh{}ph3Q5k-dF`lT8tIi6bTF{*xCjE19$MocLOD7$k_&6W2ph?U1d-h@ zBmCgx0yC!(n0-lPq}CCBp4XWEf3)xK)xPVC&|8e& zcg&YovV!|AtWuEsiyn{7wlYA+e=E(4-QR|jqVz7LS0Gk8JO3kM54qhzM=Tmv)o?hM z`wF`O-A0~#vU}m>)CJ1t`d(ZN(nZ4llz{fcz0X zX?JkW&VV#=sBcQY^rzLd-<>Rn3*#Xifijru!PchX_Ks>Gol6JiRgIm$%r^_!+;BFO zi#dZ~xW7hYAybbOmWE2><*Zswm%pkrm{WN*&Ki|LWlQqjpU;CxdxX?3>3zI5$?r61 zhn7*aqZc<)@K5^}gtuF`Rv2blFs8-aYa~tt1&M)?rRE_dhS*fD1tN#kV1A9rbc2X2 zkAcWqs*gw}-OIe#Pgh|-neTyeX&khV{?OYqYe%iHo24JH?d)Qlr7+)vn2Vl)|3M4L zQ}pDjzZfcp!o?uIh6+Cj6@uXcZhi>`3n9`5W$7aH-i1+TiOsoWN<=9JtHokO6DRHE zMw4Gzd7#(Zti&ppf2Bk!%;L0BJM zy_6j-yZqL-|JyOOI{0f+`QIWwWX|Bez+~Vw!|DEWlzsl`z(4wD{3jYu44gi2dhmvS z4D7Q$a4-98?GyD+$e*~A-O;$KcK^>mj=9dgf|oVP#-|1o@*OyGybg|0yp0>Kq0bgs z2k(68etnWn49Xu(y?g@M9!(Mu7h{X@ReAucw41Mzdu^3ykuY_72|2~Vue?eR9I{Fm zuD*}mBL56p)_$+W?9(aIE_?`4Ru8Rt?CrZQ+BkpyPT$b>@lQ2 zW};h9dfnLA>t3}jMpUi<-+MHCHhZ7HNgg4xgm{A*$>Y_&*3OAkYJB^UZ|C{*8!tL* zDz@!agyy8J(V4XYkE5Vt5ByC}=dEm}}KKQN>q?CyG9~-Z*AZiSGEZDB?uFRAzj`DbZsofqgZo@@Pjo_GXxO&FYR`lgE>B`kR*=_PIxP#n&uYvBs z5|XtSuY98;VOO%`Pk(m2{FkYhFC9M!dcdd=4`ycg5y}j^t^13g{gfq-AD6F~BJBHE zgk8fE+); zyG-Ge4@j?r^cN-n1L(zvqRPO{Xhk2RW0d4+h*E`aZH=KrJ~<`pOL#OlDctz`u#OY|v+g&x?+`W|hB1(m`#{YGPL1<(%<%|kN%S$WW5XfrV5$E3 zHV!ji;JY38nmv4QDQQ~ZFxY#@l)mxzDCU(VX_|eJ-2g-Bz|J#SNvAh>N~zc;Lm3j^ zesRnDiWgjd-3Q-`uhVyh-d%pvJ4)gkIKB)V|FVVyhlMp9XL6Gp7!coBzI6Eo#rJhT zMc>{#U3$lx%I^-{CB9*1Hv-FEj>UWrrvm0q49*&SbZuPP&;77`w4UJzJjBP)kYmqW z!`{ozk#9B>MX!lb>&g>5_S6Ys#3<(sFL zhimC{ZD=#?*Eg`6*_9mE(nko_(gNZ7Ha?vNE*D}EhLiJ+$FUkDU?Xdrjtz8dHU-)< z1fTL|cXqUr_u`CrG~8YW|Ef|@`I!7Vtbr9CjNK>?AX8y5o6!u4txUVe!)@c4?R>t? z0t3(q>{INH?g{Lrzg;Dt8Qk=%Uv29CX-u9*o!5XH0IwMMjJz3I1Rceosyv750wiQt z9cJWy2L=oV3+ndVXC%L|_ZLJjBFZM@y}$Bn+iYs|Z`TZ5@lwf9f2lNZ#iddCQ}H{e zr@u5k{Y4s0#(NeYVn2{Sg3)SJ>&{Ckp2nDq;>pKb_#Y|=*=uk46Bke?Swib#tW^<87$vKkTJ-tbO>gC{c{^;{} zZ9Zp9)xU8pZZMf0&TL5CcCn_b#QRm~D-FCh9nZ~WhL^MB9L1?i)>YsQ5Lvju&TOIJB7vCMGX zxXVsXHuu{@P#-*(L*6zI&Wa-0+pDeygJVaaydJ>j=vs` z)B`7GYN1dqld6S7wU4Uf{(2}}_m8V%PM6aO4>(YdMCyM0;BvZLk$62Cuf_2{ZZ{&{ zPObhM?vs2wveUEBQ1f$z>V^$LHJ6(lo^{sdyo+X*3b}%*;AKH2h-+?)9fzYWF}pPT zIQ0;f(M1RQdC;u<#$GMIWex&fE({2U*Fkhws9sj`bkh)UDkK`em0&S7l zEf(7%(fhmHY&%pjD4s$sGP0v%myNion#mSZLGh=9qq1|r>akmtFM9{a2lJbnrr8UO zsS0uuG23yUQouUdPu$uyvd_uY7)0FxP4<|laqCD6E5WCl7iWj>(Jm0 zM|h(gP5S`)8#KA&Jt{aWhBvtf)?E|Yt2l^>wy44XB(|aZrHkY3YBW|GjYrEVOB)(Z zHjWz_ZY?#&?Q@LhC(b?E$L*6GaKss=;ZWWygRfh{zoHv)*`lYAxl ztjO;!B9I!vtzP)19XqQV=tc<&r;J8_0N>Ws6WciP5A)UuM+s+v9WDFN+;XnG5RF)@ zc7HYzAE~7+e+fRt*tbLXScjG?fqWJxbCzr)uRdx5$x7SAo-vH<=JXC{Fo|NXb)H2FzZ!FX2;y7hGa8mgcM3v0w&0cCy1qSFwf+ z#)~cw8_Uz5@p|T%)ow{nS6OBJq{Y31vFQB3fZgXbg*VI`Gv0k-^Kk`OL1Y$@kv|tV zPtW5gy5H)bR?njjL+S{77w<5H=2c-A9^-bQ!K-$aiXqBSAjwI*lZ)F1iZ*)o0lK3g z?tKWE_$u&j3_e9v$RKdW-;Ue2oHM~vBQrCR%5){=D4^LW)hvb@<+QhU^l-3L^vqsf zJxQ2{qpv?@=Nq<^JuciTT~qv#u-m0h>>l5HVA|rb-!%PfIzu|bi5q@?2l*jenUG3z zJ~Vg@#C&$M#bB^S?S_@OjkW}D0~#MkQ#9!XiXB=3gc^a+Vcu2#mDTRHWA`5V!tzaM zlWVz-_}22`>i@8R$J-5z@)R+x4yOgd4RhZ)ZY@bY5lqJW3a4UvPRM%c04PvGz0@J{dSOe-IlEG(Ti z$@Wd3Km9cRcCQBw7AsyJZ3z`7OOt!Ypi4y;;MaK-CP9%blu0CFaWT|V9w!e13kf{| zEvZmOr2mo@M6QV6pecW}F*9>wb7OR}IXoFLC|heMoGy>P{4MtD;WM{)KgiB#A5|`I zX>}j6;H}T>)yrqLF{%?h_;1bwuS6YR%;-f+q`~F2ui(8T6kCXeq2dRo45j01`M>}O z3y4rWs+0Y=o9|5jeiCnY82;-NTWYbB*1xk^8bCb!C&&PGIF{fgoF=^JmRFW1Iv z!C<*uMNf~TLypv@8t3|2#>NM^{FM31M* zX7Shpo=5^o(Xo2>g>=E|Eu`G?zVbq;wmjJVnms;IzrZ+Ql}#p>CuH28E28_e;r1}s z&@aNFHqMB89Voh@4bSZ|IdiE{$`g!~_>EgegTrQb+ic;uzc|D$i43J9mDrxQ7#=&d| z^}6?1I@qtbySx8x+sS^t!_|GlkvQbNmh6dgd1txYz3gx=xCDL3BC#j5y%&(uQW|%5 z3}F8Sab`J&Zqtq)8!vp(MR}jXe61x7PS;J>T|b z{jHL3#rrP0XR;57w|lmd^U30-q0IcSh2Ad7Qq$A#g_F;4PXyOZxR<*!7qq3*W>xhz zMjb2vkG%H)kE=M>fX~d?BCVv=_M(-xYFDe?nrhA2rUqj5JIy_gG<06;kl8_h=RF#ckaA{ZrQYxM?< zCN-FJ)oL~wHN;(Emn6Xp4ht?rd=+D_)FYmV#ay?bsd<5IVRdb%tE-23qtV2%Qx1F5 zVqTiCYNO!jG%j;VwXqy*Hq!8Hn1c-#mtD4U12>o*R?Q}}ieioBJ84o?=%eBbd^S|u z#IdMDxi~{+<@BUaZ@xKt?X^+yZ5)4i=GA+rKK$Fp#_bIa%UA(6RDQ)Yi9@IYtmPNO z4jcY|Wi7w>6g%t{uuLq+bji2a@W0Hf$n#d@da8VRmG^kc^DtxXDa*?(r=yFc>VXyP zAvqn;Ic1)|w~9@!y4faQX-A($B@O%nKMyU$E!hsb-0lp;=nG}F5#mp*IZ=41u%U_t zdyoj>0`!}4(yIE#mE-r_HFjZn>4NchCHCUndQI)}@e@`wMoZ?7Zk`>D{ya+F0ZUm3 zyL!4Hjn@flV5zhU=jvu)JZ*a%o8>hI>1h~Db1+gc(GYh#sVL9~P^A66efwG-cwp6& zqaS!+^pn&wH7~SmnC-C?WEY|_G?Ut;tJ%ZsAIKduvP@&pYv2}#D<)8MKG{LAECJ&x zGRUx9=k6*w=bZAL=Tz)D_v*dpm7Kq~?ELdfF1(QRnMtVUBbEb;8+JwE{=6b9CBN2IrNM4 zf>P-q;Ped2(_@xIqt!C1wU`qiN+Gs`b_y1=cl7Aj&|I<;qm4?BwGam9!t zs-sRUknu!ok75(VJ?y^n>Cf$(^gb;q$4Isu{$!L?3~XK_i+L+0Rd1LSDJ^>?F`wOw zgiFf*S@Hg)eb1roA;}Cn${u00VbX44UBOIIaz;ZbSjL*>5L<{m?#&>8tQ~jjxsx8M zNW9;2YF$Zc25sI5DB0Cptxq7ABS>v!7NxH0j)1Z*6H&dc_Q@M~krj z(G{|`cnVC;TToPak@_xo{x#ck7@8SNI7Er?^=|}J$^tvvBHE0XC z4&QOWZ4xu!>2}azZY&ckRji=kelDKHQrkv#wmM==i2 z#H(W77`7xzF@iX>g96VX1~cl~a!l*cE&Ie*4~v95XA~Cy`~CHB1Lt;)3Ww@_iBD}& zmo?91%ClPY+($7UHhbM!Svh$oTV6IR%O4et6h;dIqrzb%Eh&unM@0&J7N6bbx6tV~ zesi|Zg<}aUezYnJT;U@)Uz4%MLCi#f;(|sdxro@RfemO5m>8^oLsl>{Vi<9Xk1~BA z{4By&SCGS=b$?bmi;iTO1;2FUv97H&)J%S=G5y(G>UNhlugYcL%T06z@usw`IT&oF z>*!!zP8n{RC~6ZPmeH7Z%7BR-;9p{HH1koXi7c_G4O9oH#Bs2w?z$4bH8v-z*N)ceoQ*XnFB?58LKuV< zIyl4eZb(3kuq7H=2ZLU3Ads11p}_=@mi}nz$5^q{8#Wzm|Eey-%-_&VG!3m)%MMPP zo?d7VS9%U?&Z`XD8`n&kw5l;Ux^T{Lp%TiLsRywqvZ4$I{Gnxa2~O#L@|KBdSGQ(25bK)hbuY zEb>rd#v_NXx`y9$^19M8zUJiJJb3aww6AVp9Q!daKSMGdOv7vwrd1_6fS4d^4v zL$i>%j$MM4bd&yeL@w2&=`>$|#6LeND6C+h9h~r1;0w4$nylBSbB%#zU|BPCAczkP ze??u+sI?M94%Ay^h}D8;pM@>@D(6pr>R?+RsD|&}HH}SaiSH(^(8E&$JbqwR`^SKK z8fpc^!@-*Xw&Kp^3>o01voe3Nw)32&KOOZ)Jqy(*PV%`Y=Tk}-d>Vah!lRJ=s!(^ztNH>FoK-+3CxFxqY3QdW#c9o%4a+M-hTGB#@f?Te%yEGU>W4~d@ z3gcod-I8U@)9F0c;v9RvL(YhnyIrLr1H0EedBLLTj)BBqi|YNxaBC?`PdwoCWxKL6 zt8+aN^Ep9tw%?iC*w~PVKJ?Mr8-5*hyasTcOq+*oDo@vK3ac(XFisUn4YN0KLF_t& z1zcr|3T8PSMwy7Mo(wofjx)zU7&WUzr?XbgYYNnrnsG{_E*PuHZE?XbM&8t=wP~0c zZtQF>YL3|COiv)+UOYOKrID@Pf{d~`HI+Sct89^6V_sI>yb7bSDJ!FC{q*{68y3{o zmrw63&Ivm+iYK&%y>LJeyvxN-lVkiW@CVv=S1palh|E%xpFbKN9%;KqrVaBEV$X(< z?W7V5GTqFua|m!vC6DCdRxv+}(LZ#H)4UgL7rI#)>ugMXpJjWoqp8~KtMYoQ^RP}e z%W2MXWt-f;OFsAIRr|9X#&n}KJ=2k$?c^IyUTp|AVX0^+)C3k9tb(sH5*fkV+Do=r zB_%caFvZgXxy)8@^E8#ngQn`8{SMm=R?f}v)L0>Lz<%;Vb; z-LN6LgXZxju{XfyS}>2^2kri6c(RmjW!Le`H9CG4r(JM5l1yj1?*kyn^Ec`W8N$s zD{(Ljk*v?$dR9dPVDBzO^_$*Z5!_(8X z`m{8-5Tj)qw2TD6p+B;RnK5Pwp;~XZuBm#+@nrE9zA!QF%D|_eglL~^2I}+nV10D@ zQ{Xd})jPEXRwu^EZzmo;q)YsY0L0qZk;EeeAr{Pxv)RbW2?%|Vgb=F2P$Qj$@yIX; zjkO2RSv0ZDK1e*SRD(@UR*hPgA+2F=V-1J`A&tz_({Q9*3WR#~%&C{_wOEq;c10pi zAY#S3LpN8j+3`#kOhU+jd_HD>#yG4jW|y+DeInDqva)nEu}gwg2Q?W6VKJsBSgAj0 zxFdQ{t!AKrwLI`pWLe_m%E&_xL{~9w5Bu{$$Ns7{YpV7;4i<0OB6_0%z2QPx=Esd% z9d??e4|Zs;fWFw@Lugvho*0(;L&}`VO6G~LdLYZNS_>yY?*s(M>WVdqnKCzL;x8M+ zUZJ23&ZVG-H^cuvC;&#@0<^-8fok<-YhL!ewEFQYw;cYFWiMM-^5KUiiEE;4z3cVY zUC7@cwE{AK7x|aQ?AmONY{G6Wwe1(cvw8XW|+vlubx{mzu=ZQ%R|1k;)2FMBvNtRAn2aODW>`j8)`>Q`Bh^Xoy!Pwsk}-Jy>0)SdzWQS_hTM4C7%zTR zKN#cBr;DNOJ8?0^U?uDe`7|-$xj?O{+;~b10WY~R2`{N-WDeElmc&L;gi;G>*Qqgf z4Ucj7)EI|{$2f6nj1$9Ru*Q)w&~G^DDcM%^;P%tR*gh=A!PCSzI2Z#3j)cZsVCsov zMHoZekuk<120J)rSY4$PBk=>dBsL1517@O};W3isB>M$#PX`gT<5GHGt75vqWTGpj zwkO9AVItbBM=W*a)Lan15|3i?!&Kt1xWjS`AU69#06;_=QF2P20hCO1L^%rV0}MvO zN@=Cq2=MYjXp)T(-ZO-jAf|q2o2%ViX$)0RspcSsqeWCpwRFT)+pU&eB$*SJun*tvvJxAs~ay)-ZX{U_j(F(Uzi&G!}A+#b?rW|r{E`~QJjvhVA{( z;ZPisJ6ib5pomZ~mAG)oC*n8Wci(*?4XzXeKzfUxC#f%@IY`-JTaub(YIev}WCf<0Napip-`7_WTqdT}4tbzjH1noZOXLB|u3DvuNPU*5 z&qjG;-^nyl7f}zXPqdIEZ|0*+5$V*Rm1sm5p^aaA@=ShhUtiyGsySdnDTH2zV}Qu2 zlBs8ym%=t@oP&+hlQ5y~Q8FQMFaDIwu18)Jj`#62g38G+6^KZE>|voP(A=y6<9;|H zQZp2Njm(e@ovl@EZethrB{=26zx?_tB`1Q2S~u!1+v8~r&gL8KDT?I1Rb6%sOe znMqa=6&u1Vf}p6SlEd!4WEROj|NiOVM{~RZ^O3~o;EJD9bB-TBc{mA|I`;jM5JY^6 z4ML^3U%xyIf~Lo?!cgomBc>zjM%%`!(X&YN#&Lq*_~U&1*Ds4Qq$6~%mN5)|D1%r* zVxwF`)k%e)=`lE!83doweu4H#(|r65r4e$?$-_hK5ZqBf56eqZ>Zd?YsUcRKDs@zI zKrNLlgk%sLnMOi3tbQ0K8j^Vs6iLh^A$gW6WdB;fIB}N9h?xl$Nv@k-i{e zqHn(hnT)_bxC$Zq8p7NZ>K%rAn^e3|<0tW4rCx!0NxV2627wj?1u6(cwU7=7Tuatd zm8g)WXuk5E5x$Rn&n_H%FVYhal67!6L^HQ!%p;LXe~^F#{R79F`IdSUMoYLM)Ok0`yO0q?jKy>eLeA?4(YWx@7|okrv@6TCz< zeG1Pj3Nk63MIDyxG|CIv=->f&$;fZwPe)FL4jl*roTAptSSNXay*1bxSVq&Z*9o>s z1q|p-MVO}y=5sgHZ>-;-HUv5BNgP2dZdO}?ov4^%Aw&?|f2hKQVW{d6R-bqgb4d+} zpRr%kZ|ZA!{~}JE>>*$pX%9#t5G~oqh#~q2d(1>1(PYv}#bRo2d*k>U2Ncp4JPxg* z9EpF!giIFrWL(6gh+uF@92fZx+DsHH5cpcN;A*?63SjkAOW>KBr@&pxlj>P*--E&h zXG+a8r8P;qQ7m%9`M$xB%2|_qAGso5;`?YA$qnMJ3g1%r4YiJ-k&;-UBuYuC)HES8#k#QBx>dlp{*f_MO6Avn12wEHhmXe576#Zr4<`3VbFhnVM7T*qkcefLX*9 z!P``IWQZ#gZ7C8*;caN5NUBma+LumakRg#EC=#txXp(e(B1(W|H|SF3Mo5)`RC)uB z=vJgXwUPL*NGHluQYK+4gt3}h)E2(_)mb^Ut~9PzWH1yA>I;;YltC&^P)$?BH_<;* zi9QrPc%Yet_F?ei;q@Grh6XmL^t+JuLgWpl%sO2Q)s*NFA(7vBBPo*!JgJg@6*!n- zzfPY`M2ebXhrZaS?xmANk9cb)6tuNfFP_=x>#Hi5P~+|MHO^dIofrcwQlz?)K7(dH zDE(M$Rlp_`paRxl*jb$tzpahZV&wIVk`-ku==eCRLm!zi0y3ndgUM+FdWg>Qm6fwv zc03oa@07)wi8s1{gygejD|)Q0fLP)o4#lBDNxNKcY& zL=oV2Si+128dR4g=O+1yahMoGN=G0+>%L8X9zn!m@{?@1Z^FuZ0eBF!i&(lwk7cT9 zLZoGa@}NZwV$~_SwyOD1HHf7E|EcFwYC0t8X}~Pr4{J&CJ-aaZUeOd3z9|@* zBIzL!>5p_gtSPt<)Y^PpNc!WD^a_@!@)J=OyMLLoUJp!u$Asg@kV=p1Qht13g?qOf7!2=D@xiiHN6V!-v< zs!*6B@j+u^IHw9y>PJPlI*lAnLPu#KGY*PUqyjFGBp``DLXsv^spY9H1Tl9dU}8*aH_xcL&7fX0SXYc#p`9rFRX{eWjfR9iswkvrqskA}y26U|KhgP8^%Jy} zdWLL(gpq^#Nm6e>YoUoH$I7bCp~`+0pJ}9C5Z){n$(R2mD$^bRZO9oVRS<_ zL$an|@xoB@;i*O1!K8woC#oe=O6;D)vI#*=KqZp4bfG`k2w~U~WHnUgVE+PUr>7hx zt?1=syNA(@TJ~x9gOZZQ4XNBgP58=Y4oAeCXJFr%2PO!7=Ne?DT&vDD$@1|C0L=}q;RBSzJ*_2M7!jO@UsDMT_N^M0# zSjxXd+LZo;PsCW3$)L@^v456S+yrFsk8B195Xg>(W{Sa9P;=7WOSC2EOSufrj{tUx z6)X+geuo6iDcMfEp0Wd767HY4SPW=sHbA9RSy<9CK;{PQn^9Plng{itQY}I0stx-z zvWI%+wA7yDaw(%?L4iXYQq>zykp@aibyTd>T-7uLMG2fADIvw!HaRLDDIduP&LC}l zgRB`wU8G|R7@v}EP;yiJk&;_ks|6~Yt6&K9BxSICnvuFyth>cNOrzGqST+q4#0ov- zOJQI_2rF3;x+%k9fp3(pIu0l6q(F9IYE&>#)II}yEevVcFRjI&F!Cf1*dCs{Xp+bk zXrbgL*1aS(5|NLrGw5~rQ_!SQ@!mJsP^EUFv4agJdVuXyJ8T}c4jvU|V!klFJla}6 zdWy~_9b&P4V&ycpfQjY8Bc^H#Mog~OlN$(S**N(vPTk~}>gw}f&B4O`y47YYcA9Qhwl{gjC@d=VU}qk! zkjJh=tW9z}Zp*F6FRIf~Psg!DKIi4rcXM5Rat{l9>L`ALLqXa=fn8*92%!A_a>P>k zBI?Gh+&t5We!Tvh`f-6Dt#@W*xs2$yQ|r&g&iy~}PH@IT>~l%?xH9tc;GkW2*(7fs zG@a6mZBxzwc{PWN0rG?s3KoPE->fxbw6$29oYzt#Cw?T?$8rNar=YYn?98k1wij!P z+w&^(T#@p!i0j16wlu%CC|pq5gq`Gh_4RqRp@OE;f^bnSHdM>NC5e5; zr(m~V5j-ftlR$|jJ6mVB=a*MhFmQib0IqOrn&QMyQq-_^7<=VGEQ+rNpij!yz3PzahV3+UUBvrb(rxlbY)4Mo+8A zZ}8_-hr?AKl$niVC;ouGqp>JZQLLG$w%X;8TzlO6o8Nek^3KGcy@~f(fObHn4;+U- zlI`g6?1Sl8m#-mXk1(0Sah;4k2F@`u+t%e6KW{(0A{q!K>x^soc_4lGzpk#0anIw5Na%gFc0cXgQ1?k=S2mVhLsG-58T*27B|RP^U<3BBDI;5KHKjeK z&WLc=>6|bjcG(>Z$~&$<|F5&Y6Po8}npKn8*meIs9gVZ^jW6EDBKzGNp1W@{<)|4r z$xZxfv>M)^4@gFx4eqU!d7$m6C34j|;o_;@VR0^7(zs^L=GH0YbEb@o#Xc{a-co7V z=wCZuH@EH5o!gpb70sDF?~J8m8ZWx_k?!aZ67TJ%{Ar)q>wrg;28v%DhN(D7Ue_(cfH@AFImCA3Y6cUbsE}i6&>jVW^)LiDnec zI&0qC9Wx`j4ebpdMc{6`ITCD$S~Zc;`E>H}clj{*fg*u0m@o@rP-RbUx0G zIf|YrgO7+AQYdC;)usL#pK(l@cA`VxRIW|mREBP9LDSGU>ih{RKcjXD$6?CGINEfo z2Wr*BEfb-KGfYm7#Mw+#GuFdDjG`f6IORmjOh%-s7S-F`VIR*B@35EVqH6^CvnZaC~ zfgNcIx~faV8MkHUI9*w?mY3QJBG!ct?5cezGZ=~5>>*D!Ye~-ugiL`_oXu{ElmOFX z1BbDb^(I(@O$R|U25jCO+ItBzF6|(t0awWUx9~U|&v53VU8AOtXBYCk%2a{2ZQ8@5G6KJ(4$Ou}q#YV|LCNzF@sUs*bQtw1g-LMA|0{M^JDMlRUam z97nH0RNYyqxFu7d2G$JIIPC;gJf~QJE9BKy1+dfM__TVVuLeiyWMpT!@=UIR9HXte zta?mtrrl==22jyJQ-!IK%Z!`ceygj{WhrPbat585)_}wB&nX)d$TX#8;wY42xFpkM zx}$JnURPW+n(HzwMnk4P(~$>c%gr+9mwL@!k6mYL2uH>i*rT~sTqkF_t(o>9yxq85 zrVMwk-(rj8r90|_frhA)<>)Q0Y_r>8P0yCIa5RD`*I`N1!g>HdPLdUT#sa@)qo6^# z4oY5ox@JPfA@=mXrU)c}sy_9KZF&OJpeBKK;BQCS+N^9gCiPzh7imJ|N4M)axR8_*8p}S=Clt^@Pz0X%S zxv+3@9bL~VnpEhoEp${DSuwUSW@xnrN50!zk(XgJ=K1qiMkg+=sa-rVife80^jH}V zWFIxNxMaqtU|`hDQd|S%MJL&2BY0Jb6vG}__n?+7Pn-tJY2#Gp=k&NsTKnH}*(PC_fyotov8ZJAc>NEZ-4vyGzn`-R{5#yMO+T z7IDdfg>)bCvmv2uQgO_V*<-9@$grAZi$%(&?I|*r;*q0h2=qqCB3MU-fmDnYsd;dd ziL)3(wb*@PUp4>T1=~ZDrv-|dP4&pw3b!nOVNF+Gop}B?YuEmUC~XJU1Y87vcqYjY zsx?bb<8TNn0N4WQG=wWcOBlJal}X_-`3`LU7OuV{4)U_+u!U^4Aln4j-+7h!cEt&J zy(4UgOK@kt1IO)vQ~VT4Nj^U)tgNVn@I49-bbl*xOyc|i$+=4rV+}v3iOB5};b&BC ze~x77ZwFShTz(nS86IV*C)f+^>#jEcw)F-%^!2;cqMb%#=}p{{7)S0XkaHz+eg-*b(7B@+uF~QWv8okR`>uhF=az7DX{0f1G5fvQ6oTW*L$&#q z;)PQTcJBmZo&)ZKOB(ZPOnF5)1u^m=8H={+;8Ltvn{Lp(UpF~yi#A0Xd)o3keWiI> z?Cn6Y!%`Ufg`;VD3LLDE&DLz03=>j@-iQk6aW=MqIIU+Rh|pC5jF=o90uF}Sb^u6# z2tazXnb`nSRl>Mr&A$~|zqiIWJ5j2^4alb!9c zSv^Mdvm1N)-)HYiZq(j#5Dpi;=@yblVoj^E#a-c@;4^UOL8B8Ap`~SEb8gd_({8c_YRk?3dOve)HwV2o zn(vS*29B`juv(LB96Gj}jJ{QHGlFsf4_Gwq73Hk9V%)_G7hVi1Nv=@#;k<+*vL{3f z3k?ByPBRqvD5X+q1-<8rOSi{+KQoOD<16ni<} zfBecTqseWRnx~&l-y6Nl(`)oNTb|q^!%-lqesghs6FR@PpzUvs-L-vsGgc251@zS^WnM_2 zyx>-rmiThrPg9DZQU$0}o}p9+NfI0>W;Hm8T$+7?$-G&asFE{6YPI`ZOG01<^i&Z=Ly zRaqdOh-nYuJqp@wZAf8B&@${;wwFA>M+cHq6%RzH#fA)BEyFS*vQaHNn0cJ?qYXM4ZbjNsGEc1oa^01IW;voMac)*PGgPHZNz_#(U~oN zt(i0D;cS=D=*q4!x*|2oPovA2Rv*bJbkBAdx#6oZQd2|UxS8!P%t=0o4&}oPLn(L6 zK86=+QOtn6luU`j15$v!j2?qWFKJW^NBUZHX1xrjqBzOgm~Aqp!vK(;4&4vvB3uc% zXk!ALGQEn+E)tUhv}46If!G;bjeW?pH&_p&Jd0hH_}p^orMBJCmvUD1x?xgzF|mKa zEjgEWTz_P7)|s0!Iuk@ELnD40cEZtFyT}+eU$J$(;R}wVk(z${eSw6GHYqjKh zEG*<@E@4xsw;QvO(r7=+?Fr_%Lpf!2raYTN95I+?$jEg$d^x!hkD%^SDP8*rdsc7~ z1>biwNh~O+ezi&*z>;(=Cfth+IVWcq4mu{+LL(mXsZP??A=SiOSvDKjGTCm*#HmR3 zG_%ekH#l)E<29` zUjr}fiO(l}_+jG3#{zs_;ITi9z7HPWxOC0HQ}SH+hanH|fmpU79sb?O$$Jd$!t}H@ zG6!BR$;4|SakgFY7{=gO_>KPoJMZKz@Ep&K4e*Nh;fK$QT3&}*wyU)a$IS5NrN=@a z;SwzdUu0ySRI5plBBfGwkA_9XOm0Udd14xS{A3K3RotW{evi5)z7N^`(7*v!CDD2f zSf3QF_Y7#gzbzRvY5D=YKfqpKVSWc>2hK_O_Hzw>k!x~h!6AVMXQW%h>;;$6pJzzJ zNt_urPdLwB3C_i&{W$NV5@QK7^2>O7@F)thIw?#CT!`2a8PzO4chKqdI$dFVF+bbm ztTSgqpg4R*0atC)H|pbO^IDt(yACZfJSvf|C35mJ7cBVf1g~vqpf+&>o5r6^ZSz>j zALj0D>EMQoimpN4>SzF)R@#ZpOV6=qP;as$WIk$=ge$qB(Fn-UDXhMZYF6Q`%)>u( z2D5Xr*)zG7USCCCS=eRrm?@uUkxzx14-=eR^;D{`Qc%2N{Cv*j&JH>mPNdGOWY1KX zJtkMUEU&`nt;|I(hj|oB(~8 z`KC-c@($j=3xAFIs7XrCkEQqdlP0mJ-l6f%MqYz)*iCR#3--YfeZ;oXk`*hQ8tgg0G38>Ye zt36SO@U5hrT1k3r9XqegQRsDq^2nKEW~H&Jz!AvJvzYAsp4_5<;%hHhMpr6X#cGs% zn<)7vp20xR&?|wDy4_Biq9DEqI~|!KVZBo%Me>+T7Bf2_$807`R+d{AwiJ3CVF%x4 zcbe>0J*HJG=5(Vm(-v`BEdeTTDf+S>eHoE=K2KxaoxRlF&D_MU$S7Q|Yfk>z*7~$ASZIYV`8i1IJH}$CHf%3)n>iKi28xvv7t_1ohhovqbs8QE)0c zKO_ScBtC)`l2SCJMZM;A4Kk_3Uq&z~g5(beni%SYp1WQ2+zkVHtQfh{3R#Ikm7{zo z=*hYTlPX4KWFdB%0#$jrbp;-G*yeq-LhB5>Jy8A4w&Eg--HTkez$^V#1J9$r=b^s! zcyjhYFWWJ28=hQ%ClzSH4(0{7Jws5q=qJ>_Hela@^@rZZMyi{7x)KIBbh;8xQaoBq zc6Li9oei(9Sxr54F?G)JZoAhDyMXt0dmawI^?G@Y*Jkt4qddFv3N=wV2l74b>2nFY zH_u`B`FNoh-X`HDk?=$1E~q_Lq>2Uhuh5POun6ZsZ~Z*>)kRUT9VWd6F4M!GKs?OJ zGp=-hGG-z&ubvxnu8UIZRDOh*dg4IfBYG@Z6Ru6GtCDUpkv-1Eak6Pu9W4VAzPQ1A zP0>XNC~GGStmg|3hIJX3dJ(Q;kQVhCi_sDs?yxgDmgetp^l{zFHQviEN16+{4)W+A zc$eiMmV%&)faxn$iXKC)0pt2&ec8;}kqumTd1&Q|5b>>Ep2;rf7Ya(l@no`QH!a5^ zN)vnwy`l0f!kDj$r}^`(O(B0%Zmy-gmS+}3O1;rgsK}LXL%UV31-;WFWgl>)?KX+5 zg!gX6oR-uhg}Gu-6X3%rCs^2DDCWjMQIy&oLwl}(W(c8lzV|c z#!BT}+&}nNYV{@jS~gY2aSifAuhRWPxL+sk9~SX9^1bYrauMFcl__eqh+oNWl%2z* z;BFzpM@Ye4fh!$1c=zxtBg=a7CnDz#el@#Rb|WWDN#p)Zeh0f)w&H%@*9P3*!Pm0+ zvIqD3Maf;L`Ta7vnLqz4xUFPYKwABkzk<;@C`#H5`Kee`g^7yJBVacgtY+<~%rGBQ zX)G+LX~_llsO=X#Aroi8T5znZvfnesJ#i{WxC>ms2GYOHmX}9Y9v{gOP}g4pip_wc z9=e1`wL<7^y}(yuV;jAXjQ^ zwbEg-TXY!p&_;e8yA0z?oqXQcX9JdM4+%=leAJO92vNwDQ>})BWcU@)Uy;jQ=ynyl zL4jsaB44XgVbtve73RSAbB@iLLuK~zr`aQLA*vTtxCbrRihjRFXesarjQS_F6cf2R z`yG`q^6*3p>4^fx(G=nNiXk$ zOg{fK&vy=c-lE3W$$QP`s%@@ zN7Vgp>0xP&@*MPf*WmN-;cdVA`~~@YgU|O$7fQzlL0-dG^RxMjvP&+Ko8_7ED)}t=GWo|EoJFb0(^P0$H7hiIntsi#nuD63 zY2MeI(3-VTZHsoUc7?W2d#Uy|ZCv}T_Sf3abw-_ESEbvnyF>SPy`3i&VI{OVp?Ll)$~_$p}EOC!`x-wV!p_Hv-u&5-BMr~Ynf|VZ`p16uH_!f z91+h=xY zVfF(1DEkci8v9QBRrc@MpR)hfp~0+K)Y0Ua;n?E1&T+5f3CGKh_Z)w9%1(!~(Anf% z?%d|Q*m<+_A?NR0O|BWPrLJwRt6lfGe(d_S>(8zMcaFQ#J=wj~y~(}TeWUw+_hI*| z?ho9b=V)^RIn_B&<-F#Jcs6^U@Vw-C*YjC!daeh@X7%TOp0_IR?7S=T?#lbj%e+=^ z*xTZr?Oo+P-+QxnpZ90p-+4dvNj|GD?5p=p@h$Xi^Ih$`+t2(~f0uu?|DM3)z=FV; zfpY>^1-=(J5co96g0^5ma8z)5a7}P$@QUD_!AFBH1V0S^J(L#836+G#gxW%DLOVlO zgnknGWxh7wl|Mayb^h7;SLEN7|3>~FFn!h*K0Ew!`2C0_k{_vyOo=RvtdAUyycRiH zkXukz5G$Bdu)5%af?Epq7ra>TPQhoyqq}?vl+V7nc02w4`)Q zX=mx$(hEy(EWN+vxurZ@-cUZRd`bDOzGA+I6QP~OncFt%Z8!`z024LuEKHf(J;r{TheD;lnE zxV_;A4G%XQY&g>JLc^;KzixQ1;g1cUHvD&EedFB58yY_w2}zifV|`47#1866s3GP-7T^XR>!?;QR7=+DO#jaf71<}p8O@wBXKxv1sdmN#1d z9-ACn6x$R#D|S!pmDm?!$BeyX?44r|jeT+K>tm0N{oB~D#>wL{#(Bo&k6ShF;c|y0W#u_0HDETAyzHUF#pm>&KUk?>*)3zy!^N#S`{VY?}D}Nrp*FCVgkpbCc62 zn(^gNrWZG@helhK>Y5zT4KfPr7^6BSHe_;9x z(~r$an^8XFj2X*j+&tsq886SQpLxN|Cuiwq70;SBYul`AX8pNsQrpwB*UtX&?4Qm4 z#q2NUxaKs^>6&xFoFC44WzJvc7R;SF_nf&0=6*EKKQA_K>AZ92-7)Wxc~8%KecrKo ze{0vY+uI}U_3e||JK9&bpVfY8`>pK{wI6AJt^H{G=kw+Hw)q9~N6()XxgIHi~1LRf6)_*e!b|w7N;#PTs&j(n#C6_zJ2kb#V;;Cw)h`QGMD5oDOoap z$?7GyEO}tb%S-;UB(c=7v}kE;>Aa=AOZP6led*&%-&*?jE>l-|*VL}%UFUV()%BCE zW8IqWNcWiTj_zIE*L6SG{Z#jFx<6UQmYJ6YmNhQxS$6fZyOuq(?89YW^cZ?VJ@q}) zdKUGZ({p3b{+>5_K3$%^Ji2`R@}g&~uR^PKm zS~F_R>@{1~+_L8GH7~FEdhM#UADwAD^S*V#b(gMtc3on9_WGRlCF>`xU$y?F^zN)@ieP{G7?OWNmp|8L1vA!dHzwG<-4#SS5klV*s_;&aK-XlL zjo+!EWyF2zyVT!ph*znGB3zSU9De_+U_+cF{F3$fwyOv6|1X4e)Fau4dHCJ%FG3C8 z|GS|cajA{IZwcQ9qvX3%q(5CqzW=7H8|loa3Di%y|7JK{-c*+)p8O}T29!gg8G+#V zZGA&^iXsrLq{8YyRlg(*sE-#S5RFB?B}~CP;%m5KC4;z1%W+K&J-DAb;#unZk-v#& zB||EG&cgH5FhRYyraVv9buylvE{sokUVz{K7lID;AY7n8ylMN0ut~iqyidL(e1Koz ze>+frPDB{3Ug`J95W};76$lqk7yg}eCZru1%JIzcZ-&#+EY)SY8mPZ14#A}ZVH5)4 z2;maduN8sthu}_hN+22s;qgtJkw|-Gi`Iz5o9z9<|S+1}&~sk8cYH@ctfz zN7ZW@uJnvTvY(Rgh-dx`!9C&~J*W0lIkgC+6P&Mx4Y*PuIZk{md8KEBmsB>vxD8B6Zy`}k5txJgaCpC0lvgVJ&d># zeN)@25a^!Tk%d4!qZ)zU)AM`;%I^vUdLBhc<+ts)r* zl{E#yhTun_@~KSheUMxTBtvr%=slHB>6aojAyB=DUg%2q83<1GN_o?>AOijVrt7!C z@!x%Sf_(uWB}$**_i6hk^|={wxEhbzM1lH%LOJ9_TnWT~lE3MiK5G@k_$R-{{ya?eMgc!ngc>WLq@v;2~RMy$*J(c|&!m|j65PpsDE{1v= z5ne)o^;UWd;T(h>gjEPz5XgGmi*Po=O$bX7HY5B1wYdz}ClM%*eF#59ptTW{j^gh? zAYQu>;U$Ed5#B;L2Vp0|g$TU}#E0ohb^Rg2SCkIn1%yiw9zvipuyIA|KsXcO4ume$ z;R?j3GJb{dBm%Xc%A2WP@4%JnzZHS%l^Ry#ek}sMr#vbD!GLtg9>eE_1LLqd|yq;4T(tPp}u+pK$K9hnMndUe9OnPT1IQ%G!@ zjrRuc?cRU-GyEpM-Jj$4`h)(6zrtVbALSqK-{{}rKihw<{}TUQ{_p$m_5aZSkbl4b zG5?SJPX(}rArK7Y2O@#8z)gXVgFL7UrUkQumY_Z84CVyA!Q$ZLU{|m=cwLBxT%m$c zap=;}tNEeuwc$I$KMlVWel5}z>5BA3RutF^T*dn0>&h$xCox+=pDaMF0y_lmWHoH6 z3X410BbaOWDf=VNggL>Bc*QVSJi(9f7x-JUB4Rp5_+g;v1AEMVatgvD|d7U4lyNa5?kFNA*%STqV)^cGkP zoWk(2do1V60Z-~5@#l^75SD%L9emIGpbg)PKbZ4D`3Gi6djInemV7W1Pa58T z^ZlFNZ<3^U-S2!UN$>pS-L!Xp|IV>@j=od-PK_k}M)RgsUMna@VwhxOL3TfIp6D7M zdA{5sFOV0>UGg${x%>n9LFKvfP51J{@{i=_)4cXr$T;Id@?=>H5KGl4FYEB}~2jcrZ%`wD0rultpEX~`R zPw-Tv)EpP_K25&Y2wNWIKKSGV`lZt(@0F_-uu(pII`{Hc9^vhLuRN2F=f%8)=kqgp z4zJ{HUI0Fk%Qy3ld=vM}GXS|}-All8d!%*X&V4w;;x_4a$bmn|zHt8)qN3@EyELo+IBT&*R%L zYp9d2=jY3_cu>BbYp|0z#S$nCsvjD!5!(WjylMXHffGD4}18w zV}^4F|A()zK`9{9>faMi`dVwW8iCW>yNP__$2I>I>>SZbnp!5 z2zuBl=!CRvj0f_i5{6aNSlQUZ3ZS#wp_^1-tWbvWM-_DJN@)hGm!`2==;@Q8uTO+s zu~}NgCQ0+zDCpnQq;58Y&4l!9lUA}h(h4>kr?Ae&tne9-f(tP!T8vT4B8&{Xq^sE3 z7%BB)zD3=48N0ID&5Pjl^$d_Ne{3aA%U;QdCT9E9%FYwleklQjU9&c zJ|rCmFL{+cDZR)JO0TdVNpG^BNN=;BO23mnWWSL9z}}F4&tAv)bTf2}IP{-l%>Axm z?HJjXOV_Y-usdJ?RwNEBWj9s~{5Pc8R~WJ7VRR6Z=CU!;0yaT9kDV!9$DJs|y@Jr1dUv-CK-8zZ~*(oO6F=>oPv`hdM6Z;`(vZ>mNL$!aX&dX7ZeV+)8`=5NciAqC z6?aN^GdiFAYMkzSi}VP)Rl1K|C*99(kmBq%=v?$6OibC#qO-XL6Sj#lwh@&KF2PU|H7J$zesxNQ>-@l4En|= z7+d;bI|xGGEQH?Zg1(W1F`)P&*HVvFlu-mAI-<`M&87m`6#(lUdThxITrEx{0zR3 zck>0jhcD(!_%ePK^rXx975uyW8h$mulwXb&3pem<`E~qyxts6g=koLTI=+hU;am9@ zxfeWo88!|6UUu@o$|nA?Z07$ZXYoJDM*e3xoxjE3;2+9q{MY;!{Fv;39#hPZ%XYb3 zF65ueIr3<^i2qU^#eXYT@GoRHRv1M2>vALijaBXkrbmI-zUv%N#3(ntj-tKeH*|qcRvv%}t-?nwj z=1t$(xMBUeGuN(Jy=vu(-sL^Zy1SMxS-fcBg3gXJ=C{wAJG*Vx%o)?CO`S4%(!{6_ z8=p$pz6?WasI}KnS|aT;WZ-8;X$gz#TH|{0Fg~l;ACJxM2uz#PF=4zX5a{%T0`XW} z6P`e!dwG9P@)L+Cjpbcm_H7Ieh@T`M{#BeulDaq&O*kW?G$Q+{{F z{XKqvTo(>4YwPF_#93F!qu$Ty0K{0gr#}!1_&Ylf4g4gRVuu3Ai%aA7h1kWj_r=)7 za~E`=(76BNc^&&XreI``${UB@B66px9goSxD{isGjmh!XV7ex4@cdk)2<_-^r5 zBOZwR9<)L{QzAVPgC5Xxh%3)bBHq3T6@)h&Z#2rAn0SL{8azu=p6ym*N7dMAcw?eB zPhdzZ0d%Od@&T04kvJqUmKIBoW%4YJzMx0@@#IGk!#Ha4(M$+k&pzad7U1zAwtHWC z%!Aq@C*{$PAX?HHJ=;C_3^k`{DH)+irQz*EEofiR@n|Ln`5y6&7~|+uS~6iDpIICl zLhIQb=)ej4*v#TCp!PJ}%HauqpmjVpw}aw#d4ThQ&d%|rC4^o6j!>^B)Va@Q>)$wG zpUKoZt-lqB4qOumwy#?k=_>B;h!e^YqJ>POfVOgYN>6BF7ovnfHu#@{M?LfXUGZgI z#rWwrP3*^Oe@{2XmK^)I9Nxz?VMx(2fSWEeZV2^`i)Vz!4ZdlST9h|BdZQ1Gi!+A; z^$DQ~e%I>$p3pL2V63fUrDsKFH}a0hLfvsqXq;!CMj8j|axpY%!aiwcG1@i_I5?}g zZ6S!1fZ^}&AMf86(?q&^y6Jv=0CeB4z6y;WPehwcF~Q#-k9GHSA;yGG5xKMkRp_4( z>h>=O^FVU}xw#?yT(E#jnYW;$KXZ9#c?b}W#rnI^Y>&UE)6?JCBOs3IplVWSiFSxP zsmzHJ4+!_Hz!%73SyyP8@_?vwP^|J2Zg<%mI{dwE<2 z76$ywI~A0X+63SD#z@E_1OyS0{OD7WdB3TN@P_X2UfkgZ|NZfm!*5p&-cH18AcH1c zrr=6k6Cup&2*lTT;%9aez9><;Ywbab08zUGTrLIMm&XPNeeu-Mu31L{wKmhwA5<5DIi63J(+932@*R z%jm18AJ`aQ3~JMc&Ha{se`7zm)?zSRO{8aj7sP_!V$Y~;1)ND>iyp0N_f4%s}kM9)6xw8Q;se{-Y@DFeW zbQA$k8=eC2#(;VrO6`wxh%&)6l=M?5sYiXHCc_iKcS&kOt|(Q=Q0l7`s-yhlnc_b^ z98U|QqvIOXL3ypG<|OkJzfox=4XT9dDhk4&tQt|i)+EEITb-$7^feSgOofZ^oNlM`v;dlmOQ4^F0%C{jL z$3Gf{I@3D^a3}(gM183o6?&)@-2rj}ZUS_aKn`C-9RVU$>8{rkU)5Q>T**wQT&DQJ zQNg);W(#d&A*dk~(1VWw0$^zV_}pR$5z)4bM2ZMdoVWvFC7T$MCIW}l9~e}|rO;%I zvO<$3kXC4Noa1)zS7<+%SX!uoF6p6$eVpmR?ZJaXrmRdb`u?7-<%*yNl%)nwGpR~| z2Ra)6=-X|?n&x$AJsP4cP*i-o(xo6Pz=a|VzTXZ`NYGLsnMUvW2VW8(MPh5#p`}Yb1Du~{Ur;CaZH;SZD#lE>HS?QpuQ~;POSDG=EnxX}uLDT*HJ>Af- z78{8|Gb3g^x1ddpXj-G%Ho#^l+TBK_qYh}WxSiKQ&CdW52;|AY3lre@69u(1@X~~a z{{#$8tvCFz5dhWL3g`e|ZJHG8E` z(}%wd4Q57yJ~Wsk-R)zUP=+-gElP~|O#s&@(RC5D1o!=;_Az}#jR1@^p_!xl`!hm{ zNC^@9e?pRCl29(CPH^Yp58}Jf_5J;6S*L%KHZp#eNI=AoUd|f4QZ6bv7+T{Qt)#!8 z1LNt$hsw~uyPhX>16BxiB1IgEM?{LLLefRB2cVGz5Z@q}8AVLOZG|9dCA*PN=XLBt zAq1)C0Sr@|;i`=UD0m2T0_WnOj19$|ipsT%aAS`ksXfJh|7z%2tqj^01SNj^!3_{Q zEh3m)KNOzT-4M;-L&SGFUC^cGkamm_K*;2WJ*Zh37KIdyn+qAB33oJm8apwrI5hB4 zF7YdXhC|3B%s*)Tr-5;u)m4E~3Vw7VItV?^oiHPM*p8r_BWr zXvAh{@EB01!SUCP#ozWC!l`v0_|+k4Rj4mOus9?w3GINo&>D*S{fod9A?goFGjcoo z`yo>MLo}|MPXir%(@WT)Dso9bAYEoKeqJudYN-!0kpbFvXyAcd8aNG=d){EV+fgpn zpg&pgA!+4lizSp}3+Y?@i>4luAdN#xMKlq$?EXdl3ou*^9Fn|LyjmZ+H|7!>7ZtjO zDij;oRulAXt(olGQnTE*x#om#Q_XUKxzLjOWd@IUM_r>w=dtaQNFT{7V7Dp~GZTD;u1r1*qy zaq)m}5v~i1PWToS4fr~X?)7yP4*1T9RQcvdclp|*1HO5Y0pHxn3E!N80pIL`6TY@c zgKt)Nz&A5I&o?7~mv4IhfNvVEQ$zRqri2E3lS3zblS1=+6N3Z32|mAXd|<%W>buuB z&Ntv2i)+k(!q?&-@Qv~B@{RV-^EG=1d`)@FeWUUQe2sZ0d<}W?eD%2}V(5x%7=uO&9^ycw43;^Jal#4mi&M}4Zi!>*daKfGpmPY>uN{T5$JGP)BV@myq_e4<1fe56Ex zUzefx|4(h-0v=U$?SJ;(lgW#OIGJQdq?l5r)+)|S-iVewCOo8~2%>1!Av2IbUd|-E zYTFcTeYB;NtJYfUwd%D>t)k-NTI!|Na;>%A)>>cnVv34ZQIHnIfN+0n?Z-?a-p~L4 z`)=}`eb!!k?X}ll`*F^kwaz+#!XDnS_ro^nqXW+^VSPNj7YA$%hxZ;n|L_6X$GCl- zhZssNh}d(QHv`tVDaf6M*s0ClsrX9vRNziUZW_`Slubw3mtbLNK|I2f@!rr>lu=0> zbo6!Y(@-z!)f#x3*NPfv;5q(O;M=^JsHMNb{Y6mK<5~Zyh^{0d96j0B=nYerj z^ixnmIr?IEoGEmhr*Vx_ycjr40cWC}g=ga)p36RqXZgZc2zV0?4GY|cZ8EULbK0j2 ztWftw1N-o-pRKm2o9B(gGkaav5Bxj>7r@tMwSf!q+<&8ii}9p>zk$bi>+$xiu3I9i zRM^0!UX8Co!+1zC#@A(F;T8E-8u%>W*BE#K%h>|^O&@F@X|EIZy$;yKtI+ER{_6nE z115#@Oj!TgLD>U)e=qFg3D6~gKMVDGy``XO=bBBht@oo&FYpw=r=x{l^sNiHPhYEf!LGtN2Ni&~t zRoGKVU$idH0kjy<3a&%zLQ{0%+|HwyL7D`905y_;(|~C`5bZgbeY0_%gSKg;b2-l* z!GXqn^z4d4GCK5{JW{(>ngm8L0e-X&7V(HyL1Mk?s(_<*o4m7erd5pSiKE6ggTByi z(1%je2hH#FO*%zwIuw08o;3b0%}{hBRGxhRlD58?Xm?+FGwM zI)#Ug2OIS7goHKO2YWI;l>m!WfhdIkOEIj=CEg-Y3Jc*_Q6`QO0mMEp$GfiUuG08+DQ3ZTyB$`$*|5~Xf(xtoS>kLlSDb^{ z7K4>=p7$p)Uz{rzz*>I3SP1Lx1>!yY z2wV0Vak;nx*5L-&cfSqmsvI_4AkFC*o#U_iuqG(yg$q-Ub`z?Re++4qT5r#m`_ZzYF&A zpNqR;Tm1#B=l6<@;yzdyyI^r#DmIA+;mi3jY_5-B2fGCp(>QF-ZLr3ZPuVZUX4p}u zdvC*YK)`ok3$E~e;@5~q^c&c`$&+9!ynUX;{YgeV1&igSh?DjQ@r?K{@kj9|d`t7s z;yLjbtj$??C;55t0^%gTBwmJbW z(z_2k%6qs%&zB(?hR4P@_SpCgygH8c&V`BRv)&tWqIVEB*W={z@bmb*ceS?w9vNSN z&lCB2oGd591LEuQ6mL*Qyl={=_YE0iyKOBjxAn3CmfI<8yCo~`m*h0>d~cy_lFjfM zY4t9Ecic`H_a2pPa=M%W+wUxShW8aY+j|rK8fVHmzaE&iJPI&Q!RVKZJMFUK7lzW$DAQ9p+@ z>NanpcdPds?@F%(v3?No!TXZ@7Vf4f0xJ2l(4AtLywbbhdq7?#ua?)~zV8X|x45@^ z(0j;x7+#GV@U@)(!2MehtkmCyoq8=S)$0(yU9s(&E!N%@@`vyq$%n=DNAQXH03LQf z9&M}jz9nz>u7_>*4%lk%ly~7?Y?rqiKX<$spX!RGzEd;!+;m*mT^O1}c@^)}eC zhveVncKMoo9Tw;v-oMK?TheTXgDLP=P%eXwZzRlX`vh4>y-F)Z07suZ^EGWgjARJjVmMqjBycZ z<6(E7ppI2l>a%L1`W(Cxk5`jmhd)7m0dWDo2#fp4@XR|!MPT8N!LnYXYT>b1uNqXN zngR>`R9N~?hn0PrYEsRrMYXE9YE#qI3^h~DQfH{y>dS~NFb5t*XTy)^95qkPhbPzq z_!ylJZ?FsC5B3#xk-Au2q7v{oXouH9N-b7P5RITyEmg}@m+DqM@HpvHm#Q?ruTkh- z1wW$ic-J86$K~)m>BsNHx4boKzf7o{ zb(OkWU8BB(NC)3jYt{GFI`sp!9^OvZs_WGC@LeIVmK)*8aua->ZiYY0E$XN0R&|@Y zUHuz;)PAP!RClQj>gVuYTkE}oyU6c(uj1Fo-{6(=-|%F5+1us~sb9d8?Or?ry-(e* z9#EUqgX$smuzE!OJN(;zsWz)$smIiRs4eQ(>T&fOc)a~qZB_pX54Yc`r_}G&)9MfE z8GqBFbZU7jf1sx`8gGj0b90?JN6a~9&NcenWX~-oZ|IvEOumWEkyymkZ?b3R1AHvM zsXNi0?(NBM>Rr;?lUi2PlhiTTgeRnN?+T2rfQ)tISN zQ>WrROAM~TKgV?|8gDh@(`vfcYR0M6RBhF=#`Ne%Vlgu~)plIXxVOaf=5+L?dvqhM zM!dM?W~f??^5T(_IUNI-lj(u(uEYR$5-N=6&&eI|T2s50+d_b}rs6r7u0*CIXA0LC z&Na*#^0oNqYK6?rQAn-nMbt=LYkCp2EyVrjov*7aK4;Y6#iB-0b%s@)VNqxB zjarasLz{0N=9hn7ju@IY5{nx3)EVLH;{N$=Oi*l^kK1!y-u$d?L^~2?SA2cWM?X!%5-M@3zno4 z%TvYYXA5<`1~a^EHU9INM5Eh`_N$Gm+8X=|nSIg1QH!!MrshxX^e3D$Fy?KVNprn9 zN6op~oXuQsvgcNfZ!+{vjV9krXG}6vzsa7N5AfCb38SEd*)|i|Z8MQ_(<2ho>oyvX zG#0mKRZ!IKI^$2eK?ju)DAMLnalibjoYJT%Z8qI(F=DqE@mfqbTey1=v$iPZ+A$h1 zJ#4l;tj|y74t3P9jMnC-vil~wVJ1#9ix7#`G9T=tjs7JpVeyh|?^t=!Sas2oQLV?K zzK-lZ+FaZ*vJUpqT7Rb-PjtXY8*3}@O;*^+cB)zg*eNp`mTyLi<6T~6U3Y4LTT3%bc_R!ty8NNdsnk9pLSCz|GufyuQ)Dg4IyGBHQi46>fW7p|{sn*+M;A8_Y1b)%sWE zZm(t(s*Q@;8vU!8ebMSsYqhaj4NUGNX0b>;GmAxIHHE8F>E6kdXeg7xEhN)4@FfPf zKCBCzEuLFKo-a^1vnFxo1%fk6gfm~NaBkF`lRdrN1-K)f%s~H&UIvQ#JFsLKEL_|> zkTy`~azmHtT&d|YxR>tHV5)OTN53ZQ=`;*=8$AQvX>Nl7YJ&jN27$T_4N)6RM{O{j zZi7MH1`~1{1R342ULuiZy;DV+Sue;Nz0@1M)Em9j8@<#Uz0@1M)HjYXdY(*5FXe$C z-Q;AcRnBVC`bbWuA;QC!Q;#_4EZ3u~t4A4@KOT)$_NCIDy-B*)$2C348*xk1yplu) z`OeHTYJ>+p5@~8JTHLv0093g1(*4&f2$#}G$)y9m{i)=lE~-#n#<>h`C%RIMDDPO+ z*OBVE7<+O8j3+0%I+uh}E8Dvg-4~}W9Y}QPl<}H%#Ha|j)2teO+YKqqRMeLb{P@Ac zmARUvxwfRew+lYmm|6Xa^eSJnw`YkjK45xlMh7?Ot-h|#bi!0J{G0i%359k3zElSF zO|hBr`r3#~5sx&;8pl`_btn3@I?P@bqm3oimFk90HIw@0f>d{3|0=L$L^P)7K`g>& z^r5zh)_7N{uWKMPHi1o0U*K5#Wt`W0fF6!SI^DZspilE^tS{nxnpT2AH54)`*}H=8 z!y#Cs#&o3?TbeqJS=8I#p`n4kq@j*$-ADBuW-O|=LY%eO&06eObEBTt&1S5cBef;T z9$qaSi*Q9>R+{X=*rqYyoGnlG@Cw%y8O9YSdpZ(biy00idlq9M<`xG{6~ji?0UutS zxrBv_2fDg6$ALb_v1FhxN1UI^9ZnG!DHmXRw$E{6(JB3ngw(Nr;AFjbbP(!{G2wFxwpY}T|i z)|A?|FhHiYOwSV3)_Ee<`7$nNYK_4B?MNk-uOjxW4^*-^s%CK&hK_I$nluMWED5g7 z3@x%U7n!BC9CZIJG1_8~+Gkd}ea2B+7Khpn2M#2%ZlAH-zD1(_EHX=LIq3E+F%$$v zQ0veeIO;sLtn*M9%9+1LlygnMbmcLFG6HADtw_`LfM=-z4YkcuBh;cP)G?QwdYrhBQfkx-YjYBatKeGSKJxqIH;)H4+}h4VW=c zQr~s&vNKzE$nY&=a>|(QW)UM85`~p|9F-h-hJ~pp+eI@CAjGJG?3`p~*&5UitJc=Z z>e;mT@q+9GV`kYJ+$}RtxyVrJxumBk*d4_+66tn`t0zPti?N+{L=b%xQC79(TyNCO z%*ByJuEk_$VhJ9kW-`DWt*L;~{LMx=s>BMe1<<=Ek8!E4Znr|4HIj}srM53)IB(}S zXAEVf%VdWHG6WetdVY^Unx2&~(l(^)jh^33Vv(kv?hDUvt!XnAZLD$EXdZ-%v=p>hb4ekH8ajp zD>?IUte%^o@jKwjLW&cM&L@$0z;zQYm&j{ z;wd#UV)>5KuV9^zX=FAASIP3@&I4rZUy=75H^w(mtJ!auY_L=X+>Z%(HGu@paOth!QboMNF znY>IN?&^H)T?32q&{DcTAKM*%iY2*Ty?z7~tJlx8an`>b>h(iOx~;9PwKC^tXwHw(fJW#3ZVi}B4i$b$WHJ})-?;o67C$wB;JWTNZ3G`} zBPk*={aX`fex!smOC5>X2cG%?DDe8RLQFpsMxLbwou%jBtjP0Ygh;jdja(h6FXYEU z?Y-TL3i$~TLELsV9;T8E)%wv6&YDNH`6*f*X)5F=R5>+jN;-Obmn9bUF2~3?KKfB! zwSMFmsn!-0oSVnsVbh{iSMQ1}ajZr+s)r$Bh6iUOxVEK5s`XP(oVB8=tLx~Y1$At4 zdm@uEnR?EYx-ROh>t`-$b8aS9^sVCE$~_7U?>%9_ha#euRK0(+l`W z+}ZLRq~}W1f}hR#NH3PCOFlM8Q!<4QHutGMq^s3>Pr=jRI>5K64S+W)$OaFAE#xtb z!$SlW4<0HWe1iqPoMn9#;HdyTC~&w64$-%$yS%Yni?kx))35<&idZ!f-r(?phELm6 zcyGzpw%PN%{@Q&b(!CQi}gL8xD z;s2swGPpF@7hE2^EVw4PHh5L=T6j8o!AEhVajY~4BDn=$!~u`>;1(QL;CL1X;jY24 z4hP|{$FbEMh{P1U6~~=85Vy!3ww&ngaSvePO$W72c%q|vh?#_Q1nF_;>nk`&2SyLU zX5c5_I1xDLBsdiZa8@^j4`MC^9`a;h2acUM_TYdwd~hDlhxqUX&-Cc6kLHy=kyrZXc%@I}mA;Ty z`USkwFX5G*;FZ3XSNivPrQgOY{Y75s@AFDO!2YE{TIujk#Y%_QC{{W=N3qi3J&KhM zZ&9ptv6NQ2SVk)yo}5_e@Z`iw7kAN0M@%cMbbJ>bD_z_}D;?kP!%7z$X{C$%X{Cz? zXpM_aw7SW!GO*F}!b5=}&#RCY3QTg);|?O+AkhV$bhu|6^qhlEvJmM~a}E$r*TrCJ z9BJ87HXez3|OAZYK!y z%R)OWM5Ptzvo1Bz9$Si;Xlj)E01bh5HD;xO7&k~bfgcQK>s9IcjkoEi?(eX}l zw}b9?&?63d%t22$=qU&N(Luy-koi6DaIZLMyMx|xP*#F0G)$!){;(7*G$t4zTreE; zJ1C0-6#6=sj=)hbQG-gaC&=mr^OQ7%QUeVUq*pgVfhyZB(Gh2qmgeF{*JV1i0Xm~+ z;#|=a9;)cCSdA|V87MHxK_^)#IKe^z!VOXld_CRN2p(slz&Hy9*IB5dz(T>N9dx3F zSOVr(;p$dYjVK*0AIbcQH7c?)Z?#;44ytf{3r1XNorSX6$U<4Yt`W{G!EK4M984K%Rr7(~1TQ1>bhiZU^nNP_`~X6)~|4k#UiBr77E>Jp^8}+V%#9n zWpNc}+0u%0Efhd&nqTEVEyOskTgkgEaS5SqO&9Vz=uQiT(KlVn@^RfT?{!>vlPwKF z&xQ{D&8eY#BrUGuLJNhV=Uh6{K}CmM@fF`j?smp~*Fl30qSE!QR6{qo(wi+5*if;7 zuZSB*LBxr1eCM$-fR@6ySDpauLk@b}L7N=}T*Z@2_ngCFq)lDmDh_e!%MLf>pdAj{ z>7YFh+HWC}MRTe2SzKk2gUTFK>7elrnrNYHj{-4^tDKCxf||-HNT=aQlKyg_Hiw(- zpt%k@&p{VCXgN5ef0dV2j)3US1Gv5srGt#Sig1-{?Q9DkUWp1TBvcqWddt6XEb zR62eP4HBK6om_X5rPH&Mx$x{HC|ftY+R{~C3;Z3dmm3{)i-YbaIxYpI?++^PceuwY zpPv&qlKun@_8Tn$S9)S2u+|0iyeZ5SQ`{ zyD4x`*qYWO1QQ4%!mh z7J5DOHd=YqL4@1NxThWNSqHu7pxutkJ_`jJEEGCup&aR@#f1wTRBEA7D{6>zs;{dA z>1!k7CILUh&`B1`)(y`Bu7^u$7u68$AR3}wN<*y8NCag|85-nLw=-mapNH3@?hRaeGay_;kZuTX3_pbP=Ip&s_;K*uOr?x_lIflS z&2tRB3}}d<9hA~vc&9$=G`xq;0<g4$o3=kiB^Jv+MADqz4Z#M7s0PKY$;5cn;wG zhg&%%{4Uf34X4BrGz{<8c=;8S9_I8_`L8Gq%LLMp{3TLPUWimmdXg*TgP?q$XuJb* zfbn?R>+MuAq#w$UIDHlAUKK)ViF^a;M1|+Y-T`$Cr$tmtRRKP%Ae-#LXL4Kjrj?oR)A}4$AfNWu!iN8DS)zYm4v52Eh0pG2lE(yycBEZ3DLw z;L;+VU1h`*G{m8Z=g%&Z5baW}D~aCwE06WNJRjaw8DQQu{i1Q)OOk+|X2mYzpfw_X!mWHj{X!gd8j(D1U9q$u@XGopx446Q^eW5qD$BEtOP^*L-ryd+ zO4`HwjKt@0SQSV@>f?SMmtp2GjHup1Y~@xa6OFiuH7r?I?=!bUtkq#|Yd_=nv(6>A z_$H60$1M)C_J(=XB)9A1{`$D5i1*AgR3ZI<)*9rYwN^rr^Tjv0m5)Rt;IGOVNRQPr zQ-8$-UiA}5=OW6x!35UWy|l7WiX8)y38<&fGfju2H5+0|AJ_HqjuGZsK3?6|ab2Ip z8W%oA^gcyvd^hhTdCb$tX_$MujrXD=PJMVYn$|e2?vOlyTH6#!emdT7#Y*A1v73AJ z5Z<*H-gd@pXGt==8*Eo65T19r?Zn61efT1z!?aIgCHep_$Gg!2(e_EUQn{B(SsuDm z5Ml1SAe0Gbl+^>fr~?22T48djCSFG1{c8VjCp-Wz~Rx!qcBcOti2%I%i2 zo=dsiiLB33Zha!tm$LST`PwneD`KboD_VI+P6y9A-8;U@{6()p8eeY*@$}wgo*rKp zKBRULNr6`DD?E-{7{8O#9W2k+bSZ1~0!}B=wFW(6B`TG)wXVQ!19^Bc1~vk>&;6MUsX|@?nw?vw}va zkn7@?Am9o3-VW*ci%3g({f1eFH^fW8Tt_Li4@;^z4G|9LYDl}*SGX?4q7sNPLoy#` zc$ne6ti3n5-Fa9SRPJFU7kda}#g@qT*b=!1(U`)bO1y=bA18<{u(PZ~oR6nO1OBJs ze}?-%3$~d)aS7sW^dW-8O8jpVgZPfcIz+#?1raUogccw1!ihvzzZ^#fe{pAfdv~9l z$Z3pT8W8u;YY}48V!VYUr_yU<;!#ewaJrS#r#XF=(-%42#_8+yE`)fyH=XQ}CuY!! z*iElDh<)_Zg*ZrWE=ZXfSd@_k8F>6iet|*;F^4@l%`0eH7_XAo;_Zkk`Dn{rq(f&k zEv!=hFVC4>rB0eV=gU=U?mU#Lm3Wn{O5M!*5_uEY4lI0Ybl&%zw$<`ZKw~$k@%ZWm z=~v)u0{Yx<=qH=}QgiMw=f&onH0SFL-%E7fS7-9DNHgC@%z56Z{E4s)ql1W1FsvcG z>3@;I?Z@uTrHD3g978_5^M4KA>0e1ay<<78;}0H+k|OCXK}rF!j_Fg%7+eScTR za0RCmIsF``$8idqEb7v?a0Ea07v5(9^L2#J5)maRKOS)^$d*m{0)}+d462qj7`0D^Y zf%7%6R`2t9s3}oHAwCFVUwG12fVO^tZ*AO&crDWBM~t_>;2MO3t?XJJ^~v)1&-L;U zzut6R%P15{g)Y{C4DDr?@7vTn3q^)}rlW{tQQ+24366ZmV2>5_Ylbl67_%Cf zA%i)FFyckjJl$YE!^ot~$4 zV-b(l{wBsx!{dPh-h;l;bl^QGNP#JqY(5ZAR3ZH zR3z*vDB(WKZrM$9RP4gu62i{410yjk@kuVkY%5w+zeuM81ZHlt?(UyN*|2vat`lz3GPaa5_Sp7KG_h`!H+jz~k7bMLSA z#xTDU<`-ao*xlhHMI(X{!l=wd959R%MVS%@bE8Yii5S_>(+q)TFv8&a@i;VB=fs+^ zk!EaMmn3grQQ>4%b!wiV$wP#zDEK1= y6iZWpQHSx1Q+Qsjfaf)~5V0cJ-y!r~LU-_(pW-a|ePL*5Z6KD~I-Kx?=zjqE3;5Xp literal 0 HcmV?d00001 diff --git a/scwx-qt/scwx-qt.qrc b/scwx-qt/scwx-qt.qrc index 3939ddf4..8f29eed1 100644 --- a/scwx-qt/scwx-qt.qrc +++ b/scwx-qt/scwx-qt.qrc @@ -13,6 +13,7 @@ res/config/radar_sites.json res/fonts/din1451alt.ttf res/fonts/din1451alt_g.ttf + res/fonts/Inconsolata-Regular.ttf res/icons/scwx-256.ico res/icons/scwx-256.png res/icons/font-awesome-6/angle-left-solid.svg diff --git a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp index c6f8106e..bbe06e88 100644 --- a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp @@ -26,9 +26,11 @@ static void LoadTextures(); static const std::unordered_map fontNames_ { {types::Font::din1451alt, ":/res/fonts/din1451alt.ttf"}, - {types::Font::din1451alt_g, ":/res/fonts/din1451alt_g.ttf"}}; + {types::Font::din1451alt_g, ":/res/fonts/din1451alt_g.ttf"}, + {types::Font::Inconsolata_Regular, ":/res/fonts/Inconsolata-Regular.ttf"}}; -static std::unordered_map fontIds_ {}; +static std::unordered_map fontIds_ {}; +static std::unordered_map> fonts_ {}; void Initialize() { @@ -50,6 +52,16 @@ int FontId(types::Font font) return -1; } +std::shared_ptr Font(types::Font font) +{ + auto it = fonts_.find(font); + if (it != fonts_.cend()) + { + return it->second; + } + return nullptr; +} + static void LoadFonts() { for (auto& fontName : fontNames_) @@ -58,7 +70,8 @@ static void LoadFonts() QString::fromStdString(fontName.second)); fontIds_.emplace(fontName.first, fontId); - util::Font::Create(fontName.second); + auto font = util::Font::Create(fontName.second); + fonts_.emplace(fontName.first, font); } ImFontAtlas* fontAtlas = model::ImGuiContextModel::Instance().font_atlas(); diff --git a/scwx-qt/source/scwx/qt/manager/resource_manager.hpp b/scwx-qt/source/scwx/qt/manager/resource_manager.hpp index a6ff13e1..909373db 100644 --- a/scwx-qt/source/scwx/qt/manager/resource_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/resource_manager.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include namespace scwx { @@ -14,7 +15,8 @@ namespace ResourceManager void Initialize(); void Shutdown(); -int FontId(types::Font font); +int FontId(types::Font font); +std::shared_ptr Font(types::Font font); } // namespace ResourceManager } // namespace manager diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp index 09dbd2af..55b275e3 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include #include #include @@ -40,6 +42,7 @@ public: float mapScale_ {1.0f}; float halfWidth_ {}; float halfHeight_ {}; + ImFont* monospaceFont_ {}; }; PlacefileLayer::PlacefileLayer(std::shared_ptr context) : @@ -110,7 +113,9 @@ void PlacefileLayer::Impl::RenderText( if (!hoverText.empty() && ImGui::IsItemHovered()) { ImGui::BeginTooltip(); + ImGui::PushFont(monospaceFont_); ImGui::TextUnformatted(hoverText.c_str()); + ImGui::PopFont(); ImGui::EndTooltip(); } @@ -136,6 +141,18 @@ void PlacefileLayer::Render( p->halfWidth_ = params.width * 0.5f; p->halfHeight_ = params.height * 0.5f; + // Get monospace font pointer + std::size_t fontSize = 16; + auto fontSizes = + manager::SettingsManager::general_settings().font_sizes().GetValue(); + if (fontSizes.size() > 0) + { + fontSize = fontSizes[0]; + } + auto monospace = + manager::ResourceManager::Font(types::Font::Inconsolata_Regular); + p->monospaceFont_ = monospace->ImGuiFont(fontSize); + std::shared_ptr placefileManager = manager::PlacefileManager::Instance(); diff --git a/scwx-qt/source/scwx/qt/types/font_types.hpp b/scwx-qt/source/scwx/qt/types/font_types.hpp index 95a62469..c6f42506 100644 --- a/scwx-qt/source/scwx/qt/types/font_types.hpp +++ b/scwx-qt/source/scwx/qt/types/font_types.hpp @@ -10,7 +10,8 @@ namespace types enum class Font { din1451alt, - din1451alt_g + din1451alt_g, + Inconsolata_Regular }; } // namespace types diff --git a/scwx-qt/source/scwx/qt/util/font.cpp b/scwx-qt/source/scwx/qt/util/font.cpp index ce87714c..49fdee3a 100644 --- a/scwx-qt/source/scwx/qt/util/font.cpp +++ b/scwx-qt/source/scwx/qt/util/font.cpp @@ -299,6 +299,16 @@ void FontImpl::CreateImGuiFont(QFile& fontFile, } } +ImFont* Font::ImGuiFont(std::size_t fontPixelSize) +{ + auto it = p->imGuiFonts_.find(fontPixelSize); + if (it != p->imGuiFonts_.cend()) + { + return it->second; + } + return nullptr; +} + std::shared_ptr Font::Create(const std::string& resource) { logger_->debug("Loading font file: {}", resource); diff --git a/scwx-qt/source/scwx/qt/util/font.hpp b/scwx-qt/source/scwx/qt/util/font.hpp index dc87570a..f545a93b 100644 --- a/scwx-qt/source/scwx/qt/util/font.hpp +++ b/scwx-qt/source/scwx/qt/util/font.hpp @@ -6,7 +6,9 @@ #include #include -#include +#include + +struct ImFont; namespace scwx { @@ -23,10 +25,10 @@ public: explicit Font(const std::string& resource); ~Font(); - Font(const Font&) = delete; + Font(const Font&) = delete; Font& operator=(const Font&) = delete; - Font(Font&&) = delete; + Font(Font&&) = delete; Font& operator=(Font&&) = delete; float BufferText(std::shared_ptr buffer, @@ -38,6 +40,8 @@ public: float Kerning(char c1, char c2) const; float TextLength(const std::string& text, float pointSize) const; + ImFont* ImGuiFont(std::size_t fontPixelSize); + GLuint GenerateTexture(gl::OpenGLFunctions& gl); static std::shared_ptr Create(const std::string& resource); From 783e8f34900289596dddae2953fffb3c3f27b6e9 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 22 Jul 2023 00:24:17 -0500 Subject: [PATCH 013/199] Add placefile font --- wxdata/include/scwx/gr/placefile.hpp | 8 ++++++++ wxdata/source/scwx/gr/placefile.cpp | 25 ++++++++++++++++++++++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/wxdata/include/scwx/gr/placefile.hpp b/wxdata/include/scwx/gr/placefile.hpp index 71f26cb3..c4744b17 100644 --- a/wxdata/include/scwx/gr/placefile.hpp +++ b/wxdata/include/scwx/gr/placefile.hpp @@ -47,6 +47,14 @@ public: Unknown }; + struct Font + { + std::size_t fontNumber_ {}; + std::size_t pixels_ {}; + std::int32_t flags_ {}; + std::string face_ {}; + }; + struct DrawItem { ItemType itemType_ {ItemType::Unknown}; diff --git a/wxdata/source/scwx/gr/placefile.cpp b/wxdata/source/scwx/gr/placefile.cpp index 04dc0ae3..765d59dc 100644 --- a/wxdata/source/scwx/gr/placefile.cpp +++ b/wxdata/source/scwx/gr/placefile.cpp @@ -48,6 +48,7 @@ public: double& y); void ProcessLine(const std::string& line); + static void ProcessEscapeCharacters(std::string& s); static void TrimQuotes(std::string& s); std::chrono::seconds refresh_ {-1}; @@ -61,8 +62,8 @@ public: DrawingStatement currentStatement_ {DrawingStatement::Standard}; // References - std::unordered_map iconFiles_ {}; - std::unordered_map fonts_ {}; + std::unordered_map iconFiles_ {}; + std::unordered_map> fonts_ {}; std::vector> drawItems_ {}; }; @@ -282,8 +283,26 @@ void Placefile::Impl::ProcessLine(const std::string& line) else if (boost::istarts_with(line, fontKey_)) { // Font: fontNumber, pixels, flags, "face" + std::vector tokenList = + util::ParseTokens(line, {",", ",", ",", ","}, fontKey_.size()); - // TODO + if (tokenList.size() >= 4) + { + std::shared_ptr font = std::make_shared(); + + font->fontNumber_ = std::stoul(tokenList[0]); + font->pixels_ = std::stoul(tokenList[1]); + font->flags_ = std::stoi(tokenList[2]); + + TrimQuotes(tokenList[3]); + font->face_.swap(tokenList[3]); + + fonts_.insert_or_assign(font->fontNumber_, font); + } + else + { + logger_->warn("Font statement malformed: {}", line); + } } else if (boost::istarts_with(line, textKey_)) { From 4ee7731023ca1f26712f997d4682a803e2481a31 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 22 Jul 2023 01:29:25 -0500 Subject: [PATCH 014/199] Create accessor functions for placefile fonts --- wxdata/include/scwx/gr/placefile.hpp | 3 +++ wxdata/source/scwx/gr/placefile.cpp | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/wxdata/include/scwx/gr/placefile.hpp b/wxdata/include/scwx/gr/placefile.hpp index c4744b17..2f1c0833 100644 --- a/wxdata/include/scwx/gr/placefile.hpp +++ b/wxdata/include/scwx/gr/placefile.hpp @@ -84,6 +84,9 @@ public: */ std::vector> GetDrawItems(); + std::unordered_map> fonts(); + std::shared_ptr font(std::size_t i); + static std::shared_ptr Load(const std::string& filename); static std::shared_ptr Load(std::istream& is); diff --git a/wxdata/source/scwx/gr/placefile.cpp b/wxdata/source/scwx/gr/placefile.cpp index 765d59dc..d35c67d6 100644 --- a/wxdata/source/scwx/gr/placefile.cpp +++ b/wxdata/source/scwx/gr/placefile.cpp @@ -84,6 +84,22 @@ std::vector> Placefile::GetDrawItems() return p->drawItems_; } +std::unordered_map> +Placefile::fonts() +{ + return p->fonts_; +} + +std::shared_ptr Placefile::font(std::size_t i) +{ + auto it = p->fonts_.find(i); + if (it != p->fonts_.cend()) + { + return it->second; + } + return nullptr; +} + std::shared_ptr Placefile::Load(const std::string& filename) { logger_->debug("Loading placefile: {}", filename); From ae1bca36024df491e63b952e8a9ac5eab2fd781b Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 22 Jul 2023 13:18:51 -0500 Subject: [PATCH 015/199] Parse title from placefile --- wxdata/include/scwx/gr/placefile.hpp | 4 +++- wxdata/source/scwx/gr/placefile.cpp | 26 +++++++++++++++++++------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/wxdata/include/scwx/gr/placefile.hpp b/wxdata/include/scwx/gr/placefile.hpp index 2f1c0833..20672008 100644 --- a/wxdata/include/scwx/gr/placefile.hpp +++ b/wxdata/include/scwx/gr/placefile.hpp @@ -84,11 +84,13 @@ public: */ std::vector> GetDrawItems(); + std::string title() const; std::unordered_map> fonts(); std::shared_ptr font(std::size_t i); static std::shared_ptr Load(const std::string& filename); - static std::shared_ptr Load(std::istream& is); + static std::shared_ptr Load(const std::string& name, + std::istream& is); private: class Impl; diff --git a/wxdata/source/scwx/gr/placefile.cpp b/wxdata/source/scwx/gr/placefile.cpp index d35c67d6..a3ef8f16 100644 --- a/wxdata/source/scwx/gr/placefile.cpp +++ b/wxdata/source/scwx/gr/placefile.cpp @@ -51,6 +51,7 @@ public: static void ProcessEscapeCharacters(std::string& s); static void TrimQuotes(std::string& s); + std::string title_ {}; std::chrono::seconds refresh_ {-1}; // Parsing state @@ -84,6 +85,11 @@ std::vector> Placefile::GetDrawItems() return p->drawItems_; } +std::string Placefile::title() const +{ + return p->title_; +} + std::unordered_map> Placefile::fonts() { @@ -104,13 +110,16 @@ std::shared_ptr Placefile::Load(const std::string& filename) { logger_->debug("Loading placefile: {}", filename); std::ifstream f(filename, std::ios_base::in); - return Load(f); + return Load(filename, f); } -std::shared_ptr Placefile::Load(std::istream& is) +std::shared_ptr Placefile::Load(const std::string& name, + std::istream& is) { std::shared_ptr placefile = std::make_shared(); + placefile->p->title_ = name; + std::string line; while (scwx::util::getline(is, line)) { @@ -167,6 +176,7 @@ std::shared_ptr Placefile::Load(std::istream& is) void Placefile::Impl::ProcessLine(const std::string& line) { + static const std::string titleKey_ {"Title:"}; static const std::string thresholdKey_ {"Threshold:"}; static const std::string hsluvKey_ {"HSLuv:"}; static const std::string colorKey_ {"Color:"}; @@ -189,7 +199,13 @@ void Placefile::Impl::ProcessLine(const std::string& line) // When tokenizing, add one additional delimiter to discard unexpected // parameters (where appropriate) - if (boost::istarts_with(line, thresholdKey_)) + if (boost::istarts_with(line, titleKey_)) + { + // Title: title + title_ = line.substr(titleKey_.size()); + boost::trim(title_); + } + else if (boost::istarts_with(line, thresholdKey_)) { // Threshold: nautical_miles std::vector tokenList = @@ -394,10 +410,6 @@ void Placefile::Impl::ProcessLine(const std::string& line) { objectStack_.pop_back(); } - else - { - logger_->warn("End found without Object"); - } } else if (boost::istarts_with(line, lineKey_)) { From 4c685e5abb0d164e7f84a9cbda2e4d689b271c18 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 22 Jul 2023 14:15:56 -0500 Subject: [PATCH 016/199] Add placefile settings initial layout --- .../font-awesome-6/earth-americas-solid.svg | 1 + scwx-qt/scwx-qt.cmake | 3 + scwx-qt/scwx-qt.qrc | 1 + .../scwx/qt/ui/placefile_settings_widget.cpp | 38 ++++++++ .../scwx/qt/ui/placefile_settings_widget.hpp | 35 ++++++++ .../scwx/qt/ui/placefile_settings_widget.ui | 88 +++++++++++++++++++ scwx-qt/source/scwx/qt/ui/settings_dialog.cpp | 16 +++- scwx-qt/source/scwx/qt/ui/settings_dialog.ui | 29 +++++- 8 files changed, 207 insertions(+), 4 deletions(-) create mode 100644 scwx-qt/res/icons/font-awesome-6/earth-americas-solid.svg create mode 100644 scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp create mode 100644 scwx-qt/source/scwx/qt/ui/placefile_settings_widget.hpp create mode 100644 scwx-qt/source/scwx/qt/ui/placefile_settings_widget.ui diff --git a/scwx-qt/res/icons/font-awesome-6/earth-americas-solid.svg b/scwx-qt/res/icons/font-awesome-6/earth-americas-solid.svg new file mode 100644 index 00000000..f427a71e --- /dev/null +++ b/scwx-qt/res/icons/font-awesome-6/earth-americas-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 3856117f..691741a9 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -160,6 +160,7 @@ set(HDR_UI source/scwx/qt/ui/about_dialog.hpp source/scwx/qt/ui/level2_products_widget.hpp source/scwx/qt/ui/level2_settings_widget.hpp source/scwx/qt/ui/level3_products_widget.hpp + source/scwx/qt/ui/placefile_settings_widget.hpp source/scwx/qt/ui/radar_site_dialog.hpp source/scwx/qt/ui/settings_dialog.hpp source/scwx/qt/ui/update_dialog.hpp) @@ -174,6 +175,7 @@ set(SRC_UI source/scwx/qt/ui/about_dialog.cpp source/scwx/qt/ui/level2_products_widget.cpp source/scwx/qt/ui/level2_settings_widget.cpp source/scwx/qt/ui/level3_products_widget.cpp + source/scwx/qt/ui/placefile_settings_widget.cpp source/scwx/qt/ui/radar_site_dialog.cpp source/scwx/qt/ui/settings_dialog.cpp source/scwx/qt/ui/update_dialog.cpp) @@ -183,6 +185,7 @@ set(UI_UI source/scwx/qt/ui/about_dialog.ui source/scwx/qt/ui/animation_dock_widget.ui source/scwx/qt/ui/collapsible_group.ui source/scwx/qt/ui/imgui_debug_dialog.ui + source/scwx/qt/ui/placefile_settings_widget.ui source/scwx/qt/ui/radar_site_dialog.ui source/scwx/qt/ui/settings_dialog.ui source/scwx/qt/ui/update_dialog.ui) diff --git a/scwx-qt/scwx-qt.qrc b/scwx-qt/scwx-qt.qrc index 8f29eed1..6d786fff 100644 --- a/scwx-qt/scwx-qt.qrc +++ b/scwx-qt/scwx-qt.qrc @@ -21,6 +21,7 @@ res/icons/font-awesome-6/backward-step-solid.svg res/icons/font-awesome-6/book-solid.svg res/icons/font-awesome-6/discord.svg + res/icons/font-awesome-6/earth-americas-solid.svg res/icons/font-awesome-6/forward-step-solid.svg res/icons/font-awesome-6/gears-solid.svg res/icons/font-awesome-6/github.svg diff --git a/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp b/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp new file mode 100644 index 00000000..c8578c78 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp @@ -0,0 +1,38 @@ +#include "placefile_settings_widget.hpp" +#include "ui_placefile_settings_widget.h" + +namespace scwx +{ +namespace qt +{ +namespace ui +{ + +class PlacefileSettingsWidgetImpl +{ +public: + explicit PlacefileSettingsWidgetImpl(PlacefileSettingsWidget* self) : + self_ {self} + { + } + ~PlacefileSettingsWidgetImpl() = default; + + PlacefileSettingsWidget* self_; +}; + +PlacefileSettingsWidget::PlacefileSettingsWidget(QWidget* parent) : + QFrame(parent), + p {std::make_unique(this)}, + ui(new Ui::PlacefileSettingsWidget) +{ + ui->setupUi(this); +} + +PlacefileSettingsWidget::~PlacefileSettingsWidget() +{ + delete ui; +} + +} // namespace ui +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.hpp b/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.hpp new file mode 100644 index 00000000..69628446 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include + +namespace Ui +{ +class PlacefileSettingsWidget; +} + +namespace scwx +{ +namespace qt +{ +namespace ui +{ + +class PlacefileSettingsWidgetImpl; + +class PlacefileSettingsWidget : public QFrame +{ + Q_OBJECT + +public: + explicit PlacefileSettingsWidget(QWidget* parent = nullptr); + ~PlacefileSettingsWidget(); + +private: + friend class PlacefileSettingsWidgetImpl; + std::unique_ptr p; + Ui::PlacefileSettingsWidget* ui; +}; + +} // namespace ui +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.ui b/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.ui new file mode 100644 index 00000000..145aef86 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.ui @@ -0,0 +1,88 @@ + + + PlacefileSettingsWidget + + + + 0 + 0 + 400 + 300 + + + + Frame + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Filter + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + &Add + + + + + + + false + + + &Remove + + + + + + + + + + + diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp index ee962794..e3672c5f 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -115,6 +116,7 @@ public: void SetupGeneralTab(); void SetupPalettesColorTablesTab(); void SetupPalettesAlertsTab(); + void SetupPlacefilesTab(); void ShowColorDialog(QLineEdit* lineEdit, QFrame* frame = nullptr); void UpdateRadarDialogLocation(const std::string& id); @@ -136,8 +138,9 @@ public: RadarSiteLabel(std::shared_ptr& radarSite); static void SetBackgroundColor(const std::string& value, QFrame* frame); - SettingsDialog* self_; - RadarSiteDialog* radarSiteDialog_; + SettingsDialog* self_; + PlacefileSettingsWidget* placefileSettingsWidget_; + RadarSiteDialog* radarSiteDialog_; settings::SettingsInterface defaultRadarSite_ {}; settings::SettingsInterface> fontSizes_ {}; @@ -178,6 +181,9 @@ SettingsDialog::SettingsDialog(QWidget* parent) : // Palettes > Alerts p->SetupPalettesAlertsTab(); + // Placefiles + p->SetupPlacefilesTab(); + p->ConnectSignals(); } @@ -618,6 +624,12 @@ void SettingsDialogImpl::SetupPalettesAlertsTab() } } +void SettingsDialogImpl::SetupPlacefilesTab() +{ + placefileSettingsWidget_ = new PlacefileSettingsWidget(self_); + self_->ui->placefiles->layout()->addWidget(placefileSettingsWidget_); +} + QImage SettingsDialogImpl::GenerateColorTableImage( std::shared_ptr colorTable, std::uint16_t min, diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui index e12f8909..9a4b2c86 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui @@ -73,6 +73,15 @@ :/res/icons/font-awesome-6/palette-solid.svg:/res/icons/font-awesome-6/palette-solid.svg + + + Placefiles + + + + :/res/icons/font-awesome-6/earth-americas-solid.svg:/res/icons/font-awesome-6/earth-americas-solid.svg + + @@ -338,8 +347,8 @@ 0 0 - 66 - 18 + 481 + 382 @@ -410,6 +419,22 @@ + + + + 0 + + + 0 + + + 0 + + + 0 + + + From 157500e20a9e5ac09e78c3c2285afa9fe2fc02a4 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 22 Jul 2023 21:17:09 -0500 Subject: [PATCH 017/199] Add open URL dialog to add a placefile --- scwx-qt/scwx-qt.cmake | 3 + scwx-qt/source/scwx/qt/ui/open_url_dialog.cpp | 103 +++++++++++++++ scwx-qt/source/scwx/qt/ui/open_url_dialog.hpp | 40 ++++++ scwx-qt/source/scwx/qt/ui/open_url_dialog.ui | 118 ++++++++++++++++++ .../scwx/qt/ui/placefile_settings_widget.cpp | 28 +++++ 5 files changed, 292 insertions(+) create mode 100644 scwx-qt/source/scwx/qt/ui/open_url_dialog.cpp create mode 100644 scwx-qt/source/scwx/qt/ui/open_url_dialog.hpp create mode 100644 scwx-qt/source/scwx/qt/ui/open_url_dialog.ui diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 691741a9..a2c758bd 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -160,6 +160,7 @@ set(HDR_UI source/scwx/qt/ui/about_dialog.hpp source/scwx/qt/ui/level2_products_widget.hpp source/scwx/qt/ui/level2_settings_widget.hpp source/scwx/qt/ui/level3_products_widget.hpp + source/scwx/qt/ui/open_url_dialog.hpp source/scwx/qt/ui/placefile_settings_widget.hpp source/scwx/qt/ui/radar_site_dialog.hpp source/scwx/qt/ui/settings_dialog.hpp @@ -175,6 +176,7 @@ set(SRC_UI source/scwx/qt/ui/about_dialog.cpp source/scwx/qt/ui/level2_products_widget.cpp source/scwx/qt/ui/level2_settings_widget.cpp source/scwx/qt/ui/level3_products_widget.cpp + source/scwx/qt/ui/open_url_dialog.cpp source/scwx/qt/ui/placefile_settings_widget.cpp source/scwx/qt/ui/radar_site_dialog.cpp source/scwx/qt/ui/settings_dialog.cpp @@ -185,6 +187,7 @@ set(UI_UI source/scwx/qt/ui/about_dialog.ui source/scwx/qt/ui/animation_dock_widget.ui source/scwx/qt/ui/collapsible_group.ui source/scwx/qt/ui/imgui_debug_dialog.ui + source/scwx/qt/ui/open_url_dialog.ui source/scwx/qt/ui/placefile_settings_widget.ui source/scwx/qt/ui/radar_site_dialog.ui source/scwx/qt/ui/settings_dialog.ui diff --git a/scwx-qt/source/scwx/qt/ui/open_url_dialog.cpp b/scwx-qt/source/scwx/qt/ui/open_url_dialog.cpp new file mode 100644 index 00000000..0019a472 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/open_url_dialog.cpp @@ -0,0 +1,103 @@ +#include "open_url_dialog.hpp" +#include "ui_open_url_dialog.h" + +#include + +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace ui +{ + +static const std::string logPrefix_ = "scwx::qt::ui::open_url_dialog"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +class OpenUrlDialogImpl +{ +public: + explicit OpenUrlDialogImpl(OpenUrlDialog* self) : self_ {self} {} + ~OpenUrlDialogImpl() = default; + + void ConnectSignals(); + void SelectFile(); + + OpenUrlDialog* self_; +}; + +OpenUrlDialog::OpenUrlDialog(const QString& title, QWidget* parent) : + QDialog(parent), + p {std::make_unique(this)}, + ui(new Ui::OpenUrlDialog) +{ + ui->setupUi(this); + + setWindowTitle(title); + + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + + p->ConnectSignals(); +} + +OpenUrlDialog::~OpenUrlDialog() +{ + delete ui; +} + +void OpenUrlDialogImpl::ConnectSignals() +{ + QObject::connect(self_->ui->urlEdit, + &QLineEdit::textChanged, + self_, + [this](const QString& text) + { + QUrl url(text); + self_->ui->buttonBox->button(QDialogButtonBox::Ok) + ->setEnabled(url.isValid()); + }); + + QObject::connect(self_->ui->fileButton, + &QToolButton::clicked, + self_, + [this]() { SelectFile(); }); +} + +void OpenUrlDialog::showEvent(QShowEvent* event) +{ + ui->urlEdit->setText(QString()); + QDialog::showEvent(event); +} + +QString OpenUrlDialog::url() const +{ + return ui->urlEdit->text(); +} + +void OpenUrlDialogImpl::SelectFile() +{ + static const std::string placefileFilter = "Placefiles (*)"; + + QFileDialog* dialog = new QFileDialog(self_); + + dialog->setFileMode(QFileDialog::ExistingFile); + dialog->setNameFilter(QObject::tr(placefileFilter.c_str())); + dialog->setAttribute(Qt::WA_DeleteOnClose); + + QObject::connect(dialog, + &QFileDialog::fileSelected, + self_, + [this](const QString& file) + { + logger_->debug("Selected: {}", file.toStdString()); + self_->ui->urlEdit->setText(file); + }); + + dialog->open(); +} + +} // namespace ui +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/open_url_dialog.hpp b/scwx-qt/source/scwx/qt/ui/open_url_dialog.hpp new file mode 100644 index 00000000..ef7666e7 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/open_url_dialog.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include + +namespace Ui +{ +class OpenUrlDialog; +} + +namespace scwx +{ +namespace qt +{ +namespace ui +{ + +class OpenUrlDialogImpl; + +class OpenUrlDialog : public QDialog +{ + Q_OBJECT + +public: + explicit OpenUrlDialog(const QString& title, QWidget* parent = nullptr); + ~OpenUrlDialog(); + + QString url() const; + +protected: + void showEvent(QShowEvent* event); + +private: + friend class OpenUrlDialogImpl; + std::unique_ptr p; + Ui::OpenUrlDialog* ui; +}; + +} // namespace ui +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/open_url_dialog.ui b/scwx-qt/source/scwx/qt/ui/open_url_dialog.ui new file mode 100644 index 00000000..a796d20d --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/open_url_dialog.ui @@ -0,0 +1,118 @@ + + + OpenUrlDialog + + + + 0 + 0 + 400 + 80 + + + + Dialog + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + URL: + + + + + + + + + + ... + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + OpenUrlDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + OpenUrlDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp b/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp index c8578c78..420f915a 100644 --- a/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp @@ -1,6 +1,9 @@ #include "placefile_settings_widget.hpp" #include "ui_placefile_settings_widget.h" +#include +#include + namespace scwx { namespace qt @@ -8,6 +11,9 @@ namespace qt namespace ui { +static const std::string logPrefix_ = "scwx::qt::ui::placefile_settings_widget"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + class PlacefileSettingsWidgetImpl { public: @@ -17,7 +23,10 @@ public: } ~PlacefileSettingsWidgetImpl() = default; + void ConnectSignals(); + PlacefileSettingsWidget* self_; + OpenUrlDialog* openUrlDialog_ {nullptr}; }; PlacefileSettingsWidget::PlacefileSettingsWidget(QWidget* parent) : @@ -26,6 +35,10 @@ PlacefileSettingsWidget::PlacefileSettingsWidget(QWidget* parent) : ui(new Ui::PlacefileSettingsWidget) { ui->setupUi(this); + + p->openUrlDialog_ = new OpenUrlDialog("Add Placefile", this); + + p->ConnectSignals(); } PlacefileSettingsWidget::~PlacefileSettingsWidget() @@ -33,6 +46,21 @@ PlacefileSettingsWidget::~PlacefileSettingsWidget() delete ui; } +void PlacefileSettingsWidgetImpl::ConnectSignals() +{ + QObject::connect(self_->ui->addButton, + &QPushButton::clicked, + self_, + [this]() { openUrlDialog_->open(); }); + + QObject::connect( + openUrlDialog_, + &OpenUrlDialog::accepted, + self_, + [this]() + { logger_->info("Add URL: {}", openUrlDialog_->url().toStdString()); }); +} + } // namespace ui } // namespace qt } // namespace scwx From 014ea9d39ee8d5666bcbb08c50f373e90c7bb030 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 23 Jul 2023 00:14:48 -0500 Subject: [PATCH 018/199] Display placefiles in settings view --- scwx-qt/scwx-qt.cmake | 2 + .../scwx/qt/manager/placefile_manager.cpp | 1 + .../source/scwx/qt/model/placefile_model.cpp | 185 ++++++++++++++++++ .../source/scwx/qt/model/placefile_model.hpp | 54 +++++ .../scwx/qt/ui/placefile_settings_widget.cpp | 32 ++- .../scwx/qt/ui/placefile_settings_widget.ui | 9 +- 6 files changed, 279 insertions(+), 4 deletions(-) create mode 100644 scwx-qt/source/scwx/qt/model/placefile_model.cpp create mode 100644 scwx-qt/source/scwx/qt/model/placefile_model.hpp diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index a2c758bd..745cd8a2 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -104,6 +104,7 @@ set(SRC_MAP source/scwx/qt/map/alert_layer.cpp set(HDR_MODEL source/scwx/qt/model/alert_model.hpp source/scwx/qt/model/alert_proxy_model.hpp source/scwx/qt/model/imgui_context_model.hpp + source/scwx/qt/model/placefile_model.hpp source/scwx/qt/model/radar_product_model.hpp source/scwx/qt/model/radar_site_model.hpp source/scwx/qt/model/tree_item.hpp @@ -111,6 +112,7 @@ set(HDR_MODEL source/scwx/qt/model/alert_model.hpp set(SRC_MODEL source/scwx/qt/model/alert_model.cpp source/scwx/qt/model/alert_proxy_model.cpp source/scwx/qt/model/imgui_context_model.cpp + source/scwx/qt/model/placefile_model.cpp source/scwx/qt/model/radar_product_model.cpp source/scwx/qt/model/radar_site_model.cpp source/scwx/qt/model/tree_item.cpp diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index 88e8f232..0318d03e 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -117,6 +117,7 @@ void PlacefileManager::LoadFile(const std::string& filename) std::make_unique(filename, placefile)); Q_EMIT PlacefileEnabled(filename, record->enabled_); + Q_EMIT PlacefileUpdated(filename); } }); } diff --git a/scwx-qt/source/scwx/qt/model/placefile_model.cpp b/scwx-qt/source/scwx/qt/model/placefile_model.cpp new file mode 100644 index 00000000..5975547e --- /dev/null +++ b/scwx-qt/source/scwx/qt/model/placefile_model.cpp @@ -0,0 +1,185 @@ +#include +#include +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace model +{ + +static const std::string logPrefix_ = "scwx::qt::model::placefile_model"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +static constexpr int kFirstColumn = + static_cast(PlacefileModel::Column::Enabled); +static constexpr int kLastColumn = + static_cast(PlacefileModel::Column::Description); +static constexpr int kNumColumns = kLastColumn - kFirstColumn + 1; + +class PlacefileModelImpl +{ +public: + explicit PlacefileModelImpl() {} + ~PlacefileModelImpl() = default; + + std::shared_ptr placefileManager_ { + manager::PlacefileManager::Instance()}; + + std::vector placefileNames_ {}; +}; + +PlacefileModel::PlacefileModel(QObject* parent) : + QAbstractTableModel(parent), p(std::make_unique()) +{ + connect(p->placefileManager_.get(), + &manager::PlacefileManager::PlacefileUpdated, + this, + &PlacefileModel::HandlePlacefileUpdate); +} +PlacefileModel::~PlacefileModel() = default; + +int PlacefileModel::rowCount(const QModelIndex& parent) const +{ + return parent.isValid() ? 0 : static_cast(p->placefileNames_.size()); +} + +int PlacefileModel::columnCount(const QModelIndex& parent) const +{ + return parent.isValid() ? 0 : kNumColumns; +} + +Qt::ItemFlags PlacefileModel::flags(const QModelIndex& index) const +{ + Qt::ItemFlags flags = QAbstractTableModel::flags(index); + + switch (index.column()) + { + case static_cast(Column::Enabled): + case static_cast(Column::Thresholds): + flags |= Qt::ItemFlag::ItemIsUserCheckable; + + default: + break; + } + + return flags; +} + +QVariant PlacefileModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid() || index.row() < 0 || + index.row() >= p->placefileNames_.size()) + { + return QVariant(); + } + + const auto& placefileName = p->placefileNames_.at(index.row()); + + if (role == Qt::ItemDataRole::DisplayRole || + role == types::ItemDataRole::SortRole) + { + switch (index.column()) + { + case static_cast(Column::Enabled): + if (role == types::ItemDataRole::SortRole) + { + return true; // TODO + } + break; + + case static_cast(Column::Thresholds): + if (role == types::ItemDataRole::SortRole) + { + return true; // TODO + } + break; + + case static_cast(Column::Url): + return QString::fromStdString(placefileName); + + case static_cast(Column::Description): + return QString {}; + + default: + break; + } + } + else if (role == Qt::ItemDataRole::CheckStateRole) + { + switch (index.column()) + { + case static_cast(Column::Enabled): + return true; // TODO + + case static_cast(Column::Thresholds): + return true; // TODO + + default: + break; + } + } + + return QVariant(); +} + +QVariant PlacefileModel::headerData(int section, + Qt::Orientation orientation, + int role) const +{ + if (role == Qt::DisplayRole) + { + if (orientation == Qt::Horizontal) + { + switch (section) + { + case static_cast(Column::Enabled): + return tr("Enabled"); + + case static_cast(Column::Thresholds): + return tr("Thresholds"); + + case static_cast(Column::Url): + return tr("URL"); + + case static_cast(Column::Description): + return tr("Description"); + + default: + break; + } + } + } + + return QVariant(); +} + +void PlacefileModel::HandlePlacefileUpdate(const std::string& name) +{ + auto it = + std::find(p->placefileNames_.begin(), p->placefileNames_.end(), name); + + if (it != p->placefileNames_.end()) + { + // Placefile exists, mark row as updated + const int row = std::distance(p->placefileNames_.begin(), it); + QModelIndex topLeft = createIndex(row, kFirstColumn); + QModelIndex bottomRight = createIndex(row, kLastColumn); + + Q_EMIT dataChanged(topLeft, bottomRight); + } + else + { + // Placefile is new, append row + const int newIndex = static_cast(p->placefileNames_.size()); + beginInsertRows(QModelIndex(), newIndex, newIndex); + p->placefileNames_.push_back(name); + endInsertRows(); + } +} + +} // namespace model +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/model/placefile_model.hpp b/scwx-qt/source/scwx/qt/model/placefile_model.hpp new file mode 100644 index 00000000..0f5a1b10 --- /dev/null +++ b/scwx-qt/source/scwx/qt/model/placefile_model.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include +#include + +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace model +{ + +class PlacefileModelImpl; + +class PlacefileModel : public QAbstractTableModel +{ +public: + enum class Column : int + { + Enabled = 0, + Thresholds = 1, + Url = 2, + Description = 3 + }; + + explicit PlacefileModel(QObject* parent = nullptr); + ~PlacefileModel(); + + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + + Qt::ItemFlags flags(const QModelIndex& index) const override; + + QVariant data(const QModelIndex& index, + int role = Qt::DisplayRole) const override; + QVariant headerData(int section, + Qt::Orientation orientation, + int role = Qt::DisplayRole) const override; + +public slots: + void HandlePlacefileUpdate(const std::string& name); + +private: + friend class PlacefileModelImpl; + std::unique_ptr p; +}; + +} // namespace model +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp b/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp index 420f915a..f75522a7 100644 --- a/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp @@ -1,9 +1,13 @@ #include "placefile_settings_widget.hpp" #include "ui_placefile_settings_widget.h" +#include +#include #include #include +#include + namespace scwx { namespace qt @@ -18,15 +22,25 @@ class PlacefileSettingsWidgetImpl { public: explicit PlacefileSettingsWidgetImpl(PlacefileSettingsWidget* self) : - self_ {self} + self_ {self}, + openUrlDialog_ {new OpenUrlDialog(QObject::tr("Add Placefile"), self_)}, + placefileModel_ {new model::PlacefileModel(self_)}, + placefileProxyModel_ {new QSortFilterProxyModel(self_)} { + placefileProxyModel_->setSourceModel(placefileModel_); + placefileProxyModel_->setSortRole(types::SortRole); + placefileProxyModel_->setFilterCaseSensitivity(Qt::CaseInsensitive); + placefileProxyModel_->setFilterKeyColumn(-1); } ~PlacefileSettingsWidgetImpl() = default; void ConnectSignals(); PlacefileSettingsWidget* self_; - OpenUrlDialog* openUrlDialog_ {nullptr}; + OpenUrlDialog* openUrlDialog_; + + model::PlacefileModel* placefileModel_; + QSortFilterProxyModel* placefileProxyModel_; }; PlacefileSettingsWidget::PlacefileSettingsWidget(QWidget* parent) : @@ -36,7 +50,14 @@ PlacefileSettingsWidget::PlacefileSettingsWidget(QWidget* parent) : { ui->setupUi(this); - p->openUrlDialog_ = new OpenUrlDialog("Add Placefile", this); + ui->placefileView->setModel(p->placefileProxyModel_); + ui->placefileView->header()->setSortIndicator( + static_cast(model::PlacefileModel::Column::Url), Qt::AscendingOrder); + + ui->placefileView->resizeColumnToContents( + static_cast(model::PlacefileModel::Column::Enabled)); + ui->placefileView->resizeColumnToContents( + static_cast(model::PlacefileModel::Column::Thresholds)); p->ConnectSignals(); } @@ -59,6 +80,11 @@ void PlacefileSettingsWidgetImpl::ConnectSignals() self_, [this]() { logger_->info("Add URL: {}", openUrlDialog_->url().toStdString()); }); + + QObject::connect(self_->ui->placefileFilter, + &QLineEdit::textChanged, + placefileProxyModel_, + &QSortFilterProxyModel::setFilterWildcard); } } // namespace ui diff --git a/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.ui b/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.ui index 145aef86..ef2f24fd 100644 --- a/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.ui +++ b/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.ui @@ -15,7 +15,14 @@ - + + + 0 + + + true + + From 36dd2945b031f354c75fdf6d0acc5c61bd54dc81 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 23 Jul 2023 00:36:08 -0500 Subject: [PATCH 019/199] Placefile URL readability updates --- scwx-qt/scwx-qt.cmake | 2 ++ .../source/scwx/qt/model/placefile_model.cpp | 25 +++++++++++++++++ .../scwx/qt/ui/left_elided_item_delegate.cpp | 28 +++++++++++++++++++ .../scwx/qt/ui/left_elided_item_delegate.hpp | 25 +++++++++++++++++ .../scwx/qt/ui/placefile_settings_widget.cpp | 15 ++++++++-- 5 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 scwx-qt/source/scwx/qt/ui/left_elided_item_delegate.cpp create mode 100644 scwx-qt/source/scwx/qt/ui/left_elided_item_delegate.hpp diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 745cd8a2..ebf8ca16 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -159,6 +159,7 @@ set(HDR_UI source/scwx/qt/ui/about_dialog.hpp source/scwx/qt/ui/flow_layout.hpp source/scwx/qt/ui/imgui_debug_dialog.hpp source/scwx/qt/ui/imgui_debug_widget.hpp + source/scwx/qt/ui/left_elided_item_delegate.hpp source/scwx/qt/ui/level2_products_widget.hpp source/scwx/qt/ui/level2_settings_widget.hpp source/scwx/qt/ui/level3_products_widget.hpp @@ -175,6 +176,7 @@ set(SRC_UI source/scwx/qt/ui/about_dialog.cpp source/scwx/qt/ui/flow_layout.cpp source/scwx/qt/ui/imgui_debug_dialog.cpp source/scwx/qt/ui/imgui_debug_widget.cpp + source/scwx/qt/ui/left_elided_item_delegate.cpp source/scwx/qt/ui/level2_products_widget.cpp source/scwx/qt/ui/level2_settings_widget.cpp source/scwx/qt/ui/level3_products_widget.cpp diff --git a/scwx-qt/source/scwx/qt/model/placefile_model.cpp b/scwx-qt/source/scwx/qt/model/placefile_model.cpp index 5975547e..6cfbe807 100644 --- a/scwx-qt/source/scwx/qt/model/placefile_model.cpp +++ b/scwx-qt/source/scwx/qt/model/placefile_model.cpp @@ -3,6 +3,9 @@ #include #include +#include +#include + namespace scwx { namespace qt @@ -79,6 +82,7 @@ QVariant PlacefileModel::data(const QModelIndex& index, int role) const const auto& placefileName = p->placefileNames_.at(index.row()); if (role == Qt::ItemDataRole::DisplayRole || + role == Qt::ItemDataRole::ToolTipRole || role == types::ItemDataRole::SortRole) { switch (index.column()) @@ -152,6 +156,27 @@ QVariant PlacefileModel::headerData(int section, } } } + else if (role == Qt::ItemDataRole::SizeHintRole) + { + static const QFontMetrics fontMetrics(QApplication::font()); + + QSize contentsSize {}; + + switch (section) + { + case static_cast(Column::Url): + contentsSize = fontMetrics.size(0, QString(15, 'W')); + break; + + default: + break; + } + + if (contentsSize != QSize {}) + { + return contentsSize; + } + } return QVariant(); } diff --git a/scwx-qt/source/scwx/qt/ui/left_elided_item_delegate.cpp b/scwx-qt/source/scwx/qt/ui/left_elided_item_delegate.cpp new file mode 100644 index 00000000..a102e052 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/left_elided_item_delegate.cpp @@ -0,0 +1,28 @@ +#include + +namespace scwx +{ +namespace qt +{ +namespace ui +{ + +LeftElidedItemDelegate::LeftElidedItemDelegate(QObject* parent) : + QStyledItemDelegate(parent) +{ +} + +LeftElidedItemDelegate::~LeftElidedItemDelegate() {} + +void LeftElidedItemDelegate::paint(QPainter* painter, + const QStyleOptionViewItem& option, + const QModelIndex& index) const +{ + QStyleOptionViewItem newOption = option; + newOption.textElideMode = Qt::TextElideMode::ElideLeft; + QStyledItemDelegate::paint(painter, newOption, index); +} + +} // namespace ui +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/left_elided_item_delegate.hpp b/scwx-qt/source/scwx/qt/ui/left_elided_item_delegate.hpp new file mode 100644 index 00000000..9b8223a1 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/left_elided_item_delegate.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include + +namespace scwx +{ +namespace qt +{ +namespace ui +{ + +class LeftElidedItemDelegate : public QStyledItemDelegate +{ +public: + explicit LeftElidedItemDelegate(QObject* parent = nullptr); + ~LeftElidedItemDelegate(); + + void paint(QPainter* painter, + const QStyleOptionViewItem& option, + const QModelIndex& index) const override; +}; + +} // namespace ui +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp b/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp index f75522a7..01612320 100644 --- a/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -25,7 +26,8 @@ public: self_ {self}, openUrlDialog_ {new OpenUrlDialog(QObject::tr("Add Placefile"), self_)}, placefileModel_ {new model::PlacefileModel(self_)}, - placefileProxyModel_ {new QSortFilterProxyModel(self_)} + placefileProxyModel_ {new QSortFilterProxyModel(self_)}, + leftElidedItemDelegate_ {new LeftElidedItemDelegate(self_)} { placefileProxyModel_->setSourceModel(placefileModel_); placefileProxyModel_->setSortRole(types::SortRole); @@ -39,8 +41,9 @@ public: PlacefileSettingsWidget* self_; OpenUrlDialog* openUrlDialog_; - model::PlacefileModel* placefileModel_; - QSortFilterProxyModel* placefileProxyModel_; + model::PlacefileModel* placefileModel_; + QSortFilterProxyModel* placefileProxyModel_; + LeftElidedItemDelegate* leftElidedItemDelegate_; }; PlacefileSettingsWidget::PlacefileSettingsWidget(QWidget* parent) : @@ -58,6 +61,12 @@ PlacefileSettingsWidget::PlacefileSettingsWidget(QWidget* parent) : static_cast(model::PlacefileModel::Column::Enabled)); ui->placefileView->resizeColumnToContents( static_cast(model::PlacefileModel::Column::Thresholds)); + ui->placefileView->resizeColumnToContents( + static_cast(model::PlacefileModel::Column::Url)); + + ui->placefileView->setItemDelegateForColumn( + static_cast(model::PlacefileModel::Column::Url), + p->leftElidedItemDelegate_); p->ConnectSignals(); } From 0064733679bdaa728eff51d272fd22a4bf8500bd Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 23 Jul 2023 08:45:14 -0500 Subject: [PATCH 020/199] Placefile fixes for Linux/gcc --- scwx-qt/source/scwx/qt/map/placefile_layer.cpp | 7 +++++-- scwx-qt/source/scwx/qt/model/placefile_model.cpp | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp index 55b275e3..31eb1f46 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp @@ -23,7 +23,7 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_); class PlacefileLayer::Impl { public: - explicit Impl(std::shared_ptr context) {}; + explicit Impl() {}; ~Impl() = default; void @@ -46,7 +46,7 @@ public: }; PlacefileLayer::PlacefileLayer(std::shared_ptr context) : - DrawLayer(context), p(std::make_unique(context)) + DrawLayer(context), p(std::make_unique()) { } @@ -168,6 +168,9 @@ void PlacefileLayer::Render( params, std::static_pointer_cast(drawItem)); break; + + default: + break; } } } diff --git a/scwx-qt/source/scwx/qt/model/placefile_model.cpp b/scwx-qt/source/scwx/qt/model/placefile_model.cpp index 6cfbe807..50b5ad73 100644 --- a/scwx-qt/source/scwx/qt/model/placefile_model.cpp +++ b/scwx-qt/source/scwx/qt/model/placefile_model.cpp @@ -74,7 +74,7 @@ Qt::ItemFlags PlacefileModel::flags(const QModelIndex& index) const QVariant PlacefileModel::data(const QModelIndex& index, int role) const { if (!index.isValid() || index.row() < 0 || - index.row() >= p->placefileNames_.size()) + static_cast(index.row()) >= p->placefileNames_.size()) { return QVariant(); } From 18c05b3a634d0fe34f8b261839815a1e16d22ace Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 23 Jul 2023 23:24:45 -0500 Subject: [PATCH 021/199] Additional placefile view/add enhancements --- .../scwx/qt/manager/placefile_manager.cpp | 103 ++++++++++++++++-- .../scwx/qt/manager/placefile_manager.hpp | 5 + .../source/scwx/qt/model/placefile_model.cpp | 17 ++- .../scwx/qt/ui/placefile_settings_widget.cpp | 6 +- wxdata/include/scwx/gr/placefile.hpp | 3 +- wxdata/source/scwx/gr/placefile.cpp | 7 +- 6 files changed, 119 insertions(+), 22 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index 0318d03e..b235ded2 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -5,6 +5,8 @@ #include #include +#include +#include #include #include #include @@ -39,6 +41,7 @@ public: std::string name_; std::shared_ptr placefile_; bool enabled_; + bool thresholded_ {true}; boost::asio::thread_pool threadPool_ {1u}; boost::asio::steady_timer refreshTimer_ {threadPool_}; std::mutex refreshMutex_ {}; @@ -54,13 +57,52 @@ public: PlacefileManager* self_; - std::vector> placefileRecords_ {}; - std::shared_mutex placefileRecordLock_ {}; + std::vector> placefileRecords_ {}; + std::unordered_map> + placefileRecordMap_ {}; + std::shared_mutex placefileRecordLock_ {}; }; PlacefileManager::PlacefileManager() : p(std::make_unique(this)) {} PlacefileManager::~PlacefileManager() = default; +bool PlacefileManager::PlacefileEnabled(const std::string& name) +{ + std::shared_lock lock(p->placefileRecordLock_); + + auto it = p->placefileRecordMap_.find(name); + if (it != p->placefileRecordMap_.cend()) + { + return it->second->enabled_; + } + return false; +} + +bool PlacefileManager::PlacefileThresholded(const std::string& name) +{ + std::shared_lock lock(p->placefileRecordLock_); + + auto it = p->placefileRecordMap_.find(name); + if (it != p->placefileRecordMap_.cend()) + { + return it->second->thresholded_; + } + return false; +} + +std::shared_ptr +PlacefileManager::Placefile(const std::string& name) +{ + std::shared_lock lock(p->placefileRecordLock_); + + auto it = p->placefileRecordMap_.find(name); + if (it != p->placefileRecordMap_.cend()) + { + return it->second->placefile_; + } + return nullptr; +} + std::vector> PlacefileManager::GetActivePlacefiles() { @@ -79,6 +121,47 @@ PlacefileManager::GetActivePlacefiles() return placefiles; } +void PlacefileManager::AddUrl(const std::string& urlString) +{ + std::string normalizedUrl; + + // Normalize URL string + QUrl url = QUrl::fromUserInput(QString::fromStdString(urlString)); + if (url.isLocalFile()) + { + normalizedUrl = QDir::toNativeSeparators(url.toLocalFile()).toStdString(); + } + else + { + normalizedUrl = urlString; + } + + std::unique_lock lock(p->placefileRecordLock_); + + // Determine if the placefile has been loaded previously + auto it = std::find_if(p->placefileRecords_.begin(), + p->placefileRecords_.end(), + [&normalizedUrl](auto& record) + { return record->name_ == normalizedUrl; }); + if (it != p->placefileRecords_.end()) + { + logger_->debug("Placefile already added: {}", normalizedUrl); + return; + } + + // Placefile is new, proceed with adding + logger_->info("AddUrl: {}", normalizedUrl); + + // Add an empty placefile record for the new URL + auto placefileRecord = p->placefileRecords_.emplace_back( + std::make_shared(normalizedUrl, nullptr, false)); + p->placefileRecordMap_.insert_or_assign(normalizedUrl, placefileRecord); + + lock.unlock(); + + Q_EMIT PlacefileUpdated(normalizedUrl); +} + void PlacefileManager::LoadFile(const std::string& filename) { logger_->debug("LoadFile: {}", filename); @@ -99,14 +182,13 @@ void PlacefileManager::LoadFile(const std::string& filename) std::unique_lock lock(p->placefileRecordLock_); // Determine if the placefile has been loaded previously - auto it = std::find_if(p->placefileRecords_.begin(), - p->placefileRecords_.end(), - [&filename](auto& record) - { return record->name_ == filename; }); - if (it != p->placefileRecords_.end()) + auto it = p->placefileRecordMap_.find(filename); + if (it != p->placefileRecordMap_.end()) { // If the placefile has been loaded previously, update it - (*it)->Update(placefile); + it->second->Update(placefile); + + lock.unlock(); Q_EMIT PlacefileUpdated(filename); } @@ -114,7 +196,10 @@ void PlacefileManager::LoadFile(const std::string& filename) { // If this is a new placefile, add it auto& record = p->placefileRecords_.emplace_back( - std::make_unique(filename, placefile)); + std::make_shared(filename, placefile)); + p->placefileRecordMap_.insert_or_assign(filename, record); + + lock.unlock(); Q_EMIT PlacefileEnabled(filename, record->enabled_); Q_EMIT PlacefileUpdated(filename); diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp index 55be1e90..4036a3b0 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp @@ -19,6 +19,10 @@ public: explicit PlacefileManager(); ~PlacefileManager(); + bool PlacefileEnabled(const std::string& name); + bool PlacefileThresholded(const std::string& name); + std::shared_ptr Placefile(const std::string& name); + /** * @brief Gets a list of active placefiles * @@ -26,6 +30,7 @@ public: */ std::vector> GetActivePlacefiles(); + void AddUrl(const std::string& urlString); void LoadFile(const std::string& filename); static std::shared_ptr Instance(); diff --git a/scwx-qt/source/scwx/qt/model/placefile_model.cpp b/scwx-qt/source/scwx/qt/model/placefile_model.cpp index 50b5ad73..716e54a4 100644 --- a/scwx-qt/source/scwx/qt/model/placefile_model.cpp +++ b/scwx-qt/source/scwx/qt/model/placefile_model.cpp @@ -90,14 +90,14 @@ QVariant PlacefileModel::data(const QModelIndex& index, int role) const case static_cast(Column::Enabled): if (role == types::ItemDataRole::SortRole) { - return true; // TODO + return p->placefileManager_->PlacefileEnabled(placefileName); } break; case static_cast(Column::Thresholds): if (role == types::ItemDataRole::SortRole) { - return true; // TODO + return p->placefileManager_->PlacefileThresholded(placefileName); } break; @@ -105,7 +105,14 @@ QVariant PlacefileModel::data(const QModelIndex& index, int role) const return QString::fromStdString(placefileName); case static_cast(Column::Description): + { + auto placefile = p->placefileManager_->Placefile(placefileName); + if (placefile != nullptr) + { + return QString::fromStdString(placefile->title()); + } return QString {}; + } default: break; @@ -116,10 +123,10 @@ QVariant PlacefileModel::data(const QModelIndex& index, int role) const switch (index.column()) { case static_cast(Column::Enabled): - return true; // TODO + return p->placefileManager_->PlacefileEnabled(placefileName); case static_cast(Column::Thresholds): - return true; // TODO + return p->placefileManager_->PlacefileThresholded(placefileName); default: break; @@ -133,7 +140,7 @@ QVariant PlacefileModel::headerData(int section, Qt::Orientation orientation, int role) const { - if (role == Qt::DisplayRole) + if (role == Qt::ItemDataRole::DisplayRole) { if (orientation == Qt::Horizontal) { diff --git a/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp b/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp index 01612320..d9167fb6 100644 --- a/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp @@ -1,6 +1,7 @@ #include "placefile_settings_widget.hpp" #include "ui_placefile_settings_widget.h" +#include #include #include #include @@ -41,6 +42,9 @@ public: PlacefileSettingsWidget* self_; OpenUrlDialog* openUrlDialog_; + std::shared_ptr placefileManager_ { + manager::PlacefileManager::Instance()}; + model::PlacefileModel* placefileModel_; QSortFilterProxyModel* placefileProxyModel_; LeftElidedItemDelegate* leftElidedItemDelegate_; @@ -88,7 +92,7 @@ void PlacefileSettingsWidgetImpl::ConnectSignals() &OpenUrlDialog::accepted, self_, [this]() - { logger_->info("Add URL: {}", openUrlDialog_->url().toStdString()); }); + { placefileManager_->AddUrl(openUrlDialog_->url().toStdString()); }); QObject::connect(self_->ui->placefileFilter, &QLineEdit::textChanged, diff --git a/wxdata/include/scwx/gr/placefile.hpp b/wxdata/include/scwx/gr/placefile.hpp index 20672008..9a111468 100644 --- a/wxdata/include/scwx/gr/placefile.hpp +++ b/wxdata/include/scwx/gr/placefile.hpp @@ -89,8 +89,7 @@ public: std::shared_ptr font(std::size_t i); static std::shared_ptr Load(const std::string& filename); - static std::shared_ptr Load(const std::string& name, - std::istream& is); + static std::shared_ptr Load(std::istream& is); private: class Impl; diff --git a/wxdata/source/scwx/gr/placefile.cpp b/wxdata/source/scwx/gr/placefile.cpp index a3ef8f16..249d7e89 100644 --- a/wxdata/source/scwx/gr/placefile.cpp +++ b/wxdata/source/scwx/gr/placefile.cpp @@ -110,16 +110,13 @@ std::shared_ptr Placefile::Load(const std::string& filename) { logger_->debug("Loading placefile: {}", filename); std::ifstream f(filename, std::ios_base::in); - return Load(filename, f); + return Load(f); } -std::shared_ptr Placefile::Load(const std::string& name, - std::istream& is) +std::shared_ptr Placefile::Load(std::istream& is) { std::shared_ptr placefile = std::make_shared(); - placefile->p->title_ = name; - std::string line; while (scwx::util::getline(is, line)) { From 3ff34caa0270265b3d387c2659b0394ab561e130 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 24 Jul 2023 22:27:42 -0500 Subject: [PATCH 022/199] Placefile view enhancements - Size enabled/threshold columns for a checkbox - Combine URL/description columns - Tooltips - Alternating row colors --- .../scwx/qt/manager/placefile_manager.cpp | 21 +-- .../source/scwx/qt/model/placefile_model.cpp | 120 +++++++++++++----- .../source/scwx/qt/model/placefile_model.hpp | 7 +- .../scwx/qt/ui/placefile_settings_widget.cpp | 24 ++-- .../scwx/qt/ui/placefile_settings_widget.ui | 3 + 5 files changed, 117 insertions(+), 58 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index b235ded2..ec496de4 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -164,15 +164,18 @@ void PlacefileManager::AddUrl(const std::string& urlString) void PlacefileManager::LoadFile(const std::string& filename) { - logger_->debug("LoadFile: {}", filename); + const std::string placefileName = + QDir::toNativeSeparators(QString::fromStdString(filename)).toStdString(); + + logger_->debug("LoadFile: {}", placefileName); boost::asio::post( p->threadPool_, - [=, this]() + [placefileName, this]() { // Load file std::shared_ptr placefile = - gr::Placefile::Load(filename); + gr::Placefile::Load(placefileName); if (placefile == nullptr) { @@ -182,7 +185,7 @@ void PlacefileManager::LoadFile(const std::string& filename) std::unique_lock lock(p->placefileRecordLock_); // Determine if the placefile has been loaded previously - auto it = p->placefileRecordMap_.find(filename); + auto it = p->placefileRecordMap_.find(placefileName); if (it != p->placefileRecordMap_.end()) { // If the placefile has been loaded previously, update it @@ -190,19 +193,19 @@ void PlacefileManager::LoadFile(const std::string& filename) lock.unlock(); - Q_EMIT PlacefileUpdated(filename); + Q_EMIT PlacefileUpdated(placefileName); } else { // If this is a new placefile, add it auto& record = p->placefileRecords_.emplace_back( - std::make_shared(filename, placefile)); - p->placefileRecordMap_.insert_or_assign(filename, record); + std::make_shared(placefileName, placefile)); + p->placefileRecordMap_.insert_or_assign(placefileName, record); lock.unlock(); - Q_EMIT PlacefileEnabled(filename, record->enabled_); - Q_EMIT PlacefileUpdated(filename); + Q_EMIT PlacefileEnabled(placefileName, record->enabled_); + Q_EMIT PlacefileUpdated(placefileName); } }); } diff --git a/scwx-qt/source/scwx/qt/model/placefile_model.cpp b/scwx-qt/source/scwx/qt/model/placefile_model.cpp index 716e54a4..7fbf5984 100644 --- a/scwx-qt/source/scwx/qt/model/placefile_model.cpp +++ b/scwx-qt/source/scwx/qt/model/placefile_model.cpp @@ -4,7 +4,10 @@ #include #include +#include #include +#include +#include namespace scwx { @@ -19,7 +22,7 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_); static constexpr int kFirstColumn = static_cast(PlacefileModel::Column::Enabled); static constexpr int kLastColumn = - static_cast(PlacefileModel::Column::Description); + static_cast(PlacefileModel::Column::Placefile); static constexpr int kNumColumns = kLastColumn - kFirstColumn + 1; class PlacefileModelImpl @@ -82,51 +85,88 @@ QVariant PlacefileModel::data(const QModelIndex& index, int role) const const auto& placefileName = p->placefileNames_.at(index.row()); if (role == Qt::ItemDataRole::DisplayRole || - role == Qt::ItemDataRole::ToolTipRole || - role == types::ItemDataRole::SortRole) + role == Qt::ItemDataRole::ToolTipRole) { + static const QString enabledString = QObject::tr("Enabled"); + static const QString disabledString = QObject::tr("Disabled"); + + static const QString thresholdsEnabledString = + QObject::tr("Thresholds Enabled"); + static const QString thresholdsDisabledString = + QObject::tr("Thresholds Disabled"); + switch (index.column()) { case static_cast(Column::Enabled): - if (role == types::ItemDataRole::SortRole) + if (role == Qt::ItemDataRole::ToolTipRole) { - return p->placefileManager_->PlacefileEnabled(placefileName); + return p->placefileManager_->PlacefileEnabled(placefileName) ? + enabledString : + disabledString; } break; case static_cast(Column::Thresholds): - if (role == types::ItemDataRole::SortRole) + if (role == Qt::ItemDataRole::ToolTipRole) { - return p->placefileManager_->PlacefileThresholded(placefileName); + return p->placefileManager_->PlacefileThresholded(placefileName) ? + thresholdsEnabledString : + thresholdsDisabledString; } break; - case static_cast(Column::Url): - return QString::fromStdString(placefileName); - - case static_cast(Column::Description): + case static_cast(Column::Placefile): { - auto placefile = p->placefileManager_->Placefile(placefileName); + std::string description = placefileName; + auto placefile = p->placefileManager_->Placefile(placefileName); if (placefile != nullptr) { - return QString::fromStdString(placefile->title()); + std::string title = placefile->title(); + if (!title.empty()) + { + description = title + '\n' + description; + } } - return QString {}; + + return QString::fromStdString(description); } default: break; } } + else if (role == types::ItemDataRole::SortRole) + { + switch (index.column()) + { + case static_cast(Column::Enabled): + return p->placefileManager_->PlacefileEnabled(placefileName); + + case static_cast(Column::Thresholds): + return p->placefileManager_->PlacefileThresholded(placefileName); + + case static_cast(Column::Placefile): + return QString::fromStdString(placefileName); + + default: + break; + } + } else if (role == Qt::ItemDataRole::CheckStateRole) { switch (index.column()) { case static_cast(Column::Enabled): - return p->placefileManager_->PlacefileEnabled(placefileName); + return static_cast( + p->placefileManager_->PlacefileEnabled(placefileName) ? + Qt::CheckState::Checked : + Qt::CheckState::Unchecked); case static_cast(Column::Thresholds): - return p->placefileManager_->PlacefileThresholded(placefileName); + return static_cast( + p->placefileManager_->PlacefileThresholded(placefileName) ? + Qt::CheckState::Checked : + Qt::CheckState::Unchecked); default: break; @@ -147,41 +187,53 @@ QVariant PlacefileModel::headerData(int section, switch (section) { case static_cast(Column::Enabled): - return tr("Enabled"); + return tr("E"); case static_cast(Column::Thresholds): - return tr("Thresholds"); + return tr("T"); - case static_cast(Column::Url): - return tr("URL"); - - case static_cast(Column::Description): - return tr("Description"); + case static_cast(Column::Placefile): + return tr("Placefile"); default: break; } } } - else if (role == Qt::ItemDataRole::SizeHintRole) + else if (role == Qt::ItemDataRole::ToolTipRole) { - static const QFontMetrics fontMetrics(QApplication::font()); - - QSize contentsSize {}; - switch (section) { - case static_cast(Column::Url): - contentsSize = fontMetrics.size(0, QString(15, 'W')); - break; + case static_cast(Column::Enabled): + return tr("Enabled"); + + case static_cast(Column::Thresholds): + return tr("Thresholds"); default: break; } - - if (contentsSize != QSize {}) + } + else if (role == Qt::ItemDataRole::SizeHintRole) + { + switch (section) { - return contentsSize; + case static_cast(Column::Enabled): + case static_cast(Column::Thresholds): + { + static const QCheckBox checkBox {}; + QStyleOptionButton option {}; + option.initFrom(&checkBox); + + // Width values from QCheckBox + return QApplication::style()->sizeFromContents( + QStyle::ContentsType::CT_CheckBox, + &option, + {option.iconSize.width() + 4, 0}); + } + + default: + break; } } diff --git a/scwx-qt/source/scwx/qt/model/placefile_model.hpp b/scwx-qt/source/scwx/qt/model/placefile_model.hpp index 0f5a1b10..68bffdcf 100644 --- a/scwx-qt/source/scwx/qt/model/placefile_model.hpp +++ b/scwx-qt/source/scwx/qt/model/placefile_model.hpp @@ -21,10 +21,9 @@ class PlacefileModel : public QAbstractTableModel public: enum class Column : int { - Enabled = 0, - Thresholds = 1, - Url = 2, - Description = 3 + Enabled = 0, + Thresholds = 1, + Placefile = 2 }; explicit PlacefileModel(QObject* parent = nullptr); diff --git a/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp b/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp index d9167fb6..c5f73945 100644 --- a/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp @@ -58,19 +58,21 @@ PlacefileSettingsWidget::PlacefileSettingsWidget(QWidget* parent) : ui->setupUi(this); ui->placefileView->setModel(p->placefileProxyModel_); - ui->placefileView->header()->setSortIndicator( - static_cast(model::PlacefileModel::Column::Url), Qt::AscendingOrder); - ui->placefileView->resizeColumnToContents( - static_cast(model::PlacefileModel::Column::Enabled)); - ui->placefileView->resizeColumnToContents( - static_cast(model::PlacefileModel::Column::Thresholds)); - ui->placefileView->resizeColumnToContents( - static_cast(model::PlacefileModel::Column::Url)); + auto placefileViewHeader = ui->placefileView->header(); - ui->placefileView->setItemDelegateForColumn( - static_cast(model::PlacefileModel::Column::Url), - p->leftElidedItemDelegate_); + placefileViewHeader->setMinimumSectionSize(10); + placefileViewHeader->setSortIndicator( + static_cast(model::PlacefileModel::Column::Placefile), + Qt::AscendingOrder); + + // Enabled and Thresholds columns have a fixed size (checkbox) + placefileViewHeader->setSectionResizeMode( + static_cast(model::PlacefileModel::Column::Enabled), + QHeaderView::ResizeMode::ResizeToContents); + placefileViewHeader->setSectionResizeMode( + static_cast(model::PlacefileModel::Column::Thresholds), + QHeaderView::ResizeMode::ResizeToContents); p->ConnectSignals(); } diff --git a/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.ui b/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.ui index ef2f24fd..e5f3595b 100644 --- a/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.ui +++ b/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.ui @@ -16,6 +16,9 @@ + + true + 0 From 7c21ccaf411766bae91ef82d0017b6998de7e752 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 25 Jul 2023 23:15:12 -0500 Subject: [PATCH 023/199] Make placefile settings view editable --- .../scwx/qt/manager/placefile_manager.cpp | 62 ++++- .../scwx/qt/manager/placefile_manager.hpp | 12 +- .../source/scwx/qt/model/placefile_model.cpp | 214 ++++++++++++------ .../source/scwx/qt/model/placefile_model.hpp | 6 + wxdata/include/scwx/gr/placefile.hpp | 4 +- wxdata/source/scwx/gr/placefile.cpp | 13 +- 6 files changed, 234 insertions(+), 77 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index ec496de4..83cf97c1 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -66,7 +66,7 @@ public: PlacefileManager::PlacefileManager() : p(std::make_unique(this)) {} PlacefileManager::~PlacefileManager() = default; -bool PlacefileManager::PlacefileEnabled(const std::string& name) +bool PlacefileManager::placefile_enabled(const std::string& name) { std::shared_lock lock(p->placefileRecordLock_); @@ -78,7 +78,7 @@ bool PlacefileManager::PlacefileEnabled(const std::string& name) return false; } -bool PlacefileManager::PlacefileThresholded(const std::string& name) +bool PlacefileManager::placefile_thresholded(const std::string& name) { std::shared_lock lock(p->placefileRecordLock_); @@ -91,7 +91,7 @@ bool PlacefileManager::PlacefileThresholded(const std::string& name) } std::shared_ptr -PlacefileManager::Placefile(const std::string& name) +PlacefileManager::placefile(const std::string& name) { std::shared_lock lock(p->placefileRecordLock_); @@ -103,6 +103,60 @@ PlacefileManager::Placefile(const std::string& name) return nullptr; } +void PlacefileManager::set_placefile_enabled(const std::string& name, + bool enabled) +{ + std::shared_lock lock(p->placefileRecordLock_); + + auto it = p->placefileRecordMap_.find(name); + if (it != p->placefileRecordMap_.cend()) + { + it->second->enabled_ = enabled; + + lock.unlock(); + + Q_EMIT PlacefileEnabled(name, enabled); + } +} + +void PlacefileManager::set_placefile_thresholded(const std::string& name, + bool thresholded) +{ + std::shared_lock lock(p->placefileRecordLock_); + + auto it = p->placefileRecordMap_.find(name); + if (it != p->placefileRecordMap_.cend()) + { + it->second->thresholded_ = thresholded; + + lock.unlock(); + + Q_EMIT PlacefileUpdated(name); + } +} + +void PlacefileManager::set_placefile_url(const std::string& name, + const std::string& newUrl) +{ + std::unique_lock lock(p->placefileRecordLock_); + + auto it = p->placefileRecordMap_.find(name); + auto itNew = p->placefileRecordMap_.find(newUrl); + if (it != p->placefileRecordMap_.cend() && + itNew == p->placefileRecordMap_.cend()) + { + auto placefileRecord = it->second; + placefileRecord->name_ = newUrl; + placefileRecord->placefile_ = nullptr; + p->placefileRecordMap_.erase(it); + p->placefileRecordMap_.emplace(newUrl, placefileRecord); + + lock.unlock(); + + Q_EMIT PlacefileRenamed(name, newUrl); + } +} + std::vector> PlacefileManager::GetActivePlacefiles() { @@ -112,7 +166,7 @@ PlacefileManager::GetActivePlacefiles() for (const auto& record : p->placefileRecords_) { - if (record->enabled_) + if (record->enabled_ && record->placefile_ != nullptr) { placefiles.emplace_back(record->placefile_); } diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp index 4036a3b0..d763ef3a 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp @@ -19,9 +19,13 @@ public: explicit PlacefileManager(); ~PlacefileManager(); - bool PlacefileEnabled(const std::string& name); - bool PlacefileThresholded(const std::string& name); - std::shared_ptr Placefile(const std::string& name); + bool placefile_enabled(const std::string& name); + bool placefile_thresholded(const std::string& name); + std::shared_ptr placefile(const std::string& name); + + void set_placefile_enabled(const std::string& name, bool enabled); + void set_placefile_thresholded(const std::string& name, bool thresholded); + void set_placefile_url(const std::string& name, const std::string& newUrl); /** * @brief Gets a list of active placefiles @@ -37,6 +41,8 @@ public: signals: void PlacefileEnabled(const std::string& name, bool enabled); + void PlacefileRenamed(const std::string& oldName, + const std::string& newName); void PlacefileUpdated(const std::string& name); private: diff --git a/scwx-qt/source/scwx/qt/model/placefile_model.cpp b/scwx-qt/source/scwx/qt/model/placefile_model.cpp index 7fbf5984..87d81c28 100644 --- a/scwx-qt/source/scwx/qt/model/placefile_model.cpp +++ b/scwx-qt/source/scwx/qt/model/placefile_model.cpp @@ -40,6 +40,16 @@ public: PlacefileModel::PlacefileModel(QObject* parent) : QAbstractTableModel(parent), p(std::make_unique()) { + connect(p->placefileManager_.get(), + &manager::PlacefileManager::PlacefileEnabled, + this, + &PlacefileModel::HandlePlacefileUpdate); + + connect(p->placefileManager_.get(), + &manager::PlacefileManager::PlacefileRenamed, + this, + &PlacefileModel::HandlePlacefileRenamed); + connect(p->placefileManager_.get(), &manager::PlacefileManager::PlacefileUpdated, this, @@ -65,7 +75,12 @@ Qt::ItemFlags PlacefileModel::flags(const QModelIndex& index) const { case static_cast(Column::Enabled): case static_cast(Column::Thresholds): - flags |= Qt::ItemFlag::ItemIsUserCheckable; + flags |= Qt::ItemFlag::ItemIsUserCheckable | Qt::ItemFlag::ItemIsEditable; + break; + + case static_cast(Column::Placefile): + flags |= Qt::ItemFlag::ItemIsEditable; + break; default: break; @@ -76,6 +91,14 @@ Qt::ItemFlags PlacefileModel::flags(const QModelIndex& index) const QVariant PlacefileModel::data(const QModelIndex& index, int role) const { + static const QString enabledString = QObject::tr("Enabled"); + static const QString disabledString = QObject::tr("Disabled"); + + static const QString thresholdsEnabledString = + QObject::tr("Thresholds Enabled"); + static const QString thresholdsDisabledString = + QObject::tr("Thresholds Disabled"); + if (!index.isValid() || index.row() < 0 || static_cast(index.row()) >= p->placefileNames_.size()) { @@ -84,41 +107,54 @@ QVariant PlacefileModel::data(const QModelIndex& index, int role) const const auto& placefileName = p->placefileNames_.at(index.row()); - if (role == Qt::ItemDataRole::DisplayRole || - role == Qt::ItemDataRole::ToolTipRole) + switch (index.column()) { - static const QString enabledString = QObject::tr("Enabled"); - static const QString disabledString = QObject::tr("Disabled"); - - static const QString thresholdsEnabledString = - QObject::tr("Thresholds Enabled"); - static const QString thresholdsDisabledString = - QObject::tr("Thresholds Disabled"); - - switch (index.column()) + case static_cast(Column::Enabled): + if (role == Qt::ItemDataRole::ToolTipRole) { - case static_cast(Column::Enabled): - if (role == Qt::ItemDataRole::ToolTipRole) - { - return p->placefileManager_->PlacefileEnabled(placefileName) ? - enabledString : - disabledString; - } - break; + return p->placefileManager_->placefile_enabled(placefileName) ? + enabledString : + disabledString; + } + else if (role == Qt::ItemDataRole::CheckStateRole) + { + return static_cast( + p->placefileManager_->placefile_enabled(placefileName) ? + Qt::CheckState::Checked : + Qt::CheckState::Unchecked); + } + else if (role == types::ItemDataRole::SortRole) + { + return p->placefileManager_->placefile_enabled(placefileName); + } + break; - case static_cast(Column::Thresholds): - if (role == Qt::ItemDataRole::ToolTipRole) - { - return p->placefileManager_->PlacefileThresholded(placefileName) ? - thresholdsEnabledString : - thresholdsDisabledString; - } - break; + case static_cast(Column::Thresholds): + if (role == Qt::ItemDataRole::ToolTipRole) + { + return p->placefileManager_->placefile_thresholded(placefileName) ? + thresholdsEnabledString : + thresholdsDisabledString; + } + else if (role == Qt::ItemDataRole::CheckStateRole) + { + return static_cast( + p->placefileManager_->placefile_thresholded(placefileName) ? + Qt::CheckState::Checked : + Qt::CheckState::Unchecked); + } + else if (role == types::ItemDataRole::SortRole) + { + return p->placefileManager_->placefile_thresholded(placefileName); + } + break; - case static_cast(Column::Placefile): + case static_cast(Column::Placefile): + if (role == Qt::ItemDataRole::DisplayRole || + role == Qt::ItemDataRole::ToolTipRole) { std::string description = placefileName; - auto placefile = p->placefileManager_->Placefile(placefileName); + auto placefile = p->placefileManager_->placefile(placefileName); if (placefile != nullptr) { std::string title = placefile->title(); @@ -130,47 +166,15 @@ QVariant PlacefileModel::data(const QModelIndex& index, int role) const return QString::fromStdString(description); } - - default: - break; - } - } - else if (role == types::ItemDataRole::SortRole) - { - switch (index.column()) + else if (role == Qt::ItemDataRole::EditRole || + role == types::ItemDataRole::SortRole) { - case static_cast(Column::Enabled): - return p->placefileManager_->PlacefileEnabled(placefileName); - - case static_cast(Column::Thresholds): - return p->placefileManager_->PlacefileThresholded(placefileName); - - case static_cast(Column::Placefile): return QString::fromStdString(placefileName); - - default: - break; } - } - else if (role == Qt::ItemDataRole::CheckStateRole) - { - switch (index.column()) - { - case static_cast(Column::Enabled): - return static_cast( - p->placefileManager_->PlacefileEnabled(placefileName) ? - Qt::CheckState::Checked : - Qt::CheckState::Unchecked); + break; - case static_cast(Column::Thresholds): - return static_cast( - p->placefileManager_->PlacefileThresholded(placefileName) ? - Qt::CheckState::Checked : - Qt::CheckState::Unchecked); - - default: - break; - } + default: + break; } return QVariant(); @@ -240,6 +244,82 @@ QVariant PlacefileModel::headerData(int section, return QVariant(); } +bool PlacefileModel::setData(const QModelIndex& index, + const QVariant& value, + int role) +{ + if (!index.isValid() || index.row() < 0 || + static_cast(index.row()) >= p->placefileNames_.size()) + { + return false; + } + + const auto& placefileName = p->placefileNames_.at(index.row()); + + switch (index.column()) + { + case static_cast(Column::Enabled): + if (role == Qt::ItemDataRole::CheckStateRole) + { + p->placefileManager_->set_placefile_enabled(placefileName, + value.toBool()); + return true; + } + break; + + case static_cast(Column::Thresholds): + if (role == Qt::ItemDataRole::CheckStateRole) + { + p->placefileManager_->set_placefile_thresholded(placefileName, + value.toBool()); + return true; + } + break; + + case static_cast(Column::Placefile): + if (role == Qt::ItemDataRole::EditRole) + { + p->placefileManager_->set_placefile_url( + placefileName, value.toString().toStdString()); + return true; + } + break; + + default: + break; + } + + return true; +} + +void PlacefileModel::HandlePlacefileRenamed(const std::string& oldName, + const std::string& newName) +{ + auto it = + std::find(p->placefileNames_.begin(), p->placefileNames_.end(), oldName); + + if (it != p->placefileNames_.end()) + { + // Placefile exists, mark row as updated + const int row = std::distance(p->placefileNames_.begin(), it); + QModelIndex topLeft = createIndex(row, kFirstColumn); + QModelIndex bottomRight = createIndex(row, kLastColumn); + + // Rename placefile + *it = newName; + + Q_EMIT dataChanged(topLeft, bottomRight); + } + else + { + // Placefile is new, append row + const int newIndex = static_cast(p->placefileNames_.size()); + beginInsertRows(QModelIndex(), newIndex, newIndex); + p->placefileNames_.push_back(newName); + endInsertRows(); + } +} + void PlacefileModel::HandlePlacefileUpdate(const std::string& name) { auto it = diff --git a/scwx-qt/source/scwx/qt/model/placefile_model.hpp b/scwx-qt/source/scwx/qt/model/placefile_model.hpp index 68bffdcf..f4f01167 100644 --- a/scwx-qt/source/scwx/qt/model/placefile_model.hpp +++ b/scwx-qt/source/scwx/qt/model/placefile_model.hpp @@ -40,7 +40,13 @@ public: Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex& index, + const QVariant& value, + int role = Qt::EditRole) override; + public slots: + void HandlePlacefileRenamed(const std::string& oldName, + const std::string& newName); void HandlePlacefileUpdate(const std::string& name); private: diff --git a/wxdata/include/scwx/gr/placefile.hpp b/wxdata/include/scwx/gr/placefile.hpp index 9a111468..4a4f624b 100644 --- a/wxdata/include/scwx/gr/placefile.hpp +++ b/wxdata/include/scwx/gr/placefile.hpp @@ -84,12 +84,14 @@ public: */ std::vector> GetDrawItems(); + std::string name() const; std::string title() const; std::unordered_map> fonts(); std::shared_ptr font(std::size_t i); static std::shared_ptr Load(const std::string& filename); - static std::shared_ptr Load(std::istream& is); + static std::shared_ptr Load(const std::string& name, + std::istream& is); private: class Impl; diff --git a/wxdata/source/scwx/gr/placefile.cpp b/wxdata/source/scwx/gr/placefile.cpp index 249d7e89..a2e62dcf 100644 --- a/wxdata/source/scwx/gr/placefile.cpp +++ b/wxdata/source/scwx/gr/placefile.cpp @@ -51,6 +51,7 @@ public: static void ProcessEscapeCharacters(std::string& s); static void TrimQuotes(std::string& s); + std::string name_ {}; std::string title_ {}; std::chrono::seconds refresh_ {-1}; @@ -85,6 +86,11 @@ std::vector> Placefile::GetDrawItems() return p->drawItems_; } +std::string Placefile::name() const +{ + return p->name_; +} + std::string Placefile::title() const { return p->title_; @@ -110,13 +116,16 @@ std::shared_ptr Placefile::Load(const std::string& filename) { logger_->debug("Loading placefile: {}", filename); std::ifstream f(filename, std::ios_base::in); - return Load(f); + return Load(filename, f); } -std::shared_ptr Placefile::Load(std::istream& is) +std::shared_ptr Placefile::Load(const std::string& name, + std::istream& is) { std::shared_ptr placefile = std::make_shared(); + placefile->p->name_ = name; + std::string line; while (scwx::util::getline(is, line)) { From 6dedce508970bd193be2e972f0ce811576846dec Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 25 Jul 2023 23:16:36 -0500 Subject: [PATCH 024/199] Update placefile view when placefiles are edited from settings --- scwx-qt/source/scwx/qt/map/map_widget.cpp | 22 +++++++++++++++++++ .../source/scwx/qt/map/placefile_layer.cpp | 17 +++++++++++--- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index a59de3d5..710da1e9 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -94,6 +95,8 @@ public: // Set Map Provider Details mapProvider_ = GetMapProvider(generalSettings.map_provider().GetValue()); + + ConnectSignals(); } ~MapWidgetImpl() @@ -115,6 +118,7 @@ public: void AddLayer(const std::string& id, std::shared_ptr layer, const std::string& before = {}); + void ConnectSignals(); void InitializeNewRadarProductView(const std::string& colorPalette); void RadarProductManagerConnect(); void RadarProductManagerDisconnect(); @@ -142,6 +146,8 @@ public: std::string imGuiContextName_; bool imGuiRendererInitialized_; + std::shared_ptr placefileManager_ { + manager::PlacefileManager::Instance()}; std::shared_ptr radarProductManager_; std::shared_ptr colorTable_; @@ -188,6 +194,22 @@ MapWidget::~MapWidget() makeCurrent(); } +void MapWidgetImpl::ConnectSignals() +{ + connect(placefileManager_.get(), + &manager::PlacefileManager::PlacefileEnabled, + widget_, + [this]() { widget_->update(); }); + connect(placefileManager_.get(), + &manager::PlacefileManager::PlacefileRenamed, + widget_, + [this]() { widget_->update(); }); + connect(placefileManager_.get(), + &manager::PlacefileManager::PlacefileUpdated, + widget_, + [this]() { widget_->update(); }); +} + common::Level3ProductCategoryMap MapWidget::GetAvailableLevel3Categories() { if (p->radarProductManager_ != nullptr) diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp index 31eb1f46..c796cc93 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp @@ -42,6 +42,7 @@ public: float mapScale_ {1.0f}; float halfWidth_ {}; float halfHeight_ {}; + bool thresholded_ {true}; ImFont* monospaceFont_ {}; }; @@ -63,8 +64,11 @@ void PlacefileLayer::Impl::RenderTextDrawItem( const QMapLibreGL::CustomLayerRenderParameters& params, std::shared_ptr di) { - auto distance = util::GeographicLib::GetDistance( - params.latitude, params.longitude, di->latitude_, di->longitude_); + auto distance = + (thresholded_) ? + util::GeographicLib::GetDistance( + params.latitude, params.longitude, di->latitude_, di->longitude_) : + 0; if (distance < di->threshold_) { @@ -145,7 +149,11 @@ void PlacefileLayer::Render( std::size_t fontSize = 16; auto fontSizes = manager::SettingsManager::general_settings().font_sizes().GetValue(); - if (fontSizes.size() > 0) + if (fontSizes.size() > 1) + { + fontSize = fontSizes[1]; + } + else if (fontSizes.size() > 0) { fontSize = fontSizes[0]; } @@ -159,6 +167,9 @@ void PlacefileLayer::Render( // Render text for (auto& placefile : placefileManager->GetActivePlacefiles()) { + p->thresholded_ = + placefileManager->placefile_thresholded(placefile->name()); + for (auto& drawItem : placefile->GetDrawItems()) { switch (drawItem->itemType_) From c6801722a1ad506df711d43c5f855a8a40b3415c Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 25 Jul 2023 23:21:34 -0500 Subject: [PATCH 025/199] Always resize ImGui windows when rendering placefile text to avoid clipping --- scwx-qt/source/scwx/qt/map/placefile_layer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp index c796cc93..1b82af67 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp @@ -104,7 +104,8 @@ void PlacefileLayer::Impl::RenderText( ImVec2 {x, y}, ImGuiCond_Always, ImVec2 {0.5f, 0.5f}); ImGui::Begin(windowName.c_str(), nullptr, - ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoNav | + ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoBackground); // Render text From 61bfeb88d02f9906b704da326e863de5ec262b7e Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 25 Jul 2023 23:26:52 -0500 Subject: [PATCH 026/199] Normalize edited URL --- .../scwx/qt/manager/placefile_manager.cpp | 43 ++++++++++++------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index 83cf97c1..9a0a2a90 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -53,6 +53,8 @@ public: explicit Impl(PlacefileManager* self) : self_ {self} {} ~Impl() {} + static std::string NormalizeUrl(const std::string& urlString); + boost::asio::thread_pool threadPool_ {1u}; PlacefileManager* self_; @@ -138,22 +140,24 @@ void PlacefileManager::set_placefile_thresholded(const std::string& name, void PlacefileManager::set_placefile_url(const std::string& name, const std::string& newUrl) { + std::string normalizedUrl = Impl::NormalizeUrl(newUrl); + std::unique_lock lock(p->placefileRecordLock_); auto it = p->placefileRecordMap_.find(name); - auto itNew = p->placefileRecordMap_.find(newUrl); + auto itNew = p->placefileRecordMap_.find(normalizedUrl); if (it != p->placefileRecordMap_.cend() && itNew == p->placefileRecordMap_.cend()) { auto placefileRecord = it->second; - placefileRecord->name_ = newUrl; + placefileRecord->name_ = normalizedUrl; placefileRecord->placefile_ = nullptr; p->placefileRecordMap_.erase(it); - p->placefileRecordMap_.emplace(newUrl, placefileRecord); + p->placefileRecordMap_.emplace(normalizedUrl, placefileRecord); lock.unlock(); - Q_EMIT PlacefileRenamed(name, newUrl); + Q_EMIT PlacefileRenamed(name, normalizedUrl); } } @@ -177,18 +181,7 @@ PlacefileManager::GetActivePlacefiles() void PlacefileManager::AddUrl(const std::string& urlString) { - std::string normalizedUrl; - - // Normalize URL string - QUrl url = QUrl::fromUserInput(QString::fromStdString(urlString)); - if (url.isLocalFile()) - { - normalizedUrl = QDir::toNativeSeparators(url.toLocalFile()).toStdString(); - } - else - { - normalizedUrl = urlString; - } + std::string normalizedUrl = Impl::NormalizeUrl(urlString); std::unique_lock lock(p->placefileRecordLock_); @@ -291,6 +284,24 @@ std::shared_ptr PlacefileManager::Instance() return placefileManager; } +std::string PlacefileManager::Impl::NormalizeUrl(const std::string& urlString) +{ + std::string normalizedUrl; + + // Normalize URL string + QUrl url = QUrl::fromUserInput(QString::fromStdString(urlString)); + if (url.isLocalFile()) + { + normalizedUrl = QDir::toNativeSeparators(url.toLocalFile()).toStdString(); + } + else + { + normalizedUrl = urlString; + } + + return normalizedUrl; +} + } // namespace manager } // namespace qt } // namespace scwx From dcace245791c5ff514a111b84543b13a1a9149e6 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 26 Jul 2023 00:18:10 -0500 Subject: [PATCH 027/199] Load placefiles from remote URLs --- .../scwx/qt/manager/placefile_manager.cpp | 104 +++++++++++++++++- 1 file changed, 98 insertions(+), 6 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index 9a0a2a90..b0c79e5d 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -10,6 +10,7 @@ #include #include #include +#include namespace scwx { @@ -21,8 +22,10 @@ namespace manager static const std::string logPrefix_ = "scwx::qt::manager::placefile_manager"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); -class PlacefileRecord +class PlacefileRecord : public QObject { + Q_OBJECT + public: explicit PlacefileRecord(const std::string& name, std::shared_ptr placefile, @@ -36,7 +39,9 @@ public: refreshTimer_.cancel(); } - void Update(std::shared_ptr placefile); + void Update(); + void UpdateAsync(); + void UpdatePlacefile(std::shared_ptr placefile); std::string name_; std::shared_ptr placefile_; @@ -45,6 +50,10 @@ public: boost::asio::thread_pool threadPool_ {1u}; boost::asio::steady_timer refreshTimer_ {threadPool_}; std::mutex refreshMutex_ {}; + +signals: + void Updated(const std::string& name, + std::shared_ptr placefile); }; class PlacefileManager::Impl @@ -55,6 +64,8 @@ public: static std::string NormalizeUrl(const std::string& urlString); + void ConnectRecordSignals(std::shared_ptr record); + boost::asio::thread_pool threadPool_ {1u}; PlacefileManager* self_; @@ -158,9 +169,36 @@ void PlacefileManager::set_placefile_url(const std::string& name, lock.unlock(); Q_EMIT PlacefileRenamed(name, normalizedUrl); + + // Queue a placefile update + placefileRecord->UpdateAsync(); } } +void PlacefileManager::Impl::ConnectRecordSignals( + std::shared_ptr record) +{ + QObject::connect( + record.get(), + &PlacefileRecord::Updated, + self_, + [this](const std::string& name, std::shared_ptr placefile) + { + PlacefileRecord* sender = + static_cast(self_->sender()); + + // Check the name matches, in case the name updated + if (sender->name_ == name) + { + // Update the placefile + sender->placefile_ = placefile; + + // Notify slots of the placefile update + Q_EMIT self_->PlacefileUpdated(name); + } + }); +} + std::vector> PlacefileManager::GetActivePlacefiles() { @@ -200,13 +238,17 @@ void PlacefileManager::AddUrl(const std::string& urlString) logger_->info("AddUrl: {}", normalizedUrl); // Add an empty placefile record for the new URL - auto placefileRecord = p->placefileRecords_.emplace_back( + auto& record = p->placefileRecords_.emplace_back( std::make_shared(normalizedUrl, nullptr, false)); - p->placefileRecordMap_.insert_or_assign(normalizedUrl, placefileRecord); + p->placefileRecordMap_.insert_or_assign(normalizedUrl, record); + p->ConnectRecordSignals(record); lock.unlock(); Q_EMIT PlacefileUpdated(normalizedUrl); + + // Queue a placefile update + record->UpdateAsync(); } void PlacefileManager::LoadFile(const std::string& filename) @@ -236,7 +278,7 @@ void PlacefileManager::LoadFile(const std::string& filename) if (it != p->placefileRecordMap_.end()) { // If the placefile has been loaded previously, update it - it->second->Update(placefile); + it->second->UpdatePlacefile(placefile); lock.unlock(); @@ -248,6 +290,7 @@ void PlacefileManager::LoadFile(const std::string& filename) auto& record = p->placefileRecords_.emplace_back( std::make_shared(placefileName, placefile)); p->placefileRecordMap_.insert_or_assign(placefileName, record); + p->ConnectRecordSignals(record); lock.unlock(); @@ -257,7 +300,54 @@ void PlacefileManager::LoadFile(const std::string& filename) }); } -void PlacefileRecord::Update(std::shared_ptr placefile) +void PlacefileRecord::Update() +{ + // Make a copy of name in the event it changes. + const std::string name {name_}; + + std::shared_ptr updatedPlacefile {}; + + QUrl url = QUrl::fromUserInput(QString::fromStdString(name)); + if (url.isLocalFile()) + { + updatedPlacefile = gr::Placefile::Load(name); + } + else + { + // TODO: Update hard coded parameters + auto response = cpr::Get( + cpr::Url {name}, + cpr::Header {{"User-Agent", "Supercell Wx/0.2.2"}}, + cpr::Parameters { + {"version", "1.2"}, {"lat", "38.699"}, {"lon", "-90.683"}}); + + if (response.status_code == cpr::status::HTTP_OK) + { + std::istringstream responseBody {response.text}; + updatedPlacefile = gr::Placefile::Load(name, responseBody); + } + else + { + logger_->warn("Error loading placefile: {}", response.error.message); + } + } + + if (updatedPlacefile != nullptr) + { + Q_EMIT Updated(name, updatedPlacefile); + } + + // TODO: Update refresh timer + // TODO: Can running this function out of sync with an existing refresh timer + // cause issues? +} + +void PlacefileRecord::UpdateAsync() +{ + boost::asio::post(threadPool_, [this]() { Update(); }); +} + +void PlacefileRecord::UpdatePlacefile(std::shared_ptr placefile) { // Update placefile placefile_ = placefile; @@ -305,3 +395,5 @@ std::string PlacefileManager::Impl::NormalizeUrl(const std::string& urlString) } // namespace manager } // namespace qt } // namespace scwx + +#include "placefile_manager.moc" From 6f44d9f4e0d4647eb8b4f792b9b4ea5159b72553 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 26 Jul 2023 23:07:19 -0500 Subject: [PATCH 028/199] Handle query string in supplied placefile URL, and improve error messages --- .../scwx/qt/manager/placefile_manager.cpp | 59 ++++++++++++++++--- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index b0c79e5d..ba2cb5ff 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -7,9 +7,11 @@ #include #include +#include #include #include #include +#include #include namespace scwx @@ -314,21 +316,62 @@ void PlacefileRecord::Update() } else { - // TODO: Update hard coded parameters - auto response = cpr::Get( - cpr::Url {name}, - cpr::Header {{"User-Agent", "Supercell Wx/0.2.2"}}, - cpr::Parameters { - {"version", "1.2"}, {"lat", "38.699"}, {"lon", "-90.683"}}); + std::string decodedUrl {name}; + auto queryPos = decodedUrl.find('?'); + if (queryPos != std::string::npos) + { + decodedUrl.erase(queryPos); + } - if (response.status_code == cpr::status::HTTP_OK) + // TODO: Update hard coded parameters + auto parameters = cpr::Parameters { + {"version", "1.2"}, {"lat", "38.699"}, {"lon", "-90.683"}}; + + // Iterate through each query parameter in the URL + if (url.hasQuery()) + { + auto query = url.query(QUrl::ComponentFormattingOption::FullyEncoded) + .toStdString(); + + boost::char_separator delimiter("&"); + boost::tokenizer tokens(query, delimiter); + + for (auto& token : tokens) + { + std::vector split {}; + boost::split(split, token, boost::is_any_of("=")); + if (split.size() >= 2) + { + // Token is a key=value parameter + parameters.Add({split[0], split[1]}); + } + else + { + // Token is a single key with no value + parameters.Add({token, {}}); + } + } + } + + // Send HTTP GET request + // TODO: Update hard coded User-Agent + auto response = + cpr::Get(cpr::Url {decodedUrl}, + cpr::Header {{"User-Agent", "SupercellWx/0.2.2"}}, + parameters); + + if (cpr::status::is_success(response.status_code)) { std::istringstream responseBody {response.text}; updatedPlacefile = gr::Placefile::Load(name, responseBody); } + else if (response.status_code == 0) + { + logger_->error("Error loading placefile: {}", response.error.message); + } else { - logger_->warn("Error loading placefile: {}", response.error.message); + logger_->error("Error loading placefile: {}", response.status_line); } } From 751cafbfe71aa609182b1e9da8d5b2f237e80126 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 26 Jul 2023 23:52:59 -0500 Subject: [PATCH 029/199] Load placefiles according to the current radar site --- scwx-qt/source/scwx/qt/main/main_window.cpp | 2 + .../scwx/qt/manager/placefile_manager.cpp | 144 +++++++++++------- .../scwx/qt/manager/placefile_manager.hpp | 3 + 3 files changed, 90 insertions(+), 59 deletions(-) diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp index a92e3791..11ae2877 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -1136,6 +1136,8 @@ void MainWindowImpl::UpdateRadarSite() timelineManager_->SetRadarSite("?"); } + + placefileManager_->SetRadarSite(radarSite); } void MainWindowImpl::UpdateVcp() diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index ba2cb5ff..80344b5f 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -24,15 +24,36 @@ namespace manager static const std::string logPrefix_ = "scwx::qt::manager::placefile_manager"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); -class PlacefileRecord : public QObject +class PlacefileManager::Impl { - Q_OBJECT - public: - explicit PlacefileRecord(const std::string& name, + class PlacefileRecord; + + explicit Impl(PlacefileManager* self) : self_ {self} {} + ~Impl() {} + + static std::string NormalizeUrl(const std::string& urlString); + + boost::asio::thread_pool threadPool_ {1u}; + + PlacefileManager* self_; + + std::shared_ptr radarSite_ {}; + + std::vector> placefileRecords_ {}; + std::unordered_map> + placefileRecordMap_ {}; + std::shared_mutex placefileRecordLock_ {}; +}; + +class PlacefileManager::Impl::PlacefileRecord +{ +public: + explicit PlacefileRecord(Impl* impl, + const std::string& name, std::shared_ptr placefile, bool enabled = true) : - name_ {name}, placefile_ {placefile}, enabled_ {enabled} + p {impl}, name_ {name}, placefile_ {placefile}, enabled_ {enabled} { } ~PlacefileRecord() @@ -45,6 +66,8 @@ public: void UpdateAsync(); void UpdatePlacefile(std::shared_ptr placefile); + Impl* p; + std::string name_; std::shared_ptr placefile_; bool enabled_; @@ -58,26 +81,6 @@ signals: std::shared_ptr placefile); }; -class PlacefileManager::Impl -{ -public: - explicit Impl(PlacefileManager* self) : self_ {self} {} - ~Impl() {} - - static std::string NormalizeUrl(const std::string& urlString); - - void ConnectRecordSignals(std::shared_ptr record); - - boost::asio::thread_pool threadPool_ {1u}; - - PlacefileManager* self_; - - std::vector> placefileRecords_ {}; - std::unordered_map> - placefileRecordMap_ {}; - std::shared_mutex placefileRecordLock_ {}; -}; - PlacefileManager::PlacefileManager() : p(std::make_unique(this)) {} PlacefileManager::~PlacefileManager() = default; @@ -126,11 +129,19 @@ void PlacefileManager::set_placefile_enabled(const std::string& name, auto it = p->placefileRecordMap_.find(name); if (it != p->placefileRecordMap_.cend()) { - it->second->enabled_ = enabled; + auto record = it->second; + record->enabled_ = enabled; lock.unlock(); Q_EMIT PlacefileEnabled(name, enabled); + + // Update the placefile + // TODO: Only update if it's out of date, or if the radar site has changed + if (enabled) + { + it->second->UpdateAsync(); + } } } @@ -177,28 +188,28 @@ void PlacefileManager::set_placefile_url(const std::string& name, } } -void PlacefileManager::Impl::ConnectRecordSignals( - std::shared_ptr record) +void PlacefileManager::SetRadarSite( + std::shared_ptr radarSite) { - QObject::connect( - record.get(), - &PlacefileRecord::Updated, - self_, - [this](const std::string& name, std::shared_ptr placefile) + if (p->radarSite_ == radarSite || radarSite == nullptr) + { + // No action needed + return; + } + + logger_->debug("SetRadarSite: {}", radarSite->id()); + + p->radarSite_ = radarSite; + + // Update all enabled records + std::shared_lock lock(p->placefileRecordLock_); + for (auto& record : p->placefileRecords_) + { + if (record->enabled_) { - PlacefileRecord* sender = - static_cast(self_->sender()); - - // Check the name matches, in case the name updated - if (sender->name_ == name) - { - // Update the placefile - sender->placefile_ = placefile; - - // Notify slots of the placefile update - Q_EMIT self_->PlacefileUpdated(name); - } - }); + record->UpdateAsync(); + } + } } std::vector> @@ -240,10 +251,10 @@ void PlacefileManager::AddUrl(const std::string& urlString) logger_->info("AddUrl: {}", normalizedUrl); // Add an empty placefile record for the new URL - auto& record = p->placefileRecords_.emplace_back( - std::make_shared(normalizedUrl, nullptr, false)); + auto& record = + p->placefileRecords_.emplace_back(std::make_shared( + p.get(), normalizedUrl, nullptr, false)); p->placefileRecordMap_.insert_or_assign(normalizedUrl, record); - p->ConnectRecordSignals(record); lock.unlock(); @@ -290,9 +301,9 @@ void PlacefileManager::LoadFile(const std::string& filename) { // If this is a new placefile, add it auto& record = p->placefileRecords_.emplace_back( - std::make_shared(placefileName, placefile)); + std::make_shared( + p.get(), placefileName, placefile)); p->placefileRecordMap_.insert_or_assign(placefileName, record); - p->ConnectRecordSignals(record); lock.unlock(); @@ -302,7 +313,7 @@ void PlacefileManager::LoadFile(const std::string& filename) }); } -void PlacefileRecord::Update() +void PlacefileManager::Impl::PlacefileRecord::Update() { // Make a copy of name in the event it changes. const std::string name {name_}; @@ -323,9 +334,17 @@ void PlacefileRecord::Update() decodedUrl.erase(queryPos); } - // TODO: Update hard coded parameters + if (p->radarSite_ == nullptr) + { + // Wait to process until a radar site is selected + return; + } + + // Specify parameters auto parameters = cpr::Parameters { - {"version", "1.2"}, {"lat", "38.699"}, {"lon", "-90.683"}}; + {"version", "1.2"}, // Placefile Version Supported + {"lat", fmt::format("{:0.3f}", p->radarSite_->latitude())}, + {"lon", fmt::format("{:0.3f}", p->radarSite_->longitude())}}; // Iterate through each query parameter in the URL if (url.hasQuery()) @@ -377,7 +396,15 @@ void PlacefileRecord::Update() if (updatedPlacefile != nullptr) { - Q_EMIT Updated(name, updatedPlacefile); + // Check the name matches, in case the name updated + if (name_ == name) + { + // Update the placefile + placefile_ = updatedPlacefile; + + // Notify slots of the placefile update + Q_EMIT p->self_->PlacefileUpdated(name); + } } // TODO: Update refresh timer @@ -385,12 +412,13 @@ void PlacefileRecord::Update() // cause issues? } -void PlacefileRecord::UpdateAsync() +void PlacefileManager::Impl::PlacefileRecord::UpdateAsync() { boost::asio::post(threadPool_, [this]() { Update(); }); } -void PlacefileRecord::UpdatePlacefile(std::shared_ptr placefile) +void PlacefileManager::Impl::PlacefileRecord::UpdatePlacefile( + std::shared_ptr placefile) { // Update placefile placefile_ = placefile; @@ -438,5 +466,3 @@ std::string PlacefileManager::Impl::NormalizeUrl(const std::string& urlString) } // namespace manager } // namespace qt } // namespace scwx - -#include "placefile_manager.moc" diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp index d763ef3a..5776bb48 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include @@ -27,6 +28,8 @@ public: void set_placefile_thresholded(const std::string& name, bool thresholded); void set_placefile_url(const std::string& name, const std::string& newUrl); + void SetRadarSite(std::shared_ptr radarSite); + /** * @brief Gets a list of active placefiles * From e3449e382d24a73348eadcd735959ff62cd89410 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 28 Jul 2023 22:33:39 -0500 Subject: [PATCH 030/199] Don't use a hard-coded user-agent for placefiles, add DPI parameter --- scwx-qt/source/scwx/qt/main/main.cpp | 4 +++ .../scwx/qt/manager/placefile_manager.cpp | 11 +++++--- wxdata/include/scwx/network/cpr.hpp | 17 ++++++++++++ wxdata/source/scwx/network/cpr.cpp | 26 +++++++++++++++++++ wxdata/wxdata.cmake | 6 +++-- 5 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 wxdata/include/scwx/network/cpr.hpp create mode 100644 wxdata/source/scwx/network/cpr.cpp diff --git a/scwx-qt/source/scwx/qt/main/main.cpp b/scwx-qt/source/scwx/qt/main/main.cpp index 400909d7..8e9967b7 100644 --- a/scwx-qt/source/scwx/qt/main/main.cpp +++ b/scwx-qt/source/scwx/qt/main/main.cpp @@ -1,8 +1,10 @@ #include #include +#include #include #include #include +#include #include #include @@ -26,6 +28,8 @@ int main(int argc, char* argv[]) QApplication a(argc, argv); QCoreApplication::setApplicationName("Supercell Wx"); + scwx::network::cpr::SetUserAgent( + fmt::format("SupercellWx/{}", scwx::qt::main::kVersionString_)); // Enable internationalization support QTranslator translator; diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index 80344b5f..8793f9f9 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -1,11 +1,14 @@ #include #include +#include #include #include #include #include +#include +#include #include #include #include @@ -340,9 +343,12 @@ void PlacefileManager::Impl::PlacefileRecord::Update() return; } + auto dpi = QGuiApplication::primaryScreen()->logicalDotsPerInch(); + // Specify parameters auto parameters = cpr::Parameters { {"version", "1.2"}, // Placefile Version Supported + {"dpi", fmt::format("{:0.0f}", dpi)}, {"lat", fmt::format("{:0.3f}", p->radarSite_->latitude())}, {"lon", fmt::format("{:0.3f}", p->radarSite_->longitude())}}; @@ -373,11 +379,8 @@ void PlacefileManager::Impl::PlacefileRecord::Update() } // Send HTTP GET request - // TODO: Update hard coded User-Agent auto response = - cpr::Get(cpr::Url {decodedUrl}, - cpr::Header {{"User-Agent", "SupercellWx/0.2.2"}}, - parameters); + cpr::Get(cpr::Url {decodedUrl}, network::cpr::GetHeader(), parameters); if (cpr::status::is_success(response.status_code)) { diff --git a/wxdata/include/scwx/network/cpr.hpp b/wxdata/include/scwx/network/cpr.hpp new file mode 100644 index 00000000..f05c093a --- /dev/null +++ b/wxdata/include/scwx/network/cpr.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include + +namespace scwx +{ +namespace network +{ +namespace cpr +{ + +::cpr::Header GetHeader(); +void SetUserAgent(const std::string& userAgent); + +} // namespace cpr +} // namespace network +} // namespace scwx diff --git a/wxdata/source/scwx/network/cpr.cpp b/wxdata/source/scwx/network/cpr.cpp new file mode 100644 index 00000000..81dea5ad --- /dev/null +++ b/wxdata/source/scwx/network/cpr.cpp @@ -0,0 +1,26 @@ +#include + +namespace scwx +{ +namespace network +{ +namespace cpr +{ + +static const std::string logPrefix_ = "scwx::network::cpr"; + +static ::cpr::Header header_ {}; + +::cpr::Header GetHeader() +{ + return header_; +} + +void SetUserAgent(const std::string& userAgent) +{ + header_.insert_or_assign("User-Agent", userAgent); +} + +} // namespace cpr +} // namespace network +} // namespace scwx diff --git a/wxdata/wxdata.cmake b/wxdata/wxdata.cmake index 3f7ab8d9..a9a8dce7 100644 --- a/wxdata/wxdata.cmake +++ b/wxdata/wxdata.cmake @@ -50,8 +50,10 @@ set(HDR_GR include/scwx/gr/color.hpp include/scwx/gr/placefile.hpp) set(SRC_GR source/scwx/gr/color.cpp source/scwx/gr/placefile.cpp) -set(HDR_NETWORK include/scwx/network/dir_list.hpp) -set(SRC_NETWORK source/scwx/network/dir_list.cpp) +set(HDR_NETWORK include/scwx/network/cpr.hpp + include/scwx/network/dir_list.hpp) +set(SRC_NETWORK source/scwx/network/cpr.cpp + source/scwx/network/dir_list.cpp) set(HDR_PROVIDER include/scwx/provider/aws_level2_data_provider.hpp include/scwx/provider/aws_level3_data_provider.hpp include/scwx/provider/aws_nexrad_data_provider.hpp From 1f2d5227c410914d3c10e9fd59ade46848279b4d Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 28 Jul 2023 23:05:10 -0500 Subject: [PATCH 031/199] Add placefile parsing for Icon and IconFile, stub for TimeRange --- wxdata/include/scwx/gr/placefile.hpp | 25 ++++++++++ wxdata/source/scwx/gr/placefile.cpp | 74 ++++++++++++++++++++++++++-- 2 files changed, 95 insertions(+), 4 deletions(-) diff --git a/wxdata/include/scwx/gr/placefile.hpp b/wxdata/include/scwx/gr/placefile.hpp index 4a4f624b..d2a70c76 100644 --- a/wxdata/include/scwx/gr/placefile.hpp +++ b/wxdata/include/scwx/gr/placefile.hpp @@ -7,6 +7,7 @@ #include #include +#include #include namespace scwx @@ -47,6 +48,16 @@ public: Unknown }; + struct IconFile + { + std::size_t fileNumber_ {}; + std::size_t iconWidth_ {}; + std::size_t iconHeight_ {}; + std::size_t hotX_ {}; + std::size_t hotY_ {}; + std::string filename_ {}; + }; + struct Font { std::size_t fontNumber_ {}; @@ -61,6 +72,20 @@ public: boost::units::quantity threshold_ {}; }; + struct IconDrawItem : DrawItem + { + IconDrawItem() { itemType_ = ItemType::Icon; } + + double latitude_ {}; + double longitude_ {}; + double x_ {}; + double y_ {}; + boost::units::quantity angle_ {}; + std::size_t fileNumber_ {0u}; + std::size_t iconNumber_ {0u}; + std::string hoverText_ {}; + }; + struct TextDrawItem : DrawItem { TextDrawItem() { itemType_ = ItemType::Text; } diff --git a/wxdata/source/scwx/gr/placefile.cpp b/wxdata/source/scwx/gr/placefile.cpp index a2e62dcf..10b9bedd 100644 --- a/wxdata/source/scwx/gr/placefile.cpp +++ b/wxdata/source/scwx/gr/placefile.cpp @@ -64,8 +64,8 @@ public: DrawingStatement currentStatement_ {DrawingStatement::Standard}; // References - std::unordered_map iconFiles_ {}; - std::unordered_map> fonts_ {}; + std::unordered_map> iconFiles_ {}; + std::unordered_map> fonts_ {}; std::vector> drawItems_ {}; }; @@ -184,6 +184,7 @@ void Placefile::Impl::ProcessLine(const std::string& line) { static const std::string titleKey_ {"Title:"}; static const std::string thresholdKey_ {"Threshold:"}; + static const std::string timeRangeKey_ {"TimeRange:"}; static const std::string hsluvKey_ {"HSLuv:"}; static const std::string colorKey_ {"Color:"}; static const std::string refreshKey_ {"Refresh:"}; @@ -225,6 +226,12 @@ void Placefile::Impl::ProcessLine(const std::string& line) boost::units::metric::nautical_mile_base_unit::unit_type()); } } + else if (boost::istarts_with(line, timeRangeKey_)) + { + // TimeRange: start_time end_time + + // TODO + } else if (boost::istarts_with(line, hsluvKey_)) { // HSLuv: value @@ -309,14 +316,73 @@ void Placefile::Impl::ProcessLine(const std::string& line) else if (boost::istarts_with(line, iconFileKey_)) { // IconFile: fileNumber, iconWidth, iconHeight, hotX, hotY, fileName + std::vector tokenList = util::ParseTokens( + line, {",", ",", ",", ",", ","}, iconFileKey_.size()); - // TODO + if (tokenList.size() >= 6) + { + std::shared_ptr iconFile = std::make_shared(); + + iconFile->fileNumber_ = std::stoul(tokenList[0]); + iconFile->iconWidth_ = std::stoul(tokenList[1]); + iconFile->iconHeight_ = std::stoul(tokenList[2]); + iconFile->hotX_ = std::stoul(tokenList[3]); + iconFile->hotY_ = std::stoul(tokenList[4]); + + TrimQuotes(tokenList[5]); + iconFile->filename_.swap(tokenList[5]); + + iconFiles_.insert_or_assign(iconFile->fileNumber_, iconFile); + } + else + { + logger_->warn("IconFile statement malformed: {}", line); + } } else if (boost::istarts_with(line, iconKey_)) { // Icon: lat, lon, angle, fileNumber, iconNumber, hoverText + std::vector tokenList = + util::ParseTokens(line, {",", ",", ",", ",", ","}, iconKey_.size()); - // TODO + std::shared_ptr di = nullptr; + + if (tokenList.size() >= 5) + { + di = std::make_shared(); + + di->threshold_ = threshold_; + + ParseLocation(tokenList[0], + tokenList[1], + di->latitude_, + di->longitude_, + di->x_, + di->y_); + + di->angle_ = static_cast< + boost::units::quantity>( + std::stod(tokenList[2]) * + boost::units::angle::degree_base_unit::unit_type()); + + di->fileNumber_ = std::stoul(tokenList[3]); + di->iconNumber_ = std::stoul(tokenList[4]); + } + if (tokenList.size() >= 6) + { + ProcessEscapeCharacters(tokenList[5]); + TrimQuotes(tokenList[5]); + di->hoverText_.swap(tokenList[5]); + } + + if (di != nullptr) + { + drawItems_.emplace_back(std::move(di)); + } + else + { + logger_->warn("Icon statement malformed: {}", line); + } } else if (boost::istarts_with(line, fontKey_)) { From 0f9fbdbf635fdc1d8f28450b70fcb4e47bdb7a76 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 30 Jul 2023 00:40:05 -0500 Subject: [PATCH 032/199] Load placefile icons into texture atlas cache --- scwx-qt/scwx-qt.cmake | 5 +- scwx-qt/source/scwx/qt/external/stb_image.cpp | 4 + scwx-qt/source/scwx/qt/main/main.cpp | 1 + .../scwx/qt/manager/placefile_manager.cpp | 47 +++--- .../scwx/qt/manager/resource_manager.cpp | 9 +- .../scwx/qt/manager/resource_manager.hpp | 2 + scwx-qt/source/scwx/qt/util/network.cpp | 36 +++++ scwx-qt/source/scwx/qt/util/network.hpp | 26 ++++ scwx-qt/source/scwx/qt/util/texture_atlas.cpp | 135 +++++++++++++++--- scwx-qt/source/scwx/qt/util/texture_atlas.hpp | 1 + wxdata/include/scwx/gr/placefile.hpp | 2 + wxdata/source/scwx/gr/placefile.cpp | 13 ++ 12 files changed, 237 insertions(+), 44 deletions(-) create mode 100644 scwx-qt/source/scwx/qt/external/stb_image.cpp create mode 100644 scwx-qt/source/scwx/qt/util/network.cpp create mode 100644 scwx-qt/source/scwx/qt/util/network.hpp diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index ebf8ca16..9a60ef01 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -46,7 +46,8 @@ set(HDR_CONFIG source/scwx/qt/config/county_database.hpp source/scwx/qt/config/radar_site.hpp) set(SRC_CONFIG source/scwx/qt/config/county_database.cpp source/scwx/qt/config/radar_site.cpp) -set(SRC_EXTERNAL source/scwx/qt/external/stb_rect_pack.cpp) +set(SRC_EXTERNAL source/scwx/qt/external/stb_image.cpp + source/scwx/qt/external/stb_rect_pack.cpp) set(HDR_GL source/scwx/qt/gl/gl.hpp source/scwx/qt/gl/gl_context.hpp source/scwx/qt/gl/shader_program.hpp @@ -203,6 +204,7 @@ set(HDR_UTIL source/scwx/qt/util/color.hpp source/scwx/qt/util/geographic_lib.hpp source/scwx/qt/util/json.hpp source/scwx/qt/util/maplibre.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_file_buffer.hpp @@ -215,6 +217,7 @@ set(SRC_UTIL source/scwx/qt/util/color.cpp source/scwx/qt/util/geographic_lib.cpp source/scwx/qt/util/json.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_file_buffer.cpp source/scwx/qt/util/q_file_input_stream.cpp diff --git a/scwx-qt/source/scwx/qt/external/stb_image.cpp b/scwx-qt/source/scwx/qt/external/stb_image.cpp new file mode 100644 index 00000000..c9598836 --- /dev/null +++ b/scwx-qt/source/scwx/qt/external/stb_image.cpp @@ -0,0 +1,4 @@ +#define STB_IMAGE_IMPLEMENTATION +#define STBI_ASSERT(x) +#define STBI_FAILURE_USERMSG +#include diff --git a/scwx-qt/source/scwx/qt/main/main.cpp b/scwx-qt/source/scwx/qt/main/main.cpp index 8e9967b7..274c13ca 100644 --- a/scwx-qt/source/scwx/qt/main/main.cpp +++ b/scwx-qt/source/scwx/qt/main/main.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index 8793f9f9..1a378a18 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include #include #include @@ -35,7 +37,7 @@ public: explicit Impl(PlacefileManager* self) : self_ {self} {} ~Impl() {} - static std::string NormalizeUrl(const std::string& urlString); + static void LoadResources(const std::shared_ptr& placefile); boost::asio::thread_pool threadPool_ {1u}; @@ -67,7 +69,7 @@ public: void Update(); void UpdateAsync(); - void UpdatePlacefile(std::shared_ptr placefile); + void UpdatePlacefile(const std::shared_ptr& placefile); Impl* p; @@ -78,10 +80,6 @@ public: boost::asio::thread_pool threadPool_ {1u}; boost::asio::steady_timer refreshTimer_ {threadPool_}; std::mutex refreshMutex_ {}; - -signals: - void Updated(const std::string& name, - std::shared_ptr placefile); }; PlacefileManager::PlacefileManager() : p(std::make_unique(this)) {} @@ -167,7 +165,7 @@ void PlacefileManager::set_placefile_thresholded(const std::string& name, void PlacefileManager::set_placefile_url(const std::string& name, const std::string& newUrl) { - std::string normalizedUrl = Impl::NormalizeUrl(newUrl); + std::string normalizedUrl = util::network::NormalizeUrl(newUrl); std::unique_lock lock(p->placefileRecordLock_); @@ -235,7 +233,7 @@ PlacefileManager::GetActivePlacefiles() void PlacefileManager::AddUrl(const std::string& urlString) { - std::string normalizedUrl = Impl::NormalizeUrl(urlString); + std::string normalizedUrl = util::network::NormalizeUrl(urlString); std::unique_lock lock(p->placefileRecordLock_); @@ -318,6 +316,8 @@ void PlacefileManager::LoadFile(const std::string& filename) void PlacefileManager::Impl::PlacefileRecord::Update() { + logger_->debug("Update: {}", name_); + // Make a copy of name in the event it changes. const std::string name {name_}; @@ -399,6 +399,9 @@ void PlacefileManager::Impl::PlacefileRecord::Update() if (updatedPlacefile != nullptr) { + // Load placefile resources + Impl::LoadResources(updatedPlacefile); + // Check the name matches, in case the name updated if (name_ == name) { @@ -421,7 +424,7 @@ void PlacefileManager::Impl::PlacefileRecord::UpdateAsync() } void PlacefileManager::Impl::PlacefileRecord::UpdatePlacefile( - std::shared_ptr placefile) + const std::shared_ptr& placefile) { // Update placefile placefile_ = placefile; @@ -448,22 +451,22 @@ std::shared_ptr PlacefileManager::Instance() return placefileManager; } -std::string PlacefileManager::Impl::NormalizeUrl(const std::string& urlString) +void PlacefileManager::Impl::LoadResources( + const std::shared_ptr& placefile) { - std::string normalizedUrl; + const auto iconFiles = placefile->icon_files(); - // Normalize URL string - QUrl url = QUrl::fromUserInput(QString::fromStdString(urlString)); - if (url.isLocalFile()) - { - normalizedUrl = QDir::toNativeSeparators(url.toLocalFile()).toStdString(); - } - else - { - normalizedUrl = urlString; - } + const QUrl baseUrl = + QUrl::fromUserInput(QString::fromStdString(placefile->name())); - return normalizedUrl; + // TODO: Parallelize + for (auto& iconFile : iconFiles) + { + QUrl fileUrl = QUrl(QString::fromStdString(iconFile->filename_)); + QUrl resolvedUrl = baseUrl.resolved(fileUrl); + + ResourceManager::LoadImageResource(resolvedUrl.toString().toStdString()); + } } } // namespace manager diff --git a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp index bbe06e88..b74de3e9 100644 --- a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp @@ -5,9 +5,8 @@ #include #include -#include - #include +#include namespace scwx { @@ -62,6 +61,12 @@ std::shared_ptr Font(types::Font font) return nullptr; } +void LoadImageResource(const std::string& urlString) +{ + util::TextureAtlas& textureAtlas = util::TextureAtlas::Instance(); + textureAtlas.CacheTexture(urlString, urlString); +} + static void LoadFonts() { for (auto& fontName : fontNames_) diff --git a/scwx-qt/source/scwx/qt/manager/resource_manager.hpp b/scwx-qt/source/scwx/qt/manager/resource_manager.hpp index 909373db..47df5461 100644 --- a/scwx-qt/source/scwx/qt/manager/resource_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/resource_manager.hpp @@ -18,6 +18,8 @@ void Shutdown(); int FontId(types::Font font); std::shared_ptr Font(types::Font font); +void LoadImageResource(const std::string& urlString); + } // namespace ResourceManager } // namespace manager } // namespace qt diff --git a/scwx-qt/source/scwx/qt/util/network.cpp b/scwx-qt/source/scwx/qt/util/network.cpp new file mode 100644 index 00000000..2e8d442f --- /dev/null +++ b/scwx-qt/source/scwx/qt/util/network.cpp @@ -0,0 +1,36 @@ +#include + +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace util +{ +namespace network +{ + +std::string NormalizeUrl(const std::string& urlString) +{ + std::string normalizedUrl; + + // Normalize URL string + QUrl url = QUrl::fromUserInput(QString::fromStdString(urlString)); + if (url.isLocalFile()) + { + normalizedUrl = QDir::toNativeSeparators(url.toLocalFile()).toStdString(); + } + else + { + normalizedUrl = urlString; + } + + return normalizedUrl; +} + +} // namespace network +} // namespace util +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/util/network.hpp b/scwx-qt/source/scwx/qt/util/network.hpp new file mode 100644 index 00000000..23142b3a --- /dev/null +++ b/scwx-qt/source/scwx/qt/util/network.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include + +namespace scwx +{ +namespace qt +{ +namespace util +{ +namespace network +{ + +/** + * @brief Converts a local or remote URL to a consistent format. + * + * @param [in] urlString URL to normalize + * + * @return Normalized URL string + */ +std::string NormalizeUrl(const std::string& urlString); + +} // namespace network +} // namespace util +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp index 9c5dcc04..864eeae8 100644 --- a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp +++ b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -13,8 +14,11 @@ #include #include +#include +#include #include #include +#include #if defined(_MSC_VER) # pragma warning(pop) @@ -48,6 +52,9 @@ public: std::unordered_map texturePathMap_; std::shared_mutex texturePathMutex_; + std::shared_mutex textureCacheMutex_; + std::unordered_map textureCache_; + boost::gil::rgba8_image_t atlas_; std::unordered_map atlasMap_; std::shared_mutex atlasMutex_; @@ -66,6 +73,34 @@ void TextureAtlas::RegisterTexture(const std::string& name, p->texturePathMap_.insert_or_assign(name, path); } +bool TextureAtlas::CacheTexture(const std::string& name, + const std::string& path) +{ + // If the image is already loaded, we don't need to load it again + { + std::shared_lock lock(p->textureCacheMutex_); + + if (p->textureCache_.contains(path)) + { + return false; + } + } + + // Attempt to load the image + boost::gil::rgba8_image_t image = TextureAtlas::Impl::LoadImage(path); + + // If the image is valid + if (image.width() > 0 && image.height() > 0) + { + // Store it in the texture cache + std::unique_lock lock(p->textureCacheMutex_); + + p->textureCache_.emplace(name, std::move(image)); + } + + return true; +} + void TextureAtlas::BuildAtlas(size_t width, size_t height) { logger_->debug("Building {}x{} texture atlas", width, height); @@ -110,6 +145,11 @@ void TextureAtlas::BuildAtlas(size_t width, size_t height) }); } + // TODO: Cached images + { + + } + // Pack images { logger_->trace("Packing {} images", images.size()); @@ -260,29 +300,86 @@ TextureAtlas::Impl::LoadImage(const std::string& imagePath) boost::gil::rgba8_image_t image; - QFile imageFile(imagePath.c_str()); + QUrl url = QUrl::fromUserInput(QString::fromStdString(imagePath)); - imageFile.open(QIODevice::ReadOnly); - - if (!imageFile.isOpen()) + if (url.isLocalFile()) { - logger_->error("Could not open image: {}", imagePath); - return image; + QFile imageFile(imagePath.c_str()); + + imageFile.open(QIODevice::ReadOnly); + + if (!imageFile.isOpen()) + { + logger_->error("Could not open image: {}", imagePath); + return image; + } + + boost::iostreams::stream dataStream(imageFile); + + try + { + boost::gil::read_and_convert_image( + dataStream, image, boost::gil::png_tag()); + } + catch (const std::exception& ex) + { + logger_->error("Error reading image: {}", ex.what()); + return image; + } } - - boost::iostreams::stream dataStream(imageFile); - - boost::gil::image x; - - try + else { - boost::gil::read_and_convert_image( - dataStream, image, boost::gil::png_tag()); - } - catch (const std::exception& ex) - { - logger_->error("Error reading image: {}", ex.what()); - return image; + auto response = cpr::Get(cpr::Url {imagePath}, network::cpr::GetHeader()); + + if (cpr::status::is_success(response.status_code)) + { + // Use stbi, since we can only guess the image format + static constexpr int desiredChannels = 4; + + int width; + int height; + int numChannels; + + unsigned char* pixelData = stbi_load_from_memory( + reinterpret_cast(response.text.data()), + static_cast( + std::clamp(response.text.size(), 0, INT32_MAX)), + &width, + &height, + &numChannels, + desiredChannels); + + if (pixelData == nullptr) + { + logger_->error("Error loading image: {}", stbi_failure_reason()); + return image; + } + + // Create a view pointing to the STB image data + auto imageView = boost::gil::interleaved_view( + width, + height, + reinterpret_cast(pixelData), + width * desiredChannels); + + // Copy the view to the destination image + image = boost::gil::rgba8_image_t(imageView); + + if (numChannels == 3) + { + // TODO: If no alpha channel, replace black with transparent + } + + stbi_image_free(pixelData); + } + else if (response.status_code == 0) + { + logger_->error("Error loading image: {}", response.error.message); + } + else + { + logger_->error("Error loading image: {}", response.status_line); + } } return image; diff --git a/scwx-qt/source/scwx/qt/util/texture_atlas.hpp b/scwx-qt/source/scwx/qt/util/texture_atlas.hpp index bf904e6c..5bf07018 100644 --- a/scwx-qt/source/scwx/qt/util/texture_atlas.hpp +++ b/scwx-qt/source/scwx/qt/util/texture_atlas.hpp @@ -67,6 +67,7 @@ public: static TextureAtlas& Instance(); void RegisterTexture(const std::string& name, const std::string& path); + bool CacheTexture(const std::string& name, const std::string& path); void BuildAtlas(size_t width, size_t height); GLuint BufferAtlas(gl::OpenGLFunctions& gl); diff --git a/wxdata/include/scwx/gr/placefile.hpp b/wxdata/include/scwx/gr/placefile.hpp index d2a70c76..c6d391cd 100644 --- a/wxdata/include/scwx/gr/placefile.hpp +++ b/wxdata/include/scwx/gr/placefile.hpp @@ -109,6 +109,8 @@ public: */ std::vector> GetDrawItems(); + std::vector> icon_files(); + std::string name() const; std::string title() const; std::unordered_map> fonts(); diff --git a/wxdata/source/scwx/gr/placefile.cpp b/wxdata/source/scwx/gr/placefile.cpp index 10b9bedd..4ecb74c3 100644 --- a/wxdata/source/scwx/gr/placefile.cpp +++ b/wxdata/source/scwx/gr/placefile.cpp @@ -86,6 +86,19 @@ std::vector> Placefile::GetDrawItems() return p->drawItems_; } +std::vector> Placefile::icon_files() +{ + std::vector> iconFiles {}; + iconFiles.reserve(p->iconFiles_.size()); + + std::transform(p->iconFiles_.begin(), + p->iconFiles_.end(), + std::back_inserter(iconFiles), + [](auto& iconFile) { return iconFile.second; }); + + return iconFiles; +} + std::string Placefile::name() const { return p->name_; From 4c093d65f6bf83f3c1abd8e7ab436390f5347226 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 30 Jul 2023 22:31:05 -0500 Subject: [PATCH 033/199] Populate cached images into the texture atlas --- scwx-qt/source/scwx/qt/gl/draw/geo_line.cpp | 7 ++- .../scwx/qt/manager/placefile_manager.cpp | 23 +++++--- .../scwx/qt/manager/resource_manager.cpp | 35 ++++++++++-- .../scwx/qt/manager/resource_manager.hpp | 3 +- scwx-qt/source/scwx/qt/util/texture_atlas.cpp | 54 ++++++++++++++++--- 5 files changed, 104 insertions(+), 18 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/geo_line.cpp b/scwx-qt/source/scwx/qt/gl/draw/geo_line.cpp index a009f6bb..2b939293 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/geo_line.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/geo_line.cpp @@ -27,6 +27,8 @@ static constexpr size_t kPointsPerVertex = 10; static constexpr size_t kBufferLength = kNumTriangles * kVerticesPerTriangle * kPointsPerVertex; +static const std::string kTextureName = "lines/default-1x7"; + class GeoLine::Impl { public: @@ -115,7 +117,7 @@ void GeoLine::Initialize() } p->texture_ = - util::TextureAtlas::Instance().GetTextureAttributes("lines/default-1x7"); + util::TextureAtlas::Instance().GetTextureAttributes(kTextureName); gl.glGenVertexArrays(1, &p->vao_); gl.glGenBuffers(1, &p->vbo_); @@ -248,6 +250,9 @@ void GeoLine::Impl::Update() { gl::OpenGLFunctions& gl = context_->gl(); + texture_ = + util::TextureAtlas::Instance().GetTextureAttributes(kTextureName); + // Latitude and longitude coordinates in degrees const float lx = points_[0].latitude_; const float rx = points_[1].latitude_; diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index 1a378a18..ef1696fb 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -459,14 +459,23 @@ void PlacefileManager::Impl::LoadResources( const QUrl baseUrl = QUrl::fromUserInput(QString::fromStdString(placefile->name())); - // TODO: Parallelize - for (auto& iconFile : iconFiles) - { - QUrl fileUrl = QUrl(QString::fromStdString(iconFile->filename_)); - QUrl resolvedUrl = baseUrl.resolved(fileUrl); + std::vector urlStrings; + urlStrings.reserve(iconFiles.size()); - ResourceManager::LoadImageResource(resolvedUrl.toString().toStdString()); - } + std::transform(iconFiles.cbegin(), + iconFiles.cend(), + std::back_inserter(urlStrings), + [&baseUrl](auto& iconFile) + { + // Resolve target URL relative to base URL + QUrl fileUrl = + QUrl(QString::fromStdString(iconFile->filename_)); + QUrl resolvedUrl = baseUrl.resolved(fileUrl); + + return resolvedUrl.toString().toStdString(); + }); + + ResourceManager::LoadImageResources(urlStrings); } } // namespace manager diff --git a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp index b74de3e9..19d5c50f 100644 --- a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp @@ -5,6 +5,9 @@ #include #include +#include +#include + #include #include @@ -61,10 +64,36 @@ std::shared_ptr Font(types::Font font) return nullptr; } -void LoadImageResource(const std::string& urlString) +bool LoadImageResource(const std::string& urlString) { util::TextureAtlas& textureAtlas = util::TextureAtlas::Instance(); - textureAtlas.CacheTexture(urlString, urlString); + return textureAtlas.CacheTexture(urlString, urlString); +} + +void LoadImageResources(const std::vector& urlStrings) +{ + std::mutex m {}; + bool textureCached = false; + + std::for_each(std::execution::par_unseq, + urlStrings.begin(), + urlStrings.end(), + [&](auto& urlString) + { + bool value = LoadImageResource(urlString); + + if (value) + { + std::unique_lock lock {m}; + textureCached = true; + } + }); + + if (textureCached) + { + util::TextureAtlas& textureAtlas = util::TextureAtlas::Instance(); + textureAtlas.BuildAtlas(1024, 1024); + } } static void LoadFonts() @@ -90,7 +119,7 @@ static void LoadTextures() ":/res/textures/lines/default-1x7.png"); textureAtlas.RegisterTexture("lines/test-pattern", ":/res/textures/lines/test-pattern.png"); - textureAtlas.BuildAtlas(8, 8); + textureAtlas.BuildAtlas(1024, 1024); } } // namespace ResourceManager diff --git a/scwx-qt/source/scwx/qt/manager/resource_manager.hpp b/scwx-qt/source/scwx/qt/manager/resource_manager.hpp index 47df5461..17b00b44 100644 --- a/scwx-qt/source/scwx/qt/manager/resource_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/resource_manager.hpp @@ -18,7 +18,8 @@ void Shutdown(); int FontId(types::Font font); std::shared_ptr Font(types::Font font); -void LoadImageResource(const std::string& urlString); +bool LoadImageResource(const std::string& urlString); +void LoadImageResources(const std::vector& urlStrings); } // namespace ResourceManager } // namespace manager diff --git a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp index 864eeae8..841c42d3 100644 --- a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp +++ b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp @@ -5,6 +5,7 @@ #include #include +#include #if defined(_MSC_VER) # pragma warning(push, 0) @@ -111,8 +112,11 @@ void TextureAtlas::BuildAtlas(size_t width, size_t height) return; } - std::vector> images; - std::vector stbrpRects; + std::vector>> + images; + std::vector stbrpRects; // Load images { @@ -145,9 +149,33 @@ void TextureAtlas::BuildAtlas(size_t width, size_t height) }); } - // TODO: Cached images - { + // Cached images + // Take a read lock on the texture cache map. The read lock must persist + // through atlas population, as a pointer to the image is taken and used. + std::shared_lock textureCacheLock(p->textureCacheMutex_); + + { + // For each cached texture + for (auto& texture : p->textureCache_) + { + auto& image = texture.second; + + if (image.width() > 0u && image.height() > 0u) + { + // Store STB rectangle pack data in a vector + stbrpRects.push_back( + stbrp_rect {0, + static_cast(image.width()), + static_cast(image.height()), + 0, + 0, + 0}); + + // Store image data in a vector + images.emplace_back(texture.first, &image); + } + } } // Pack images @@ -195,8 +223,22 @@ void TextureAtlas::BuildAtlas(size_t width, size_t height) if (stbrpRects[i].was_packed != 0) { // Populate the atlas - boost::gil::rgba8c_view_t imageView = - boost::gil::const_view(images[i].second); + boost::gil::rgba8c_view_t imageView; + + // Retrieve the image + if (std::holds_alternative( + images[i].second)) + { + imageView = boost::gil::const_view( + std::get(images[i].second)); + } + else if (std::holds_alternative( + images[i].second)) + { + imageView = boost::gil::const_view( + *std::get(images[i].second)); + } + boost::gil::rgba8_view_t atlasSubView = boost::gil::subimage_view(atlasView, stbrpRects[i].x, From f074e487dea2a6d1e473b70dee400522a1c21500 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 1 Aug 2023 00:30:40 -0500 Subject: [PATCH 034/199] First attempt at rendering placefile icons, does not work yet --- scwx-qt/gl/geo_texture2d.vert | 42 ++ scwx-qt/scwx-qt.cmake | 3 + scwx-qt/scwx-qt.qrc | 1 + .../scwx/qt/gl/draw/placefile_icons.cpp | 370 ++++++++++++++++++ .../scwx/qt/gl/draw/placefile_icons.hpp | 66 ++++ .../source/scwx/qt/map/placefile_layer.cpp | 52 ++- 6 files changed, 527 insertions(+), 7 deletions(-) create mode 100644 scwx-qt/gl/geo_texture2d.vert create mode 100644 scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp create mode 100644 scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp diff --git a/scwx-qt/gl/geo_texture2d.vert b/scwx-qt/gl/geo_texture2d.vert new file mode 100644 index 00000000..35bc85fb --- /dev/null +++ b/scwx-qt/gl/geo_texture2d.vert @@ -0,0 +1,42 @@ +#version 330 core + +#define DEGREES_MAX 360.0f +#define LATITUDE_MAX 85.051128779806604f +#define LONGITUDE_MAX 180.0f +#define PI 3.1415926535897932384626433f +#define RAD2DEG 57.295779513082320876798156332941f + +layout (location = 0) in vec2 aLatLong; +layout (location = 1) in vec2 aXYOffset; +layout (location = 2) in vec2 aTexCoord; +layout (location = 3) in vec4 aModulate; +layout (location = 4) in float aAngle; + +uniform mat4 uMVPMatrix; +uniform mat4 uMapMatrix; +uniform vec2 uMapScreenCoord; + +smooth out vec2 texCoord; +flat out vec4 modulate; + +vec2 latLngToScreenCoordinate(in vec2 latLng) +{ + vec2 p; + latLng.x = clamp(latLng.x, -LATITUDE_MAX, LATITUDE_MAX); + p.xy = vec2(LONGITUDE_MAX + latLng.y, + -(LONGITUDE_MAX - RAD2DEG * log(tan(PI / 4 + latLng.x * PI / DEGREES_MAX)))); + return p; +} + +void main() +{ + // Pass the texture coordinate and color modulate to the fragment shader + texCoord = aTexCoord; + modulate = aModulate; + + vec2 p = latLngToScreenCoordinate(aLatLong) - uMapScreenCoord; + + // Transform the position to screen coordinates + gl_Position = uMapMatrix * vec4(p, 0.0f, 1.0f) - + uMVPMatrix * vec4(aXYOffset, 0.0f, 0.0f); +} diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 9a60ef01..a024e12e 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -57,9 +57,11 @@ set(SRC_GL source/scwx/qt/gl/gl_context.cpp source/scwx/qt/gl/text_shader.cpp) set(HDR_GL_DRAW source/scwx/qt/gl/draw/draw_item.hpp source/scwx/qt/gl/draw/geo_line.hpp + source/scwx/qt/gl/draw/placefile_icons.hpp source/scwx/qt/gl/draw/rectangle.hpp) set(SRC_GL_DRAW source/scwx/qt/gl/draw/draw_item.cpp source/scwx/qt/gl/draw/geo_line.cpp + source/scwx/qt/gl/draw/placefile_icons.cpp source/scwx/qt/gl/draw/rectangle.cpp) set(HDR_MANAGER source/scwx/qt/manager/placefile_manager.hpp source/scwx/qt/manager/radar_product_manager.hpp @@ -240,6 +242,7 @@ set(RESOURCE_FILES scwx-qt.qrc) set(SHADER_FILES gl/color.frag gl/color.vert gl/geo_line.vert + gl/geo_texture2d.vert gl/radar.frag gl/radar.vert gl/text.frag diff --git a/scwx-qt/scwx-qt.qrc b/scwx-qt/scwx-qt.qrc index 6d786fff..fa10ef6f 100644 --- a/scwx-qt/scwx-qt.qrc +++ b/scwx-qt/scwx-qt.qrc @@ -3,6 +3,7 @@ gl/color.frag gl/color.vert gl/geo_line.vert + gl/geo_texture2d.vert gl/radar.frag gl/radar.vert gl/text.frag diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp new file mode 100644 index 00000000..69e6887e --- /dev/null +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp @@ -0,0 +1,370 @@ +#include +#include +#include + +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace gl +{ +namespace draw +{ + +static const std::string logPrefix_ = "scwx::qt::gl::draw::placefile_icons"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +static constexpr std::size_t kNumRectangles = 1; +static constexpr std::size_t kNumTriangles = kNumRectangles * 2; +static constexpr std::size_t kVerticesPerTriangle = 3; +static constexpr std::size_t kVerticesPerRectangle = kVerticesPerTriangle * 2; +static constexpr std::size_t kPointsPerVertex = 11; +static constexpr std::size_t kBufferLength = + kNumTriangles * kVerticesPerTriangle * kPointsPerVertex; + +struct PlacefileIconInfo +{ + PlacefileIconInfo( + const std::shared_ptr& iconFile, + const std::string& baseUrlString) : + iconFile_ {iconFile} + { + // Resolve using base URL + auto baseUrl = QUrl::fromUserInput(QString::fromStdString(baseUrlString)); + auto relativeUrl = QUrl(QString::fromStdString(iconFile->filename_)); + + texture_ = util::TextureAtlas::Instance().GetTextureAttributes( + baseUrl.resolved(relativeUrl).toString().toStdString()); + + if (iconFile->iconWidth_ > 0 && iconFile->iconHeight_ > 0) + { + columns_ = texture_.size_.x / iconFile->iconWidth_; + rows_ = texture_.size_.y / iconFile->iconHeight_; + } + else + { + columns_ = 0u; + rows_ = 0u; + } + + numIcons_ = columns_ * rows_; + + float xFactor = 0.0f; + float yFactor = 0.0f; + + if (texture_.size_.x > 0 && texture_.size_.y > 0) + { + xFactor = (texture_.sRight_ - texture_.sLeft_) / texture_.size_.x; + yFactor = (texture_.tBottom_ - texture_.tTop_) / texture_.size_.y; + } + + scaledWidth_ = iconFile_->iconWidth_ * xFactor; + scaledHeight_ = iconFile_->iconHeight_ * yFactor; + } + + std::shared_ptr iconFile_; + util::TextureAttributes texture_; + std::size_t rows_ {}; + std::size_t columns_ {}; + std::size_t numIcons_ {}; + float scaledWidth_ {}; + float scaledHeight_ {}; +}; + +class PlacefileIcons::Impl +{ +public: + explicit Impl(std::shared_ptr context) : + context_ {context}, + shaderProgram_ {nullptr}, + uMVPMatrixLocation_(GL_INVALID_INDEX), + uMapMatrixLocation_(GL_INVALID_INDEX), + uMapScreenCoordLocation_(GL_INVALID_INDEX), + vao_ {GL_INVALID_INDEX}, + vbo_ {GL_INVALID_INDEX}, + numVertices_ {0} + { + } + + ~Impl() {} + + std::shared_ptr context_; + + bool dirty_ {false}; + + boost::unordered_flat_map + iconFiles_ {}; + + std::vector> iconList_ {}; + + std::shared_ptr shaderProgram_; + GLint uMVPMatrixLocation_; + GLint uMapMatrixLocation_; + GLint uMapScreenCoordLocation_; + + GLuint vao_; + GLuint vbo_; + + GLsizei numVertices_; + + void Update(); +}; + +PlacefileIcons::PlacefileIcons(std::shared_ptr context) : + DrawItem(context->gl()), p(std::make_unique(context)) +{ +} +PlacefileIcons::~PlacefileIcons() = default; + +PlacefileIcons::PlacefileIcons(PlacefileIcons&&) noexcept = default; +PlacefileIcons& PlacefileIcons::operator=(PlacefileIcons&&) noexcept = default; + +void PlacefileIcons::Initialize() +{ + gl::OpenGLFunctions& gl = p->context_->gl(); + + p->shaderProgram_ = p->context_->GetShaderProgram(":/gl/geo_texture2d.vert", + ":/gl/texture2d.frag"); + + p->uMVPMatrixLocation_ = + gl.glGetUniformLocation(p->shaderProgram_->id(), "uMVPMatrix"); + if (p->uMVPMatrixLocation_ == -1) + { + logger_->warn("Could not find uMVPMatrix"); + } + + p->uMapMatrixLocation_ = + gl.glGetUniformLocation(p->shaderProgram_->id(), "uMapMatrix"); + if (p->uMapMatrixLocation_ == -1) + { + logger_->warn("Could not find uMapMatrix"); + } + + p->uMapScreenCoordLocation_ = + gl.glGetUniformLocation(p->shaderProgram_->id(), "uMapScreenCoord"); + if (p->uMapScreenCoordLocation_ == -1) + { + logger_->warn("Could not find uMapScreenCoord"); + } + + gl.glGenVertexArrays(1, &p->vao_); + gl.glGenBuffers(1, &p->vbo_); + + gl.glBindVertexArray(p->vao_); + gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_); + gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + + // aLatLong + gl.glVertexAttribPointer(0, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + static_cast(0)); + gl.glEnableVertexAttribArray(0); + + // aXYOffset + gl.glVertexAttribPointer(1, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(2 * sizeof(float))); + gl.glEnableVertexAttribArray(1); + + // aTexCoord + gl.glVertexAttribPointer(2, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(4 * sizeof(float))); + gl.glEnableVertexAttribArray(2); + + // aModulate + gl.glVertexAttribPointer(3, + 4, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(6 * sizeof(float))); + gl.glEnableVertexAttribArray(3); + + // aAngle + gl.glVertexAttribPointer(4, + 1, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(10 * sizeof(float))); + gl.glEnableVertexAttribArray(4); + + p->dirty_ = true; +} + +void PlacefileIcons::Render( + const QMapLibreGL::CustomLayerRenderParameters& params) +{ + if (!p->iconList_.empty()) + { + gl::OpenGLFunctions& gl = p->context_->gl(); + + gl.glBindVertexArray(p->vao_); + gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_); + + p->Update(); + p->shaderProgram_->Use(); + UseDefaultProjection(params, p->uMVPMatrixLocation_); + UseMapProjection( + params, p->uMapMatrixLocation_, p->uMapScreenCoordLocation_); + + // Draw icons + gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); + } +} + +void PlacefileIcons::Deinitialize() +{ + gl::OpenGLFunctions& gl = p->context_->gl(); + + gl.glDeleteVertexArrays(1, &p->vao_); + gl.glDeleteBuffers(1, &p->vbo_); +} + +void PlacefileIcons::SetIconFiles( + const std::vector>& iconFiles, + const std::string& baseUrl) +{ + p->dirty_ = true; + + // Populate icon file map + p->iconFiles_.clear(); + + for (auto& file : iconFiles) + { + p->iconFiles_.emplace( + std::piecewise_construct, + std::tuple {file->fileNumber_}, + std::forward_as_tuple(PlacefileIconInfo {file, baseUrl})); + } +} + +void PlacefileIcons::AddIcon( + const std::shared_ptr& di) +{ + if (di != nullptr) + { + p->iconList_.emplace_back(di); + p->dirty_ = true; + } +} + +void PlacefileIcons::Reset() +{ + // Clear the icon list, and mark the draw item dirty + p->iconList_.clear(); + p->dirty_ = true; +} + +void PlacefileIcons::Impl::Update() +{ + if (dirty_) + { + static std::vector buffer {}; + buffer.clear(); + buffer.reserve(iconList_.size() * kBufferLength); + numVertices_ = 0; + + for (auto& di : iconList_) + { + auto it = iconFiles_.find(di->fileNumber_); + if (it == iconFiles_.cend()) + { + // No file found + logger_->trace("Could not find file number: {}", di->fileNumber_); + continue; + } + + auto& icon = it->second; + + // Validate icon + if (di->iconNumber_ == 0 || di->iconNumber_ > icon.numIcons_) + { + // No icon found + logger_->trace("Invalid icon number: {}", di->iconNumber_); + continue; + } + + // Latitude and longitude coordinates in degrees + const float lat = static_cast(di->latitude_); + const float lon = static_cast(di->longitude_); + + // Base X/Y offsets in pixels + const float x = static_cast(di->x_); + const float y = static_cast(di->y_); + + // Half icon size + const float hw = static_cast(icon.iconFile_->iconWidth_) * 0.5f; + const float hh = + static_cast(icon.iconFile_->iconHeight_) * 0.5f; + + // Final X/Y offsets in pixels + const float lx = x - hw; + const float rx = x + hw; + const float by = y - hh; + const float ty = y + hh; + + // Angle in degrees + // TODO: Properly convert + const float a = static_cast(di->angle_.value()); + + // Texture coordinates + const std::size_t iconRow = (di->iconNumber_ - 1) / icon.columns_; + const std::size_t iconColumn = (di->iconNumber_ - 1) % icon.columns_; + + const float iconX = iconColumn * icon.scaledWidth_; + const float iconY = iconRow * icon.scaledHeight_; + + const float ls = icon.texture_.sLeft_ + iconX; + const float rs = ls + icon.scaledWidth_; + const float tt = icon.texture_.tTop_ + iconY; + const float bt = tt + icon.scaledHeight_; + + // Fixed modulate color + const float mc0 = 1.0f; + const float mc1 = 1.0f; + const float mc2 = 1.0f; + const float mc3 = 1.0f; + + buffer.insert(buffer.end(), + { + // Icon + lat, lon, lx, by, ls, bt, mc0, mc1, mc2, mc3, a, // BL + lat, lon, lx, by, ls, tt, mc0, mc1, mc2, mc3, a, // TL + lat, lon, rx, ty, rs, bt, mc0, mc1, mc2, mc3, a, // BR + lat, lon, rx, ty, rs, bt, mc0, mc1, mc2, mc3, a, // BR + lat, lon, rx, ty, rs, tt, mc0, mc1, mc2, mc3, a, // TR + lat, lon, lx, by, ls, tt, mc0, mc1, mc2, mc3, a // TL + }); + + numVertices_ += 6; + } + + gl::OpenGLFunctions& gl = context_->gl(); + + gl.glBufferData(GL_ARRAY_BUFFER, + sizeof(float) * buffer.size(), + buffer.data(), + GL_DYNAMIC_DRAW); + + dirty_ = false; + } +} + +} // namespace draw +} // namespace gl +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp new file mode 100644 index 00000000..9f771d8e --- /dev/null +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include +#include +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace gl +{ +namespace draw +{ + +class PlacefileIcons : public DrawItem +{ +public: + explicit PlacefileIcons(std::shared_ptr context); + ~PlacefileIcons(); + + PlacefileIcons(const PlacefileIcons&) = delete; + PlacefileIcons& operator=(const PlacefileIcons&) = delete; + + PlacefileIcons(PlacefileIcons&&) noexcept; + PlacefileIcons& operator=(PlacefileIcons&&) noexcept; + + void Initialize() override; + void Render(const QMapLibreGL::CustomLayerRenderParameters& params) override; + void Deinitialize() override; + + /** + * Configures the textures for drawing the placefile icons. + * + * @param [in] iconFiles A list of icon files + * @param [in] baseUrl The base URL of the placefile + */ + void SetIconFiles( + const std::vector>& + iconFiles, + const std::string& baseUrl); + + /** + * Adds a placefile icon to the internal draw list. + * + * @param [in] di Placefile icon + */ + void AddIcon(const std::shared_ptr& di); + + /** + * Resets the list of icons in preparation for rendering a new frame. + */ + void Reset(); + +private: + class Impl; + + std::unique_ptr p; +}; + +} // namespace draw +} // namespace gl +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp index 1b82af67..6030e264 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -23,12 +24,18 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_); class PlacefileLayer::Impl { public: - explicit Impl() {}; + explicit Impl(std::shared_ptr context) : + placefileIcons_ {std::make_shared(context)} + { + } ~Impl() = default; + void + RenderIconDrawItem(const QMapLibreGL::CustomLayerRenderParameters& params, + const std::shared_ptr& di); void RenderTextDrawItem(const QMapLibreGL::CustomLayerRenderParameters& params, - std::shared_ptr di); + const std::shared_ptr& di); void RenderText(const QMapLibreGL::CustomLayerRenderParameters& params, const std::string& text, @@ -44,11 +51,14 @@ public: float halfHeight_ {}; bool thresholded_ {true}; ImFont* monospaceFont_ {}; + + std::shared_ptr placefileIcons_; }; PlacefileLayer::PlacefileLayer(std::shared_ptr context) : - DrawLayer(context), p(std::make_unique()) + DrawLayer(context), p(std::make_unique(context)) { + AddDrawItem(p->placefileIcons_); } PlacefileLayer::~PlacefileLayer() = default; @@ -60,9 +70,25 @@ void PlacefileLayer::Initialize() DrawLayer::Initialize(); } +void PlacefileLayer::Impl::RenderIconDrawItem( + const QMapLibreGL::CustomLayerRenderParameters& params, + const std::shared_ptr& di) +{ + auto distance = + (thresholded_) ? + util::GeographicLib::GetDistance( + params.latitude, params.longitude, di->latitude_, di->longitude_) : + 0; + + if (distance < di->threshold_) + { + placefileIcons_->AddIcon(di); + } +} + void PlacefileLayer::Impl::RenderTextDrawItem( - const QMapLibreGL::CustomLayerRenderParameters& params, - std::shared_ptr di) + const QMapLibreGL::CustomLayerRenderParameters& params, + const std::shared_ptr& di) { auto distance = (thresholded_) ? @@ -133,11 +159,12 @@ void PlacefileLayer::Render( { gl::OpenGLFunctions& gl = context()->gl(); - DrawLayer::Render(params); - // Reset text ID per frame p->textId_ = 0; + // Reset graphics + p->placefileIcons_->Reset(); + // Update map screen coordinate and scale information p->mapScreenCoordLocation_ = util::maplibre::LatLongToScreenCoordinate( {params.latitude, params.longitude}); @@ -171,6 +198,9 @@ void PlacefileLayer::Render( p->thresholded_ = placefileManager->placefile_thresholded(placefile->name()); + p->placefileIcons_->SetIconFiles(placefile->icon_files(), + placefile->name()); + for (auto& drawItem : placefile->GetDrawItems()) { switch (drawItem->itemType_) @@ -181,12 +211,20 @@ void PlacefileLayer::Render( std::static_pointer_cast(drawItem)); break; + case gr::Placefile::ItemType::Icon: + p->RenderIconDrawItem( + params, + std::static_pointer_cast(drawItem)); + break; + default: break; } } } + DrawLayer::Render(params); + SCWX_GL_CHECK_ERROR(); } From bc1bf8cef6c7b673c7919fe2ff6d77a5e5d33689 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 2 Aug 2023 00:02:33 -0500 Subject: [PATCH 035/199] Fix rendering of placefile icons, now works on one map --- .../scwx/qt/gl/draw/placefile_icons.cpp | 8 ++--- scwx-qt/source/scwx/qt/gl/gl_context.cpp | 6 ++-- scwx-qt/source/scwx/qt/map/draw_layer.cpp | 1 + scwx-qt/source/scwx/qt/util/texture_atlas.cpp | 36 +++++++++++-------- scwx-qt/source/scwx/qt/util/texture_atlas.hpp | 2 ++ 5 files changed, 32 insertions(+), 21 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp index 69e6887e..3e46d3a6 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp @@ -343,11 +343,11 @@ void PlacefileIcons::Impl::Update() { // Icon lat, lon, lx, by, ls, bt, mc0, mc1, mc2, mc3, a, // BL - lat, lon, lx, by, ls, tt, mc0, mc1, mc2, mc3, a, // TL - lat, lon, rx, ty, rs, bt, mc0, mc1, mc2, mc3, a, // BR - lat, lon, rx, ty, rs, bt, mc0, mc1, mc2, mc3, a, // BR + lat, lon, lx, ty, ls, tt, mc0, mc1, mc2, mc3, a, // TL + lat, lon, rx, by, rs, bt, mc0, mc1, mc2, mc3, a, // BR + lat, lon, rx, by, rs, bt, mc0, mc1, mc2, mc3, a, // BR lat, lon, rx, ty, rs, tt, mc0, mc1, mc2, mc3, a, // TR - lat, lon, lx, by, ls, tt, mc0, mc1, mc2, mc3, a // TL + lat, lon, lx, ty, ls, tt, mc0, mc1, mc2, mc3, a // TL }); numVertices_ += 6; diff --git a/scwx-qt/source/scwx/qt/gl/gl_context.cpp b/scwx-qt/source/scwx/qt/gl/gl_context.cpp index d0e0fd80..f875bf14 100644 --- a/scwx-qt/source/scwx/qt/gl/gl_context.cpp +++ b/scwx-qt/source/scwx/qt/gl/gl_context.cpp @@ -77,9 +77,11 @@ GLuint GlContext::GetTextureAtlas() { std::unique_lock lock(p->textureMutex_); - if (p->textureAtlas_ == GL_INVALID_INDEX) + auto& textureAtlas = util::TextureAtlas::Instance(); + + if (p->textureAtlas_ == GL_INVALID_INDEX || textureAtlas.NeedsBuffered()) { - p->textureAtlas_ = util::TextureAtlas::Instance().BufferAtlas(p->gl_); + p->textureAtlas_ = textureAtlas.BufferAtlas(p->gl_); } return p->textureAtlas_; diff --git a/scwx-qt/source/scwx/qt/map/draw_layer.cpp b/scwx-qt/source/scwx/qt/map/draw_layer.cpp index 2b055454..91c823e3 100644 --- a/scwx-qt/source/scwx/qt/map/draw_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/draw_layer.cpp @@ -45,6 +45,7 @@ void DrawLayer::Initialize() void DrawLayer::Render(const QMapLibreGL::CustomLayerRenderParameters& params) { gl::OpenGLFunctions& gl = p->context_->gl(); + p->textureAtlas_ = p->context_->GetTextureAtlas(); gl.glActiveTexture(GL_TEXTURE0); gl.glBindTexture(GL_TEXTURE_2D, p->textureAtlas_); diff --git a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp index 841c42d3..a23a119c 100644 --- a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp +++ b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp @@ -38,27 +38,22 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_); class TextureAtlas::Impl { public: - explicit Impl() : - texturePathMap_ {}, - texturePathMutex_ {}, - atlas_ {}, - atlasMap_ {}, - atlasMutex_ {} - { - } + explicit Impl() {} ~Impl() {} static boost::gil::rgba8_image_t LoadImage(const std::string& imagePath); - std::unordered_map texturePathMap_; - std::shared_mutex texturePathMutex_; + std::unordered_map texturePathMap_ {}; + std::shared_mutex texturePathMutex_ {}; - std::shared_mutex textureCacheMutex_; - std::unordered_map textureCache_; + std::shared_mutex textureCacheMutex_ {}; + std::unordered_map textureCache_ {}; - boost::gil::rgba8_image_t atlas_; - std::unordered_map atlasMap_; - std::shared_mutex atlasMutex_; + boost::gil::rgba8_image_t atlas_ {}; + std::unordered_map atlasMap_ {}; + std::shared_mutex atlasMutex_ {}; + + bool needsBuffered_ {true}; }; TextureAtlas::TextureAtlas() : p(std::make_unique()) {} @@ -67,6 +62,11 @@ TextureAtlas::~TextureAtlas() = default; TextureAtlas::TextureAtlas(TextureAtlas&&) noexcept = default; TextureAtlas& TextureAtlas::operator=(TextureAtlas&&) noexcept = default; +bool TextureAtlas::NeedsBuffered() const +{ + return p->needsBuffered_; +} + void TextureAtlas::RegisterTexture(const std::string& name, const std::string& path) { @@ -275,6 +275,9 @@ void TextureAtlas::BuildAtlas(size_t width, size_t height) logger_->warn("Unable to pack texture: {}", images[i].first); } } + + // Mark the need to buffer the atlas + p->needsBuffered_ = true; } GLuint TextureAtlas::BufferAtlas(gl::OpenGLFunctions& gl) @@ -318,6 +321,9 @@ GLuint TextureAtlas::BufferAtlas(gl::OpenGLFunctions& gl) pixelData.data()); } + // Atlas has been successfully buffered + p->needsBuffered_ = false; + return texture; } diff --git a/scwx-qt/source/scwx/qt/util/texture_atlas.hpp b/scwx-qt/source/scwx/qt/util/texture_atlas.hpp index 5bf07018..5faff70d 100644 --- a/scwx-qt/source/scwx/qt/util/texture_atlas.hpp +++ b/scwx-qt/source/scwx/qt/util/texture_atlas.hpp @@ -66,6 +66,8 @@ public: static TextureAtlas& Instance(); + bool NeedsBuffered() const; + void RegisterTexture(const std::string& name, const std::string& path); bool CacheTexture(const std::string& name, const std::string& path); void BuildAtlas(size_t width, size_t height); From f0bdeb09b19b0dbc9116ff5cc80750ba6ef85579 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 2 Aug 2023 00:04:00 -0500 Subject: [PATCH 036/199] Replace black with transparent, increase atlas size --- .../scwx/qt/manager/resource_manager.cpp | 4 ++-- scwx-qt/source/scwx/qt/util/texture_atlas.cpp | 20 ++++++++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp index 19d5c50f..aba8c918 100644 --- a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp @@ -92,7 +92,7 @@ void LoadImageResources(const std::vector& urlStrings) if (textureCached) { util::TextureAtlas& textureAtlas = util::TextureAtlas::Instance(); - textureAtlas.BuildAtlas(1024, 1024); + textureAtlas.BuildAtlas(2048, 2048); } } @@ -119,7 +119,7 @@ static void LoadTextures() ":/res/textures/lines/default-1x7.png"); textureAtlas.RegisterTexture("lines/test-pattern", ":/res/textures/lines/test-pattern.png"); - textureAtlas.BuildAtlas(1024, 1024); + textureAtlas.BuildAtlas(2048, 2048); } } // namespace ResourceManager diff --git a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp index a23a119c..0b272a3f 100644 --- a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp +++ b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -404,18 +405,31 @@ TextureAtlas::Impl::LoadImage(const std::string& imagePath) } // Create a view pointing to the STB image data - auto imageView = boost::gil::interleaved_view( + auto stbView = boost::gil::interleaved_view( width, height, reinterpret_cast(pixelData), width * desiredChannels); // Copy the view to the destination image - image = boost::gil::rgba8_image_t(imageView); + image = boost::gil::rgba8_image_t(stbView); + auto& view = boost::gil::view(image); + // If no alpha channel, replace black with transparent if (numChannels == 3) { - // TODO: If no alpha channel, replace black with transparent + std::for_each( + std::execution::par_unseq, + view.begin(), + view.end(), + [](boost::gil::rgba8_pixel_t& pixel) + { + static const boost::gil::rgba8_pixel_t kBlack {0, 0, 0, 255}; + if (pixel == kBlack) + { + pixel[3] = 0; + } + }); } stbi_image_free(pixelData); From b735589e9a9b6e2c55dc71a5ebe21ee5e367a0f0 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 2 Aug 2023 00:23:52 -0500 Subject: [PATCH 037/199] Fix placefile icon orientation --- scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp index 3e46d3a6..35952346 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp @@ -342,12 +342,12 @@ void PlacefileIcons::Impl::Update() buffer.insert(buffer.end(), { // Icon - lat, lon, lx, by, ls, bt, mc0, mc1, mc2, mc3, a, // BL - lat, lon, lx, ty, ls, tt, mc0, mc1, mc2, mc3, a, // TL - lat, lon, rx, by, rs, bt, mc0, mc1, mc2, mc3, a, // BR - lat, lon, rx, by, rs, bt, mc0, mc1, mc2, mc3, a, // BR - lat, lon, rx, ty, rs, tt, mc0, mc1, mc2, mc3, a, // TR - lat, lon, lx, ty, ls, tt, mc0, mc1, mc2, mc3, a // TL + lat, lon, lx, by, rs, tt, mc0, mc1, mc2, mc3, a, // BL + lat, lon, lx, ty, rs, bt, mc0, mc1, mc2, mc3, a, // TL + lat, lon, rx, by, ls, tt, mc0, mc1, mc2, mc3, a, // BR + lat, lon, rx, by, ls, tt, mc0, mc1, mc2, mc3, a, // BR + lat, lon, rx, ty, ls, bt, mc0, mc1, mc2, mc3, a, // TR + lat, lon, lx, ty, rs, bt, mc0, mc1, mc2, mc3, a // TL }); numVertices_ += 6; From a536d46bb4d69d8c6431a2cc291aedde680143e6 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 2 Aug 2023 20:22:14 -0500 Subject: [PATCH 038/199] Don't interpolate placefile icon pixels This is a 90% solution to icon placement/blurring. The pixel coordinates will need rounded in the vertex shader once the MVP and map matrices are broken out for proper rotation. --- scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp index 35952346..b23ec909 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp @@ -52,6 +52,7 @@ struct PlacefileIconInfo numIcons_ = columns_ * rows_; + // Pixel size float xFactor = 0.0f; float yFactor = 0.0f; @@ -221,6 +222,10 @@ void PlacefileIcons::Render( UseMapProjection( params, p->uMapMatrixLocation_, p->uMapScreenCoordLocation_); + // Don't interpolate texture coordinates + gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + // Draw icons gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); } @@ -312,10 +317,10 @@ void PlacefileIcons::Impl::Update() static_cast(icon.iconFile_->iconHeight_) * 0.5f; // Final X/Y offsets in pixels - const float lx = x - hw; - const float rx = x + hw; - const float by = y - hh; - const float ty = y + hh; + const float lx = std::roundf(x - hw); + const float rx = std::roundf(x + hw); + const float by = std::roundf(y - hh); + const float ty = std::roundf(y + hh); // Angle in degrees // TODO: Properly convert From 4ff95094a3afdaba00bcbdeb3fc891de3fa4e546 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 2 Aug 2023 20:24:28 -0500 Subject: [PATCH 039/199] Disable thresholding by default --- scwx-qt/source/scwx/qt/manager/placefile_manager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index ef1696fb..978f37d8 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -76,7 +76,7 @@ public: std::string name_; std::shared_ptr placefile_; bool enabled_; - bool thresholded_ {true}; + bool thresholded_ {false}; boost::asio::thread_pool threadPool_ {1u}; boost::asio::steady_timer refreshTimer_ {threadPool_}; std::mutex refreshMutex_ {}; From 0be94c4de305f5ac3655542f98e50c9e46c71eb6 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 2 Aug 2023 20:49:48 -0500 Subject: [PATCH 040/199] Buffer texture atlas on more than the first context after change - Icons now work on all maps --- scwx-qt/source/scwx/qt/gl/gl_context.cpp | 8 ++++++-- scwx-qt/source/scwx/qt/util/texture_atlas.cpp | 11 ++++------- scwx-qt/source/scwx/qt/util/texture_atlas.hpp | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/gl_context.cpp b/scwx-qt/source/scwx/qt/gl/gl_context.cpp index f875bf14..e09ed177 100644 --- a/scwx-qt/source/scwx/qt/gl/gl_context.cpp +++ b/scwx-qt/source/scwx/qt/gl/gl_context.cpp @@ -35,6 +35,8 @@ public: GLuint textureAtlas_; std::mutex textureMutex_; + + std::uint64_t textureBufferCount_ {}; }; GlContext::GlContext() : p(std::make_unique()) {} @@ -79,9 +81,11 @@ GLuint GlContext::GetTextureAtlas() auto& textureAtlas = util::TextureAtlas::Instance(); - if (p->textureAtlas_ == GL_INVALID_INDEX || textureAtlas.NeedsBuffered()) + if (p->textureAtlas_ == GL_INVALID_INDEX || + p->textureBufferCount_ != textureAtlas.BuildCount()) { - p->textureAtlas_ = textureAtlas.BufferAtlas(p->gl_); + p->textureBufferCount_ = textureAtlas.BuildCount(); + p->textureAtlas_ = textureAtlas.BufferAtlas(p->gl_); } return p->textureAtlas_; diff --git a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp index 0b272a3f..65de8c16 100644 --- a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp +++ b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp @@ -54,7 +54,7 @@ public: std::unordered_map atlasMap_ {}; std::shared_mutex atlasMutex_ {}; - bool needsBuffered_ {true}; + std::uint64_t buildCount_ {0u}; }; TextureAtlas::TextureAtlas() : p(std::make_unique()) {} @@ -63,9 +63,9 @@ TextureAtlas::~TextureAtlas() = default; TextureAtlas::TextureAtlas(TextureAtlas&&) noexcept = default; TextureAtlas& TextureAtlas::operator=(TextureAtlas&&) noexcept = default; -bool TextureAtlas::NeedsBuffered() const +std::uint64_t TextureAtlas::BuildCount() const { - return p->needsBuffered_; + return p->buildCount_; } void TextureAtlas::RegisterTexture(const std::string& name, @@ -278,7 +278,7 @@ void TextureAtlas::BuildAtlas(size_t width, size_t height) } // Mark the need to buffer the atlas - p->needsBuffered_ = true; + ++p->buildCount_; } GLuint TextureAtlas::BufferAtlas(gl::OpenGLFunctions& gl) @@ -322,9 +322,6 @@ GLuint TextureAtlas::BufferAtlas(gl::OpenGLFunctions& gl) pixelData.data()); } - // Atlas has been successfully buffered - p->needsBuffered_ = false; - return texture; } diff --git a/scwx-qt/source/scwx/qt/util/texture_atlas.hpp b/scwx-qt/source/scwx/qt/util/texture_atlas.hpp index 5faff70d..5aae76b4 100644 --- a/scwx-qt/source/scwx/qt/util/texture_atlas.hpp +++ b/scwx-qt/source/scwx/qt/util/texture_atlas.hpp @@ -66,7 +66,7 @@ public: static TextureAtlas& Instance(); - bool NeedsBuffered() const; + std::uint64_t BuildCount() const; void RegisterTexture(const std::string& name, const std::string& path); bool CacheTexture(const std::string& name, const std::string& path); From 117a4736899dbf5c5841339985218daa5a01dece Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 2 Aug 2023 22:39:19 -0500 Subject: [PATCH 041/199] Separate placefiles into their own layers - Placefile rename is partially - Texture repack might be broken --- .../scwx/qt/manager/placefile_manager.cpp | 2 +- .../scwx/qt/manager/placefile_manager.hpp | 2 +- scwx-qt/source/scwx/qt/map/map_widget.cpp | 89 ++++++++++++++++++- .../source/scwx/qt/map/placefile_layer.cpp | 70 ++++++++++++--- .../source/scwx/qt/map/placefile_layer.hpp | 9 +- 5 files changed, 155 insertions(+), 17 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index 978f37d8..ff3da32f 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -109,7 +109,7 @@ bool PlacefileManager::placefile_thresholded(const std::string& name) return false; } -std::shared_ptr +std::shared_ptr PlacefileManager::placefile(const std::string& name) { std::shared_lock lock(p->placefileRecordLock_); diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp index 5776bb48..c2cf2d45 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp @@ -22,7 +22,7 @@ public: bool placefile_enabled(const std::string& name); bool placefile_thresholded(const std::string& name); - std::shared_ptr placefile(const std::string& name); + std::shared_ptr placefile(const std::string& name); void set_placefile_enabled(const std::string& name, bool enabled); void set_placefile_thresholded(const std::string& name, bool thresholded); diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index 710da1e9..d318ad3d 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -124,12 +125,16 @@ public: void RadarProductManagerDisconnect(); void RadarProductViewConnect(); void RadarProductViewDisconnect(); + void RemovePlacefileLayer(const std::string& placefileName); void SetRadarSite(const std::string& radarSite); + void UpdatePlacefileLayers(); bool UpdateStoredMapParameters(); common::Level2Product GetLevel2ProductOrDefault(const std::string& productName) const; + static std::string GetPlacefileLayerName(const std::string& placefileName); + boost::asio::thread_pool threadPool_ {1u}; boost::uuids::uuid uuid_; @@ -158,6 +163,9 @@ public: std::shared_ptr placefileLayer_; std::shared_ptr colorTableLayer_; + std::set enabledPlacefiles_ {}; + std::list> placefileLayers_ {}; + bool autoRefreshEnabled_; bool autoUpdateEnabled_; @@ -199,11 +207,28 @@ void MapWidgetImpl::ConnectSignals() connect(placefileManager_.get(), &manager::PlacefileManager::PlacefileEnabled, widget_, - [this]() { widget_->update(); }); + [this](const std::string& name, bool enabled) + { + if (enabled && !enabledPlacefiles_.contains(name)) + { + enabledPlacefiles_.emplace(name); + UpdatePlacefileLayers(); + } + else if (!enabled && enabledPlacefiles_.contains(name)) + { + enabledPlacefiles_.erase(name); + RemovePlacefileLayer(name); + } + widget_->update(); + }); connect(placefileManager_.get(), &manager::PlacefileManager::PlacefileRenamed, widget_, - [this]() { widget_->update(); }); + [this]() + { + // TODO + widget_->update(); + }); connect(placefileManager_.get(), &manager::PlacefileManager::PlacefileUpdated, widget_, @@ -679,6 +704,7 @@ void MapWidget::AddLayers() p->map_->removeLayer(id.c_str()); } p->layerList_.clear(); + p->placefileLayers_.clear(); auto radarProductView = p->context_->radar_product_view(); @@ -724,13 +750,68 @@ void MapWidget::AddLayers() p->alertLayer_->AddLayers("colorTable"); - p->placefileLayer_ = std::make_shared(p->context_); - p->AddLayer("placefile", p->placefileLayer_); + p->UpdatePlacefileLayers(); p->overlayLayer_ = std::make_shared(p->context_); p->AddLayer("overlay", p->overlayLayer_); } +void MapWidgetImpl::RemovePlacefileLayer(const std::string& placefileName) +{ + std::string layerName = GetPlacefileLayerName(placefileName); + + // Remove layer from map + map_->removeLayer(layerName.c_str()); + + // Remove layer from internal layer list + auto layerIt = std::find(layerList_.begin(), layerList_.end(), layerName); + if (layerIt != layerList_.end()) + { + layerList_.erase(layerIt); + } + + // Determine if a layer exists for the placefile + auto placefileIt = + std::find_if(placefileLayers_.begin(), + placefileLayers_.end(), + [&placefileName](auto& layer) + { return placefileName == layer->placefile_name(); }); + if (placefileIt != placefileLayers_.end()) + { + placefileLayers_.erase(placefileIt); + } +} + +void MapWidgetImpl::UpdatePlacefileLayers() +{ + // Loop through enabled placefiles + for (auto& placefileName : enabledPlacefiles_) + { + // Determine if a layer exists for the placefile + auto it = std::find_if(placefileLayers_.begin(), + placefileLayers_.end(), + [&placefileName](auto& layer) { + return placefileName == layer->placefile_name(); + }); + + // If the layer doesn't exist, create it + if (it == placefileLayers_.end()) + { + std::shared_ptr placefileLayer = + std::make_shared(context_, placefileName); + placefileLayers_.push_back(placefileLayer); + AddLayer( + GetPlacefileLayerName(placefileName), placefileLayer, "colorTable"); + } + } +} + +std::string +MapWidgetImpl::GetPlacefileLayerName(const std::string& placefileName) +{ + return fmt::format("placefile-{}", placefileName); +} + void MapWidgetImpl::AddLayer(const std::string& id, std::shared_ptr layer, const std::string& before) diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp index 6030e264..2838e4ec 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp @@ -24,12 +24,19 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_); class PlacefileLayer::Impl { public: - explicit Impl(std::shared_ptr context) : + explicit Impl(PlacefileLayer* self, + std::shared_ptr context, + const std::string& placefileName) : + self_ {self}, + placefileName_ {placefileName}, placefileIcons_ {std::make_shared(context)} { + ConnectSignals(); } ~Impl() = default; + void ConnectSignals(); + void RenderIconDrawItem(const QMapLibreGL::CustomLayerRenderParameters& params, const std::shared_ptr& di); @@ -44,6 +51,11 @@ public: float x, float y); + PlacefileLayer* self_; + + std::string placefileName_; + bool dirty_ {true}; + std::uint32_t textId_ {}; glm::vec2 mapScreenCoordLocation_ {}; float mapScale_ {1.0f}; @@ -55,14 +67,43 @@ public: std::shared_ptr placefileIcons_; }; -PlacefileLayer::PlacefileLayer(std::shared_ptr context) : - DrawLayer(context), p(std::make_unique(context)) +PlacefileLayer::PlacefileLayer(std::shared_ptr context, + const std::string& placefileName) : + DrawLayer(context), + p(std::make_unique(this, context, placefileName)) { AddDrawItem(p->placefileIcons_); } PlacefileLayer::~PlacefileLayer() = default; +void PlacefileLayer::Impl::ConnectSignals() +{ + auto placefileManager = manager::PlacefileManager::Instance(); + + QObject::connect(placefileManager.get(), + &manager::PlacefileManager::PlacefileUpdated, + self_, + [this](const std::string& name) + { + if (name == placefileName_) + { + dirty_ = true; + } + }); +} + +std::string PlacefileLayer::placefile_name() const +{ + return p->placefileName_; +} + +void PlacefileLayer::set_placefile_name(const std::string& placefileName) +{ + p->placefileName_ = placefileName; + p->dirty_ = true; +} + void PlacefileLayer::Initialize() { logger_->debug("Initialize()"); @@ -74,6 +115,11 @@ void PlacefileLayer::Impl::RenderIconDrawItem( const QMapLibreGL::CustomLayerRenderParameters& params, const std::shared_ptr& di) { + if (!dirty_) + { + return; + } + auto distance = (thresholded_) ? util::GeographicLib::GetDistance( @@ -162,9 +208,6 @@ void PlacefileLayer::Render( // Reset text ID per frame p->textId_ = 0; - // Reset graphics - p->placefileIcons_->Reset(); - // Update map screen coordinate and scale information p->mapScreenCoordLocation_ = util::maplibre::LatLongToScreenCoordinate( {params.latitude, params.longitude}); @@ -192,14 +235,21 @@ void PlacefileLayer::Render( std::shared_ptr placefileManager = manager::PlacefileManager::Instance(); - // Render text - for (auto& placefile : placefileManager->GetActivePlacefiles()) + auto placefile = placefileManager->placefile(p->placefileName_); + + // Render placefile + if (placefile != nullptr) { p->thresholded_ = placefileManager->placefile_thresholded(placefile->name()); - p->placefileIcons_->SetIconFiles(placefile->icon_files(), - placefile->name()); + if (p->dirty_) + { + // Reset Placefile Icons + p->placefileIcons_->Reset(); + p->placefileIcons_->SetIconFiles(placefile->icon_files(), + placefile->name()); + } for (auto& drawItem : placefile->GetDrawItems()) { diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.hpp b/scwx-qt/source/scwx/qt/map/placefile_layer.hpp index 9a293d38..2e07c5c9 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.hpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.hpp @@ -2,6 +2,8 @@ #include +#include + namespace scwx { namespace qt @@ -12,9 +14,14 @@ namespace map class PlacefileLayer : public DrawLayer { public: - explicit PlacefileLayer(std::shared_ptr context); + explicit PlacefileLayer(std::shared_ptr context, + const std::string& placefileName); ~PlacefileLayer(); + std::string placefile_name() const; + + void set_placefile_name(const std::string& placefileName); + void Initialize() override final; void Render(const QMapLibreGL::CustomLayerRenderParameters&) override final; void Deinitialize() override final; From 95f4d03c7b961f6fcf1deed89e59ca96c3256379 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 2 Aug 2023 23:25:38 -0500 Subject: [PATCH 042/199] Fix map layer update on placefile rename --- scwx-qt/source/scwx/qt/map/map_widget.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index d318ad3d..406384db 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -211,11 +211,13 @@ void MapWidgetImpl::ConnectSignals() { if (enabled && !enabledPlacefiles_.contains(name)) { + // Placefile enabled, add layer enabledPlacefiles_.emplace(name); UpdatePlacefileLayers(); } else if (!enabled && enabledPlacefiles_.contains(name)) { + // Placefile disabled, remove layer enabledPlacefiles_.erase(name); RemovePlacefileLayer(name); } @@ -224,9 +226,20 @@ void MapWidgetImpl::ConnectSignals() connect(placefileManager_.get(), &manager::PlacefileManager::PlacefileRenamed, widget_, - [this]() + [this](const std::string& oldName, const std::string& newName) { - // TODO + if (enabledPlacefiles_.contains(oldName)) + { + // Remove old placefile layer + enabledPlacefiles_.erase(oldName); + RemovePlacefileLayer(oldName); + } + if (!enabledPlacefiles_.contains(newName)) + { + // Add new placefile layer + enabledPlacefiles_.emplace(newName); + UpdatePlacefileLayers(); + } widget_->update(); }); connect(placefileManager_.get(), From f9fc32e479236bc93cff3dbaa3d15405d6e846b8 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 3 Aug 2023 06:34:19 -0500 Subject: [PATCH 043/199] Ignore Linux stb_image warning --- scwx-qt/source/scwx/qt/external/stb_image.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/scwx-qt/source/scwx/qt/external/stb_image.cpp b/scwx-qt/source/scwx/qt/external/stb_image.cpp index c9598836..b2c73443 100644 --- a/scwx-qt/source/scwx/qt/external/stb_image.cpp +++ b/scwx-qt/source/scwx/qt/external/stb_image.cpp @@ -1,4 +1,14 @@ #define STB_IMAGE_IMPLEMENTATION #define STBI_ASSERT(x) #define STBI_FAILURE_USERMSG + +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-but-set-variable" +#endif + #include + +#if defined(__GNUC__) +# pragma GCC diagnostic pop +#endif From 6607eb0e56fcbef3790c50f9a7dd9989d1683af7 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 3 Aug 2023 22:40:20 -0500 Subject: [PATCH 044/199] Fix transparency in color table layer and radar product layer --- scwx-qt/source/scwx/qt/map/color_table_layer.cpp | 3 +++ scwx-qt/source/scwx/qt/map/radar_product_layer.cpp | 3 +++ 2 files changed, 6 insertions(+) diff --git a/scwx-qt/source/scwx/qt/map/color_table_layer.cpp b/scwx-qt/source/scwx/qt/map/color_table_layer.cpp index 3b0d89e4..f6181b5c 100644 --- a/scwx-qt/source/scwx/qt/map/color_table_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/color_table_layer.cpp @@ -134,6 +134,9 @@ void ColorTableLayer::Render( p->shaderProgram_->Use(); + // Set OpenGL blend mode for transparency + gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + gl.glUniformMatrix4fv( p->uMVPMatrixLocation_, 1, GL_FALSE, glm::value_ptr(projection)); diff --git a/scwx-qt/source/scwx/qt/map/radar_product_layer.cpp b/scwx-qt/source/scwx/qt/map/radar_product_layer.cpp index be3218c5..c495e23a 100644 --- a/scwx-qt/source/scwx/qt/map/radar_product_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/radar_product_layer.cpp @@ -262,6 +262,9 @@ void RadarProductLayer::Render( p->shaderProgram_->Use(); + // Set OpenGL blend mode for transparency + gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + if (p->colorTableNeedsUpdate_) { UpdateColorTable(); From ec946b71f4bd6530af8608bafe737ef29c79118f Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 3 Aug 2023 23:20:23 -0500 Subject: [PATCH 045/199] Respect alpha channel value for warning boxes using line-opacity property --- scwx-qt/source/scwx/qt/map/alert_layer.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scwx-qt/source/scwx/qt/map/alert_layer.cpp b/scwx-qt/source/scwx/qt/map/alert_layer.cpp index 02740b02..f769231c 100644 --- a/scwx-qt/source/scwx/qt/map/alert_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/alert_layer.cpp @@ -413,11 +413,14 @@ static void AddAlertLayer(std::shared_ptr map, map->removeLayer(fgLayerId); } + const float opacity = outlineColor[3] / 255.0f; + map->addLayer({{"id", bgLayerId}, {"type", "line"}, {"source", sourceId}}, beforeLayer); map->setLayoutProperty(bgLayerId, "line-join", "round"); map->setLayoutProperty(bgLayerId, "line-cap", "round"); map->setPaintProperty(bgLayerId, "line-color", "rgba(0, 0, 0, 255)"); + map->setPaintProperty(bgLayerId, "line-opacity", QString("%1").arg(opacity)); map->setPaintProperty(bgLayerId, "line-width", "5"); map->addLayer({{"id", fgLayerId}, {"type", "line"}, {"source", sourceId}}, @@ -431,6 +434,7 @@ static void AddAlertLayer(std::shared_ptr map, .arg(outlineColor[1]) .arg(outlineColor[2]) .arg(outlineColor[3])); + map->setPaintProperty(fgLayerId, "line-opacity", QString("%1").arg(opacity)); map->setPaintProperty(fgLayerId, "line-width", "3"); } From 6e42001281c7f389f369512f912f8ce6316830d0 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 3 Aug 2023 23:38:27 -0500 Subject: [PATCH 046/199] Set OpenGL blend mode for placefile transparency --- scwx-qt/source/scwx/qt/map/placefile_layer.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp index 2838e4ec..76b430cc 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp @@ -205,6 +205,9 @@ void PlacefileLayer::Render( { gl::OpenGLFunctions& gl = context()->gl(); + // Set OpenGL blend mode for transparency + gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + // Reset text ID per frame p->textId_ = 0; From eaf50895262e38486289e576b299517486c066fd Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 3 Aug 2023 23:39:10 -0500 Subject: [PATCH 047/199] Ensure placefile text IDs are unique, ensuring correct text placement --- scwx-qt/source/scwx/qt/map/placefile_layer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp index 76b430cc..37254a0d 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp @@ -166,7 +166,8 @@ void PlacefileLayer::Impl::RenderText( float x, float y) { - const std::string windowName {fmt::format("PlacefileText-{}", ++textId_)}; + const std::string windowName { + fmt::format("PlacefileText-{}-{}", placefileName_, ++textId_)}; // Convert screen to ImGui coordinates y = params.height - y; From e66c202edf876cccc415b165693052a2d949c1bf Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 5 Aug 2023 00:44:37 -0500 Subject: [PATCH 048/199] Fix x/y pixel offsets of placefile icons --- scwx-qt/gl/geo_texture2d.vert | 2 +- .../scwx/qt/gl/draw/placefile_icons.cpp | 31 ++++++++++--------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/scwx-qt/gl/geo_texture2d.vert b/scwx-qt/gl/geo_texture2d.vert index 35bc85fb..d8bd9b09 100644 --- a/scwx-qt/gl/geo_texture2d.vert +++ b/scwx-qt/gl/geo_texture2d.vert @@ -37,6 +37,6 @@ void main() vec2 p = latLngToScreenCoordinate(aLatLong) - uMapScreenCoord; // Transform the position to screen coordinates - gl_Position = uMapMatrix * vec4(p, 0.0f, 1.0f) - + gl_Position = uMapMatrix * vec4(p, 0.0f, 1.0f) + uMVPMatrix * vec4(aXYOffset, 0.0f, 0.0f); } diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp index b23ec909..41881ebd 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp @@ -311,16 +311,19 @@ void PlacefileIcons::Impl::Update() const float x = static_cast(di->x_); const float y = static_cast(di->y_); - // Half icon size - const float hw = static_cast(icon.iconFile_->iconWidth_) * 0.5f; - const float hh = - static_cast(icon.iconFile_->iconHeight_) * 0.5f; + // Icon size + const float iw = static_cast(icon.iconFile_->iconWidth_); + const float ih = static_cast(icon.iconFile_->iconHeight_); + + // Hot X/Y (zero-based icon center) + const float hx = static_cast(icon.iconFile_->hotX_); + const float hy = static_cast(icon.iconFile_->hotY_); // Final X/Y offsets in pixels - const float lx = std::roundf(x - hw); - const float rx = std::roundf(x + hw); - const float by = std::roundf(y - hh); - const float ty = std::roundf(y + hh); + const float lx = std::roundf(x - hx); + const float rx = std::roundf(lx + iw); + const float ty = std::roundf(y + hy); + const float by = std::roundf(ty - ih); // Angle in degrees // TODO: Properly convert @@ -347,12 +350,12 @@ void PlacefileIcons::Impl::Update() buffer.insert(buffer.end(), { // Icon - lat, lon, lx, by, rs, tt, mc0, mc1, mc2, mc3, a, // BL - lat, lon, lx, ty, rs, bt, mc0, mc1, mc2, mc3, a, // TL - lat, lon, rx, by, ls, tt, mc0, mc1, mc2, mc3, a, // BR - lat, lon, rx, by, ls, tt, mc0, mc1, mc2, mc3, a, // BR - lat, lon, rx, ty, ls, bt, mc0, mc1, mc2, mc3, a, // TR - lat, lon, lx, ty, rs, bt, mc0, mc1, mc2, mc3, a // TL + lat, lon, lx, by, ls, bt, mc0, mc1, mc2, mc3, a, // BL + lat, lon, lx, ty, ls, tt, mc0, mc1, mc2, mc3, a, // TL + lat, lon, rx, by, rs, bt, mc0, mc1, mc2, mc3, a, // BR + lat, lon, rx, by, rs, bt, mc0, mc1, mc2, mc3, a, // BR + lat, lon, rx, ty, rs, tt, mc0, mc1, mc2, mc3, a, // TR + lat, lon, lx, ty, ls, tt, mc0, mc1, mc2, mc3, a // TL }); numVertices_ += 6; From 6c0b62709fe3429b0857147945a976eac481f1d0 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 5 Aug 2023 01:28:34 -0500 Subject: [PATCH 049/199] Rotate placefile icons --- scwx-qt/gl/geo_texture2d.vert | 10 ++++++++-- scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp | 17 +++++++++++++++++ scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp | 3 +++ .../source/scwx/qt/gl/draw/placefile_icons.cpp | 2 +- 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/scwx-qt/gl/geo_texture2d.vert b/scwx-qt/gl/geo_texture2d.vert index d8bd9b09..467aa72f 100644 --- a/scwx-qt/gl/geo_texture2d.vert +++ b/scwx-qt/gl/geo_texture2d.vert @@ -5,12 +5,13 @@ #define LONGITUDE_MAX 180.0f #define PI 3.1415926535897932384626433f #define RAD2DEG 57.295779513082320876798156332941f +#define DEG2RAD 0.0174532925199432957692369055556f layout (location = 0) in vec2 aLatLong; layout (location = 1) in vec2 aXYOffset; layout (location = 2) in vec2 aTexCoord; layout (location = 3) in vec4 aModulate; -layout (location = 4) in float aAngle; +layout (location = 4) in float aAngleDeg; uniform mat4 uMVPMatrix; uniform mat4 uMapMatrix; @@ -36,7 +37,12 @@ void main() vec2 p = latLngToScreenCoordinate(aLatLong) - uMapScreenCoord; + // Rotate clockwise + float angle = aAngleDeg * DEG2RAD; + mat2 rotate = mat2(cos(angle), -sin(angle), + sin(angle), cos(angle)); + // Transform the position to screen coordinates gl_Position = uMapMatrix * vec4(p, 0.0f, 1.0f) + - uMVPMatrix * vec4(aXYOffset, 0.0f, 0.0f); + uMVPMatrix * vec4(rotate * aXYOffset, 0.0f, 0.0f); } diff --git a/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp b/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp index b2c92279..1cc558a5 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp @@ -54,6 +54,23 @@ void DrawItem::UseDefaultProjection( uMVPMatrixLocation, 1, GL_FALSE, glm::value_ptr(projection)); } +void DrawItem::UseRotationProjection( + const QMapLibreGL::CustomLayerRenderParameters& params, + GLint uMVPMatrixLocation) +{ + glm::mat4 projection = glm::ortho(0.0f, + static_cast(params.width), + 0.0f, + static_cast(params.height)); + + projection = glm::rotate(projection, + glm::radians(params.bearing), + glm::vec3(0.0f, 0.0f, 1.0f)); + + p->gl_.glUniformMatrix4fv( + uMVPMatrixLocation, 1, GL_FALSE, glm::value_ptr(projection)); +} + // TODO: Refactor to utility class static glm::vec2 LatLongToScreenCoordinate(const QMapLibreGL::Coordinate& coordinate) diff --git a/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp b/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp index de94ebe7..dbacb008 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp @@ -36,6 +36,9 @@ protected: void UseDefaultProjection(const QMapLibreGL::CustomLayerRenderParameters& params, GLint uMVPMatrixLocation); + void + UseRotationProjection(const QMapLibreGL::CustomLayerRenderParameters& params, + GLint uMVPMatrixLocation); void UseMapProjection(const QMapLibreGL::CustomLayerRenderParameters& params, GLint uMVPMatrixLocation, GLint uMapScreenCoordLocation); diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp index 41881ebd..ff63792b 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp @@ -218,7 +218,7 @@ void PlacefileIcons::Render( p->Update(); p->shaderProgram_->Use(); - UseDefaultProjection(params, p->uMVPMatrixLocation_); + UseRotationProjection(params, p->uMVPMatrixLocation_); UseMapProjection( params, p->uMapMatrixLocation_, p->uMapScreenCoordLocation_); From f0c2f8eec023c7cddc6ac03e0ac18a295948a9f3 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 6 Aug 2023 22:49:18 -0500 Subject: [PATCH 050/199] Correct text not following map rotation --- .../source/scwx/qt/map/placefile_layer.cpp | 24 +++++++++++++++---- wxdata/include/scwx/common/geographic.hpp | 2 ++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp index 37254a0d..2117932b 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -59,6 +60,8 @@ public: std::uint32_t textId_ {}; glm::vec2 mapScreenCoordLocation_ {}; float mapScale_ {1.0f}; + float mapBearingCos_ {1.0f}; + float mapBearingSin_ {0.0f}; float halfWidth_ {}; float halfHeight_ {}; bool thresholded_ {true}; @@ -149,12 +152,23 @@ void PlacefileLayer::Impl::RenderTextDrawItem( mapScreenCoordLocation_) * mapScale_; + // Rotate text according to map rotation + float rotatedX = screenCoordinates.x; + float rotatedY = screenCoordinates.y; + if (params.bearing != 0.0) + { + rotatedX = screenCoordinates.x * mapBearingCos_ - + screenCoordinates.y * mapBearingSin_; + rotatedY = screenCoordinates.x * mapBearingSin_ + + screenCoordinates.y * mapBearingCos_; + } + RenderText(params, di->text_, di->hoverText_, di->color_, - screenCoordinates.x + di->x_ + halfWidth_, - screenCoordinates.y + di->y_ + halfHeight_); + rotatedX + di->x_ + halfWidth_, + rotatedY + di->y_ + halfHeight_); } } @@ -217,8 +231,10 @@ void PlacefileLayer::Render( {params.latitude, params.longitude}); p->mapScale_ = std::pow(2.0, params.zoom) * mbgl::util::tileSize_D / mbgl::util::DEGREES_MAX; - p->halfWidth_ = params.width * 0.5f; - p->halfHeight_ = params.height * 0.5f; + p->mapBearingCos_ = std::cosf(params.bearing * common::kDegreesToRadians); + p->mapBearingSin_ = std::sinf(params.bearing * common::kDegreesToRadians); + p->halfWidth_ = params.width * 0.5f; + p->halfHeight_ = params.height * 0.5f; // Get monospace font pointer std::size_t fontSize = 16; diff --git a/wxdata/include/scwx/common/geographic.hpp b/wxdata/include/scwx/common/geographic.hpp index 1318f43b..8945db17 100644 --- a/wxdata/include/scwx/common/geographic.hpp +++ b/wxdata/include/scwx/common/geographic.hpp @@ -11,6 +11,8 @@ namespace common constexpr double kMilesPerMeter = 0.00062137119; constexpr double kKilometersPerMeter = 0.001; +constexpr double kDegreesToRadians = 0.0174532925199432957692369055556; + /** * @brief Coordinate type to hold latitude and longitude of a location. */ From 1929ee9eef2b7bf68bc478fd4dc074cd41ffc6e0 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 6 Aug 2023 23:52:26 -0500 Subject: [PATCH 051/199] Add placefile parsing for Line --- wxdata/include/scwx/gr/placefile.hpp | 20 +++++++++ wxdata/source/scwx/gr/placefile.cpp | 66 +++++++++++++++++++++++++++- 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/wxdata/include/scwx/gr/placefile.hpp b/wxdata/include/scwx/gr/placefile.hpp index c6d391cd..816471dd 100644 --- a/wxdata/include/scwx/gr/placefile.hpp +++ b/wxdata/include/scwx/gr/placefile.hpp @@ -100,6 +100,26 @@ public: std::string hoverText_ {}; }; + struct LineDrawItem : DrawItem + { + LineDrawItem() { itemType_ = ItemType::Line; } + + boost::gil::rgba8_pixel_t color_ {}; + double width_ {}; + std::int32_t flags_ {}; + std::string hoverText_ {}; + + struct Element + { + double latitude_ {}; + double longitude_ {}; + double x_ {}; + double y_ {}; + }; + + std::vector elements_ {}; + }; + bool IsValid() const; /** diff --git a/wxdata/source/scwx/gr/placefile.cpp b/wxdata/source/scwx/gr/placefile.cpp index 4ecb74c3..8bd0277b 100644 --- a/wxdata/source/scwx/gr/placefile.cpp +++ b/wxdata/source/scwx/gr/placefile.cpp @@ -46,6 +46,7 @@ public: double& longitude, double& x, double& y); + void ProcessElement(const std::string& line); void ProcessLine(const std::string& line); static void ProcessEscapeCharacters(std::string& s); @@ -62,6 +63,7 @@ public: ColorMode colorMode_ {ColorMode::RGBA}; std::vector objectStack_ {}; DrawingStatement currentStatement_ {DrawingStatement::Standard}; + std::shared_ptr currentDrawItem_ {nullptr}; // References std::unordered_map> iconFiles_ {}; @@ -179,6 +181,11 @@ std::shared_ptr Placefile::Load(const std::string& name, if (boost::istarts_with(line, "End:")) { placefile->p->currentStatement_ = DrawingStatement::Standard; + placefile->p->currentDrawItem_ = nullptr; + } + else if (placefile->p->currentDrawItem_ != nullptr) + { + placefile->p->ProcessElement(line); } break; } @@ -502,9 +509,39 @@ void Placefile::Impl::ProcessLine(const std::string& line) // lat, lon // ... // End: + std::vector tokenList = + util::ParseTokens(line, {",", ","}, lineKey_.size()); + currentStatement_ = DrawingStatement::Line; - // TODO + std::shared_ptr di = nullptr; + + if (tokenList.size() >= 2) + { + di = std::make_shared(); + + di->threshold_ = threshold_; + di->color_ = color_; + + di->width_ = std::stoul(tokenList[0]); + di->flags_ = std::stoul(tokenList[1]); + } + if (tokenList.size() >= 3) + { + ProcessEscapeCharacters(tokenList[2]); + TrimQuotes(tokenList[2]); + di->hoverText_.swap(tokenList[2]); + } + + if (di != nullptr) + { + currentDrawItem_ = di; + drawItems_.emplace_back(std::move(di)); + } + else + { + logger_->warn("Line statement malformed: {}", line); + } } else if (boost::istarts_with(line, trianglesKey_)) { @@ -548,6 +585,33 @@ void Placefile::Impl::ProcessLine(const std::string& line) } } +void Placefile::Impl::ProcessElement(const std::string& line) +{ + if (currentStatement_ == DrawingStatement::Line) + { + // Line: width, flags [, hover_text] + // lat, lon + // ... + // End: + std::vector tokenList = util::ParseTokens(line, {",", ","}); + + if (tokenList.size() >= 2) + { + LineDrawItem::Element element; + + ParseLocation(tokenList[0], + tokenList[1], + element.latitude_, + element.longitude_, + element.x_, + element.y_); + + std::static_pointer_cast(currentDrawItem_) + ->elements_.emplace_back(std::move(element)); + } + } +} + void Placefile::Impl::ParseLocation(const std::string& latitudeToken, const std::string& longitudeToken, double& latitude, From 4fcfb548900635aacb7279219ff28fe64fbb5116 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 7 Aug 2023 22:57:26 -0500 Subject: [PATCH 052/199] Textures may change between loads, don't skip reloading --- scwx-qt/source/scwx/qt/util/texture_atlas.cpp | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp index 65de8c16..274ea8fb 100644 --- a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp +++ b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp @@ -78,16 +78,6 @@ void TextureAtlas::RegisterTexture(const std::string& name, bool TextureAtlas::CacheTexture(const std::string& name, const std::string& path) { - // If the image is already loaded, we don't need to load it again - { - std::shared_lock lock(p->textureCacheMutex_); - - if (p->textureCache_.contains(path)) - { - return false; - } - } - // Attempt to load the image boost::gil::rgba8_image_t image = TextureAtlas::Impl::LoadImage(path); @@ -97,10 +87,12 @@ bool TextureAtlas::CacheTexture(const std::string& name, // Store it in the texture cache std::unique_lock lock(p->textureCacheMutex_); - p->textureCache_.emplace(name, std::move(image)); + p->textureCache_.insert_or_assign(name, std::move(image)); + + return true; } - return true; + return false; } void TextureAtlas::BuildAtlas(size_t width, size_t height) From d9a53ea8d7baa5109b3fe3f352ebce81d5a6d2ee Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 7 Aug 2023 23:52:29 -0500 Subject: [PATCH 053/199] Add placefile parsing for Triangles and Polygon --- wxdata/include/scwx/gr/placefile.hpp | 39 ++++++++ wxdata/source/scwx/gr/placefile.cpp | 135 ++++++++++++++++++++++++++- 2 files changed, 172 insertions(+), 2 deletions(-) diff --git a/wxdata/include/scwx/gr/placefile.hpp b/wxdata/include/scwx/gr/placefile.hpp index 816471dd..0da773a5 100644 --- a/wxdata/include/scwx/gr/placefile.hpp +++ b/wxdata/include/scwx/gr/placefile.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -120,6 +121,44 @@ public: std::vector elements_ {}; }; + struct TrianglesDrawItem : DrawItem + { + TrianglesDrawItem() { itemType_ = ItemType::Triangles; } + + boost::gil::rgba8_pixel_t color_ {}; + + struct Element + { + double latitude_ {}; + double longitude_ {}; + double x_ {}; + double y_ {}; + + std::optional color_ {}; + }; + + std::vector elements_ {}; + }; + + struct PolygonDrawItem : DrawItem + { + PolygonDrawItem() { itemType_ = ItemType::Polygon; } + + boost::gil::rgba8_pixel_t color_ {}; + + struct Element + { + double latitude_ {}; + double longitude_ {}; + double x_ {}; + double y_ {}; + + std::optional color_ {}; + }; + + std::vector> contours_ {}; + }; + bool IsValid() const; /** diff --git a/wxdata/source/scwx/gr/placefile.cpp b/wxdata/source/scwx/gr/placefile.cpp index 8bd0277b..fa98a6a5 100644 --- a/wxdata/source/scwx/gr/placefile.cpp +++ b/wxdata/source/scwx/gr/placefile.cpp @@ -47,6 +47,7 @@ public: double& x, double& y); void ProcessElement(const std::string& line); + void ProcessElementEnd(); void ProcessLine(const std::string& line); static void ProcessEscapeCharacters(std::string& s); @@ -64,6 +65,7 @@ public: std::vector objectStack_ {}; DrawingStatement currentStatement_ {DrawingStatement::Standard}; std::shared_ptr currentDrawItem_ {nullptr}; + std::vector currentPolygonContour_ {}; // References std::unordered_map> iconFiles_ {}; @@ -180,6 +182,8 @@ std::shared_ptr Placefile::Load(const std::string& name, case DrawingStatement::Polygon: if (boost::istarts_with(line, "End:")) { + placefile->p->ProcessElementEnd(); + placefile->p->currentStatement_ = DrawingStatement::Standard; placefile->p->currentDrawItem_ = nullptr; } @@ -551,7 +555,14 @@ void Placefile::Impl::ProcessLine(const std::string& line) // End: currentStatement_ = DrawingStatement::Triangles; - // TODO + std::shared_ptr di = + std::make_shared(); + + di->threshold_ = threshold_; + di->color_ = color_; + + currentDrawItem_ = di; + drawItems_.emplace_back(std::move(di)); } else if (boost::istarts_with(line, imageKey_)) { @@ -577,7 +588,13 @@ void Placefile::Impl::ProcessLine(const std::string& line) // End: currentStatement_ = DrawingStatement::Polygon; - // TODO + std::shared_ptr di = std::make_shared(); + + di->threshold_ = threshold_; + di->color_ = color_; + + currentDrawItem_ = di; + drawItems_.emplace_back(std::move(di)); } else { @@ -609,6 +626,120 @@ void Placefile::Impl::ProcessElement(const std::string& line) std::static_pointer_cast(currentDrawItem_) ->elements_.emplace_back(std::move(element)); } + else + { + logger_->warn("Line sub-statement malformed: {}", line); + } + } + else if (currentStatement_ == DrawingStatement::Triangles) + { + // Triangles: + // lat, lon [, r, g, b [,a]] + // ... + // End: + std::vector tokenList = + util::ParseTokens(line, {",", ",", ",", ",", ",", ","}); + + TrianglesDrawItem::Element element; + + if (tokenList.size() >= 5) + { + element.color_ = ParseColor(tokenList, 2, colorMode_); + } + + if (tokenList.size() >= 2) + { + ParseLocation(tokenList[0], + tokenList[1], + element.latitude_, + element.longitude_, + element.x_, + element.y_); + + std::static_pointer_cast(currentDrawItem_) + ->elements_.emplace_back(std::move(element)); + } + else + { + logger_->warn("Triangles sub-statement malformed: {}", line); + } + } + else if (currentStatement_ == DrawingStatement::Polygon) + { + // Polygon: + // lat1, lon1 [, r, g, b [,a]] ; start of the first contour + // ... + // lat1, lon1 ; repeating the first point closes the + // ; contour + // + // lat2, lon2 ; next point starts a new contour + // ... + // lat2, lon2 ; and repeating it ends the contour + // End: + std::vector tokenList = + util::ParseTokens(line, {",", ",", ",", ",", ",", ","}); + + PolygonDrawItem::Element element; + + if (tokenList.size() >= 5) + { + element.color_ = ParseColor(tokenList, 2, colorMode_); + } + + if (tokenList.size() >= 2) + { + ParseLocation(tokenList[0], + tokenList[1], + element.latitude_, + element.longitude_, + element.x_, + element.y_); + + currentPolygonContour_.emplace_back(std::move(element)); + + if (currentPolygonContour_.size() >= 2) + { + auto& first = currentPolygonContour_.front(); + auto& last = currentPolygonContour_.back(); + + // Repeating the first point closes the contour + if (first.latitude_ == last.latitude_ && + first.longitude_ == last.longitude_ && // + first.x_ == last.x_ && // + first.y_ == last.y_) + { + auto& contours = + std::static_pointer_cast(currentDrawItem_) + ->contours_; + + auto& newContour = contours.emplace_back( + std::vector {}); + newContour.swap(currentPolygonContour_); + } + } + } + else + { + logger_->warn("Polygon sub-statement malformed: {}", line); + } + } +} + +void Placefile::Impl::ProcessElementEnd() +{ + if (currentStatement_ == DrawingStatement::Polygon) + { + // Complete the current contour when ending the Polygon statement + if (!currentPolygonContour_.empty()) + { + auto& contours = + std::static_pointer_cast(currentDrawItem_) + ->contours_; + + auto& newContour = + contours.emplace_back(std::vector {}); + newContour.swap(currentPolygonContour_); + } } } From cdef5a9938cb9abc8a2aa6ee456bb70df56f00d9 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 11 Aug 2023 00:45:42 -0500 Subject: [PATCH 054/199] Placefile polygon rendering --- scwx-qt/gl/map_color.vert | 23 + scwx-qt/scwx-qt.cmake | 3 + scwx-qt/scwx-qt.qrc | 1 + .../scwx/qt/gl/draw/placefile_polygons.cpp | 393 ++++++++++++++++++ .../scwx/qt/gl/draw/placefile_polygons.hpp | 60 +++ .../source/scwx/qt/map/placefile_layer.cpp | 50 ++- wxdata/include/scwx/gr/placefile.hpp | 3 + wxdata/source/scwx/gr/placefile.cpp | 19 +- 8 files changed, 547 insertions(+), 5 deletions(-) create mode 100644 scwx-qt/gl/map_color.vert create mode 100644 scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp create mode 100644 scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.hpp diff --git a/scwx-qt/gl/map_color.vert b/scwx-qt/gl/map_color.vert new file mode 100644 index 00000000..e2f96d5a --- /dev/null +++ b/scwx-qt/gl/map_color.vert @@ -0,0 +1,23 @@ +#version 330 core + +layout (location = 0) in vec2 aScreenCoord; +layout (location = 1) in vec2 aXYOffset; +layout (location = 2) in vec4 aColor; + +uniform mat4 uMVPMatrix; +uniform mat4 uMapMatrix; +uniform vec2 uMapScreenCoord; + +out vec4 color; + +void main() +{ + // Pass the color to the fragment shader + color = aColor; + + vec2 p = aScreenCoord - uMapScreenCoord; + + // Transform the position to screen coordinates + gl_Position = uMapMatrix * vec4(p, 0.0f, 1.0f) + + uMVPMatrix * vec4(aXYOffset, 0.0f, 0.0f); +} diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index a024e12e..7af57f18 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -58,10 +58,12 @@ set(SRC_GL source/scwx/qt/gl/gl_context.cpp set(HDR_GL_DRAW source/scwx/qt/gl/draw/draw_item.hpp source/scwx/qt/gl/draw/geo_line.hpp source/scwx/qt/gl/draw/placefile_icons.hpp + source/scwx/qt/gl/draw/placefile_polygons.hpp source/scwx/qt/gl/draw/rectangle.hpp) set(SRC_GL_DRAW source/scwx/qt/gl/draw/draw_item.cpp source/scwx/qt/gl/draw/geo_line.cpp source/scwx/qt/gl/draw/placefile_icons.cpp + source/scwx/qt/gl/draw/placefile_polygons.cpp source/scwx/qt/gl/draw/rectangle.cpp) set(HDR_MANAGER source/scwx/qt/manager/placefile_manager.hpp source/scwx/qt/manager/radar_product_manager.hpp @@ -243,6 +245,7 @@ set(SHADER_FILES gl/color.frag gl/color.vert gl/geo_line.vert gl/geo_texture2d.vert + gl/map_color.vert gl/radar.frag gl/radar.vert gl/text.frag diff --git a/scwx-qt/scwx-qt.qrc b/scwx-qt/scwx-qt.qrc index fa10ef6f..1f9e1123 100644 --- a/scwx-qt/scwx-qt.qrc +++ b/scwx-qt/scwx-qt.qrc @@ -4,6 +4,7 @@ gl/color.vert gl/geo_line.vert gl/geo_texture2d.vert + gl/map_color.vert gl/radar.frag gl/radar.vert gl/text.frag diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp new file mode 100644 index 00000000..a2337d33 --- /dev/null +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp @@ -0,0 +1,393 @@ +#include +#include +#include + +#include + +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace gl +{ +namespace draw +{ + +static const std::string logPrefix_ = "scwx::qt::gl::draw::placefile_polygons"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +static constexpr std::size_t kVerticesPerTriangle = 3; +static constexpr std::size_t kPointsPerVertex = 8; + +static constexpr std::size_t kTessVertexScreenX_ = 0; +static constexpr std::size_t kTessVertexScreenY_ = 1; +static constexpr std::size_t kTessVertexScreenZ_ = 2; +static constexpr std::size_t kTessVertexXOffset_ = 3; +static constexpr std::size_t kTessVertexYOffset_ = 4; +static constexpr std::size_t kTessVertexR_ = 5; +static constexpr std::size_t kTessVertexG_ = 6; +static constexpr std::size_t kTessVertexB_ = 7; +static constexpr std::size_t kTessVertexA_ = 8; +static constexpr std::size_t kTessVertexSize_ = kTessVertexA_ + 1; + +typedef std::array TessVertexArray; + +class PlacefilePolygons::Impl +{ +public: + explicit Impl(std::shared_ptr context) : + context_ {context}, + shaderProgram_ {nullptr}, + uMVPMatrixLocation_(GL_INVALID_INDEX), + uMapMatrixLocation_(GL_INVALID_INDEX), + uMapScreenCoordLocation_(GL_INVALID_INDEX), + vao_ {GL_INVALID_INDEX}, + vbo_ {GL_INVALID_INDEX}, + numVertices_ {0} + { + tessellator_ = gluNewTess(); + + gluTessCallback(tessellator_, // + GLU_TESS_COMBINE_DATA, + (GLvoid(*)()) & TessellateCombineCallback); + gluTessCallback(tessellator_, // + GLU_TESS_VERTEX_DATA, + (GLvoid(*)()) & TessellateVertexCallback); + + // Force GLU_TRIANGLES + gluTessCallback(tessellator_, // + GLU_TESS_EDGE_FLAG, + []() {}); + + gluTessCallback(tessellator_, // + GLU_TESS_ERROR, + (GLvoid(*)()) & TessellateErrorCallback); + } + + ~Impl() { gluDeleteTess(tessellator_); } + + void Update(); + + void Tessellate(const std::shared_ptr& di); + + static void TessellateCombineCallback(GLdouble coords[3], + void* vertexData[4], + GLfloat weight[4], + void** outData, + void* polygonData); + static void TessellateVertexCallback(void* vertexData, void* polygonData); + static void TessellateErrorCallback(GLenum errorCode); + + std::shared_ptr context_; + + bool dirty_ {false}; + + std::vector> + polygonList_ {}; + + boost::container::stable_vector tessCombineBuffer_ {}; + + std::mutex bufferMutex_ {}; + std::vector currentBuffer_ {}; + std::vector newBuffer_ {}; + + GLUtesselator* tessellator_; + + std::shared_ptr shaderProgram_; + GLint uMVPMatrixLocation_; + GLint uMapMatrixLocation_; + GLint uMapScreenCoordLocation_; + + GLuint vao_; + GLuint vbo_; + + GLsizei numVertices_; + + boost::gil::rgba8_pixel_t currentColor_ {255, 255, 255, 255}; +}; + +PlacefilePolygons::PlacefilePolygons(std::shared_ptr context) : + DrawItem(context->gl()), p(std::make_unique(context)) +{ +} +PlacefilePolygons::~PlacefilePolygons() = default; + +PlacefilePolygons::PlacefilePolygons(PlacefilePolygons&&) noexcept = default; +PlacefilePolygons& +PlacefilePolygons::operator=(PlacefilePolygons&&) noexcept = default; + +void PlacefilePolygons::Initialize() +{ + gl::OpenGLFunctions& gl = p->context_->gl(); + + p->shaderProgram_ = + p->context_->GetShaderProgram(":/gl/map_color.vert", ":/gl/color.frag"); + + p->uMVPMatrixLocation_ = + gl.glGetUniformLocation(p->shaderProgram_->id(), "uMVPMatrix"); + if (p->uMVPMatrixLocation_ == -1) + { + logger_->warn("Could not find uMVPMatrix"); + } + + p->uMapMatrixLocation_ = + gl.glGetUniformLocation(p->shaderProgram_->id(), "uMapMatrix"); + if (p->uMapMatrixLocation_ == -1) + { + logger_->warn("Could not find uMapMatrix"); + } + + p->uMapScreenCoordLocation_ = + gl.glGetUniformLocation(p->shaderProgram_->id(), "uMapScreenCoord"); + if (p->uMapScreenCoordLocation_ == -1) + { + logger_->warn("Could not find uMapScreenCoord"); + } + + gl.glGenVertexArrays(1, &p->vao_); + gl.glGenBuffers(1, &p->vbo_); + + gl.glBindVertexArray(p->vao_); + gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_); + gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + + // aScreenCoord + gl.glVertexAttribPointer(0, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + static_cast(0)); + gl.glEnableVertexAttribArray(0); + + // aXYOffset + gl.glVertexAttribPointer(1, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(2 * sizeof(float))); + gl.glEnableVertexAttribArray(1); + + // aColor + gl.glVertexAttribPointer(2, + 4, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(4 * sizeof(float))); + gl.glEnableVertexAttribArray(2); + + p->dirty_ = true; +} + +void PlacefilePolygons::Render( + const QMapLibreGL::CustomLayerRenderParameters& params) +{ + if (!p->polygonList_.empty()) + { + gl::OpenGLFunctions& gl = p->context_->gl(); + + gl.glBindVertexArray(p->vao_); + gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_); + + p->Update(); + p->shaderProgram_->Use(); + UseRotationProjection(params, p->uMVPMatrixLocation_); + UseMapProjection( + params, p->uMapMatrixLocation_, p->uMapScreenCoordLocation_); + + // Draw icons + gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); + } +} + +void PlacefilePolygons::Deinitialize() +{ + gl::OpenGLFunctions& gl = p->context_->gl(); + + gl.glDeleteVertexArrays(1, &p->vao_); + gl.glDeleteBuffers(1, &p->vbo_); +} + +void PlacefilePolygons::StartPolygons() +{ + // Clear the new buffer + p->newBuffer_.clear(); + + // Clear the polygon list + p->polygonList_.clear(); +} + +void PlacefilePolygons::AddPolygon( + const std::shared_ptr& di) +{ + if (di != nullptr) + { + p->polygonList_.emplace_back(di); + p->Tessellate(di); + } +} + +void PlacefilePolygons::FinishPolygons() +{ + std::unique_lock lock {p->bufferMutex_}; + + // Swap buffers + p->currentBuffer_.swap(p->newBuffer_); + + // Mark the draw item dirty + p->dirty_ = true; +} + +void PlacefilePolygons::Impl::Update() +{ + if (dirty_) + { + gl::OpenGLFunctions& gl = context_->gl(); + + std::unique_lock lock {bufferMutex_}; + + gl.glBufferData(GL_ARRAY_BUFFER, + sizeof(GLfloat) * currentBuffer_.size(), + currentBuffer_.data(), + GL_DYNAMIC_DRAW); + + numVertices_ = + static_cast(currentBuffer_.size() / kPointsPerVertex); + + dirty_ = false; + } +} + +void PlacefilePolygons::Impl::Tessellate( + const std::shared_ptr& di) +{ + // Vertex storage + boost::container::stable_vector vertices {}; + + // Default color to "Color" statement + boost::gil::rgba8_pixel_t lastColor = di->color_; + + gluTessBeginPolygon(tessellator_, this); + + for (auto& contour : di->contours_) + { + gluTessBeginContour(tessellator_); + + for (auto& element : contour) + { + // Calculate screen coordinate + auto screenCoordinate = util::maplibre::LatLongToScreenCoordinate( + {element.latitude_, element.longitude_}); + + // Update the most recent color if specified + if (element.color_.has_value()) + { + lastColor = element.color_.value(); + } + + // Add vertex to temporary storage + auto& vertex = + vertices.emplace_back(TessVertexArray {screenCoordinate.x, + screenCoordinate.y, + 0.0, // z + element.x_, + element.y_, + lastColor[0] / 255.0, + lastColor[1] / 255.0, + lastColor[2] / 255.0, + lastColor[3] / 255.0}); + + // Tessellate vertex + gluTessVertex(tessellator_, vertex.data(), vertex.data()); + } + + gluTessEndContour(tessellator_); + } + + gluTessEndPolygon(tessellator_); + + // Clear temporary storage + tessCombineBuffer_.clear(); + + // Remove extra vertices that don't correspond to a full triangle + while (newBuffer_.size() % kVerticesPerTriangle != 0) + { + newBuffer_.pop_back(); + } +} + +void PlacefilePolygons::Impl::TessellateCombineCallback(GLdouble coords[3], + void* vertexData[4], + GLfloat w[4], + void** outData, + void* polygonData) +{ + static constexpr std::size_t r = kTessVertexR_; + static constexpr std::size_t g = kTessVertexG_; + static constexpr std::size_t b = kTessVertexB_; + static constexpr std::size_t a = kTessVertexA_; + + Impl* self = static_cast(polygonData); + + // Create new vertex data with given coordinates and interpolated color + auto& newVertexData = self->tessCombineBuffer_.emplace_back( // + TessVertexArray { + coords[0], + coords[1], + coords[2], + 0.0, // offsetX + 0.0, // offsetY + 0.0, // r + 0.0, // g + 0.0, // b + 0.0 // a + }); + + for (std::size_t i = 0; i < 4; ++i) + { + GLdouble* d = static_cast(vertexData[i]); + if (d != nullptr) + { + for (std::size_t color = r; color <= a; ++color) + { + newVertexData[color] += w[i] * d[color]; + } + } + } + + // Return new vertex data + *outData = &newVertexData; +} + +void PlacefilePolygons::Impl::TessellateVertexCallback(void* vertexData, + void* polygonData) +{ + Impl* self = static_cast(polygonData); + GLdouble* data = static_cast(vertexData); + + // Buffer vertex + self->newBuffer_.insert(self->newBuffer_.end(), + {static_cast(data[kTessVertexScreenX_]), + static_cast(data[kTessVertexScreenY_]), + static_cast(data[kTessVertexXOffset_]), + static_cast(data[kTessVertexYOffset_]), + static_cast(data[kTessVertexR_]), + static_cast(data[kTessVertexG_]), + static_cast(data[kTessVertexB_]), + static_cast(data[kTessVertexA_])}); +} + +void PlacefilePolygons::Impl::TessellateErrorCallback(GLenum errorCode) +{ + logger_->error("GL Error: {}", errorCode); +} + +} // namespace draw +} // namespace gl +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.hpp new file mode 100644 index 00000000..f4a44ecd --- /dev/null +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace gl +{ +namespace draw +{ + +class PlacefilePolygons : public DrawItem +{ +public: + explicit PlacefilePolygons(std::shared_ptr context); + ~PlacefilePolygons(); + + PlacefilePolygons(const PlacefilePolygons&) = delete; + PlacefilePolygons& operator=(const PlacefilePolygons&) = delete; + + PlacefilePolygons(PlacefilePolygons&&) noexcept; + PlacefilePolygons& operator=(PlacefilePolygons&&) noexcept; + + void Initialize() override; + void Render(const QMapLibreGL::CustomLayerRenderParameters& params) override; + void Deinitialize() override; + + /** + * Resets and prepares the draw item for adding a new set of polygons. + */ + void StartPolygons(); + + /** + * Adds a placefile polygon to the internal draw list. + * + * @param [in] di Placefile polygon + */ + void AddPolygon(const std::shared_ptr& di); + + /** + * Finalizes the draw item after adding new polygons. + */ + void FinishPolygons(); + +private: + class Impl; + + std::unique_ptr p; +}; + +} // namespace draw +} // namespace gl +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp index 2117932b..c98338f7 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -30,7 +31,9 @@ public: const std::string& placefileName) : self_ {self}, placefileName_ {placefileName}, - placefileIcons_ {std::make_shared(context)} + placefileIcons_ {std::make_shared(context)}, + placefilePolygons_ { + std::make_shared(context)} { ConnectSignals(); } @@ -41,6 +44,9 @@ public: void RenderIconDrawItem(const QMapLibreGL::CustomLayerRenderParameters& params, const std::shared_ptr& di); + void RenderPolygonDrawItem( + const QMapLibreGL::CustomLayerRenderParameters& params, + const std::shared_ptr& di); void RenderTextDrawItem(const QMapLibreGL::CustomLayerRenderParameters& params, const std::shared_ptr& di); @@ -67,7 +73,8 @@ public: bool thresholded_ {true}; ImFont* monospaceFont_ {}; - std::shared_ptr placefileIcons_; + std::shared_ptr placefileIcons_; + std::shared_ptr placefilePolygons_; }; PlacefileLayer::PlacefileLayer(std::shared_ptr context, @@ -76,6 +83,7 @@ PlacefileLayer::PlacefileLayer(std::shared_ptr context, p(std::make_unique(this, context, placefileName)) { AddDrawItem(p->placefileIcons_); + AddDrawItem(p->placefilePolygons_); } PlacefileLayer::~PlacefileLayer() = default; @@ -135,6 +143,28 @@ void PlacefileLayer::Impl::RenderIconDrawItem( } } +void PlacefileLayer::Impl::RenderPolygonDrawItem( + const QMapLibreGL::CustomLayerRenderParameters& params, + const std::shared_ptr& di) +{ + if (!dirty_) + { + return; + } + + auto distance = (thresholded_) ? + util::GeographicLib::GetDistance(params.latitude, + params.longitude, + di->center_.latitude_, + di->center_.longitude_) : + 0; + + if (distance < di->threshold_) + { + placefilePolygons_->AddPolygon(di); + } +} + void PlacefileLayer::Impl::RenderTextDrawItem( const QMapLibreGL::CustomLayerRenderParameters& params, const std::shared_ptr& di) @@ -269,6 +299,9 @@ void PlacefileLayer::Render( p->placefileIcons_->Reset(); p->placefileIcons_->SetIconFiles(placefile->icon_files(), placefile->name()); + + // Reset Placefile Polygons + p->placefilePolygons_->StartPolygons(); } for (auto& drawItem : placefile->GetDrawItems()) @@ -287,10 +320,23 @@ void PlacefileLayer::Render( std::static_pointer_cast(drawItem)); break; + case gr::Placefile::ItemType::Polygon: + p->RenderPolygonDrawItem( + params, + std::static_pointer_cast( + drawItem)); + break; + default: break; } } + + if (p->dirty_) + { + // Finish Placefile Polygons + p->placefilePolygons_->FinishPolygons(); + } } DrawLayer::Render(params); diff --git a/wxdata/include/scwx/gr/placefile.hpp b/wxdata/include/scwx/gr/placefile.hpp index 0da773a5..8330aa4f 100644 --- a/wxdata/include/scwx/gr/placefile.hpp +++ b/wxdata/include/scwx/gr/placefile.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include @@ -157,6 +159,7 @@ public: }; std::vector> contours_ {}; + scwx::common::Coordinate center_ {}; }; bool IsValid() const; diff --git a/wxdata/source/scwx/gr/placefile.cpp b/wxdata/source/scwx/gr/placefile.cpp index fa98a6a5..1725dd64 100644 --- a/wxdata/source/scwx/gr/placefile.cpp +++ b/wxdata/source/scwx/gr/placefile.cpp @@ -729,17 +729,30 @@ void Placefile::Impl::ProcessElementEnd() { if (currentStatement_ == DrawingStatement::Polygon) { + auto di = std::static_pointer_cast(currentDrawItem_); + // Complete the current contour when ending the Polygon statement if (!currentPolygonContour_.empty()) { - auto& contours = - std::static_pointer_cast(currentDrawItem_) - ->contours_; + auto& contours = di->contours_; auto& newContour = contours.emplace_back(std::vector {}); newContour.swap(currentPolygonContour_); } + + if (!di->contours_.empty()) + { + std::vector coordinates {}; + std::transform(di->contours_[0].cbegin(), + di->contours_[0].cend(), + std::back_inserter(coordinates), + [](auto& element) { + return common::Coordinate {element.latitude_, + element.longitude_}; + }); + di->center_ = GetCentroid(coordinates); + } } } From 0c5a504ad521c95dd9bea42ed239e04b6c21dcbd Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 11 Aug 2023 08:36:08 -0500 Subject: [PATCH 055/199] Placefile polygon Linux fixes --- .../source/scwx/qt/gl/draw/placefile_polygons.cpp | 12 +++++++----- scwx-qt/source/scwx/qt/map/placefile_layer.cpp | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp index a2337d33..216b4a9b 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp @@ -7,6 +7,10 @@ #include #include +#if defined(_WIN32) +typedef void (*_GLUfuncptr)(void); +#endif + namespace scwx { namespace qt @@ -52,10 +56,10 @@ public: gluTessCallback(tessellator_, // GLU_TESS_COMBINE_DATA, - (GLvoid(*)()) & TessellateCombineCallback); + (_GLUfuncptr) &TessellateCombineCallback); gluTessCallback(tessellator_, // GLU_TESS_VERTEX_DATA, - (GLvoid(*)()) & TessellateVertexCallback); + (_GLUfuncptr) &TessellateVertexCallback); // Force GLU_TRIANGLES gluTessCallback(tessellator_, // @@ -64,7 +68,7 @@ public: gluTessCallback(tessellator_, // GLU_TESS_ERROR, - (GLvoid(*)()) & TessellateErrorCallback); + (_GLUfuncptr) &TessellateErrorCallback); } ~Impl() { gluDeleteTess(tessellator_); } @@ -328,8 +332,6 @@ void PlacefilePolygons::Impl::TessellateCombineCallback(GLdouble coords[3], void* polygonData) { static constexpr std::size_t r = kTessVertexR_; - static constexpr std::size_t g = kTessVertexG_; - static constexpr std::size_t b = kTessVertexB_; static constexpr std::size_t a = kTessVertexA_; Impl* self = static_cast(polygonData); diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp index c98338f7..a7e258e6 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp @@ -261,8 +261,8 @@ void PlacefileLayer::Render( {params.latitude, params.longitude}); p->mapScale_ = std::pow(2.0, params.zoom) * mbgl::util::tileSize_D / mbgl::util::DEGREES_MAX; - p->mapBearingCos_ = std::cosf(params.bearing * common::kDegreesToRadians); - p->mapBearingSin_ = std::sinf(params.bearing * common::kDegreesToRadians); + p->mapBearingCos_ = cosf(params.bearing * common::kDegreesToRadians); + p->mapBearingSin_ = sinf(params.bearing * common::kDegreesToRadians); p->halfWidth_ = params.width * 0.5f; p->halfHeight_ = params.height * 0.5f; From 913151e063e9c4946bad1a8a5ae50e9c416d9b60 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 12 Aug 2023 00:35:20 -0500 Subject: [PATCH 056/199] Add support for loading geometry shaders (and other shaders) --- scwx-qt/source/scwx/qt/gl/shader_program.cpp | 134 ++++++++++--------- scwx-qt/source/scwx/qt/gl/shader_program.hpp | 1 + 2 files changed, 70 insertions(+), 65 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/shader_program.cpp b/scwx-qt/source/scwx/qt/gl/shader_program.cpp index 1d9e2143..f27c4a1b 100644 --- a/scwx-qt/source/scwx/qt/gl/shader_program.cpp +++ b/scwx-qt/source/scwx/qt/gl/shader_program.cpp @@ -15,6 +15,11 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_); static constexpr GLsizei kInfoLogBufSize = 512; +static const std::unordered_map kShaderNames_ { + {GL_VERTEX_SHADER, "vertex"}, + {GL_GEOMETRY_SHADER, "geometry"}, + {GL_FRAGMENT_SHADER, "fragment"}}; + class ShaderProgram::Impl { public: @@ -30,6 +35,8 @@ public: gl_.glDeleteProgram(id_); } + static std::string ShaderName(GLenum type); + OpenGLFunctions& gl_; GLuint id_; @@ -49,10 +56,27 @@ GLuint ShaderProgram::id() const return p->id_; } +std::string ShaderProgram::Impl::ShaderName(GLenum type) +{ + auto it = kShaderNames_.find(type); + if (it != kShaderNames_.cend()) + { + return it->second; + } + return fmt::format("{:#06x}", type); +} + bool ShaderProgram::Load(const std::string& vertexPath, const std::string& fragmentPath) { - logger_->debug("Load: {}, {}", vertexPath, fragmentPath); + return Load({{GL_VERTEX_SHADER, vertexPath}, // + {GL_FRAGMENT_SHADER, fragmentPath}}); +} + +bool ShaderProgram::Load( + std::initializer_list> shaders) +{ + logger_->debug("Load()"); OpenGLFunctions& gl = p->gl_; @@ -61,81 +85,59 @@ bool ShaderProgram::Load(const std::string& vertexPath, char infoLog[kInfoLogBufSize]; GLsizei logLength; - QFile vertexFile(vertexPath.c_str()); - QFile fragmentFile(fragmentPath.c_str()); + std::vector shaderIds {}; - vertexFile.open(QIODevice::ReadOnly | QIODevice::Text); - fragmentFile.open(QIODevice::ReadOnly | QIODevice::Text); - - if (!vertexFile.isOpen()) + for (auto& shader : shaders) { - logger_->error("Could not load vertex shader: {}", vertexPath); - return false; - } + logger_->debug("Loading {} shader: {}", + Impl::ShaderName(shader.first), + shader.second); - if (!fragmentFile.isOpen()) - { - logger_->error("Could not load fragment shader: {}", fragmentPath); - return false; - } + QFile file(shader.second.c_str()); + file.open(QIODevice::ReadOnly | QIODevice::Text); - QTextStream vertexShaderStream(&vertexFile); - QTextStream fragmentShaderStream(&fragmentFile); + if (!file.isOpen()) + { + logger_->error("Could not load shader"); + success = false; + break; + } - vertexShaderStream.setEncoding(QStringConverter::Utf8); - fragmentShaderStream.setEncoding(QStringConverter::Utf8); + QTextStream shaderStream(&file); + shaderStream.setEncoding(QStringConverter::Utf8); - std::string vertexShaderSource = vertexShaderStream.readAll().toStdString(); - std::string fragmentShaderSource = - fragmentShaderStream.readAll().toStdString(); + std::string shaderSource = shaderStream.readAll().toStdString(); + const char* shaderSourceC = shaderSource.c_str(); - const char* vertexShaderSourceC = vertexShaderSource.c_str(); - const char* fragmentShaderSourceC = fragmentShaderSource.c_str(); + // Create a vertex shader + GLuint shaderId = gl.glCreateShader(shader.first); + shaderIds.push_back(shaderId); - // Create a vertex shader - GLuint vertexShader = gl.glCreateShader(GL_VERTEX_SHADER); + // Attach the shader source code and compile the shader + gl.glShaderSource(shaderId, 1, &shaderSourceC, NULL); + gl.glCompileShader(shaderId); - // Attach the shader source code and compile the shader - gl.glShaderSource(vertexShader, 1, &vertexShaderSourceC, NULL); - gl.glCompileShader(vertexShader); - - // Check for errors - gl.glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &glSuccess); - gl.glGetShaderInfoLog(vertexShader, kInfoLogBufSize, &logLength, infoLog); - if (!glSuccess) - { - logger_->error("Vertex shader compilation failed: {}", infoLog); - success = false; - } - else if (logLength > 0) - { - logger_->error("Vertex shader compiled with warnings: {}", infoLog); - } - - // Create a fragment shader - GLuint fragmentShader = gl.glCreateShader(GL_FRAGMENT_SHADER); - - // Attach the shader source and compile the shader - gl.glShaderSource(fragmentShader, 1, &fragmentShaderSourceC, NULL); - gl.glCompileShader(fragmentShader); - - // Check for errors - gl.glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &glSuccess); - gl.glGetShaderInfoLog(fragmentShader, kInfoLogBufSize, &logLength, infoLog); - if (!glSuccess) - { - logger_->error("Fragment shader compilation failed: {}", infoLog); - success = false; - } - else if (logLength > 0) - { - logger_->error("Fragment shader compiled with warnings: {}", infoLog); + // Check for errors + gl.glGetShaderiv(shaderId, GL_COMPILE_STATUS, &glSuccess); + gl.glGetShaderInfoLog(shaderId, kInfoLogBufSize, &logLength, infoLog); + if (!glSuccess) + { + logger_->error("Shader compilation failed: {}", infoLog); + success = false; + break; + } + else if (logLength > 0) + { + logger_->error("Shader compiled with warnings: {}", infoLog); + } } if (success) { - gl.glAttachShader(p->id_, vertexShader); - gl.glAttachShader(p->id_, fragmentShader); + for (auto& shaderId : shaderIds) + { + gl.glAttachShader(p->id_, shaderId); + } gl.glLinkProgram(p->id_); // Check for errors @@ -153,8 +155,10 @@ bool ShaderProgram::Load(const std::string& vertexPath, } // Delete shaders - gl.glDeleteShader(vertexShader); - gl.glDeleteShader(fragmentShader); + for (auto& shaderId : shaderIds) + { + gl.glDeleteShader(shaderId); + } return success; } diff --git a/scwx-qt/source/scwx/qt/gl/shader_program.hpp b/scwx-qt/source/scwx/qt/gl/shader_program.hpp index 9f84053e..dcc36b3e 100644 --- a/scwx-qt/source/scwx/qt/gl/shader_program.hpp +++ b/scwx-qt/source/scwx/qt/gl/shader_program.hpp @@ -31,6 +31,7 @@ public: GLuint id() const; bool Load(const std::string& vertexPath, const std::string& fragmentPath); + bool Load(std::initializer_list> shaderPaths); void Use() const; From 2113cb9ba8688f2ae2e5c7188c427eb624ea6075 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 16 Aug 2023 00:13:09 -0500 Subject: [PATCH 057/199] Additional support for loading geometry shaders (and other shaders) --- scwx-qt/source/scwx/qt/gl/gl_context.cpp | 35 ++++++++++++++++---- scwx-qt/source/scwx/qt/gl/gl_context.hpp | 2 ++ scwx-qt/source/scwx/qt/gl/shader_program.cpp | 2 +- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/gl_context.cpp b/scwx-qt/source/scwx/qt/gl/gl_context.cpp index e09ed177..2b996c54 100644 --- a/scwx-qt/source/scwx/qt/gl/gl_context.cpp +++ b/scwx-qt/source/scwx/qt/gl/gl_context.cpp @@ -1,8 +1,9 @@ #include #include -#include #include +#include + namespace scwx { namespace qt @@ -25,11 +26,12 @@ public: } ~Impl() {} + static std::size_t + GetShaderKey(std::initializer_list> shaders); + gl::OpenGLFunctions gl_; - std::unordered_map, - std::shared_ptr, - scwx::util::hash>> + std::unordered_map> shaderProgramMap_; std::mutex shaderProgramMutex_; @@ -54,8 +56,15 @@ std::shared_ptr GlContext::GetShaderProgram(const std::string& vertexPath, const std::string& fragmentPath) { - const std::pair key {vertexPath, fragmentPath}; - std::shared_ptr shaderProgram; + return GetShaderProgram( + {{GL_VERTEX_SHADER, vertexPath}, {GL_FRAGMENT_SHADER, fragmentPath}}); +} + +std::shared_ptr GlContext::GetShaderProgram( + std::initializer_list> shaders) +{ + const auto key = Impl::GetShaderKey(shaders); + std::shared_ptr shaderProgram; std::unique_lock lock(p->shaderProgramMutex_); @@ -64,7 +73,7 @@ GlContext::GetShaderProgram(const std::string& vertexPath, if (it == p->shaderProgramMap_.end()) { shaderProgram = std::make_shared(p->gl_); - shaderProgram->Load(vertexPath, fragmentPath); + shaderProgram->Load(shaders); p->shaderProgramMap_[key] = shaderProgram; } else @@ -91,6 +100,18 @@ GLuint GlContext::GetTextureAtlas() return p->textureAtlas_; } +std::size_t GlContext::Impl::GetShaderKey( + std::initializer_list> shaders) +{ + std::size_t seed = 0; + for (auto& shader : shaders) + { + boost::hash_combine(seed, shader.first); + boost::hash_combine(seed, shader.second); + } + return seed; +} + } // namespace gl } // namespace qt } // namespace scwx diff --git a/scwx-qt/source/scwx/qt/gl/gl_context.hpp b/scwx-qt/source/scwx/qt/gl/gl_context.hpp index 623b5855..5c53de59 100644 --- a/scwx-qt/source/scwx/qt/gl/gl_context.hpp +++ b/scwx-qt/source/scwx/qt/gl/gl_context.hpp @@ -27,6 +27,8 @@ public: std::shared_ptr GetShaderProgram(const std::string& vertexPath, const std::string& fragmentPath); + std::shared_ptr GetShaderProgram( + std::initializer_list> shaders); GLuint GetTextureAtlas(); diff --git a/scwx-qt/source/scwx/qt/gl/shader_program.cpp b/scwx-qt/source/scwx/qt/gl/shader_program.cpp index f27c4a1b..54db55fc 100644 --- a/scwx-qt/source/scwx/qt/gl/shader_program.cpp +++ b/scwx-qt/source/scwx/qt/gl/shader_program.cpp @@ -109,7 +109,7 @@ bool ShaderProgram::Load( std::string shaderSource = shaderStream.readAll().toStdString(); const char* shaderSourceC = shaderSource.c_str(); - // Create a vertex shader + // Create a shader GLuint shaderId = gl.glCreateShader(shader.first); shaderIds.push_back(shaderId); From 5e1b1a5ba6a5e13ad6275852d3ff9b47fb8db85c Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 16 Aug 2023 00:15:08 -0500 Subject: [PATCH 058/199] Map distance helper function --- scwx-qt/source/scwx/qt/util/maplibre.cpp | 8 ++++++++ scwx-qt/source/scwx/qt/util/maplibre.hpp | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/scwx-qt/source/scwx/qt/util/maplibre.cpp b/scwx-qt/source/scwx/qt/util/maplibre.cpp index c2170425..5b3f331e 100644 --- a/scwx-qt/source/scwx/qt/util/maplibre.cpp +++ b/scwx-qt/source/scwx/qt/util/maplibre.cpp @@ -1,5 +1,6 @@ #include +#include #include namespace scwx @@ -11,6 +12,13 @@ namespace util namespace maplibre { +boost::units::quantity +GetMapDistance(const QMapLibreGL::CustomLayerRenderParameters& params) +{ + return QMapLibreGL::metersPerPixelAtLatitude(params.latitude, params.zoom) * + (params.width + params.height) / 2.0 * boost::units::si::meters; +} + glm::vec2 LatLongToScreenCoordinate(const QMapLibreGL::Coordinate& coordinate) { static constexpr double RAD2DEG_D = 180.0 / M_PI; diff --git a/scwx-qt/source/scwx/qt/util/maplibre.hpp b/scwx-qt/source/scwx/qt/util/maplibre.hpp index 03d548de..65aa4409 100644 --- a/scwx-qt/source/scwx/qt/util/maplibre.hpp +++ b/scwx-qt/source/scwx/qt/util/maplibre.hpp @@ -1,6 +1,8 @@ #pragma once #include +#include +#include #include namespace scwx @@ -12,6 +14,8 @@ namespace util namespace maplibre { +boost::units::quantity +GetMapDistance(const QMapLibreGL::CustomLayerRenderParameters& params); glm::vec2 LatLongToScreenCoordinate(const QMapLibreGL::Coordinate& coordinate); } // namespace maplibre From 69f93d6faf8dff738600dbbcf0e0415f32978ae2 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 18 Aug 2023 00:30:27 -0500 Subject: [PATCH 059/199] Add threshold geometry shader, refactor vertex and fragment shaders --- scwx-qt/gl/color.frag | 2 +- scwx-qt/gl/color.vert | 2 +- scwx-qt/gl/geo_line.vert | 4 ++-- scwx-qt/gl/geo_texture2d.vert | 22 +++++++++++++++---- scwx-qt/gl/map_color.vert | 18 ++++++++++++--- scwx-qt/gl/texture2d.frag | 4 ++-- scwx-qt/gl/threshold.geom | 41 +++++++++++++++++++++++++++++++++++ scwx-qt/scwx-qt.cmake | 3 ++- scwx-qt/scwx-qt.qrc | 1 + 9 files changed, 83 insertions(+), 14 deletions(-) create mode 100644 scwx-qt/gl/threshold.geom diff --git a/scwx-qt/gl/color.frag b/scwx-qt/gl/color.frag index 003c6c8d..1266306b 100644 --- a/scwx-qt/gl/color.frag +++ b/scwx-qt/gl/color.frag @@ -1,5 +1,5 @@ #version 330 core -in vec4 color; +smooth in vec4 color; layout (location = 0) out vec4 fragColor; diff --git a/scwx-qt/gl/color.vert b/scwx-qt/gl/color.vert index 5c8d1ff7..f849710c 100644 --- a/scwx-qt/gl/color.vert +++ b/scwx-qt/gl/color.vert @@ -4,7 +4,7 @@ layout (location = 1) in vec4 aColor; uniform mat4 uMVPMatrix; -out vec4 color; +smooth out vec4 color; void main() { diff --git a/scwx-qt/gl/geo_line.vert b/scwx-qt/gl/geo_line.vert index ea7d6351..490fc3eb 100644 --- a/scwx-qt/gl/geo_line.vert +++ b/scwx-qt/gl/geo_line.vert @@ -16,7 +16,7 @@ uniform mat4 uMapMatrix; uniform vec2 uMapScreenCoord; smooth out vec2 texCoord; -flat out vec4 modulate; +smooth out vec4 color; vec2 latLngToScreenCoordinate(in vec2 latLng) { @@ -31,7 +31,7 @@ void main() { // Pass the texture coordinate and color modulate to the fragment shader texCoord = aTexCoord; - modulate = aModulate; + color = aModulate; vec2 p = latLngToScreenCoordinate(aLatLong) - uMapScreenCoord; diff --git a/scwx-qt/gl/geo_texture2d.vert b/scwx-qt/gl/geo_texture2d.vert index 467aa72f..05a8c5d0 100644 --- a/scwx-qt/gl/geo_texture2d.vert +++ b/scwx-qt/gl/geo_texture2d.vert @@ -12,13 +12,21 @@ layout (location = 1) in vec2 aXYOffset; layout (location = 2) in vec2 aTexCoord; layout (location = 3) in vec4 aModulate; layout (location = 4) in float aAngleDeg; +layout (location = 5) in int aThreshold; uniform mat4 uMVPMatrix; uniform mat4 uMapMatrix; uniform vec2 uMapScreenCoord; +out VertexData +{ + int threshold; + vec2 texCoord; + vec4 color; +} vsOut; + smooth out vec2 texCoord; -flat out vec4 modulate; +smooth out vec4 color; vec2 latLngToScreenCoordinate(in vec2 latLng) { @@ -31,9 +39,15 @@ vec2 latLngToScreenCoordinate(in vec2 latLng) void main() { - // Pass the texture coordinate and color modulate to the fragment shader - texCoord = aTexCoord; - modulate = aModulate; + // Pass the threshold to the geometry shader + vsOut.threshold = aThreshold; + + // Pass the texture coordinate and color modulate to the geometry and + // fragment shaders + vsOut.texCoord = aTexCoord; + vsOut.color = aModulate; + texCoord = aTexCoord; + color = aModulate; vec2 p = latLngToScreenCoordinate(aLatLong) - uMapScreenCoord; diff --git a/scwx-qt/gl/map_color.vert b/scwx-qt/gl/map_color.vert index e2f96d5a..cc19f8a4 100644 --- a/scwx-qt/gl/map_color.vert +++ b/scwx-qt/gl/map_color.vert @@ -3,17 +3,29 @@ layout (location = 0) in vec2 aScreenCoord; layout (location = 1) in vec2 aXYOffset; layout (location = 2) in vec4 aColor; +layout (location = 3) in int aThreshold; uniform mat4 uMVPMatrix; uniform mat4 uMapMatrix; uniform vec2 uMapScreenCoord; -out vec4 color; +out VertexData +{ + int threshold; + vec2 texCoord; + vec4 color; +} vsOut; + +smooth out vec4 color; void main() { - // Pass the color to the fragment shader - color = aColor; + // Pass the threshold and color to the geometry shader + vsOut.threshold = aThreshold; + + // Pass the color to the geometry and fragment shaders + vsOut.color = aColor; + color = aColor; vec2 p = aScreenCoord - uMapScreenCoord; diff --git a/scwx-qt/gl/texture2d.frag b/scwx-qt/gl/texture2d.frag index 16ab8960..725d66ad 100644 --- a/scwx-qt/gl/texture2d.frag +++ b/scwx-qt/gl/texture2d.frag @@ -6,11 +6,11 @@ precision mediump float; uniform sampler2D uTexture; smooth in vec2 texCoord; -flat in vec4 modulate; +smooth in vec4 color; layout (location = 0) out vec4 fragColor; void main() { - fragColor = texture(uTexture, texCoord) * modulate; + fragColor = texture(uTexture, texCoord) * color; } diff --git a/scwx-qt/gl/threshold.geom b/scwx-qt/gl/threshold.geom new file mode 100644 index 00000000..4d9d3b53 --- /dev/null +++ b/scwx-qt/gl/threshold.geom @@ -0,0 +1,41 @@ +#version 330 core + +layout (triangles) in; +layout (triangle_strip, max_vertices = 3) out; + +uniform float uMapDistance; + +in VertexData +{ + int threshold; + vec2 texCoord; + vec4 color; +} gsIn[]; + +smooth out vec2 texCoord; +smooth out vec4 color; + +void main() +{ + if (gsIn[0].threshold <= 0 || // If Threshold: 0 was specified, no threshold + gsIn[0].threshold >= uMapDistance || // If Threshold is above current map distance + gsIn[0].threshold >= 999) // If Threshold: 999 was specified (or greater), no threshold + { + gl_Position = gl_in[0].gl_Position; + texCoord = gsIn[0].texCoord; + color = gsIn[0].color; + EmitVertex(); + + gl_Position = gl_in[1].gl_Position; + texCoord = gsIn[1].texCoord; + color = gsIn[1].color; + + EmitVertex(); + gl_Position = gl_in[2].gl_Position; + texCoord = gsIn[2].texCoord; + color = gsIn[2].color; + + EmitVertex(); + EndPrimitive(); + } +} diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 7af57f18..f5d6a489 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -252,7 +252,8 @@ set(SHADER_FILES gl/color.frag gl/text.vert gl/texture1d.frag gl/texture1d.vert - gl/texture2d.frag) + gl/texture2d.frag + gl/threshold.geom) set(CMAKE_FILES scwx-qt.cmake) diff --git a/scwx-qt/scwx-qt.qrc b/scwx-qt/scwx-qt.qrc index 1f9e1123..05a94725 100644 --- a/scwx-qt/scwx-qt.qrc +++ b/scwx-qt/scwx-qt.qrc @@ -12,6 +12,7 @@ gl/texture1d.frag gl/texture1d.vert gl/texture2d.frag + gl/threshold.geom res/config/radar_sites.json res/fonts/din1451alt.ttf res/fonts/din1451alt_g.ttf From 12833202b7a829af3825e04fe823b6e4aa51b913 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 18 Aug 2023 00:42:55 -0500 Subject: [PATCH 060/199] Use geometry shaders for icon and polygon thresholds, fix threshold method to map distance for placefile text --- .../scwx/qt/gl/draw/placefile_icons.cpp | 99 ++++++++++++++----- .../scwx/qt/gl/draw/placefile_icons.hpp | 2 + .../scwx/qt/gl/draw/placefile_polygons.cpp | 99 +++++++++++++------ .../scwx/qt/gl/draw/placefile_polygons.hpp | 2 + scwx-qt/source/scwx/qt/gl/shader_program.cpp | 10 ++ scwx-qt/source/scwx/qt/gl/shader_program.hpp | 2 + .../source/scwx/qt/map/placefile_layer.cpp | 60 +++-------- 7 files changed, 174 insertions(+), 100 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp index ff63792b..dbbc2308 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -84,6 +85,7 @@ public: uMVPMatrixLocation_(GL_INVALID_INDEX), uMapMatrixLocation_(GL_INVALID_INDEX), uMapScreenCoordLocation_(GL_INVALID_INDEX), + uMapDistanceLocation_(GL_INVALID_INDEX), vao_ {GL_INVALID_INDEX}, vbo_ {GL_INVALID_INDEX}, numVertices_ {0} @@ -95,6 +97,7 @@ public: std::shared_ptr context_; bool dirty_ {false}; + bool thresholded_ {false}; boost::unordered_flat_map iconFiles_ {}; @@ -105,9 +108,10 @@ public: GLint uMVPMatrixLocation_; GLint uMapMatrixLocation_; GLint uMapScreenCoordLocation_; + GLint uMapDistanceLocation_; - GLuint vao_; - GLuint vbo_; + GLuint vao_; + std::array vbo_; GLsizei numVertices_; @@ -123,39 +127,32 @@ PlacefileIcons::~PlacefileIcons() = default; PlacefileIcons::PlacefileIcons(PlacefileIcons&&) noexcept = default; PlacefileIcons& PlacefileIcons::operator=(PlacefileIcons&&) noexcept = default; +void PlacefileIcons::set_thresholded(bool thresholded) +{ + p->thresholded_ = thresholded; +} + void PlacefileIcons::Initialize() { gl::OpenGLFunctions& gl = p->context_->gl(); - p->shaderProgram_ = p->context_->GetShaderProgram(":/gl/geo_texture2d.vert", - ":/gl/texture2d.frag"); - - p->uMVPMatrixLocation_ = - gl.glGetUniformLocation(p->shaderProgram_->id(), "uMVPMatrix"); - if (p->uMVPMatrixLocation_ == -1) - { - logger_->warn("Could not find uMVPMatrix"); - } - - p->uMapMatrixLocation_ = - gl.glGetUniformLocation(p->shaderProgram_->id(), "uMapMatrix"); - if (p->uMapMatrixLocation_ == -1) - { - logger_->warn("Could not find uMapMatrix"); - } + p->shaderProgram_ = p->context_->GetShaderProgram( + {{GL_VERTEX_SHADER, ":/gl/geo_texture2d.vert"}, + {GL_GEOMETRY_SHADER, ":/gl/threshold.geom"}, + {GL_FRAGMENT_SHADER, ":/gl/texture2d.frag"}}); + p->uMVPMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMVPMatrix"); + p->uMapMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMapMatrix"); p->uMapScreenCoordLocation_ = - gl.glGetUniformLocation(p->shaderProgram_->id(), "uMapScreenCoord"); - if (p->uMapScreenCoordLocation_ == -1) - { - logger_->warn("Could not find uMapScreenCoord"); - } + p->shaderProgram_->GetUniformLocation("uMapScreenCoord"); + p->uMapDistanceLocation_ = + p->shaderProgram_->GetUniformLocation("uMapDistance"); gl.glGenVertexArrays(1, &p->vao_); - gl.glGenBuffers(1, &p->vbo_); + gl.glGenBuffers(2, p->vbo_.data()); gl.glBindVertexArray(p->vao_); - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_); + gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); // aLatLong @@ -203,6 +200,17 @@ void PlacefileIcons::Initialize() reinterpret_cast(10 * sizeof(float))); gl.glEnableVertexAttribArray(4); + gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); + gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + + // aThreshold + gl.glVertexAttribIPointer(5, // + 1, + GL_INT, + 0, + static_cast(0)); + gl.glEnableVertexAttribArray(5); + p->dirty_ = true; } @@ -214,7 +222,6 @@ void PlacefileIcons::Render( gl::OpenGLFunctions& gl = p->context_->gl(); gl.glBindVertexArray(p->vao_); - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_); p->Update(); p->shaderProgram_->Use(); @@ -222,6 +229,21 @@ void PlacefileIcons::Render( UseMapProjection( params, p->uMapMatrixLocation_, p->uMapScreenCoordLocation_); + if (p->thresholded_) + { + // If thresholding is enabled, set the map distance + // TODO: nautical miles + auto mapDistance = + util::maplibre::GetMapDistance(params).value() / 1852.0f; + gl.glUniform1f(p->uMapDistanceLocation_, + static_cast(mapDistance)); + } + else + { + // If thresholding is disabled, set the map distance to 0 + gl.glUniform1f(p->uMapDistanceLocation_, 0.0f); + } + // Don't interpolate texture coordinates gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); @@ -236,7 +258,7 @@ void PlacefileIcons::Deinitialize() gl::OpenGLFunctions& gl = p->context_->gl(); gl.glDeleteVertexArrays(1, &p->vao_); - gl.glDeleteBuffers(1, &p->vbo_); + gl.glDeleteBuffers(2, p->vbo_.data()); } void PlacefileIcons::SetIconFiles( @@ -279,8 +301,11 @@ void PlacefileIcons::Impl::Update() if (dirty_) { static std::vector buffer {}; + static std::vector thresholds {}; buffer.clear(); buffer.reserve(iconList_.size() * kBufferLength); + thresholds.clear(); + thresholds.reserve(iconList_.size() * kVerticesPerRectangle); numVertices_ = 0; for (auto& di : iconList_) @@ -303,6 +328,10 @@ void PlacefileIcons::Impl::Update() continue; } + // TODO: nautical miles + GLint threshold = + static_cast(std::roundf(di->threshold_.value() / 1852.0f)); + // Latitude and longitude coordinates in degrees const float lat = static_cast(di->latitude_); const float lon = static_cast(di->longitude_); @@ -357,17 +386,33 @@ void PlacefileIcons::Impl::Update() lat, lon, rx, ty, rs, tt, mc0, mc1, mc2, mc3, a, // TR lat, lon, lx, ty, ls, tt, mc0, mc1, mc2, mc3, a // TL }); + thresholds.insert(thresholds.end(), + {threshold, // + threshold, + threshold, + threshold, + threshold, + threshold}); numVertices_ += 6; } gl::OpenGLFunctions& gl = context_->gl(); + // Buffer vertex data + gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); gl.glBufferData(GL_ARRAY_BUFFER, sizeof(float) * buffer.size(), buffer.data(), GL_DYNAMIC_DRAW); + // Buffer threshold data + gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); + gl.glBufferData(GL_ARRAY_BUFFER, + sizeof(GLint) * thresholds.size(), + thresholds.data(), + GL_DYNAMIC_DRAW); + dirty_ = false; } } diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp index 9f771d8e..9dd1b3e7 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp @@ -27,6 +27,8 @@ public: PlacefileIcons(PlacefileIcons&&) noexcept; PlacefileIcons& operator=(PlacefileIcons&&) noexcept; + void set_thresholded(bool thresholded); + void Initialize() override; void Render(const QMapLibreGL::CustomLayerRenderParameters& params) override; void Deinitialize() override; diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp index 216b4a9b..202a5c0e 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp @@ -6,6 +6,7 @@ #include #include +#include #if defined(_WIN32) typedef void (*_GLUfuncptr)(void); @@ -48,6 +49,7 @@ public: uMVPMatrixLocation_(GL_INVALID_INDEX), uMapMatrixLocation_(GL_INVALID_INDEX), uMapScreenCoordLocation_(GL_INVALID_INDEX), + uMapDistanceLocation_(GL_INVALID_INDEX), vao_ {GL_INVALID_INDEX}, vbo_ {GL_INVALID_INDEX}, numVertices_ {0} @@ -88,6 +90,7 @@ public: std::shared_ptr context_; bool dirty_ {false}; + bool thresholded_ {false}; std::vector> polygonList_ {}; @@ -96,7 +99,9 @@ public: std::mutex bufferMutex_ {}; std::vector currentBuffer_ {}; + std::vector currentThresholdBuffer_ {}; std::vector newBuffer_ {}; + std::vector newThresholdBuffer_ {}; GLUtesselator* tessellator_; @@ -104,13 +109,14 @@ public: GLint uMVPMatrixLocation_; GLint uMapMatrixLocation_; GLint uMapScreenCoordLocation_; + GLint uMapDistanceLocation_; - GLuint vao_; - GLuint vbo_; + GLuint vao_; + std::array vbo_; GLsizei numVertices_; - boost::gil::rgba8_pixel_t currentColor_ {255, 255, 255, 255}; + GLint currentThreshold_; }; PlacefilePolygons::PlacefilePolygons(std::shared_ptr context) : @@ -123,39 +129,32 @@ PlacefilePolygons::PlacefilePolygons(PlacefilePolygons&&) noexcept = default; PlacefilePolygons& PlacefilePolygons::operator=(PlacefilePolygons&&) noexcept = default; +void PlacefilePolygons::set_thresholded(bool thresholded) +{ + p->thresholded_ = thresholded; +} + void PlacefilePolygons::Initialize() { gl::OpenGLFunctions& gl = p->context_->gl(); - p->shaderProgram_ = - p->context_->GetShaderProgram(":/gl/map_color.vert", ":/gl/color.frag"); - - p->uMVPMatrixLocation_ = - gl.glGetUniformLocation(p->shaderProgram_->id(), "uMVPMatrix"); - if (p->uMVPMatrixLocation_ == -1) - { - logger_->warn("Could not find uMVPMatrix"); - } - - p->uMapMatrixLocation_ = - gl.glGetUniformLocation(p->shaderProgram_->id(), "uMapMatrix"); - if (p->uMapMatrixLocation_ == -1) - { - logger_->warn("Could not find uMapMatrix"); - } + p->shaderProgram_ = p->context_->GetShaderProgram( + {{GL_VERTEX_SHADER, ":/gl/map_color.vert"}, + {GL_GEOMETRY_SHADER, ":/gl/threshold.geom"}, + {GL_FRAGMENT_SHADER, ":/gl/color.frag"}}); + p->uMVPMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMVPMatrix"); + p->uMapMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMapMatrix"); p->uMapScreenCoordLocation_ = - gl.glGetUniformLocation(p->shaderProgram_->id(), "uMapScreenCoord"); - if (p->uMapScreenCoordLocation_ == -1) - { - logger_->warn("Could not find uMapScreenCoord"); - } + p->shaderProgram_->GetUniformLocation("uMapScreenCoord"); + p->uMapDistanceLocation_ = + p->shaderProgram_->GetUniformLocation("uMapDistance"); gl.glGenVertexArrays(1, &p->vao_); - gl.glGenBuffers(1, &p->vbo_); + gl.glGenBuffers(2, p->vbo_.data()); gl.glBindVertexArray(p->vao_); - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_); + gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); // aScreenCoord @@ -185,18 +184,28 @@ void PlacefilePolygons::Initialize() reinterpret_cast(4 * sizeof(float))); gl.glEnableVertexAttribArray(2); + gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); + gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + + // aThreshold + gl.glVertexAttribIPointer(3, // + 1, + GL_INT, + 0, + static_cast(0)); + gl.glEnableVertexAttribArray(3); + p->dirty_ = true; } void PlacefilePolygons::Render( const QMapLibreGL::CustomLayerRenderParameters& params) { - if (!p->polygonList_.empty()) + if (!p->currentBuffer_.empty()) { gl::OpenGLFunctions& gl = p->context_->gl(); gl.glBindVertexArray(p->vao_); - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_); p->Update(); p->shaderProgram_->Use(); @@ -204,6 +213,21 @@ void PlacefilePolygons::Render( UseMapProjection( params, p->uMapMatrixLocation_, p->uMapScreenCoordLocation_); + if (p->thresholded_) + { + // If thresholding is enabled, set the map distance + // TODO: nautical miles + auto mapDistance = + util::maplibre::GetMapDistance(params).value() / 1852.0f; + gl.glUniform1f(p->uMapDistanceLocation_, + static_cast(mapDistance)); + } + else + { + // If thresholding is disabled, set the map distance to 0 + gl.glUniform1f(p->uMapDistanceLocation_, 0.0f); + } + // Draw icons gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); } @@ -214,13 +238,14 @@ void PlacefilePolygons::Deinitialize() gl::OpenGLFunctions& gl = p->context_->gl(); gl.glDeleteVertexArrays(1, &p->vao_); - gl.glDeleteBuffers(1, &p->vbo_); + gl.glDeleteBuffers(2, p->vbo_.data()); } void PlacefilePolygons::StartPolygons() { // Clear the new buffer p->newBuffer_.clear(); + p->newThresholdBuffer_.clear(); // Clear the polygon list p->polygonList_.clear(); @@ -242,6 +267,7 @@ void PlacefilePolygons::FinishPolygons() // Swap buffers p->currentBuffer_.swap(p->newBuffer_); + p->currentThresholdBuffer_.swap(p->newThresholdBuffer_); // Mark the draw item dirty p->dirty_ = true; @@ -255,11 +281,20 @@ void PlacefilePolygons::Impl::Update() std::unique_lock lock {bufferMutex_}; + // Buffer vertex data + gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); gl.glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * currentBuffer_.size(), currentBuffer_.data(), GL_DYNAMIC_DRAW); + // Buffer threshold data + gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); + gl.glBufferData(GL_ARRAY_BUFFER, + sizeof(GLint) * currentThresholdBuffer_.size(), + currentThresholdBuffer_.data(), + GL_DYNAMIC_DRAW); + numVertices_ = static_cast(currentBuffer_.size() / kPointsPerVertex); @@ -276,6 +311,10 @@ void PlacefilePolygons::Impl::Tessellate( // Default color to "Color" statement boost::gil::rgba8_pixel_t lastColor = di->color_; + // TODO: nautical miles + currentThreshold_ = + static_cast(std::roundf(di->threshold_.value() / 1852.0f)); + gluTessBeginPolygon(tessellator_, this); for (auto& contour : di->contours_) @@ -322,6 +361,7 @@ void PlacefilePolygons::Impl::Tessellate( while (newBuffer_.size() % kVerticesPerTriangle != 0) { newBuffer_.pop_back(); + newThresholdBuffer_.pop_back(); } } @@ -382,6 +422,7 @@ void PlacefilePolygons::Impl::TessellateVertexCallback(void* vertexData, static_cast(data[kTessVertexG_]), static_cast(data[kTessVertexB_]), static_cast(data[kTessVertexA_])}); + self->newThresholdBuffer_.push_back(self->currentThreshold_); } void PlacefilePolygons::Impl::TessellateErrorCallback(GLenum errorCode) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.hpp index f4a44ecd..451e007f 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.hpp @@ -27,6 +27,8 @@ public: PlacefilePolygons(PlacefilePolygons&&) noexcept; PlacefilePolygons& operator=(PlacefilePolygons&&) noexcept; + void set_thresholded(bool thresholded); + void Initialize() override; void Render(const QMapLibreGL::CustomLayerRenderParameters& params) override; void Deinitialize() override; diff --git a/scwx-qt/source/scwx/qt/gl/shader_program.cpp b/scwx-qt/source/scwx/qt/gl/shader_program.cpp index 54db55fc..4da07a32 100644 --- a/scwx-qt/source/scwx/qt/gl/shader_program.cpp +++ b/scwx-qt/source/scwx/qt/gl/shader_program.cpp @@ -56,6 +56,16 @@ GLuint ShaderProgram::id() const return p->id_; } +GLint ShaderProgram::GetUniformLocation(const std::string& name) +{ + GLint location = p->gl_.glGetUniformLocation(p->id_, name.c_str()); + if (location == -1) + { + logger_->warn("Could not find {}", name); + } + return location; +} + std::string ShaderProgram::Impl::ShaderName(GLenum type) { auto it = kShaderNames_.find(type); diff --git a/scwx-qt/source/scwx/qt/gl/shader_program.hpp b/scwx-qt/source/scwx/qt/gl/shader_program.hpp index dcc36b3e..a2b887d8 100644 --- a/scwx-qt/source/scwx/qt/gl/shader_program.hpp +++ b/scwx-qt/source/scwx/qt/gl/shader_program.hpp @@ -30,6 +30,8 @@ public: GLuint id() const; + GLint GetUniformLocation(const std::string& name); + bool Load(const std::string& vertexPath, const std::string& fragmentPath); bool Load(std::initializer_list> shaderPaths); diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp index a7e258e6..70db64a5 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp @@ -4,11 +4,11 @@ #include #include #include -#include #include #include #include +#include #include #include #include @@ -41,12 +41,8 @@ public: void ConnectSignals(); - void - RenderIconDrawItem(const QMapLibreGL::CustomLayerRenderParameters& params, - const std::shared_ptr& di); - void RenderPolygonDrawItem( - const QMapLibreGL::CustomLayerRenderParameters& params, - const std::shared_ptr& di); + void AddIcon(const std::shared_ptr& di); + void AddPolygon(const std::shared_ptr& di); void RenderTextDrawItem(const QMapLibreGL::CustomLayerRenderParameters& params, const std::shared_ptr& di); @@ -73,6 +69,8 @@ public: bool thresholded_ {true}; ImFont* monospaceFont_ {}; + boost::units::quantity mapDistance_ {}; + std::shared_ptr placefileIcons_; std::shared_ptr placefilePolygons_; }; @@ -122,8 +120,7 @@ void PlacefileLayer::Initialize() DrawLayer::Initialize(); } -void PlacefileLayer::Impl::RenderIconDrawItem( - const QMapLibreGL::CustomLayerRenderParameters& params, +void PlacefileLayer::Impl::AddIcon( const std::shared_ptr& di) { if (!dirty_) @@ -131,51 +128,25 @@ void PlacefileLayer::Impl::RenderIconDrawItem( return; } - auto distance = - (thresholded_) ? - util::GeographicLib::GetDistance( - params.latitude, params.longitude, di->latitude_, di->longitude_) : - 0; - - if (distance < di->threshold_) - { - placefileIcons_->AddIcon(di); - } + placefileIcons_->AddIcon(di); } -void PlacefileLayer::Impl::RenderPolygonDrawItem( - const QMapLibreGL::CustomLayerRenderParameters& params, +void PlacefileLayer::Impl::AddPolygon( const std::shared_ptr& di) { if (!dirty_) { return; - } + }; - auto distance = (thresholded_) ? - util::GeographicLib::GetDistance(params.latitude, - params.longitude, - di->center_.latitude_, - di->center_.longitude_) : - 0; - - if (distance < di->threshold_) - { - placefilePolygons_->AddPolygon(di); - } + placefilePolygons_->AddPolygon(di); } void PlacefileLayer::Impl::RenderTextDrawItem( const QMapLibreGL::CustomLayerRenderParameters& params, const std::shared_ptr& di) { - auto distance = - (thresholded_) ? - util::GeographicLib::GetDistance( - params.latitude, params.longitude, di->latitude_, di->longitude_) : - 0; - - if (distance < di->threshold_) + if (!thresholded_ || mapDistance_ <= di->threshold_) { const auto screenCoordinates = (util::maplibre::LatLongToScreenCoordinate( {di->latitude_, di->longitude_}) - @@ -265,6 +236,7 @@ void PlacefileLayer::Render( p->mapBearingSin_ = sinf(params.bearing * common::kDegreesToRadians); p->halfWidth_ = params.width * 0.5f; p->halfHeight_ = params.height * 0.5f; + p->mapDistance_ = util::maplibre::GetMapDistance(params); // Get monospace font pointer std::size_t fontSize = 16; @@ -292,6 +264,8 @@ void PlacefileLayer::Render( { p->thresholded_ = placefileManager->placefile_thresholded(placefile->name()); + p->placefileIcons_->set_thresholded(p->thresholded_); + p->placefilePolygons_->set_thresholded(p->thresholded_); if (p->dirty_) { @@ -315,14 +289,12 @@ void PlacefileLayer::Render( break; case gr::Placefile::ItemType::Icon: - p->RenderIconDrawItem( - params, + p->AddIcon( std::static_pointer_cast(drawItem)); break; case gr::Placefile::ItemType::Polygon: - p->RenderPolygonDrawItem( - params, + p->AddPolygon( std::static_pointer_cast( drawItem)); break; From 35b90fa98d1037e6812584d10b2b368eeb8d50b1 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 18 Aug 2023 10:45:52 -0500 Subject: [PATCH 061/199] Replace boost/units in favor of units library --- .gitmodules | 3 ++ external/CMakeLists.txt | 4 ++- external/units | 1 + external/units.cmake | 4 +++ .../scwx/qt/gl/draw/placefile_icons.cpp | 31 +++++++++---------- .../scwx/qt/gl/draw/placefile_polygons.cpp | 15 ++++----- .../source/scwx/qt/map/placefile_layer.cpp | 3 +- scwx-qt/source/scwx/qt/model/alert_model.cpp | 2 ++ .../source/scwx/qt/util/geographic_lib.cpp | 5 ++- .../source/scwx/qt/util/geographic_lib.hpp | 5 ++- scwx-qt/source/scwx/qt/util/maplibre.cpp | 7 +++-- scwx-qt/source/scwx/qt/util/maplibre.hpp | 5 ++- wxdata/include/scwx/gr/placefile.hpp | 25 +++++++-------- wxdata/source/scwx/gr/placefile.cpp | 21 +++++-------- wxdata/wxdata.cmake | 3 +- 15 files changed, 67 insertions(+), 67 deletions(-) create mode 160000 external/units create mode 100644 external/units.cmake diff --git a/.gitmodules b/.gitmodules index 7828c19a..a0fe596c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -31,3 +31,6 @@ [submodule "external/date"] path = external/date url = https://github.com/HowardHinnant/date.git +[submodule "external/units"] + path = external/units + url = https://github.com/nholthaus/units.git diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 65b2ef20..806b603b 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -10,7 +10,8 @@ set_property(DIRECTORY hsluv-c.cmake imgui.cmake mapbox-gl-native.cmake - stb.cmake) + stb.cmake + units.cmake) include(aws-sdk-cpp.cmake) include(date.cmake) @@ -19,3 +20,4 @@ include(hsluv-c.cmake) include(imgui.cmake) include(mapbox-gl-native.cmake) include(stb.cmake) +include(units.cmake) diff --git a/external/units b/external/units new file mode 160000 index 00000000..da6dd917 --- /dev/null +++ b/external/units @@ -0,0 +1 @@ +Subproject commit da6dd9176e8515323c75030d5e51ee19cf6c9afd diff --git a/external/units.cmake b/external/units.cmake new file mode 100644 index 00000000..d037ae54 --- /dev/null +++ b/external/units.cmake @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 3.20) +set(PROJECT_NAME scwx-units) + +add_subdirectory(units) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp index dbbc2308..5b8c876c 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp @@ -232,11 +232,9 @@ void PlacefileIcons::Render( if (p->thresholded_) { // If thresholding is enabled, set the map distance - // TODO: nautical miles - auto mapDistance = - util::maplibre::GetMapDistance(params).value() / 1852.0f; - gl.glUniform1f(p->uMapDistanceLocation_, - static_cast(mapDistance)); + units::length::nautical_miles mapDistance = + util::maplibre::GetMapDistance(params); + gl.glUniform1f(p->uMapDistanceLocation_, mapDistance.value()); } else { @@ -328,9 +326,10 @@ void PlacefileIcons::Impl::Update() continue; } - // TODO: nautical miles - GLint threshold = - static_cast(std::roundf(di->threshold_.value() / 1852.0f)); + // Threshold value + units::length::nautical_miles threshold = di->threshold_; + GLint thresholdValue = + static_cast(std::round(threshold.value())); // Latitude and longitude coordinates in degrees const float lat = static_cast(di->latitude_); @@ -355,8 +354,8 @@ void PlacefileIcons::Impl::Update() const float by = std::roundf(ty - ih); // Angle in degrees - // TODO: Properly convert - const float a = static_cast(di->angle_.value()); + units::angle::degrees angle = di->angle_; + const float a = angle.value(); // Texture coordinates const std::size_t iconRow = (di->iconNumber_ - 1) / icon.columns_; @@ -387,12 +386,12 @@ void PlacefileIcons::Impl::Update() lat, lon, lx, ty, ls, tt, mc0, mc1, mc2, mc3, a // TL }); thresholds.insert(thresholds.end(), - {threshold, // - threshold, - threshold, - threshold, - threshold, - threshold}); + {thresholdValue, // + thresholdValue, + thresholdValue, + thresholdValue, + thresholdValue, + thresholdValue}); numVertices_ += 6; } diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp index 202a5c0e..7bb54027 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp @@ -6,7 +6,6 @@ #include #include -#include #if defined(_WIN32) typedef void (*_GLUfuncptr)(void); @@ -216,11 +215,9 @@ void PlacefilePolygons::Render( if (p->thresholded_) { // If thresholding is enabled, set the map distance - // TODO: nautical miles - auto mapDistance = - util::maplibre::GetMapDistance(params).value() / 1852.0f; - gl.glUniform1f(p->uMapDistanceLocation_, - static_cast(mapDistance)); + units::length::nautical_miles mapDistance = + util::maplibre::GetMapDistance(params); + gl.glUniform1f(p->uMapDistanceLocation_, mapDistance.value()); } else { @@ -311,9 +308,9 @@ void PlacefilePolygons::Impl::Tessellate( // Default color to "Color" statement boost::gil::rgba8_pixel_t lastColor = di->color_; - // TODO: nautical miles - currentThreshold_ = - static_cast(std::roundf(di->threshold_.value() / 1852.0f)); + // Current threshold + units::length::nautical_miles threshold = di->threshold_; + currentThreshold_ = static_cast(std::round(threshold.value())); gluTessBeginPolygon(tessellator_, this); diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp index 70db64a5..ca98bdfa 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp @@ -8,7 +8,6 @@ #include #include -#include #include #include #include @@ -69,7 +68,7 @@ public: bool thresholded_ {true}; ImFont* monospaceFont_ {}; - boost::units::quantity mapDistance_ {}; + units::length::nautical_miles mapDistance_ {}; std::shared_ptr placefileIcons_; std::shared_ptr placefilePolygons_; diff --git a/scwx-qt/source/scwx/qt/model/alert_model.cpp b/scwx-qt/source/scwx/qt/model/alert_model.cpp index 4cadff42..e44e2a17 100644 --- a/scwx-qt/source/scwx/qt/model/alert_model.cpp +++ b/scwx-qt/source/scwx/qt/model/alert_model.cpp @@ -1,3 +1,5 @@ +#define NOMINMAX + #include #include #include diff --git a/scwx-qt/source/scwx/qt/util/geographic_lib.cpp b/scwx-qt/source/scwx/qt/util/geographic_lib.cpp index 84ae9e66..0496d88a 100644 --- a/scwx-qt/source/scwx/qt/util/geographic_lib.cpp +++ b/scwx-qt/source/scwx/qt/util/geographic_lib.cpp @@ -18,15 +18,14 @@ const ::GeographicLib::Geodesic& DefaultGeodesic() return geodesic_; } -boost::units::quantity +units::length::meters GetDistance(double lat1, double lon1, double lat2, double lon2) { double distance; util::GeographicLib::DefaultGeodesic().Inverse( lat1, lon1, lat2, lon2, distance); - return static_cast>( - distance * boost::units::si::meter_base_unit::unit_type()); + return units::length::meters {distance}; } } // namespace GeographicLib diff --git a/scwx-qt/source/scwx/qt/util/geographic_lib.hpp b/scwx-qt/source/scwx/qt/util/geographic_lib.hpp index c3e74ef9..d93dce2b 100644 --- a/scwx-qt/source/scwx/qt/util/geographic_lib.hpp +++ b/scwx-qt/source/scwx/qt/util/geographic_lib.hpp @@ -1,8 +1,7 @@ #pragma once #include -#include -#include +#include namespace scwx { @@ -30,7 +29,7 @@ const ::GeographicLib::Geodesic& DefaultGeodesic(); * * @return distance between point 1 and point 2 */ -boost::units::quantity +units::length::meters GetDistance(double lat1, double lon1, double lat2, double lon2); } // namespace GeographicLib diff --git a/scwx-qt/source/scwx/qt/util/maplibre.cpp b/scwx-qt/source/scwx/qt/util/maplibre.cpp index 5b3f331e..8cde77ca 100644 --- a/scwx-qt/source/scwx/qt/util/maplibre.cpp +++ b/scwx-qt/source/scwx/qt/util/maplibre.cpp @@ -12,11 +12,12 @@ namespace util namespace maplibre { -boost::units::quantity +units::length::meters GetMapDistance(const QMapLibreGL::CustomLayerRenderParameters& params) { - return QMapLibreGL::metersPerPixelAtLatitude(params.latitude, params.zoom) * - (params.width + params.height) / 2.0 * boost::units::si::meters; + return units::length::meters( + QMapLibreGL::metersPerPixelAtLatitude(params.latitude, params.zoom) * + (params.width + params.height) / 2.0); } glm::vec2 LatLongToScreenCoordinate(const QMapLibreGL::Coordinate& coordinate) diff --git a/scwx-qt/source/scwx/qt/util/maplibre.hpp b/scwx-qt/source/scwx/qt/util/maplibre.hpp index 65aa4409..37d205eb 100644 --- a/scwx-qt/source/scwx/qt/util/maplibre.hpp +++ b/scwx-qt/source/scwx/qt/util/maplibre.hpp @@ -1,9 +1,8 @@ #pragma once #include -#include -#include #include +#include namespace scwx { @@ -14,7 +13,7 @@ namespace util namespace maplibre { -boost::units::quantity +units::length::meters GetMapDistance(const QMapLibreGL::CustomLayerRenderParameters& params); glm::vec2 LatLongToScreenCoordinate(const QMapLibreGL::Coordinate& coordinate); diff --git a/wxdata/include/scwx/gr/placefile.hpp b/wxdata/include/scwx/gr/placefile.hpp index 8330aa4f..10ea2802 100644 --- a/wxdata/include/scwx/gr/placefile.hpp +++ b/wxdata/include/scwx/gr/placefile.hpp @@ -9,9 +9,8 @@ #include #include -#include -#include -#include +#include +#include namespace scwx { @@ -71,22 +70,22 @@ public: struct DrawItem { - ItemType itemType_ {ItemType::Unknown}; - boost::units::quantity threshold_ {}; + ItemType itemType_ {ItemType::Unknown}; + units::length::nautical_miles threshold_ {}; }; struct IconDrawItem : DrawItem { IconDrawItem() { itemType_ = ItemType::Icon; } - double latitude_ {}; - double longitude_ {}; - double x_ {}; - double y_ {}; - boost::units::quantity angle_ {}; - std::size_t fileNumber_ {0u}; - std::size_t iconNumber_ {0u}; - std::string hoverText_ {}; + double latitude_ {}; + double longitude_ {}; + double x_ {}; + double y_ {}; + units::degrees angle_ {}; + std::size_t fileNumber_ {0u}; + std::size_t iconNumber_ {0u}; + std::string hoverText_ {}; }; struct TextDrawItem : DrawItem diff --git a/wxdata/source/scwx/gr/placefile.cpp b/wxdata/source/scwx/gr/placefile.cpp index 1725dd64..5c86f319 100644 --- a/wxdata/source/scwx/gr/placefile.cpp +++ b/wxdata/source/scwx/gr/placefile.cpp @@ -9,7 +9,8 @@ #include #include -#include + +using namespace units::literals; namespace scwx { @@ -58,11 +59,10 @@ public: std::chrono::seconds refresh_ {-1}; // Parsing state - boost::units::quantity threshold_ { - 999.0 * boost::units::metric::nautical_mile_base_unit::unit_type()}; - boost::gil::rgba8_pixel_t color_ {255, 255, 255, 255}; - ColorMode colorMode_ {ColorMode::RGBA}; - std::vector objectStack_ {}; + units::length::nautical_miles threshold_ {999.0_nmi}; + boost::gil::rgba8_pixel_t color_ {255, 255, 255, 255}; + ColorMode colorMode_ {ColorMode::RGBA}; + std::vector objectStack_ {}; DrawingStatement currentStatement_ {DrawingStatement::Standard}; std::shared_ptr currentDrawItem_ {nullptr}; std::vector currentPolygonContour_ {}; @@ -245,9 +245,7 @@ void Placefile::Impl::ProcessLine(const std::string& line) if (tokenList.size() >= 1) { threshold_ = - static_cast>( - std::stod(tokenList[0]) * - boost::units::metric::nautical_mile_base_unit::unit_type()); + units::length::nautical_miles(std::stod(tokenList[0])); } } else if (boost::istarts_with(line, timeRangeKey_)) @@ -384,10 +382,7 @@ void Placefile::Impl::ProcessLine(const std::string& line) di->x_, di->y_); - di->angle_ = static_cast< - boost::units::quantity>( - std::stod(tokenList[2]) * - boost::units::angle::degree_base_unit::unit_type()); + di->angle_ = units::angle::degrees(std::stod(tokenList[2])); di->fileNumber_ = std::stoul(tokenList[3]); di->iconNumber_ = std::stoul(tokenList[4]); diff --git a/wxdata/wxdata.cmake b/wxdata/wxdata.cmake index a9a8dce7..38ffd1b0 100644 --- a/wxdata/wxdata.cmake +++ b/wxdata/wxdata.cmake @@ -259,7 +259,8 @@ target_link_libraries(wxdata PUBLIC aws-cpp-sdk-core aws-cpp-sdk-s3 cpr::cpr LibXml2::LibXml2 - spdlog::spdlog) + spdlog::spdlog + units::units) target_link_libraries(wxdata INTERFACE Boost::iostreams BZip2::BZip2 hsluv-c) From d6f8a339fc9fd72b78c9b4dae49cbe6c29c0a333 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 18 Aug 2023 19:09:44 -0500 Subject: [PATCH 062/199] Mask API keys in settings dialog --- scwx-qt/source/scwx/qt/ui/settings_dialog.ui | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui index 9a4b2c86..79a68f55 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui @@ -199,7 +199,11 @@ - + + + QLineEdit::Password + + @@ -234,7 +238,11 @@ - + + + QLineEdit::Password + + @@ -347,8 +355,8 @@ 0 0 - 481 - 382 + 66 + 18 From a4027ba12064a3060d945ad0663252d9e67b0b2a Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 19 Aug 2023 10:05:26 -0500 Subject: [PATCH 063/199] Refactor placefile text into its own draw item --- scwx-qt/scwx-qt.cmake | 2 + .../source/scwx/qt/gl/draw/placefile_text.cpp | 240 ++++++++++++++++++ .../source/scwx/qt/gl/draw/placefile_text.hpp | 57 +++++ .../source/scwx/qt/map/placefile_layer.cpp | 154 ++--------- 4 files changed, 321 insertions(+), 132 deletions(-) create mode 100644 scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp create mode 100644 scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index f5d6a489..129d3f14 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -59,11 +59,13 @@ set(HDR_GL_DRAW source/scwx/qt/gl/draw/draw_item.hpp source/scwx/qt/gl/draw/geo_line.hpp source/scwx/qt/gl/draw/placefile_icons.hpp source/scwx/qt/gl/draw/placefile_polygons.hpp + source/scwx/qt/gl/draw/placefile_text.hpp source/scwx/qt/gl/draw/rectangle.hpp) set(SRC_GL_DRAW source/scwx/qt/gl/draw/draw_item.cpp source/scwx/qt/gl/draw/geo_line.cpp source/scwx/qt/gl/draw/placefile_icons.cpp source/scwx/qt/gl/draw/placefile_polygons.cpp + source/scwx/qt/gl/draw/placefile_text.cpp source/scwx/qt/gl/draw/rectangle.cpp) set(HDR_MANAGER source/scwx/qt/manager/placefile_manager.hpp source/scwx/qt/manager/radar_product_manager.hpp diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp new file mode 100644 index 00000000..ee28800a --- /dev/null +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp @@ -0,0 +1,240 @@ +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace gl +{ +namespace draw +{ + +static const std::string logPrefix_ = "scwx::qt::gl::draw::placefile_text"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +class PlacefileText::Impl +{ +public: + explicit Impl(std::shared_ptr context, + const std::string& placefileName) : + context_ {context}, placefileName_ {placefileName} + { + } + + ~Impl() {} + + void RenderTextDrawItem( + const QMapLibreGL::CustomLayerRenderParameters& params, + const std::shared_ptr& di); + void RenderText(const QMapLibreGL::CustomLayerRenderParameters& params, + const std::string& text, + const std::string& hoverText, + boost::gil::rgba8_pixel_t color, + float x, + float y); + + std::shared_ptr context_; + + std::string placefileName_; + + bool dirty_ {false}; + bool thresholded_ {false}; + + std::uint32_t textId_ {}; + glm::vec2 mapScreenCoordLocation_ {}; + float mapScale_ {1.0f}; + float mapBearingCos_ {1.0f}; + float mapBearingSin_ {0.0f}; + float halfWidth_ {}; + float halfHeight_ {}; + ImFont* monospaceFont_ {}; + + units::length::nautical_miles mapDistance_ {}; + + std::vector> textList_ {}; + + void Update(); +}; + +PlacefileText::PlacefileText(std::shared_ptr context, + const std::string& placefileName) : + DrawItem(context->gl()), p(std::make_unique(context, placefileName)) +{ +} +PlacefileText::~PlacefileText() = default; + +PlacefileText::PlacefileText(PlacefileText&&) noexcept = default; +PlacefileText& PlacefileText::operator=(PlacefileText&&) noexcept = default; + +void PlacefileText::set_placefile_name(const std::string& placefileName) +{ + p->placefileName_ = placefileName; +} + +void PlacefileText::set_thresholded(bool thresholded) +{ + p->thresholded_ = thresholded; +} + +void PlacefileText::Initialize() +{ + p->dirty_ = true; +} + +void PlacefileText::Render( + const QMapLibreGL::CustomLayerRenderParameters& params) +{ + if (!p->textList_.empty()) + { + p->Update(); + + // Reset text ID per frame + p->textId_ = 0; + + // Update map screen coordinate and scale information + p->mapScreenCoordLocation_ = util::maplibre::LatLongToScreenCoordinate( + {params.latitude, params.longitude}); + p->mapScale_ = std::pow(2.0, params.zoom) * mbgl::util::tileSize_D / + mbgl::util::DEGREES_MAX; + p->mapBearingCos_ = cosf(params.bearing * common::kDegreesToRadians); + p->mapBearingSin_ = sinf(params.bearing * common::kDegreesToRadians); + p->halfWidth_ = params.width * 0.5f; + p->halfHeight_ = params.height * 0.5f; + p->mapDistance_ = util::maplibre::GetMapDistance(params); + + // Get monospace font pointer + std::size_t fontSize = 16; + auto fontSizes = + manager::SettingsManager::general_settings().font_sizes().GetValue(); + if (fontSizes.size() > 1) + { + fontSize = fontSizes[1]; + } + else if (fontSizes.size() > 0) + { + fontSize = fontSizes[0]; + } + auto monospace = + manager::ResourceManager::Font(types::Font::Inconsolata_Regular); + p->monospaceFont_ = monospace->ImGuiFont(fontSize); + + for (auto& di : p->textList_) + { + p->RenderTextDrawItem(params, di); + } + } +} + +void PlacefileText::Impl::RenderTextDrawItem( + const QMapLibreGL::CustomLayerRenderParameters& params, + const std::shared_ptr& di) +{ + if (!thresholded_ || mapDistance_ <= di->threshold_) + { + const auto screenCoordinates = (util::maplibre::LatLongToScreenCoordinate( + {di->latitude_, di->longitude_}) - + mapScreenCoordLocation_) * + mapScale_; + + // Rotate text according to map rotation + float rotatedX = screenCoordinates.x; + float rotatedY = screenCoordinates.y; + if (params.bearing != 0.0) + { + rotatedX = screenCoordinates.x * mapBearingCos_ - + screenCoordinates.y * mapBearingSin_; + rotatedY = screenCoordinates.x * mapBearingSin_ + + screenCoordinates.y * mapBearingCos_; + } + + RenderText(params, + di->text_, + di->hoverText_, + di->color_, + rotatedX + di->x_ + halfWidth_, + rotatedY + di->y_ + halfHeight_); + } +} + +void PlacefileText::Impl::RenderText( + const QMapLibreGL::CustomLayerRenderParameters& params, + const std::string& text, + const std::string& hoverText, + boost::gil::rgba8_pixel_t color, + float x, + float y) +{ + const std::string windowName { + fmt::format("PlacefileText-{}-{}", placefileName_, ++textId_)}; + + // Convert screen to ImGui coordinates + y = params.height - y; + + // Setup "window" to hold text + ImGui::SetNextWindowPos( + ImVec2 {x, y}, ImGuiCond_Always, ImVec2 {0.5f, 0.5f}); + ImGui::Begin(windowName.c_str(), + nullptr, + ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoNav | + ImGuiWindowFlags_NoBackground); + + // Render text + ImGui::PushStyleColor(ImGuiCol_Text, + IM_COL32(color[0], color[1], color[2], color[3])); + ImGui::TextUnformatted(text.c_str()); + ImGui::PopStyleColor(); + + // Create tooltip for hover text + if (!hoverText.empty() && ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::PushFont(monospaceFont_); + ImGui::TextUnformatted(hoverText.c_str()); + ImGui::PopFont(); + ImGui::EndTooltip(); + } + + // End window + ImGui::End(); +} + +void PlacefileText::Deinitialize() +{ + Reset(); +} + +void PlacefileText::AddText( + const std::shared_ptr& di) +{ + if (di != nullptr) + { + p->textList_.emplace_back(di); + p->dirty_ = true; + } +} + +void PlacefileText::Reset() +{ + // Clear the icon list, and mark the draw item dirty + p->textList_.clear(); + p->dirty_ = true; +} + +void PlacefileText::Impl::Update() +{ + dirty_ = false; +} + +} // namespace draw +} // namespace gl +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp new file mode 100644 index 00000000..04d78844 --- /dev/null +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace gl +{ +namespace draw +{ + +class PlacefileText : public DrawItem +{ +public: + explicit PlacefileText(std::shared_ptr context, + const std::string& placefileName); + ~PlacefileText(); + + PlacefileText(const PlacefileText&) = delete; + PlacefileText& operator=(const PlacefileText&) = delete; + + PlacefileText(PlacefileText&&) noexcept; + PlacefileText& operator=(PlacefileText&&) noexcept; + + void set_placefile_name(const std::string& placefileName); + void set_thresholded(bool thresholded); + + void Initialize() override; + void Render(const QMapLibreGL::CustomLayerRenderParameters& params) override; + void Deinitialize() override; + + /** + * Adds placefile text to the internal draw list. + * + * @param [in] di Placefile icon + */ + void AddText(const std::shared_ptr& di); + + /** + * Resets the list of text in preparation for rendering a new frame. + */ + void Reset(); + +private: + class Impl; + + std::unique_ptr p; +}; + +} // namespace draw +} // namespace gl +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp index ca98bdfa..e287be40 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp @@ -1,16 +1,10 @@ #include #include #include +#include #include -#include -#include -#include -#include #include -#include -#include -#include namespace scwx { @@ -32,7 +26,9 @@ public: placefileName_ {placefileName}, placefileIcons_ {std::make_shared(context)}, placefilePolygons_ { - std::make_shared(context)} + std::make_shared(context)}, + placefileText_ { + std::make_shared(context, placefileName)} { ConnectSignals(); } @@ -42,36 +38,17 @@ public: void AddIcon(const std::shared_ptr& di); void AddPolygon(const std::shared_ptr& di); - void - RenderTextDrawItem(const QMapLibreGL::CustomLayerRenderParameters& params, - const std::shared_ptr& di); + void AddText(const std::shared_ptr& di); - void RenderText(const QMapLibreGL::CustomLayerRenderParameters& params, - const std::string& text, - const std::string& hoverText, - boost::gil::rgba8_pixel_t color, - float x, - float y); PlacefileLayer* self_; std::string placefileName_; bool dirty_ {true}; - std::uint32_t textId_ {}; - glm::vec2 mapScreenCoordLocation_ {}; - float mapScale_ {1.0f}; - float mapBearingCos_ {1.0f}; - float mapBearingSin_ {0.0f}; - float halfWidth_ {}; - float halfHeight_ {}; - bool thresholded_ {true}; - ImFont* monospaceFont_ {}; - - units::length::nautical_miles mapDistance_ {}; - std::shared_ptr placefileIcons_; std::shared_ptr placefilePolygons_; + std::shared_ptr placefileText_; }; PlacefileLayer::PlacefileLayer(std::shared_ptr context, @@ -81,6 +58,7 @@ PlacefileLayer::PlacefileLayer(std::shared_ptr context, { AddDrawItem(p->placefileIcons_); AddDrawItem(p->placefilePolygons_); + AddDrawItem(p->placefileText_); } PlacefileLayer::~PlacefileLayer() = default; @@ -110,6 +88,8 @@ void PlacefileLayer::set_placefile_name(const std::string& placefileName) { p->placefileName_ = placefileName; p->dirty_ = true; + + p->placefileText_->set_placefile_name(placefileName); } void PlacefileLayer::Initialize() @@ -141,78 +121,15 @@ void PlacefileLayer::Impl::AddPolygon( placefilePolygons_->AddPolygon(di); } -void PlacefileLayer::Impl::RenderTextDrawItem( - const QMapLibreGL::CustomLayerRenderParameters& params, +void PlacefileLayer::Impl::AddText( const std::shared_ptr& di) { - if (!thresholded_ || mapDistance_ <= di->threshold_) + if (!dirty_) { - const auto screenCoordinates = (util::maplibre::LatLongToScreenCoordinate( - {di->latitude_, di->longitude_}) - - mapScreenCoordLocation_) * - mapScale_; + return; + }; - // Rotate text according to map rotation - float rotatedX = screenCoordinates.x; - float rotatedY = screenCoordinates.y; - if (params.bearing != 0.0) - { - rotatedX = screenCoordinates.x * mapBearingCos_ - - screenCoordinates.y * mapBearingSin_; - rotatedY = screenCoordinates.x * mapBearingSin_ + - screenCoordinates.y * mapBearingCos_; - } - - RenderText(params, - di->text_, - di->hoverText_, - di->color_, - rotatedX + di->x_ + halfWidth_, - rotatedY + di->y_ + halfHeight_); - } -} - -void PlacefileLayer::Impl::RenderText( - const QMapLibreGL::CustomLayerRenderParameters& params, - const std::string& text, - const std::string& hoverText, - boost::gil::rgba8_pixel_t color, - float x, - float y) -{ - const std::string windowName { - fmt::format("PlacefileText-{}-{}", placefileName_, ++textId_)}; - - // Convert screen to ImGui coordinates - y = params.height - y; - - // Setup "window" to hold text - ImGui::SetNextWindowPos( - ImVec2 {x, y}, ImGuiCond_Always, ImVec2 {0.5f, 0.5f}); - ImGui::Begin(windowName.c_str(), - nullptr, - ImGuiWindowFlags_AlwaysAutoResize | - ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoNav | - ImGuiWindowFlags_NoBackground); - - // Render text - ImGui::PushStyleColor(ImGuiCol_Text, - IM_COL32(color[0], color[1], color[2], color[3])); - ImGui::TextUnformatted(text.c_str()); - ImGui::PopStyleColor(); - - // Create tooltip for hover text - if (!hoverText.empty() && ImGui::IsItemHovered()) - { - ImGui::BeginTooltip(); - ImGui::PushFont(monospaceFont_); - ImGui::TextUnformatted(hoverText.c_str()); - ImGui::PopFont(); - ImGui::EndTooltip(); - } - - // End window - ImGui::End(); + placefileText_->AddText(di); } void PlacefileLayer::Render( @@ -223,36 +140,6 @@ void PlacefileLayer::Render( // Set OpenGL blend mode for transparency gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - // Reset text ID per frame - p->textId_ = 0; - - // Update map screen coordinate and scale information - p->mapScreenCoordLocation_ = util::maplibre::LatLongToScreenCoordinate( - {params.latitude, params.longitude}); - p->mapScale_ = std::pow(2.0, params.zoom) * mbgl::util::tileSize_D / - mbgl::util::DEGREES_MAX; - p->mapBearingCos_ = cosf(params.bearing * common::kDegreesToRadians); - p->mapBearingSin_ = sinf(params.bearing * common::kDegreesToRadians); - p->halfWidth_ = params.width * 0.5f; - p->halfHeight_ = params.height * 0.5f; - p->mapDistance_ = util::maplibre::GetMapDistance(params); - - // Get monospace font pointer - std::size_t fontSize = 16; - auto fontSizes = - manager::SettingsManager::general_settings().font_sizes().GetValue(); - if (fontSizes.size() > 1) - { - fontSize = fontSizes[1]; - } - else if (fontSizes.size() > 0) - { - fontSize = fontSizes[0]; - } - auto monospace = - manager::ResourceManager::Font(types::Font::Inconsolata_Regular); - p->monospaceFont_ = monospace->ImGuiFont(fontSize); - std::shared_ptr placefileManager = manager::PlacefileManager::Instance(); @@ -261,10 +148,11 @@ void PlacefileLayer::Render( // Render placefile if (placefile != nullptr) { - p->thresholded_ = + bool thresholded = placefileManager->placefile_thresholded(placefile->name()); - p->placefileIcons_->set_thresholded(p->thresholded_); - p->placefilePolygons_->set_thresholded(p->thresholded_); + p->placefileIcons_->set_thresholded(thresholded); + p->placefilePolygons_->set_thresholded(thresholded); + p->placefileText_->set_thresholded(thresholded); if (p->dirty_) { @@ -275,6 +163,9 @@ void PlacefileLayer::Render( // Reset Placefile Polygons p->placefilePolygons_->StartPolygons(); + + // Reset Placefile Text + p->placefileText_->Reset(); } for (auto& drawItem : placefile->GetDrawItems()) @@ -282,8 +173,7 @@ void PlacefileLayer::Render( switch (drawItem->itemType_) { case gr::Placefile::ItemType::Text: - p->RenderTextDrawItem( - params, + p->AddText( std::static_pointer_cast(drawItem)); break; From 8f2b87790a674d2a95a3b1355cac52387b714f6f Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 19 Aug 2023 18:30:49 -0500 Subject: [PATCH 064/199] Update placefile polygon data outside of render loop --- .../scwx/qt/gl/draw/placefile_polygons.cpp | 7 - .../source/scwx/qt/map/placefile_layer.cpp | 141 +++++++++--------- .../source/scwx/qt/map/placefile_layer.hpp | 2 + 3 files changed, 76 insertions(+), 74 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp index 7bb54027..67d04a3d 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp @@ -91,9 +91,6 @@ public: bool dirty_ {false}; bool thresholded_ {false}; - std::vector> - polygonList_ {}; - boost::container::stable_vector tessCombineBuffer_ {}; std::mutex bufferMutex_ {}; @@ -243,9 +240,6 @@ void PlacefilePolygons::StartPolygons() // Clear the new buffer p->newBuffer_.clear(); p->newThresholdBuffer_.clear(); - - // Clear the polygon list - p->polygonList_.clear(); } void PlacefilePolygons::AddPolygon( @@ -253,7 +247,6 @@ void PlacefilePolygons::AddPolygon( { if (di != nullptr) { - p->polygonList_.emplace_back(di); p->Tessellate(di); } } diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp index e287be40..3f5c880e 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp @@ -5,6 +5,8 @@ #include #include +#include +#include namespace scwx { @@ -36,14 +38,12 @@ public: void ConnectSignals(); - void AddIcon(const std::shared_ptr& di); - void AddPolygon(const std::shared_ptr& di); - void AddText(const std::shared_ptr& di); - + boost::asio::thread_pool threadPool_ {1}; PlacefileLayer* self_; std::string placefileName_; + std::mutex dataMutex_ {}; bool dirty_ {true}; std::shared_ptr placefileIcons_; @@ -74,7 +74,7 @@ void PlacefileLayer::Impl::ConnectSignals() { if (name == placefileName_) { - dirty_ = true; + self_->ReloadData(); } }); } @@ -99,39 +99,6 @@ void PlacefileLayer::Initialize() DrawLayer::Initialize(); } -void PlacefileLayer::Impl::AddIcon( - const std::shared_ptr& di) -{ - if (!dirty_) - { - return; - } - - placefileIcons_->AddIcon(di); -} - -void PlacefileLayer::Impl::AddPolygon( - const std::shared_ptr& di) -{ - if (!dirty_) - { - return; - }; - - placefilePolygons_->AddPolygon(di); -} - -void PlacefileLayer::Impl::AddText( - const std::shared_ptr& di) -{ - if (!dirty_) - { - return; - }; - - placefileText_->AddText(di); -} - void PlacefileLayer::Render( const QMapLibreGL::CustomLayerRenderParameters& params) { @@ -161,43 +128,30 @@ void PlacefileLayer::Render( p->placefileIcons_->SetIconFiles(placefile->icon_files(), placefile->name()); - // Reset Placefile Polygons - p->placefilePolygons_->StartPolygons(); - // Reset Placefile Text p->placefileText_->Reset(); - } - for (auto& drawItem : placefile->GetDrawItems()) - { - switch (drawItem->itemType_) + for (auto& drawItem : placefile->GetDrawItems()) { - case gr::Placefile::ItemType::Text: - p->AddText( - std::static_pointer_cast(drawItem)); - break; + switch (drawItem->itemType_) + { + case gr::Placefile::ItemType::Text: + p->placefileText_->AddText( + std::static_pointer_cast( + drawItem)); + break; - case gr::Placefile::ItemType::Icon: - p->AddIcon( - std::static_pointer_cast(drawItem)); - break; + case gr::Placefile::ItemType::Icon: + p->placefileIcons_->AddIcon( + std::static_pointer_cast( + drawItem)); + break; - case gr::Placefile::ItemType::Polygon: - p->AddPolygon( - std::static_pointer_cast( - drawItem)); - break; - - default: - break; + default: + break; + } } } - - if (p->dirty_) - { - // Finish Placefile Polygons - p->placefilePolygons_->FinishPolygons(); - } } DrawLayer::Render(params); @@ -212,6 +166,59 @@ void PlacefileLayer::Deinitialize() DrawLayer::Deinitialize(); } +void PlacefileLayer::ReloadData() +{ + // TODO: No longer needed after moving Icon and Text Render items here + p->dirty_ = true; + + boost::asio::post( + p->threadPool_, + [this]() + { + logger_->debug("ReloadData: {}", p->placefileName_); + + std::unique_lock lock {p->dataMutex_}; + + std::shared_ptr placefileManager = + manager::PlacefileManager::Instance(); + + auto placefile = placefileManager->placefile(p->placefileName_); + if (placefile == nullptr) + { + return; + } + + // Reset Placefile Polygons + p->placefilePolygons_->StartPolygons(); + + for (auto& drawItem : placefile->GetDrawItems()) + { + switch (drawItem->itemType_) + { + case gr::Placefile::ItemType::Text: + // TODO + break; + + case gr::Placefile::ItemType::Icon: + // TODO + break; + + case gr::Placefile::ItemType::Polygon: + p->placefilePolygons_->AddPolygon( + std::static_pointer_cast( + drawItem)); + break; + + default: + break; + } + } + + // Finish Placefile Polygons + p->placefilePolygons_->FinishPolygons(); + }); +} + } // namespace map } // namespace qt } // namespace scwx diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.hpp b/scwx-qt/source/scwx/qt/map/placefile_layer.hpp index 2e07c5c9..68fd38c5 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.hpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.hpp @@ -26,6 +26,8 @@ public: void Render(const QMapLibreGL::CustomLayerRenderParameters&) override final; void Deinitialize() override final; + void ReloadData(); + private: class Impl; std::unique_ptr p; From b1595402159d908e1dd3f123503a9d5044511f0a Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 19 Aug 2023 21:43:57 -0500 Subject: [PATCH 065/199] Update placefile text data outside of render loop, polygon cleanup --- .../scwx/qt/gl/draw/placefile_polygons.cpp | 10 ++++- .../source/scwx/qt/gl/draw/placefile_text.cpp | 41 ++++++++++--------- .../source/scwx/qt/gl/draw/placefile_text.hpp | 9 +++- scwx-qt/source/scwx/qt/map/map_widget.cpp | 6 +++ .../source/scwx/qt/map/placefile_layer.cpp | 25 ++++++----- .../source/scwx/qt/map/placefile_layer.hpp | 5 +++ 6 files changed, 60 insertions(+), 36 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp index 67d04a3d..11152a5a 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp @@ -233,11 +233,15 @@ void PlacefilePolygons::Deinitialize() gl.glDeleteVertexArrays(1, &p->vao_); gl.glDeleteBuffers(2, p->vbo_.data()); + + // Clear the current buffers + p->currentBuffer_.clear(); + p->currentThresholdBuffer_.clear(); } void PlacefilePolygons::StartPolygons() { - // Clear the new buffer + // Clear the new buffers p->newBuffer_.clear(); p->newThresholdBuffer_.clear(); } @@ -259,6 +263,10 @@ void PlacefilePolygons::FinishPolygons() p->currentBuffer_.swap(p->newBuffer_); p->currentThresholdBuffer_.swap(p->newThresholdBuffer_); + // Clear the new buffers + p->newBuffer_.clear(); + p->newThresholdBuffer_.clear(); + // Mark the draw item dirty p->dirty_ = true; } diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp index ee28800a..e15d7fdc 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp @@ -45,7 +45,6 @@ public: std::string placefileName_; - bool dirty_ {false}; bool thresholded_ {false}; std::uint32_t textId_ {}; @@ -59,9 +58,9 @@ public: units::length::nautical_miles mapDistance_ {}; + std::mutex listMutex_ {}; std::vector> textList_ {}; - - void Update(); + std::vector> newList_ {}; }; PlacefileText::PlacefileText(std::shared_ptr context, @@ -84,18 +83,15 @@ void PlacefileText::set_thresholded(bool thresholded) p->thresholded_ = thresholded; } -void PlacefileText::Initialize() -{ - p->dirty_ = true; -} +void PlacefileText::Initialize() {} void PlacefileText::Render( const QMapLibreGL::CustomLayerRenderParameters& params) { + std::unique_lock lock {p->listMutex_}; + if (!p->textList_.empty()) { - p->Update(); - // Reset text ID per frame p->textId_ = 0; @@ -209,7 +205,14 @@ void PlacefileText::Impl::RenderText( void PlacefileText::Deinitialize() { - Reset(); + // Clear the text list + p->textList_.clear(); +} + +void PlacefileText::StartText() +{ + // Clear the new list + p->newList_.clear(); } void PlacefileText::AddText( @@ -217,21 +220,19 @@ void PlacefileText::AddText( { if (di != nullptr) { - p->textList_.emplace_back(di); - p->dirty_ = true; + p->newList_.emplace_back(di); } } -void PlacefileText::Reset() +void PlacefileText::FinishText() { - // Clear the icon list, and mark the draw item dirty - p->textList_.clear(); - p->dirty_ = true; -} + std::unique_lock lock {p->listMutex_}; -void PlacefileText::Impl::Update() -{ - dirty_ = false; + // Swap text lists + p->textList_.swap(p->newList_); + + // Clear the new list + p->newList_.clear(); } } // namespace draw diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp index 04d78844..363164c7 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp @@ -33,6 +33,11 @@ public: void Render(const QMapLibreGL::CustomLayerRenderParameters& params) override; void Deinitialize() override; + /** + * Resets and prepares the draw item for adding a new set of text. + */ + void StartText(); + /** * Adds placefile text to the internal draw list. * @@ -41,9 +46,9 @@ public: void AddText(const std::shared_ptr& di); /** - * Resets the list of text in preparation for rendering a new frame. + * Finalizes the draw item after adding new text. */ - void Reset(); + void FinishText(); private: class Impl; diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index 406384db..61665a24 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -815,6 +815,12 @@ void MapWidgetImpl::UpdatePlacefileLayers() placefileLayers_.push_back(placefileLayer); AddLayer( GetPlacefileLayerName(placefileName), placefileLayer, "colorTable"); + + // When the layer updates, trigger a map widget update + connect(placefileLayer.get(), + &PlacefileLayer::DataReloaded, + widget_, + [this]() { widget_->update(); }); } } } diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp index 3f5c880e..fc60cefa 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp @@ -59,6 +59,8 @@ PlacefileLayer::PlacefileLayer(std::shared_ptr context, AddDrawItem(p->placefileIcons_); AddDrawItem(p->placefilePolygons_); AddDrawItem(p->placefileText_); + + ReloadData(); } PlacefileLayer::~PlacefileLayer() = default; @@ -128,19 +130,10 @@ void PlacefileLayer::Render( p->placefileIcons_->SetIconFiles(placefile->icon_files(), placefile->name()); - // Reset Placefile Text - p->placefileText_->Reset(); - for (auto& drawItem : placefile->GetDrawItems()) { switch (drawItem->itemType_) { - case gr::Placefile::ItemType::Text: - p->placefileText_->AddText( - std::static_pointer_cast( - drawItem)); - break; - case gr::Placefile::ItemType::Icon: p->placefileIcons_->AddIcon( std::static_pointer_cast( @@ -168,7 +161,7 @@ void PlacefileLayer::Deinitialize() void PlacefileLayer::ReloadData() { - // TODO: No longer needed after moving Icon and Text Render items here + // TODO: No longer needed after moving Icon Render items here p->dirty_ = true; boost::asio::post( @@ -188,15 +181,18 @@ void PlacefileLayer::ReloadData() return; } - // Reset Placefile Polygons + // Start draw items p->placefilePolygons_->StartPolygons(); + p->placefileText_->StartText(); for (auto& drawItem : placefile->GetDrawItems()) { switch (drawItem->itemType_) { case gr::Placefile::ItemType::Text: - // TODO + p->placefileText_->AddText( + std::static_pointer_cast( + drawItem)); break; case gr::Placefile::ItemType::Icon: @@ -214,8 +210,11 @@ void PlacefileLayer::ReloadData() } } - // Finish Placefile Polygons + // Finish draw items p->placefilePolygons_->FinishPolygons(); + p->placefileText_->FinishText(); + + Q_EMIT DataReloaded(); }); } diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.hpp b/scwx-qt/source/scwx/qt/map/placefile_layer.hpp index 68fd38c5..9c08db10 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.hpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.hpp @@ -13,6 +13,8 @@ namespace map class PlacefileLayer : public DrawLayer { + Q_OBJECT + public: explicit PlacefileLayer(std::shared_ptr context, const std::string& placefileName); @@ -28,6 +30,9 @@ public: void ReloadData(); +signals: + void DataReloaded(); + private: class Impl; std::unique_ptr p; From e021484bfba007f42112c3469307ff5c53e298a0 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 19 Aug 2023 22:19:32 -0500 Subject: [PATCH 066/199] Update placefile icon data outside of render loop, polygon and text cleanup --- .../scwx/qt/gl/draw/placefile_icons.cpp | 337 ++++++++++-------- .../scwx/qt/gl/draw/placefile_icons.hpp | 9 +- .../scwx/qt/gl/draw/placefile_polygons.cpp | 2 + .../source/scwx/qt/gl/draw/placefile_text.cpp | 2 + .../source/scwx/qt/map/placefile_layer.cpp | 40 +-- 5 files changed, 212 insertions(+), 178 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp index 5b8c876c..14bde0d9 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp @@ -36,39 +36,16 @@ struct PlacefileIconInfo // Resolve using base URL auto baseUrl = QUrl::fromUserInput(QString::fromStdString(baseUrlString)); auto relativeUrl = QUrl(QString::fromStdString(iconFile->filename_)); + resolvedUrl_ = baseUrl.resolved(relativeUrl).toString().toStdString(); - texture_ = util::TextureAtlas::Instance().GetTextureAttributes( - baseUrl.resolved(relativeUrl).toString().toStdString()); - - if (iconFile->iconWidth_ > 0 && iconFile->iconHeight_ > 0) - { - columns_ = texture_.size_.x / iconFile->iconWidth_; - rows_ = texture_.size_.y / iconFile->iconHeight_; - } - else - { - columns_ = 0u; - rows_ = 0u; - } - - numIcons_ = columns_ * rows_; - - // Pixel size - float xFactor = 0.0f; - float yFactor = 0.0f; - - if (texture_.size_.x > 0 && texture_.size_.y > 0) - { - xFactor = (texture_.sRight_ - texture_.sLeft_) / texture_.size_.x; - yFactor = (texture_.tBottom_ - texture_.tTop_) / texture_.size_.y; - } - - scaledWidth_ = iconFile_->iconWidth_ * xFactor; - scaledHeight_ = iconFile_->iconHeight_ * yFactor; + UpdateTextureInfo(); } + void UpdateTextureInfo(); + + std::string resolvedUrl_; std::shared_ptr iconFile_; - util::TextureAttributes texture_; + util::TextureAttributes texture_ {}; std::size_t rows_ {}; std::size_t columns_ {}; std::size_t numIcons_ {}; @@ -99,10 +76,20 @@ public: bool dirty_ {false}; bool thresholded_ {false}; - boost::unordered_flat_map - iconFiles_ {}; + std::mutex iconMutex_; - std::vector> iconList_ {}; + boost::unordered_flat_map + currentIconFiles_ {}; + boost::unordered_flat_map + newIconFiles_ {}; + + std::vector> + currentIconList_ {}; + std::vector> + newIconList_ {}; + + std::vector iconBuffer_ {}; + std::vector thresholdBuffer_ {}; std::shared_ptr shaderProgram_; GLint uMVPMatrixLocation_; @@ -115,6 +102,7 @@ public: GLsizei numVertices_; + void UpdateBuffers(); void Update(); }; @@ -217,7 +205,9 @@ void PlacefileIcons::Initialize() void PlacefileIcons::Render( const QMapLibreGL::CustomLayerRenderParameters& params) { - if (!p->iconList_.empty()) + std::unique_lock lock {p->iconMutex_}; + + if (!p->currentIconList_.empty()) { gl::OpenGLFunctions& gl = p->context_->gl(); @@ -257,20 +247,61 @@ void PlacefileIcons::Deinitialize() gl.glDeleteVertexArrays(1, &p->vao_); gl.glDeleteBuffers(2, p->vbo_.data()); + + std::unique_lock lock {p->iconMutex_}; + + p->currentIconList_.clear(); + p->currentIconFiles_.clear(); + p->iconBuffer_.clear(); + p->thresholdBuffer_.clear(); +} + +void PlacefileIconInfo::UpdateTextureInfo() +{ + texture_ = util::TextureAtlas::Instance().GetTextureAttributes(resolvedUrl_); + + if (iconFile_->iconWidth_ > 0 && iconFile_->iconHeight_ > 0) + { + columns_ = texture_.size_.x / iconFile_->iconWidth_; + rows_ = texture_.size_.y / iconFile_->iconHeight_; + } + else + { + columns_ = 0u; + rows_ = 0u; + } + + numIcons_ = columns_ * rows_; + + // Pixel size + float xFactor = 0.0f; + float yFactor = 0.0f; + + if (texture_.size_.x > 0 && texture_.size_.y > 0) + { + xFactor = (texture_.sRight_ - texture_.sLeft_) / texture_.size_.x; + yFactor = (texture_.tBottom_ - texture_.tTop_) / texture_.size_.y; + } + + scaledWidth_ = iconFile_->iconWidth_ * xFactor; + scaledHeight_ = iconFile_->iconHeight_ * yFactor; +} + +void PlacefileIcons::StartIcons() +{ + // Clear the new buffer + p->newIconList_.clear(); + p->newIconFiles_.clear(); } void PlacefileIcons::SetIconFiles( const std::vector>& iconFiles, const std::string& baseUrl) { - p->dirty_ = true; - // Populate icon file map - p->iconFiles_.clear(); - for (auto& file : iconFiles) { - p->iconFiles_.emplace( + p->newIconFiles_.emplace( std::piecewise_construct, std::tuple {file->fileNumber_}, std::forward_as_tuple(PlacefileIconInfo {file, baseUrl})); @@ -282,138 +313,152 @@ void PlacefileIcons::AddIcon( { if (di != nullptr) { - p->iconList_.emplace_back(di); - p->dirty_ = true; + p->newIconList_.emplace_back(di); } } -void PlacefileIcons::Reset() +void PlacefileIcons::FinishIcons() { - // Clear the icon list, and mark the draw item dirty - p->iconList_.clear(); + std::unique_lock lock {p->iconMutex_}; + + // Swap buffers + p->currentIconList_.swap(p->newIconList_); + p->currentIconFiles_.swap(p->newIconFiles_); + + // Clear the new buffers + p->newIconList_.clear(); + p->newIconFiles_.clear(); + + p->UpdateBuffers(); + + // Mark the draw item dirty p->dirty_ = true; } +void PlacefileIcons::Impl::UpdateBuffers() +{ + iconBuffer_.clear(); + iconBuffer_.reserve(currentIconList_.size() * kBufferLength); + thresholdBuffer_.clear(); + thresholdBuffer_.reserve(currentIconList_.size() * kVerticesPerRectangle); + numVertices_ = 0; + + for (auto& di : currentIconList_) + { + auto it = currentIconFiles_.find(di->fileNumber_); + if (it == currentIconFiles_.cend()) + { + // No file found + logger_->trace("Could not find file number: {}", di->fileNumber_); + continue; + } + + auto& icon = it->second; + + // Validate icon + if (di->iconNumber_ == 0 || di->iconNumber_ > icon.numIcons_) + { + // No icon found + logger_->trace("Invalid icon number: {}", di->iconNumber_); + continue; + } + + // Threshold value + units::length::nautical_miles threshold = di->threshold_; + GLint thresholdValue = static_cast(std::round(threshold.value())); + + // Latitude and longitude coordinates in degrees + const float lat = static_cast(di->latitude_); + const float lon = static_cast(di->longitude_); + + // Base X/Y offsets in pixels + const float x = static_cast(di->x_); + const float y = static_cast(di->y_); + + // Icon size + const float iw = static_cast(icon.iconFile_->iconWidth_); + const float ih = static_cast(icon.iconFile_->iconHeight_); + + // Hot X/Y (zero-based icon center) + const float hx = static_cast(icon.iconFile_->hotX_); + const float hy = static_cast(icon.iconFile_->hotY_); + + // Final X/Y offsets in pixels + const float lx = std::roundf(x - hx); + const float rx = std::roundf(lx + iw); + const float ty = std::roundf(y + hy); + const float by = std::roundf(ty - ih); + + // Angle in degrees + units::angle::degrees angle = di->angle_; + const float a = angle.value(); + + // Texture coordinates + const std::size_t iconRow = (di->iconNumber_ - 1) / icon.columns_; + const std::size_t iconColumn = (di->iconNumber_ - 1) % icon.columns_; + + const float iconX = iconColumn * icon.scaledWidth_; + const float iconY = iconRow * icon.scaledHeight_; + + const float ls = icon.texture_.sLeft_ + iconX; + const float rs = ls + icon.scaledWidth_; + const float tt = icon.texture_.tTop_ + iconY; + const float bt = tt + icon.scaledHeight_; + + // Fixed modulate color + const float mc0 = 1.0f; + const float mc1 = 1.0f; + const float mc2 = 1.0f; + const float mc3 = 1.0f; + + iconBuffer_.insert( + iconBuffer_.end(), + { + // Icon + lat, lon, lx, by, ls, bt, mc0, mc1, mc2, mc3, a, // BL + lat, lon, lx, ty, ls, tt, mc0, mc1, mc2, mc3, a, // TL + lat, lon, rx, by, rs, bt, mc0, mc1, mc2, mc3, a, // BR + lat, lon, rx, by, rs, bt, mc0, mc1, mc2, mc3, a, // BR + lat, lon, rx, ty, rs, tt, mc0, mc1, mc2, mc3, a, // TR + lat, lon, lx, ty, ls, tt, mc0, mc1, mc2, mc3, a // TL + }); + thresholdBuffer_.insert(thresholdBuffer_.end(), + {thresholdValue, // + thresholdValue, + thresholdValue, + thresholdValue, + thresholdValue, + thresholdValue}); + } + + dirty_ = true; +} + void PlacefileIcons::Impl::Update() { if (dirty_) { - static std::vector buffer {}; - static std::vector thresholds {}; - buffer.clear(); - buffer.reserve(iconList_.size() * kBufferLength); - thresholds.clear(); - thresholds.reserve(iconList_.size() * kVerticesPerRectangle); - numVertices_ = 0; - - for (auto& di : iconList_) - { - auto it = iconFiles_.find(di->fileNumber_); - if (it == iconFiles_.cend()) - { - // No file found - logger_->trace("Could not find file number: {}", di->fileNumber_); - continue; - } - - auto& icon = it->second; - - // Validate icon - if (di->iconNumber_ == 0 || di->iconNumber_ > icon.numIcons_) - { - // No icon found - logger_->trace("Invalid icon number: {}", di->iconNumber_); - continue; - } - - // Threshold value - units::length::nautical_miles threshold = di->threshold_; - GLint thresholdValue = - static_cast(std::round(threshold.value())); - - // Latitude and longitude coordinates in degrees - const float lat = static_cast(di->latitude_); - const float lon = static_cast(di->longitude_); - - // Base X/Y offsets in pixels - const float x = static_cast(di->x_); - const float y = static_cast(di->y_); - - // Icon size - const float iw = static_cast(icon.iconFile_->iconWidth_); - const float ih = static_cast(icon.iconFile_->iconHeight_); - - // Hot X/Y (zero-based icon center) - const float hx = static_cast(icon.iconFile_->hotX_); - const float hy = static_cast(icon.iconFile_->hotY_); - - // Final X/Y offsets in pixels - const float lx = std::roundf(x - hx); - const float rx = std::roundf(lx + iw); - const float ty = std::roundf(y + hy); - const float by = std::roundf(ty - ih); - - // Angle in degrees - units::angle::degrees angle = di->angle_; - const float a = angle.value(); - - // Texture coordinates - const std::size_t iconRow = (di->iconNumber_ - 1) / icon.columns_; - const std::size_t iconColumn = (di->iconNumber_ - 1) % icon.columns_; - - const float iconX = iconColumn * icon.scaledWidth_; - const float iconY = iconRow * icon.scaledHeight_; - - const float ls = icon.texture_.sLeft_ + iconX; - const float rs = ls + icon.scaledWidth_; - const float tt = icon.texture_.tTop_ + iconY; - const float bt = tt + icon.scaledHeight_; - - // Fixed modulate color - const float mc0 = 1.0f; - const float mc1 = 1.0f; - const float mc2 = 1.0f; - const float mc3 = 1.0f; - - buffer.insert(buffer.end(), - { - // Icon - lat, lon, lx, by, ls, bt, mc0, mc1, mc2, mc3, a, // BL - lat, lon, lx, ty, ls, tt, mc0, mc1, mc2, mc3, a, // TL - lat, lon, rx, by, rs, bt, mc0, mc1, mc2, mc3, a, // BR - lat, lon, rx, by, rs, bt, mc0, mc1, mc2, mc3, a, // BR - lat, lon, rx, ty, rs, tt, mc0, mc1, mc2, mc3, a, // TR - lat, lon, lx, ty, ls, tt, mc0, mc1, mc2, mc3, a // TL - }); - thresholds.insert(thresholds.end(), - {thresholdValue, // - thresholdValue, - thresholdValue, - thresholdValue, - thresholdValue, - thresholdValue}); - - numVertices_ += 6; - } - gl::OpenGLFunctions& gl = context_->gl(); // Buffer vertex data gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(float) * buffer.size(), - buffer.data(), + sizeof(float) * iconBuffer_.size(), + iconBuffer_.data(), GL_DYNAMIC_DRAW); // Buffer threshold data gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(GLint) * thresholds.size(), - thresholds.data(), + sizeof(GLint) * thresholdBuffer_.size(), + thresholdBuffer_.data(), GL_DYNAMIC_DRAW); - dirty_ = false; + numVertices_ = + static_cast(iconBuffer_.size() / kVerticesPerRectangle); } + + dirty_ = false; } } // namespace draw diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp index 9dd1b3e7..3abc4086 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp @@ -33,6 +33,11 @@ public: void Render(const QMapLibreGL::CustomLayerRenderParameters& params) override; void Deinitialize() override; + /** + * Resets and prepares the draw item for adding a new set of icons. + */ + void StartIcons(); + /** * Configures the textures for drawing the placefile icons. * @@ -52,9 +57,9 @@ public: void AddIcon(const std::shared_ptr& di); /** - * Resets the list of icons in preparation for rendering a new frame. + * Finalizes the draw item after adding new icons. */ - void Reset(); + void FinishIcons(); private: class Impl; diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp index 11152a5a..87077690 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp @@ -234,6 +234,8 @@ void PlacefilePolygons::Deinitialize() gl.glDeleteVertexArrays(1, &p->vao_); gl.glDeleteBuffers(2, p->vbo_.data()); + std::unique_lock lock {p->bufferMutex_}; + // Clear the current buffers p->currentBuffer_.clear(); p->currentThresholdBuffer_.clear(); diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp index e15d7fdc..d2bfa0f1 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp @@ -205,6 +205,8 @@ void PlacefileText::Impl::RenderText( void PlacefileText::Deinitialize() { + std::unique_lock lock {p->listMutex_}; + // Clear the text list p->textList_.clear(); } diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp index fc60cefa..d1bb23b2 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp @@ -44,7 +44,6 @@ public: std::string placefileName_; std::mutex dataMutex_ {}; - bool dirty_ {true}; std::shared_ptr placefileIcons_; std::shared_ptr placefilePolygons_; @@ -89,9 +88,9 @@ std::string PlacefileLayer::placefile_name() const void PlacefileLayer::set_placefile_name(const std::string& placefileName) { p->placefileName_ = placefileName; - p->dirty_ = true; - p->placefileText_->set_placefile_name(placefileName); + + ReloadData(); } void PlacefileLayer::Initialize() @@ -122,29 +121,6 @@ void PlacefileLayer::Render( p->placefileIcons_->set_thresholded(thresholded); p->placefilePolygons_->set_thresholded(thresholded); p->placefileText_->set_thresholded(thresholded); - - if (p->dirty_) - { - // Reset Placefile Icons - p->placefileIcons_->Reset(); - p->placefileIcons_->SetIconFiles(placefile->icon_files(), - placefile->name()); - - for (auto& drawItem : placefile->GetDrawItems()) - { - switch (drawItem->itemType_) - { - case gr::Placefile::ItemType::Icon: - p->placefileIcons_->AddIcon( - std::static_pointer_cast( - drawItem)); - break; - - default: - break; - } - } - } } DrawLayer::Render(params); @@ -161,9 +137,6 @@ void PlacefileLayer::Deinitialize() void PlacefileLayer::ReloadData() { - // TODO: No longer needed after moving Icon Render items here - p->dirty_ = true; - boost::asio::post( p->threadPool_, [this]() @@ -182,9 +155,13 @@ void PlacefileLayer::ReloadData() } // Start draw items + p->placefileIcons_->StartIcons(); p->placefilePolygons_->StartPolygons(); p->placefileText_->StartText(); + p->placefileIcons_->SetIconFiles(placefile->icon_files(), + placefile->name()); + for (auto& drawItem : placefile->GetDrawItems()) { switch (drawItem->itemType_) @@ -196,7 +173,9 @@ void PlacefileLayer::ReloadData() break; case gr::Placefile::ItemType::Icon: - // TODO + p->placefileIcons_->AddIcon( + std::static_pointer_cast( + drawItem)); break; case gr::Placefile::ItemType::Polygon: @@ -211,6 +190,7 @@ void PlacefileLayer::ReloadData() } // Finish draw items + p->placefileIcons_->FinishIcons(); p->placefilePolygons_->FinishPolygons(); p->placefileText_->FinishText(); From 565734217bf39cef3208c241cd7f0c39916d6f9e Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 19 Aug 2023 22:52:38 -0500 Subject: [PATCH 067/199] Update placefile icon texture coordinates when the texture atlas changes --- scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp | 11 +++++++ scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp | 7 +++-- .../scwx/qt/gl/draw/placefile_icons.cpp | 31 ++++++++++++------- .../scwx/qt/gl/draw/placefile_icons.hpp | 3 +- scwx-qt/source/scwx/qt/gl/gl_context.cpp | 5 +++ scwx-qt/source/scwx/qt/gl/gl_context.hpp | 2 ++ scwx-qt/source/scwx/qt/map/draw_layer.cpp | 12 ++++++- 7 files changed, 55 insertions(+), 16 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp b/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp index 1cc558a5..bbbe155b 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp @@ -41,6 +41,17 @@ DrawItem::~DrawItem() = default; DrawItem::DrawItem(DrawItem&&) noexcept = default; DrawItem& DrawItem::operator=(DrawItem&&) noexcept = default; +void DrawItem::Render( + const QMapLibreGL::CustomLayerRenderParameters& /* params */) +{ +} + +void DrawItem::Render(const QMapLibreGL::CustomLayerRenderParameters& params, + bool /* textureAtlasChanged */) +{ + Render(params); +} + void DrawItem::UseDefaultProjection( const QMapLibreGL::CustomLayerRenderParameters& params, GLint uMVPMatrixLocation) diff --git a/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp b/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp index dbacb008..4c3b7140 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp @@ -28,9 +28,10 @@ public: DrawItem& operator=(DrawItem&&) noexcept; virtual void Initialize() = 0; - virtual void - Render(const QMapLibreGL::CustomLayerRenderParameters& params) = 0; - virtual void Deinitialize() = 0; + virtual void Render(const QMapLibreGL::CustomLayerRenderParameters& params); + virtual void Render(const QMapLibreGL::CustomLayerRenderParameters& params, + bool textureAtlasChanged); + virtual void Deinitialize() = 0; protected: void diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp index 14bde0d9..11d844c7 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp @@ -37,8 +37,6 @@ struct PlacefileIconInfo auto baseUrl = QUrl::fromUserInput(QString::fromStdString(baseUrlString)); auto relativeUrl = QUrl(QString::fromStdString(iconFile->filename_)); resolvedUrl_ = baseUrl.resolved(relativeUrl).toString().toStdString(); - - UpdateTextureInfo(); } void UpdateTextureInfo(); @@ -78,10 +76,9 @@ public: std::mutex iconMutex_; - boost::unordered_flat_map + boost::unordered_flat_map currentIconFiles_ {}; - boost::unordered_flat_map - newIconFiles_ {}; + boost::unordered_flat_map newIconFiles_ {}; std::vector> currentIconList_ {}; @@ -103,7 +100,7 @@ public: GLsizei numVertices_; void UpdateBuffers(); - void Update(); + void Update(bool textureAtlasChanged); }; PlacefileIcons::PlacefileIcons(std::shared_ptr context) : @@ -203,7 +200,8 @@ void PlacefileIcons::Initialize() } void PlacefileIcons::Render( - const QMapLibreGL::CustomLayerRenderParameters& params) + const QMapLibreGL::CustomLayerRenderParameters& params, + bool textureAtlasChanged) { std::unique_lock lock {p->iconMutex_}; @@ -213,7 +211,7 @@ void PlacefileIcons::Render( gl.glBindVertexArray(p->vao_); - p->Update(); + p->Update(textureAtlasChanged); p->shaderProgram_->Use(); UseRotationProjection(params, p->uMVPMatrixLocation_); UseMapProjection( @@ -329,8 +327,6 @@ void PlacefileIcons::FinishIcons() p->newIconList_.clear(); p->newIconFiles_.clear(); - p->UpdateBuffers(); - // Mark the draw item dirty p->dirty_ = true; } @@ -434,8 +430,21 @@ void PlacefileIcons::Impl::UpdateBuffers() dirty_ = true; } -void PlacefileIcons::Impl::Update() +void PlacefileIcons::Impl::Update(bool textureAtlasChanged) { + // If the texture atlas has changed + if (textureAtlasChanged) + { + // Update texture coordinates + for (auto& iconFile : currentIconFiles_) + { + iconFile.second.UpdateTextureInfo(); + } + + // Update OpenGL buffer data + UpdateBuffers(); + } + if (dirty_) { gl::OpenGLFunctions& gl = context_->gl(); diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp index 3abc4086..08069ac5 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp @@ -30,7 +30,8 @@ public: void set_thresholded(bool thresholded); void Initialize() override; - void Render(const QMapLibreGL::CustomLayerRenderParameters& params) override; + void Render(const QMapLibreGL::CustomLayerRenderParameters& params, + bool textureAtlasChanged) override; void Deinitialize() override; /** diff --git a/scwx-qt/source/scwx/qt/gl/gl_context.cpp b/scwx-qt/source/scwx/qt/gl/gl_context.cpp index 2b996c54..64e2df46 100644 --- a/scwx-qt/source/scwx/qt/gl/gl_context.cpp +++ b/scwx-qt/source/scwx/qt/gl/gl_context.cpp @@ -52,6 +52,11 @@ gl::OpenGLFunctions& GlContext::gl() return p->gl_; } +std::uint64_t GlContext::texture_buffer_count() const +{ + return p->textureBufferCount_; +} + std::shared_ptr GlContext::GetShaderProgram(const std::string& vertexPath, const std::string& fragmentPath) diff --git a/scwx-qt/source/scwx/qt/gl/gl_context.hpp b/scwx-qt/source/scwx/qt/gl/gl_context.hpp index 5c53de59..b09ff403 100644 --- a/scwx-qt/source/scwx/qt/gl/gl_context.hpp +++ b/scwx-qt/source/scwx/qt/gl/gl_context.hpp @@ -24,6 +24,8 @@ public: gl::OpenGLFunctions& gl(); + std::uint64_t texture_buffer_count() const; + std::shared_ptr GetShaderProgram(const std::string& vertexPath, const std::string& fragmentPath); diff --git a/scwx-qt/source/scwx/qt/map/draw_layer.cpp b/scwx-qt/source/scwx/qt/map/draw_layer.cpp index 91c823e3..097f7a40 100644 --- a/scwx-qt/source/scwx/qt/map/draw_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/draw_layer.cpp @@ -24,6 +24,8 @@ public: std::shared_ptr context_; std::vector> drawList_; GLuint textureAtlas_; + + std::uint64_t textureAtlasBuildCount_ {}; }; DrawLayer::DrawLayer(std::shared_ptr context) : @@ -47,13 +49,21 @@ void DrawLayer::Render(const QMapLibreGL::CustomLayerRenderParameters& params) gl::OpenGLFunctions& gl = p->context_->gl(); p->textureAtlas_ = p->context_->GetTextureAtlas(); + // Determine if the texture atlas changed since last render + std::uint64_t newTextureAtlasBuildCount = + p->context_->texture_buffer_count(); + bool textureAtlasChanged = + newTextureAtlasBuildCount != p->textureAtlasBuildCount_; + gl.glActiveTexture(GL_TEXTURE0); gl.glBindTexture(GL_TEXTURE_2D, p->textureAtlas_); for (auto& item : p->drawList_) { - item->Render(params); + item->Render(params, textureAtlasChanged); } + + p->textureAtlasBuildCount_ = newTextureAtlasBuildCount; } void DrawLayer::Deinitialize() From e3c6850a1e8dea16e6d8c805be443fb49f2a2eb4 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 19 Aug 2023 22:53:18 -0500 Subject: [PATCH 068/199] Use LatLongToScreenCoordinate from maplibre utility in DrawItem --- scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp b/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp index bbbe155b..583c7929 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp @@ -1,4 +1,5 @@ #include +#include #include @@ -82,23 +83,6 @@ void DrawItem::UseRotationProjection( uMVPMatrixLocation, 1, GL_FALSE, glm::value_ptr(projection)); } -// TODO: Refactor to utility class -static glm::vec2 -LatLongToScreenCoordinate(const QMapLibreGL::Coordinate& coordinate) -{ - static constexpr double RAD2DEG_D = 180.0 / M_PI; - - double latitude = std::clamp( - coordinate.first, -mbgl::util::LATITUDE_MAX, mbgl::util::LATITUDE_MAX); - glm::vec2 screen { - mbgl::util::LONGITUDE_MAX + coordinate.second, - -(mbgl::util::LONGITUDE_MAX - - RAD2DEG_D * - std::log(std::tan(M_PI / 4.0 + - latitude * M_PI / mbgl::util::DEGREES_MAX)))}; - return screen; -} - void DrawItem::UseMapProjection( const QMapLibreGL::CustomLayerRenderParameters& params, GLint uMVPMatrixLocation, @@ -120,7 +104,7 @@ void DrawItem::UseMapProjection( gl.glUniform2fv(uMapScreenCoordLocation, 1, - glm::value_ptr(LatLongToScreenCoordinate( + glm::value_ptr(util::maplibre::LatLongToScreenCoordinate( {params.latitude, params.longitude}))); gl.glUniformMatrix4fv( From c85e4cef58b614eb4e0abd75ecdc81e2e0ba22dd Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 20 Aug 2023 22:37:46 -0500 Subject: [PATCH 069/199] Basic placefile lines rendering Desired to make the line styling look more like warning boxes --- scwx-qt/scwx-qt.cmake | 2 + .../scwx/qt/gl/draw/placefile_lines.cpp | 386 ++++++++++++++++++ .../scwx/qt/gl/draw/placefile_lines.hpp | 61 +++ .../source/scwx/qt/map/placefile_layer.cpp | 13 + .../source/scwx/qt/util/geographic_lib.cpp | 13 +- .../source/scwx/qt/util/geographic_lib.hpp | 14 + 6 files changed, 487 insertions(+), 2 deletions(-) create mode 100644 scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp create mode 100644 scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 129d3f14..fd8bd87a 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -58,12 +58,14 @@ set(SRC_GL source/scwx/qt/gl/gl_context.cpp set(HDR_GL_DRAW source/scwx/qt/gl/draw/draw_item.hpp source/scwx/qt/gl/draw/geo_line.hpp source/scwx/qt/gl/draw/placefile_icons.hpp + source/scwx/qt/gl/draw/placefile_lines.hpp source/scwx/qt/gl/draw/placefile_polygons.hpp source/scwx/qt/gl/draw/placefile_text.hpp source/scwx/qt/gl/draw/rectangle.hpp) set(SRC_GL_DRAW source/scwx/qt/gl/draw/draw_item.cpp source/scwx/qt/gl/draw/geo_line.cpp source/scwx/qt/gl/draw/placefile_icons.cpp + source/scwx/qt/gl/draw/placefile_lines.cpp source/scwx/qt/gl/draw/placefile_polygons.cpp source/scwx/qt/gl/draw/placefile_text.cpp source/scwx/qt/gl/draw/rectangle.cpp) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp new file mode 100644 index 00000000..50714a4e --- /dev/null +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp @@ -0,0 +1,386 @@ +#include +#include +#include +#include +#include + +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace gl +{ +namespace draw +{ + +static const std::string logPrefix_ = "scwx::qt::gl::draw::placefile_lines"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +static constexpr std::size_t kNumRectangles = 1; +static constexpr std::size_t kNumTriangles = kNumRectangles * 2; +static constexpr std::size_t kVerticesPerTriangle = 3; +static constexpr std::size_t kVerticesPerRectangle = kVerticesPerTriangle * 2; +static constexpr std::size_t kPointsPerVertex = 11; +static constexpr std::size_t kBufferLength = + kNumTriangles * kVerticesPerTriangle * kPointsPerVertex; + +static const std::string kTextureName_ = "lines/default-1x7"; + +class PlacefileLines::Impl +{ +public: + explicit Impl(std::shared_ptr context) : + context_ {context}, + shaderProgram_ {nullptr}, + uMVPMatrixLocation_(GL_INVALID_INDEX), + uMapMatrixLocation_(GL_INVALID_INDEX), + uMapScreenCoordLocation_(GL_INVALID_INDEX), + uMapDistanceLocation_(GL_INVALID_INDEX), + vao_ {GL_INVALID_INDEX}, + vbo_ {GL_INVALID_INDEX}, + numVertices_ {0} + { + } + + ~Impl() {} + + std::shared_ptr context_; + + bool dirty_ {false}; + bool thresholded_ {false}; + + std::mutex lineMutex_; + + std::vector> + currentLineList_ {}; + std::vector> + newLineList_ {}; + + std::size_t currentNumLines_ {}; + std::size_t newNumLines_ {}; + + std::vector lineBuffer_ {}; + std::vector thresholdBuffer_ {}; + + std::shared_ptr shaderProgram_; + GLint uMVPMatrixLocation_; + GLint uMapMatrixLocation_; + GLint uMapScreenCoordLocation_; + GLint uMapDistanceLocation_; + + GLuint vao_; + std::array vbo_; + + GLsizei numVertices_; + + void UpdateBuffers(); + void Update(bool textureAtlasChanged); +}; + +PlacefileLines::PlacefileLines(std::shared_ptr context) : + DrawItem(context->gl()), p(std::make_unique(context)) +{ +} +PlacefileLines::~PlacefileLines() = default; + +PlacefileLines::PlacefileLines(PlacefileLines&&) noexcept = default; +PlacefileLines& PlacefileLines::operator=(PlacefileLines&&) noexcept = default; + +void PlacefileLines::set_thresholded(bool thresholded) +{ + p->thresholded_ = thresholded; +} + +void PlacefileLines::Initialize() +{ + gl::OpenGLFunctions& gl = p->context_->gl(); + + p->shaderProgram_ = p->context_->GetShaderProgram( + {{GL_VERTEX_SHADER, ":/gl/geo_texture2d.vert"}, + {GL_GEOMETRY_SHADER, ":/gl/threshold.geom"}, + {GL_FRAGMENT_SHADER, ":/gl/color.frag"}}); + + p->uMVPMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMVPMatrix"); + p->uMapMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMapMatrix"); + p->uMapScreenCoordLocation_ = + p->shaderProgram_->GetUniformLocation("uMapScreenCoord"); + p->uMapDistanceLocation_ = + p->shaderProgram_->GetUniformLocation("uMapDistance"); + + gl.glGenVertexArrays(1, &p->vao_); + gl.glGenBuffers(2, p->vbo_.data()); + + gl.glBindVertexArray(p->vao_); + gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); + gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + + // aLatLong + gl.glVertexAttribPointer(0, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + static_cast(0)); + gl.glEnableVertexAttribArray(0); + + // aXYOffset + gl.glVertexAttribPointer(1, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(2 * sizeof(float))); + gl.glEnableVertexAttribArray(1); + + // aTexCoord + gl.glVertexAttribPointer(2, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(4 * sizeof(float))); + gl.glEnableVertexAttribArray(2); + + // aModulate + gl.glVertexAttribPointer(3, + 4, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(6 * sizeof(float))); + gl.glEnableVertexAttribArray(3); + + // aAngle + gl.glVertexAttribPointer(4, + 1, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(10 * sizeof(float))); + gl.glEnableVertexAttribArray(4); + + gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); + gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + + // aThreshold + gl.glVertexAttribIPointer(5, // + 1, + GL_INT, + 0, + static_cast(0)); + gl.glVertexAttribDivisor(5, 2); // One value per rectangle + gl.glEnableVertexAttribArray(5); + + p->dirty_ = true; +} + +void PlacefileLines::Render( + const QMapLibreGL::CustomLayerRenderParameters& params, + bool textureAtlasChanged) +{ + std::unique_lock lock {p->lineMutex_}; + + if (!p->currentLineList_.empty()) + { + gl::OpenGLFunctions& gl = p->context_->gl(); + + gl.glBindVertexArray(p->vao_); + + p->Update(textureAtlasChanged); + p->shaderProgram_->Use(); + UseRotationProjection(params, p->uMVPMatrixLocation_); + UseMapProjection( + params, p->uMapMatrixLocation_, p->uMapScreenCoordLocation_); + + if (p->thresholded_) + { + // If thresholding is enabled, set the map distance + units::length::nautical_miles mapDistance = + util::maplibre::GetMapDistance(params); + gl.glUniform1f(p->uMapDistanceLocation_, mapDistance.value()); + } + else + { + // If thresholding is disabled, set the map distance to 0 + gl.glUniform1f(p->uMapDistanceLocation_, 0.0f); + } + + // Interpolate texture coordinates + gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // Draw icons + gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); + } +} + +void PlacefileLines::Deinitialize() +{ + gl::OpenGLFunctions& gl = p->context_->gl(); + + gl.glDeleteVertexArrays(1, &p->vao_); + gl.glDeleteBuffers(2, p->vbo_.data()); + + std::unique_lock lock {p->lineMutex_}; + + p->currentLineList_.clear(); + p->lineBuffer_.clear(); + p->thresholdBuffer_.clear(); +} + +void PlacefileLines::StartLines() +{ + // Clear the new buffer + p->newLineList_.clear(); + + p->newNumLines_ = 0u; +} + +void PlacefileLines::AddLine( + const std::shared_ptr& di) +{ + if (di != nullptr) + { + p->newLineList_.emplace_back(di); + if (!di->elements_.empty()) + { + p->newNumLines_ += di->elements_.size() - 1; + } + } +} + +void PlacefileLines::FinishLines() +{ + std::unique_lock lock {p->lineMutex_}; + + // Swap buffers + p->currentLineList_.swap(p->newLineList_); + + // Clear the new buffers + p->newLineList_.clear(); + + // Update the number of lines + p->currentNumLines_ = p->newNumLines_; + + // Mark the draw item dirty + p->dirty_ = true; +} + +void PlacefileLines::Impl::UpdateBuffers() +{ + auto texture = + util::TextureAtlas::Instance().GetTextureAttributes(kTextureName_); + + // Texture coordinates (rotated) + const float ls = texture.tTop_; + const float rs = texture.tBottom_; + const float tt = texture.sLeft_; + const float bt = texture.sRight_; + + lineBuffer_.clear(); + lineBuffer_.reserve(currentNumLines_ * kBufferLength); + thresholdBuffer_.clear(); + thresholdBuffer_.reserve(currentNumLines_); + numVertices_ = 0; + + for (auto& di : currentLineList_) + { + // Threshold value + units::length::nautical_miles threshold = di->threshold_; + GLint thresholdValue = static_cast(std::round(threshold.value())); + + // TODO: Angle in degrees + // const float a = 0.0f; + + // Line width / half width + const float lw = static_cast(di->width_); + const float hw = lw * 0.5f; + + // For each element pair inside a Line statement + for (std::size_t i = 0; i < di->elements_.size() - 1; ++i) + { + // Latitude and longitude coordinates in degrees + const float lat1 = static_cast(di->elements_[i].latitude_); + const float lon1 = static_cast(di->elements_[i].longitude_); + const float lat2 = static_cast(di->elements_[i + 1].latitude_); + const float lon2 = static_cast(di->elements_[i + 1].longitude_); + + // TODO: Base X/Y offsets in pixels + // const float x1 = static_cast(di->elements_[i].x_); + // const float y1 = static_cast(di->elements_[i].y_); + // const float x2 = static_cast(di->elements_[i + 1].x_); + // const float y2 = static_cast(di->elements_[i + 1].y_); + + // TODO: Refactor this to placefile update time instead of buffer time + const units::angle::degrees angle = + util::GeographicLib::GetAngle(lat1, lon1, lat2, lon2); + const float a = static_cast(angle.value()); + + // Final X/Y offsets in pixels + const float lx = -hw; + const float rx = +hw; + const float ty = +hw; + const float by = -hw; + + // Modulate color + const float mc0 = di->color_[0] / 255.0f; + const float mc1 = di->color_[1] / 255.0f; + const float mc2 = di->color_[2] / 255.0f; + const float mc3 = di->color_[3] / 255.0f; + + lineBuffer_.insert( + lineBuffer_.end(), + { + // Icon + lat1, lon1, lx, by, ls, bt, mc0, mc1, mc2, mc3, a, // BL + lat2, lon2, lx, ty, ls, tt, mc0, mc1, mc2, mc3, a, // TL + lat1, lon1, rx, by, rs, bt, mc0, mc1, mc2, mc3, a, // BR + lat1, lon1, rx, by, rs, bt, mc0, mc1, mc2, mc3, a, // BR + lat2, lon2, rx, ty, rs, tt, mc0, mc1, mc2, mc3, a, // TR + lat2, lon2, lx, ty, ls, tt, mc0, mc1, mc2, mc3, a // TL + }); + thresholdBuffer_.push_back(thresholdValue); + } + } + + dirty_ = true; +} + +void PlacefileLines::Impl::Update(bool textureAtlasChanged) +{ + // If the texture atlas has changed + if (dirty_ || textureAtlasChanged) + { + // Update OpenGL buffer data + UpdateBuffers(); + + gl::OpenGLFunctions& gl = context_->gl(); + + // Buffer vertex data + gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); + gl.glBufferData(GL_ARRAY_BUFFER, + sizeof(float) * lineBuffer_.size(), + lineBuffer_.data(), + GL_DYNAMIC_DRAW); + + // Buffer threshold data + gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); + gl.glBufferData(GL_ARRAY_BUFFER, + sizeof(GLint) * thresholdBuffer_.size(), + thresholdBuffer_.data(), + GL_DYNAMIC_DRAW); + + numVertices_ = + static_cast(lineBuffer_.size() / kVerticesPerRectangle); + } + + dirty_ = false; +} + +} // namespace draw +} // namespace gl +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp new file mode 100644 index 00000000..2252e441 --- /dev/null +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace gl +{ +namespace draw +{ + +class PlacefileLines : public DrawItem +{ +public: + explicit PlacefileLines(std::shared_ptr context); + ~PlacefileLines(); + + PlacefileLines(const PlacefileLines&) = delete; + PlacefileLines& operator=(const PlacefileLines&) = delete; + + PlacefileLines(PlacefileLines&&) noexcept; + PlacefileLines& operator=(PlacefileLines&&) noexcept; + + void set_thresholded(bool thresholded); + + void Initialize() override; + void Render(const QMapLibreGL::CustomLayerRenderParameters& params, + bool textureAtlasChanged) override; + void Deinitialize() override; + + /** + * Resets and prepares the draw item for adding a new set of lines. + */ + void StartLines(); + + /** + * Adds a placefile line to the internal draw list. + * + * @param [in] di Placefile line + */ + void AddLine(const std::shared_ptr& di); + + /** + * Finalizes the draw item after adding new lines. + */ + void FinishLines(); + +private: + class Impl; + + std::unique_ptr p; +}; + +} // namespace draw +} // namespace gl +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp index d1bb23b2..b6ec78d4 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -27,6 +28,7 @@ public: self_ {self}, placefileName_ {placefileName}, placefileIcons_ {std::make_shared(context)}, + placefileLines_ {std::make_shared(context)}, placefilePolygons_ { std::make_shared(context)}, placefileText_ { @@ -46,6 +48,7 @@ public: std::mutex dataMutex_ {}; std::shared_ptr placefileIcons_; + std::shared_ptr placefileLines_; std::shared_ptr placefilePolygons_; std::shared_ptr placefileText_; }; @@ -56,6 +59,7 @@ PlacefileLayer::PlacefileLayer(std::shared_ptr context, p(std::make_unique(this, context, placefileName)) { AddDrawItem(p->placefileIcons_); + AddDrawItem(p->placefileLines_); AddDrawItem(p->placefilePolygons_); AddDrawItem(p->placefileText_); @@ -119,6 +123,7 @@ void PlacefileLayer::Render( bool thresholded = placefileManager->placefile_thresholded(placefile->name()); p->placefileIcons_->set_thresholded(thresholded); + p->placefileLines_->set_thresholded(thresholded); p->placefilePolygons_->set_thresholded(thresholded); p->placefileText_->set_thresholded(thresholded); } @@ -156,6 +161,7 @@ void PlacefileLayer::ReloadData() // Start draw items p->placefileIcons_->StartIcons(); + p->placefileLines_->StartLines(); p->placefilePolygons_->StartPolygons(); p->placefileText_->StartText(); @@ -178,6 +184,12 @@ void PlacefileLayer::ReloadData() drawItem)); break; + case gr::Placefile::ItemType::Line: + p->placefileLines_->AddLine( + std::static_pointer_cast( + drawItem)); + break; + case gr::Placefile::ItemType::Polygon: p->placefilePolygons_->AddPolygon( std::static_pointer_cast( @@ -191,6 +203,7 @@ void PlacefileLayer::ReloadData() // Finish draw items p->placefileIcons_->FinishIcons(); + p->placefileLines_->FinishLines(); p->placefilePolygons_->FinishPolygons(); p->placefileText_->FinishText(); diff --git a/scwx-qt/source/scwx/qt/util/geographic_lib.cpp b/scwx-qt/source/scwx/qt/util/geographic_lib.cpp index 0496d88a..6718715a 100644 --- a/scwx-qt/source/scwx/qt/util/geographic_lib.cpp +++ b/scwx-qt/source/scwx/qt/util/geographic_lib.cpp @@ -18,12 +18,21 @@ const ::GeographicLib::Geodesic& DefaultGeodesic() return geodesic_; } +units::angle::degrees +GetAngle(double lat1, double lon1, double lat2, double lon2) +{ + double azi1; + double azi2; + DefaultGeodesic().Inverse(lat1, lon1, lat2, lon2, azi1, azi2); + + return units::angle::degrees {azi1}; +} + units::length::meters GetDistance(double lat1, double lon1, double lat2, double lon2) { double distance; - util::GeographicLib::DefaultGeodesic().Inverse( - lat1, lon1, lat2, lon2, distance); + DefaultGeodesic().Inverse(lat1, lon1, lat2, lon2, distance); return units::length::meters {distance}; } diff --git a/scwx-qt/source/scwx/qt/util/geographic_lib.hpp b/scwx-qt/source/scwx/qt/util/geographic_lib.hpp index d93dce2b..d03aac04 100644 --- a/scwx-qt/source/scwx/qt/util/geographic_lib.hpp +++ b/scwx-qt/source/scwx/qt/util/geographic_lib.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include namespace scwx @@ -19,6 +20,19 @@ namespace GeographicLib */ const ::GeographicLib::Geodesic& DefaultGeodesic(); +/** + * Get the angle between two points. + * + * @param [in] lat1 latitude of point 1 (degrees) + * @param [in] lon1 longitude of point 1 (degrees) + * @param [in] lat2 latitude of point 2 (degrees) + * @param [in] lon2 longitude of point 2 (degrees) + * + * @return angle between point 1 and point 2 + */ +units::angle::degrees +GetAngle(double lat1, double lon1, double lat2, double lon2); + /** * Get the distance between two points. * From ddf11884a8537d38aa1d0b5bff1732aba74e36f2 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 20 Aug 2023 22:38:16 -0500 Subject: [PATCH 070/199] Buffer new icons when placefile changes --- scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp index 11d844c7..4c1ec1a4 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp @@ -433,7 +433,7 @@ void PlacefileIcons::Impl::UpdateBuffers() void PlacefileIcons::Impl::Update(bool textureAtlasChanged) { // If the texture atlas has changed - if (textureAtlasChanged) + if (dirty_ || textureAtlasChanged) { // Update texture coordinates for (auto& iconFile : currentIconFiles_) @@ -443,10 +443,7 @@ void PlacefileIcons::Impl::Update(bool textureAtlasChanged) // Update OpenGL buffer data UpdateBuffers(); - } - if (dirty_) - { gl::OpenGLFunctions& gl = context_->gl(); // Buffer vertex data From c6c64de1693925898d540e537765f03f80c824bf Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 21 Aug 2023 22:19:18 -0500 Subject: [PATCH 071/199] Clean up placefile lines, draw second line underneath for a black border --- .../scwx/qt/gl/draw/placefile_lines.cpp | 266 +++++++++--------- .../scwx/qt/gl/draw/placefile_lines.hpp | 3 +- 2 files changed, 133 insertions(+), 136 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp index 50714a4e..aa2a7da2 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp @@ -1,12 +1,8 @@ #include #include #include -#include #include -#include -#include - namespace scwx { namespace qt @@ -23,11 +19,11 @@ static constexpr std::size_t kNumRectangles = 1; static constexpr std::size_t kNumTriangles = kNumRectangles * 2; static constexpr std::size_t kVerticesPerTriangle = 3; static constexpr std::size_t kVerticesPerRectangle = kVerticesPerTriangle * 2; -static constexpr std::size_t kPointsPerVertex = 11; +static constexpr std::size_t kPointsPerVertex = 9; static constexpr std::size_t kBufferLength = kNumTriangles * kVerticesPerTriangle * kPointsPerVertex; -static const std::string kTextureName_ = "lines/default-1x7"; +static const boost::gil::rgba8_pixel_t kBlack_ {0, 0, 0, 255}; class PlacefileLines::Impl { @@ -47,23 +43,29 @@ public: ~Impl() {} + void BufferLine(const gr::Placefile::LineDrawItem::Element& e1, + const gr::Placefile::LineDrawItem::Element& e2, + const float width, + const float angle, + const boost::gil::rgba8_pixel_t color, + const GLint threshold); + void UpdateBuffers(std::shared_ptr); + void Update(); + std::shared_ptr context_; bool dirty_ {false}; bool thresholded_ {false}; - std::mutex lineMutex_; - - std::vector> - currentLineList_ {}; - std::vector> - newLineList_ {}; + std::mutex lineMutex_ {}; std::size_t currentNumLines_ {}; std::size_t newNumLines_ {}; - std::vector lineBuffer_ {}; - std::vector thresholdBuffer_ {}; + std::vector currentLinesBuffer_ {}; + std::vector currentThresholdBuffer_ {}; + std::vector newLinesBuffer_ {}; + std::vector newThresholdBuffer_ {}; std::shared_ptr shaderProgram_; GLint uMVPMatrixLocation_; @@ -75,9 +77,6 @@ public: std::array vbo_; GLsizei numVertices_; - - void UpdateBuffers(); - void Update(bool textureAtlasChanged); }; PlacefileLines::PlacefileLines(std::shared_ptr context) : @@ -135,22 +134,13 @@ void PlacefileLines::Initialize() reinterpret_cast(2 * sizeof(float))); gl.glEnableVertexAttribArray(1); - // aTexCoord - gl.glVertexAttribPointer(2, - 2, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - reinterpret_cast(4 * sizeof(float))); - gl.glEnableVertexAttribArray(2); - // aModulate gl.glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, kPointsPerVertex * sizeof(float), - reinterpret_cast(6 * sizeof(float))); + reinterpret_cast(4 * sizeof(float))); gl.glEnableVertexAttribArray(3); // aAngle @@ -159,7 +149,7 @@ void PlacefileLines::Initialize() GL_FLOAT, GL_FALSE, kPointsPerVertex * sizeof(float), - reinterpret_cast(10 * sizeof(float))); + reinterpret_cast(8 * sizeof(float))); gl.glEnableVertexAttribArray(4); gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); @@ -171,25 +161,23 @@ void PlacefileLines::Initialize() GL_INT, 0, static_cast(0)); - gl.glVertexAttribDivisor(5, 2); // One value per rectangle gl.glEnableVertexAttribArray(5); p->dirty_ = true; } void PlacefileLines::Render( - const QMapLibreGL::CustomLayerRenderParameters& params, - bool textureAtlasChanged) + const QMapLibreGL::CustomLayerRenderParameters& params) { std::unique_lock lock {p->lineMutex_}; - if (!p->currentLineList_.empty()) + if (p->currentNumLines_ > 0) { gl::OpenGLFunctions& gl = p->context_->gl(); gl.glBindVertexArray(p->vao_); - p->Update(textureAtlasChanged); + p->Update(); p->shaderProgram_->Use(); UseRotationProjection(params, p->uMVPMatrixLocation_); UseMapProjection( @@ -208,10 +196,6 @@ void PlacefileLines::Render( gl.glUniform1f(p->uMapDistanceLocation_, 0.0f); } - // Interpolate texture coordinates - gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - // Draw icons gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); } @@ -226,15 +210,15 @@ void PlacefileLines::Deinitialize() std::unique_lock lock {p->lineMutex_}; - p->currentLineList_.clear(); - p->lineBuffer_.clear(); - p->thresholdBuffer_.clear(); + p->currentLinesBuffer_.clear(); + p->currentThresholdBuffer_.clear(); } void PlacefileLines::StartLines() { - // Clear the new buffer - p->newLineList_.clear(); + // Clear the new buffers + p->newLinesBuffer_.clear(); + p->newThresholdBuffer_.clear(); p->newNumLines_ = 0u; } @@ -242,13 +226,10 @@ void PlacefileLines::StartLines() void PlacefileLines::AddLine( const std::shared_ptr& di) { - if (di != nullptr) + if (di != nullptr && !di->elements_.empty()) { - p->newLineList_.emplace_back(di); - if (!di->elements_.empty()) - { - p->newNumLines_ += di->elements_.size() - 1; - } + p->UpdateBuffers(di); + p->newNumLines_ += (di->elements_.size() - 1) * 2; } } @@ -257,124 +238,141 @@ void PlacefileLines::FinishLines() std::unique_lock lock {p->lineMutex_}; // Swap buffers - p->currentLineList_.swap(p->newLineList_); + p->currentLinesBuffer_.swap(p->newLinesBuffer_); + p->currentThresholdBuffer_.swap(p->newThresholdBuffer_); // Clear the new buffers - p->newLineList_.clear(); + p->newLinesBuffer_.clear(); + p->newThresholdBuffer_.clear(); // Update the number of lines p->currentNumLines_ = p->newNumLines_; + p->numVertices_ = + static_cast(p->currentNumLines_ * kVerticesPerRectangle); // Mark the draw item dirty p->dirty_ = true; } -void PlacefileLines::Impl::UpdateBuffers() +void PlacefileLines::Impl::UpdateBuffers( + std::shared_ptr di) { - auto texture = - util::TextureAtlas::Instance().GetTextureAttributes(kTextureName_); + // Threshold value + units::length::nautical_miles threshold = di->threshold_; + GLint thresholdValue = static_cast(std::round(threshold.value())); - // Texture coordinates (rotated) - const float ls = texture.tTop_; - const float rs = texture.tBottom_; - const float tt = texture.sLeft_; - const float bt = texture.sRight_; + std::vector angles {}; + angles.reserve(di->elements_.size() - 1); - lineBuffer_.clear(); - lineBuffer_.reserve(currentNumLines_ * kBufferLength); - thresholdBuffer_.clear(); - thresholdBuffer_.reserve(currentNumLines_); - numVertices_ = 0; - - for (auto& di : currentLineList_) + // For each element pair inside a Line statement, render a black line + for (std::size_t i = 0; i < di->elements_.size() - 1; ++i) { - // Threshold value - units::length::nautical_miles threshold = di->threshold_; - GLint thresholdValue = static_cast(std::round(threshold.value())); + // Latitude and longitude coordinates in degrees + const float lat1 = static_cast(di->elements_[i].latitude_); + const float lon1 = static_cast(di->elements_[i].longitude_); + const float lat2 = static_cast(di->elements_[i + 1].latitude_); + const float lon2 = static_cast(di->elements_[i + 1].longitude_); - // TODO: Angle in degrees - // const float a = 0.0f; + // Calculate angle + const units::angle::degrees angle = + util::GeographicLib::GetAngle(lat1, lon1, lat2, lon2); + float angleValue = angle.value(); + angles.push_back(angleValue); - // Line width / half width - const float lw = static_cast(di->width_); - const float hw = lw * 0.5f; - - // For each element pair inside a Line statement - for (std::size_t i = 0; i < di->elements_.size() - 1; ++i) - { - // Latitude and longitude coordinates in degrees - const float lat1 = static_cast(di->elements_[i].latitude_); - const float lon1 = static_cast(di->elements_[i].longitude_); - const float lat2 = static_cast(di->elements_[i + 1].latitude_); - const float lon2 = static_cast(di->elements_[i + 1].longitude_); - - // TODO: Base X/Y offsets in pixels - // const float x1 = static_cast(di->elements_[i].x_); - // const float y1 = static_cast(di->elements_[i].y_); - // const float x2 = static_cast(di->elements_[i + 1].x_); - // const float y2 = static_cast(di->elements_[i + 1].y_); - - // TODO: Refactor this to placefile update time instead of buffer time - const units::angle::degrees angle = - util::GeographicLib::GetAngle(lat1, lon1, lat2, lon2); - const float a = static_cast(angle.value()); - - // Final X/Y offsets in pixels - const float lx = -hw; - const float rx = +hw; - const float ty = +hw; - const float by = -hw; - - // Modulate color - const float mc0 = di->color_[0] / 255.0f; - const float mc1 = di->color_[1] / 255.0f; - const float mc2 = di->color_[2] / 255.0f; - const float mc3 = di->color_[3] / 255.0f; - - lineBuffer_.insert( - lineBuffer_.end(), - { - // Icon - lat1, lon1, lx, by, ls, bt, mc0, mc1, mc2, mc3, a, // BL - lat2, lon2, lx, ty, ls, tt, mc0, mc1, mc2, mc3, a, // TL - lat1, lon1, rx, by, rs, bt, mc0, mc1, mc2, mc3, a, // BR - lat1, lon1, rx, by, rs, bt, mc0, mc1, mc2, mc3, a, // BR - lat2, lon2, rx, ty, rs, tt, mc0, mc1, mc2, mc3, a, // TR - lat2, lon2, lx, ty, ls, tt, mc0, mc1, mc2, mc3, a // TL - }); - thresholdBuffer_.push_back(thresholdValue); - } + BufferLine(di->elements_[i], + di->elements_[i + 1], + di->width_ + 2, + static_cast(angleValue), + kBlack_, + thresholdValue); } - dirty_ = true; + // For each element pair inside a Line statement, render a colored line + for (std::size_t i = 0; i < di->elements_.size() - 1; ++i) + { + float angleValue = angles[i]; + + BufferLine(di->elements_[i], + di->elements_[i + 1], + di->width_, + static_cast(angleValue), + di->color_, + thresholdValue); + } } -void PlacefileLines::Impl::Update(bool textureAtlasChanged) +void PlacefileLines::Impl::BufferLine( + const gr::Placefile::LineDrawItem::Element& e1, + const gr::Placefile::LineDrawItem::Element& e2, + const float width, + const float angle, + const boost::gil::rgba8_pixel_t color, + const GLint threshold) { - // If the texture atlas has changed - if (dirty_ || textureAtlasChanged) - { - // Update OpenGL buffer data - UpdateBuffers(); + // Latitude and longitude coordinates in degrees + const float lat1 = static_cast(e1.latitude_); + const float lon1 = static_cast(e1.longitude_); + const float lat2 = static_cast(e2.latitude_); + const float lon2 = static_cast(e2.longitude_); + // TODO: Base X/Y offsets in pixels + // const float x1 = static_cast(e1.x_); + // const float y1 = static_cast(e1.y_); + // const float x2 = static_cast(e2.x_); + // const float y2 = static_cast(e2.y_); + + // Angle + const float a = angle; + + // Final X/Y offsets in pixels + const float hw = width * 0.5f; + const float lx = -hw; + const float rx = +hw; + const float ty = +hw; + const float by = -hw; + + // Modulate color + const float mc0 = color[0] / 255.0f; + const float mc1 = color[1] / 255.0f; + const float mc2 = color[2] / 255.0f; + const float mc3 = color[3] / 255.0f; + + // Update buffers + newLinesBuffer_.insert(newLinesBuffer_.end(), + { + // Line + lat1, lon1, lx, by, mc0, mc1, mc2, mc3, a, // BL + lat2, lon2, lx, ty, mc0, mc1, mc2, mc3, a, // TL + lat1, lon1, rx, by, mc0, mc1, mc2, mc3, a, // BR + lat1, lon1, rx, by, mc0, mc1, mc2, mc3, a, // BR + lat2, lon2, rx, ty, mc0, mc1, mc2, mc3, a, // TR + lat2, lon2, lx, ty, mc0, mc1, mc2, mc3, a // TL + }); + newThresholdBuffer_.insert( + newThresholdBuffer_.end(), + {threshold, threshold, threshold, threshold, threshold, threshold}); +} + +void PlacefileLines::Impl::Update() +{ + // If the placefile has been updated + if (dirty_) + { gl::OpenGLFunctions& gl = context_->gl(); - // Buffer vertex data + // Buffer lines data gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(float) * lineBuffer_.size(), - lineBuffer_.data(), + sizeof(float) * currentLinesBuffer_.size(), + currentLinesBuffer_.data(), GL_DYNAMIC_DRAW); // Buffer threshold data gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(GLint) * thresholdBuffer_.size(), - thresholdBuffer_.data(), + sizeof(GLint) * currentThresholdBuffer_.size(), + currentThresholdBuffer_.data(), GL_DYNAMIC_DRAW); - - numVertices_ = - static_cast(lineBuffer_.size() / kVerticesPerRectangle); } dirty_ = false; diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp index 2252e441..17461d49 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp @@ -28,8 +28,7 @@ public: void set_thresholded(bool thresholded); void Initialize() override; - void Render(const QMapLibreGL::CustomLayerRenderParameters& params, - bool textureAtlasChanged) override; + void Render(const QMapLibreGL::CustomLayerRenderParameters& params) override; void Deinitialize() override; /** From f512df9dd6caf48bedc4412306cf7e7690991eb9 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 21 Aug 2023 22:19:42 -0500 Subject: [PATCH 072/199] Enable anti-aliasing (MSAA x4) --- scwx-qt/source/scwx/qt/map/map_widget.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index 61665a24..212b8b34 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -191,6 +191,10 @@ public slots: MapWidget::MapWidget(const QMapLibreGL::Settings& settings) : p(std::make_unique(this, settings)) { + QSurfaceFormat surfaceFormat = QSurfaceFormat::defaultFormat(); + surfaceFormat.setSamples(4); + setFormat(surfaceFormat); + setFocusPolicy(Qt::StrongFocus); ImGui_ImplQt_RegisterWidget(this); From 232fafc9fa6df0c112bc01db9f2d23879dd4f8eb Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 21 Aug 2023 22:20:50 -0500 Subject: [PATCH 073/199] Interpolate icon coordinates for anti-aliasing - Still needs a little work, but this looks MUCH better --- scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp index 4c1ec1a4..722d5fb9 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp @@ -230,9 +230,9 @@ void PlacefileIcons::Render( gl.glUniform1f(p->uMapDistanceLocation_, 0.0f); } - // Don't interpolate texture coordinates - gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + // Interpolate texture coordinates + gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Draw icons gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); From 9955c4ccbe27d19b5d212f7c96841991343a1060 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 22 Aug 2023 21:52:11 -0500 Subject: [PATCH 074/199] Clean up placefile shared pointer usage with const references --- scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp | 10 +++++----- scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp | 2 +- scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp | 9 +++++---- scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp | 2 +- scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp | 5 +++-- scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.hpp | 2 +- scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp | 8 ++++---- scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp | 4 ++-- scwx-qt/source/scwx/qt/map/draw_layer.cpp | 4 ++-- scwx-qt/source/scwx/qt/map/draw_layer.hpp | 4 ++-- scwx-qt/source/scwx/qt/map/placefile_layer.cpp | 10 +++++----- scwx-qt/source/scwx/qt/map/placefile_layer.hpp | 4 ++-- 12 files changed, 33 insertions(+), 31 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp index 722d5fb9..e03cc887 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp @@ -54,7 +54,7 @@ struct PlacefileIconInfo class PlacefileIcons::Impl { public: - explicit Impl(std::shared_ptr context) : + explicit Impl(const std::shared_ptr& context) : context_ {context}, shaderProgram_ {nullptr}, uMVPMatrixLocation_(GL_INVALID_INDEX), @@ -69,6 +69,9 @@ public: ~Impl() {} + void UpdateBuffers(); + void Update(bool textureAtlasChanged); + std::shared_ptr context_; bool dirty_ {false}; @@ -98,12 +101,9 @@ public: std::array vbo_; GLsizei numVertices_; - - void UpdateBuffers(); - void Update(bool textureAtlasChanged); }; -PlacefileIcons::PlacefileIcons(std::shared_ptr context) : +PlacefileIcons::PlacefileIcons(const std::shared_ptr& context) : DrawItem(context->gl()), p(std::make_unique(context)) { } diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp index 08069ac5..418d774d 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp @@ -18,7 +18,7 @@ namespace draw class PlacefileIcons : public DrawItem { public: - explicit PlacefileIcons(std::shared_ptr context); + explicit PlacefileIcons(const std::shared_ptr& context); ~PlacefileIcons(); PlacefileIcons(const PlacefileIcons&) = delete; diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp index aa2a7da2..6112d078 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp @@ -28,7 +28,7 @@ static const boost::gil::rgba8_pixel_t kBlack_ {0, 0, 0, 255}; class PlacefileLines::Impl { public: - explicit Impl(std::shared_ptr context) : + explicit Impl(const std::shared_ptr& context) : context_ {context}, shaderProgram_ {nullptr}, uMVPMatrixLocation_(GL_INVALID_INDEX), @@ -49,7 +49,8 @@ public: const float angle, const boost::gil::rgba8_pixel_t color, const GLint threshold); - void UpdateBuffers(std::shared_ptr); + void + UpdateBuffers(const std::shared_ptr& di); void Update(); std::shared_ptr context_; @@ -79,7 +80,7 @@ public: GLsizei numVertices_; }; -PlacefileLines::PlacefileLines(std::shared_ptr context) : +PlacefileLines::PlacefileLines(const std::shared_ptr& context) : DrawItem(context->gl()), p(std::make_unique(context)) { } @@ -255,7 +256,7 @@ void PlacefileLines::FinishLines() } void PlacefileLines::Impl::UpdateBuffers( - std::shared_ptr di) + const std::shared_ptr& di) { // Threshold value units::length::nautical_miles threshold = di->threshold_; diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp index 17461d49..4dbf518d 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp @@ -16,7 +16,7 @@ namespace draw class PlacefileLines : public DrawItem { public: - explicit PlacefileLines(std::shared_ptr context); + explicit PlacefileLines(const std::shared_ptr& context); ~PlacefileLines(); PlacefileLines(const PlacefileLines&) = delete; diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp index 87077690..db1851ac 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp @@ -42,7 +42,7 @@ typedef std::array TessVertexArray; class PlacefilePolygons::Impl { public: - explicit Impl(std::shared_ptr context) : + explicit Impl(const std::shared_ptr& context) : context_ {context}, shaderProgram_ {nullptr}, uMVPMatrixLocation_(GL_INVALID_INDEX), @@ -115,7 +115,8 @@ public: GLint currentThreshold_; }; -PlacefilePolygons::PlacefilePolygons(std::shared_ptr context) : +PlacefilePolygons::PlacefilePolygons( + const std::shared_ptr& context) : DrawItem(context->gl()), p(std::make_unique(context)) { } diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.hpp index 451e007f..75c21793 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.hpp @@ -18,7 +18,7 @@ namespace draw class PlacefilePolygons : public DrawItem { public: - explicit PlacefilePolygons(std::shared_ptr context); + explicit PlacefilePolygons(const std::shared_ptr& context); ~PlacefilePolygons(); PlacefilePolygons(const PlacefilePolygons&) = delete; diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp index d2bfa0f1..082ee88e 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp @@ -23,8 +23,8 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_); class PlacefileText::Impl { public: - explicit Impl(std::shared_ptr context, - const std::string& placefileName) : + explicit Impl(const std::shared_ptr& context, + const std::string& placefileName) : context_ {context}, placefileName_ {placefileName} { } @@ -63,8 +63,8 @@ public: std::vector> newList_ {}; }; -PlacefileText::PlacefileText(std::shared_ptr context, - const std::string& placefileName) : +PlacefileText::PlacefileText(const std::shared_ptr& context, + const std::string& placefileName) : DrawItem(context->gl()), p(std::make_unique(context, placefileName)) { } diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp index 363164c7..58e22929 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp @@ -16,8 +16,8 @@ namespace draw class PlacefileText : public DrawItem { public: - explicit PlacefileText(std::shared_ptr context, - const std::string& placefileName); + explicit PlacefileText(const std::shared_ptr& context, + const std::string& placefileName); ~PlacefileText(); PlacefileText(const PlacefileText&) = delete; diff --git a/scwx-qt/source/scwx/qt/map/draw_layer.cpp b/scwx-qt/source/scwx/qt/map/draw_layer.cpp index 097f7a40..d6bafa4d 100644 --- a/scwx-qt/source/scwx/qt/map/draw_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/draw_layer.cpp @@ -28,7 +28,7 @@ public: std::uint64_t textureAtlasBuildCount_ {}; }; -DrawLayer::DrawLayer(std::shared_ptr context) : +DrawLayer::DrawLayer(const std::shared_ptr& context) : GenericLayer(context), p(std::make_unique(context)) { } @@ -76,7 +76,7 @@ void DrawLayer::Deinitialize() } } -void DrawLayer::AddDrawItem(std::shared_ptr drawItem) +void DrawLayer::AddDrawItem(const std::shared_ptr& drawItem) { p->drawList_.push_back(drawItem); } diff --git a/scwx-qt/source/scwx/qt/map/draw_layer.hpp b/scwx-qt/source/scwx/qt/map/draw_layer.hpp index e2835f02..0ac3e03f 100644 --- a/scwx-qt/source/scwx/qt/map/draw_layer.hpp +++ b/scwx-qt/source/scwx/qt/map/draw_layer.hpp @@ -15,7 +15,7 @@ class DrawLayerImpl; class DrawLayer : public GenericLayer { public: - explicit DrawLayer(std::shared_ptr context); + explicit DrawLayer(const std::shared_ptr& context); virtual ~DrawLayer(); virtual void Initialize(); @@ -23,7 +23,7 @@ public: virtual void Deinitialize(); protected: - void AddDrawItem(std::shared_ptr drawItem); + void AddDrawItem(const std::shared_ptr& drawItem); private: std::unique_ptr p; diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp index b6ec78d4..2c1b66ab 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp @@ -22,9 +22,9 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_); class PlacefileLayer::Impl { public: - explicit Impl(PlacefileLayer* self, - std::shared_ptr context, - const std::string& placefileName) : + explicit Impl(PlacefileLayer* self, + const std::shared_ptr& context, + const std::string& placefileName) : self_ {self}, placefileName_ {placefileName}, placefileIcons_ {std::make_shared(context)}, @@ -53,8 +53,8 @@ public: std::shared_ptr placefileText_; }; -PlacefileLayer::PlacefileLayer(std::shared_ptr context, - const std::string& placefileName) : +PlacefileLayer::PlacefileLayer(const std::shared_ptr& context, + const std::string& placefileName) : DrawLayer(context), p(std::make_unique(this, context, placefileName)) { diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.hpp b/scwx-qt/source/scwx/qt/map/placefile_layer.hpp index 9c08db10..ec351510 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.hpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.hpp @@ -16,8 +16,8 @@ class PlacefileLayer : public DrawLayer Q_OBJECT public: - explicit PlacefileLayer(std::shared_ptr context, - const std::string& placefileName); + explicit PlacefileLayer(const std::shared_ptr& context, + const std::string& placefileName); ~PlacefileLayer(); std::string placefile_name() const; From ad5c2b583d02f08887bc2958b2d8e3244e8bbf22 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 22 Aug 2023 23:52:55 -0500 Subject: [PATCH 075/199] Placefile "Remove" functionality --- .../scwx/qt/manager/placefile_manager.cpp | 27 ++++++++++ .../scwx/qt/manager/placefile_manager.hpp | 2 + scwx-qt/source/scwx/qt/map/map_widget.cpp | 13 +++++ .../source/scwx/qt/model/placefile_model.cpp | 23 +++++++++ .../source/scwx/qt/model/placefile_model.hpp | 1 + .../scwx/qt/ui/placefile_settings_widget.cpp | 51 ++++++++++++++++++- 6 files changed, 115 insertions(+), 2 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index ff3da32f..9f233d9d 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -314,6 +314,33 @@ void PlacefileManager::LoadFile(const std::string& filename) }); } +void PlacefileManager::RemoveUrl(const std::string& urlString) +{ + std::unique_lock lock(p->placefileRecordLock_); + + // Determine if the placefile has been loaded previously + auto it = std::find_if(p->placefileRecords_.begin(), + p->placefileRecords_.end(), + [&urlString](auto& record) + { return record->name_ == urlString; }); + if (it == p->placefileRecords_.end()) + { + logger_->debug("Placefile doesn't exist: {}", urlString); + return; + } + + // Placefile exists, proceed with removing + logger_->info("RemoveUrl: {}", urlString); + + // Remove record + p->placefileRecords_.erase(it); + p->placefileRecordMap_.erase(urlString); + + lock.unlock(); + + Q_EMIT PlacefileRemoved(urlString); +} + void PlacefileManager::Impl::PlacefileRecord::Update() { logger_->debug("Update: {}", name_); diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp index c2cf2d45..eaa9c21b 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp @@ -39,11 +39,13 @@ public: void AddUrl(const std::string& urlString); void LoadFile(const std::string& filename); + void RemoveUrl(const std::string& urlString); static std::shared_ptr Instance(); signals: void PlacefileEnabled(const std::string& name, bool enabled); + void PlacefileRemoved(const std::string& name); void PlacefileRenamed(const std::string& oldName, const std::string& newName); void PlacefileUpdated(const std::string& name); diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index 212b8b34..bfb2284a 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -227,6 +227,19 @@ void MapWidgetImpl::ConnectSignals() } widget_->update(); }); + connect(placefileManager_.get(), + &manager::PlacefileManager::PlacefileRemoved, + widget_, + [this](const std::string& name) + { + if (enabledPlacefiles_.contains(name)) + { + // Placefile removed, remove layer + enabledPlacefiles_.erase(name); + RemovePlacefileLayer(name); + } + widget_->update(); + }); connect(placefileManager_.get(), &manager::PlacefileManager::PlacefileRenamed, widget_, diff --git a/scwx-qt/source/scwx/qt/model/placefile_model.cpp b/scwx-qt/source/scwx/qt/model/placefile_model.cpp index 87d81c28..5ad286a1 100644 --- a/scwx-qt/source/scwx/qt/model/placefile_model.cpp +++ b/scwx-qt/source/scwx/qt/model/placefile_model.cpp @@ -45,6 +45,11 @@ PlacefileModel::PlacefileModel(QObject* parent) : this, &PlacefileModel::HandlePlacefileUpdate); + connect(p->placefileManager_.get(), + &manager::PlacefileManager::PlacefileRemoved, + this, + &PlacefileModel::HandlePlacefileRemoved); + connect(p->placefileManager_.get(), &manager::PlacefileManager::PlacefileRenamed, this, @@ -292,6 +297,24 @@ bool PlacefileModel::setData(const QModelIndex& index, return true; } +void PlacefileModel::HandlePlacefileRemoved(const std::string& name) +{ + auto it = + std::find(p->placefileNames_.begin(), p->placefileNames_.end(), name); + + if (it != p->placefileNames_.end()) + { + // Placefile exists, delete row + const int row = std::distance(p->placefileNames_.begin(), it); + QModelIndex topLeft = createIndex(row, kFirstColumn); + QModelIndex bottomRight = createIndex(row, kLastColumn); + + beginRemoveRows(QModelIndex(), row, row); + p->placefileNames_.erase(it); + endRemoveRows(); + } +} + void PlacefileModel::HandlePlacefileRenamed(const std::string& oldName, const std::string& newName) { diff --git a/scwx-qt/source/scwx/qt/model/placefile_model.hpp b/scwx-qt/source/scwx/qt/model/placefile_model.hpp index f4f01167..5dc4fa14 100644 --- a/scwx-qt/source/scwx/qt/model/placefile_model.hpp +++ b/scwx-qt/source/scwx/qt/model/placefile_model.hpp @@ -45,6 +45,7 @@ public: int role = Qt::EditRole) override; public slots: + void HandlePlacefileRemoved(const std::string& name); void HandlePlacefileRenamed(const std::string& oldName, const std::string& newName); void HandlePlacefileUpdate(const std::string& name); diff --git a/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp b/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp index c5f73945..51679e0f 100644 --- a/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp @@ -31,8 +31,9 @@ public: leftElidedItemDelegate_ {new LeftElidedItemDelegate(self_)} { placefileProxyModel_->setSourceModel(placefileModel_); - placefileProxyModel_->setSortRole(types::SortRole); - placefileProxyModel_->setFilterCaseSensitivity(Qt::CaseInsensitive); + placefileProxyModel_->setSortRole(types::ItemDataRole::SortRole); + placefileProxyModel_->setFilterCaseSensitivity( + Qt::CaseSensitivity::CaseInsensitive); placefileProxyModel_->setFilterKeyColumn(-1); } ~PlacefileSettingsWidgetImpl() = default; @@ -57,6 +58,8 @@ PlacefileSettingsWidget::PlacefileSettingsWidget(QWidget* parent) : { ui->setupUi(this); + ui->removeButton->setEnabled(false); + ui->placefileView->setModel(p->placefileProxyModel_); auto placefileViewHeader = ui->placefileView->header(); @@ -89,6 +92,31 @@ void PlacefileSettingsWidgetImpl::ConnectSignals() self_, [this]() { openUrlDialog_->open(); }); + QObject::connect(self_->ui->removeButton, + &QPushButton::clicked, + self_, + [this]() + { + auto selectionModel = + self_->ui->placefileView->selectionModel(); + + // Get selected URL string + QModelIndex selected = + selectionModel + ->selectedRows(static_cast( + model::PlacefileModel::Column::Placefile)) + .first(); + QVariant data = self_->ui->placefileView->model()->data( + selected, types::ItemDataRole::SortRole); + std::string urlString = data.toString().toStdString(); + + // Remove Placefile + if (!urlString.empty()) + { + placefileManager_->RemoveUrl(urlString); + } + }); + QObject::connect( openUrlDialog_, &OpenUrlDialog::accepted, @@ -100,6 +128,25 @@ void PlacefileSettingsWidgetImpl::ConnectSignals() &QLineEdit::textChanged, placefileProxyModel_, &QSortFilterProxyModel::setFilterWildcard); + + QObject::connect( + self_->ui->placefileView->selectionModel(), + &QItemSelectionModel::selectionChanged, + self_, + [this](const QItemSelection& selected, const QItemSelection& deselected) + { + if (selected.size() == 0 && deselected.size() == 0) + { + // Items which stay selected but change their index are not + // included in selected and deselected. Thus, this signal might + // be emitted with both selected and deselected empty, if only + // the indices of selected items change. + return; + } + + bool itemSelected = selected.size() > 0; + self_->ui->removeButton->setEnabled(itemSelected); + }); } } // namespace ui From 6f14745a594d2929c192de8f8423371b8c135f85 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 23 Aug 2023 01:06:51 -0500 Subject: [PATCH 076/199] Save (works) and reload (doesn't work) placefiles from settings --- .../scwx/qt/manager/placefile_manager.cpp | 155 +++++++++++++++++- .../scwx/qt/manager/placefile_manager.hpp | 10 +- .../source/scwx/qt/model/placefile_model.cpp | 11 +- 3 files changed, 158 insertions(+), 18 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index 9f233d9d..45963124 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -11,11 +12,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include @@ -29,6 +32,11 @@ namespace manager static const std::string logPrefix_ = "scwx::qt::manager::placefile_manager"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); +static const std::string kEnabledName_ = "enabled"; +static const std::string kThresholdedName_ = "thresholded"; +static const std::string kTitleName_ = "title"; +static const std::string kNameName_ = "name"; + class PlacefileManager::Impl { public: @@ -37,12 +45,18 @@ public: explicit Impl(PlacefileManager* self) : self_ {self} {} ~Impl() {} + void InitializePlacefileSettings(); + void ReadPlacefileSettings(); + void WritePlacefileSettings(); + static void LoadResources(const std::shared_ptr& placefile); boost::asio::thread_pool threadPool_ {1u}; PlacefileManager* self_; + std::string placefileSettingsPath_ {}; + std::shared_ptr radarSite_ {}; std::vector> placefileRecords_ {}; @@ -57,8 +71,15 @@ public: explicit PlacefileRecord(Impl* impl, const std::string& name, std::shared_ptr placefile, - bool enabled = true) : - p {impl}, name_ {name}, placefile_ {placefile}, enabled_ {enabled} + const std::string& title = {}, + bool enabled = false, + bool thresholded = false) : + p {impl}, + name_ {name}, + placefile_ {placefile}, + title_ {title}, + enabled_ {enabled}, + thresholded_ {thresholded} { } ~PlacefileRecord() @@ -71,19 +92,56 @@ public: void UpdateAsync(); void UpdatePlacefile(const std::shared_ptr& placefile); + friend void tag_invoke(boost::json::value_from_tag, + boost::json::value& jv, + const std::shared_ptr& record) + { + jv = {{kEnabledName_, record->enabled_}, + {kThresholdedName_, record->thresholded_}, + {kTitleName_, record->title_}, + {kNameName_, record->name_}}; + } + + friend PlacefileRecord tag_invoke(boost::json::value_to_tag, + const boost::json::value& jv) + { + return PlacefileRecord { + nullptr, + boost::json::value_to(jv.at(kNameName_)), + nullptr, + boost::json::value_to(jv.at(kTitleName_)), + jv.at(kEnabledName_).as_bool(), + jv.at(kThresholdedName_).as_bool()}; + } + Impl* p; std::string name_; + std::string title_; std::shared_ptr placefile_; bool enabled_; - bool thresholded_ {false}; + bool thresholded_; boost::asio::thread_pool threadPool_ {1u}; boost::asio::steady_timer refreshTimer_ {threadPool_}; std::mutex refreshMutex_ {}; }; -PlacefileManager::PlacefileManager() : p(std::make_unique(this)) {} -PlacefileManager::~PlacefileManager() = default; +PlacefileManager::PlacefileManager() : p(std::make_unique(this)) +{ + boost::asio::post(p->threadPool_, + [this]() + { + // Read placefile settings on startup + p->InitializePlacefileSettings(); + p->ReadPlacefileSettings(); + }); +} + +PlacefileManager::~PlacefileManager() +{ + // Write placefile settings on shutdown + p->WritePlacefileSettings(); +}; bool PlacefileManager::placefile_enabled(const std::string& name) { @@ -109,6 +167,18 @@ bool PlacefileManager::placefile_thresholded(const std::string& name) return false; } +std::string PlacefileManager::placefile_title(const std::string& name) +{ + std::shared_lock lock(p->placefileRecordLock_); + + auto it = p->placefileRecordMap_.find(name); + if (it != p->placefileRecordMap_.cend()) + { + return it->second->title_; + } + return {}; +} + std::shared_ptr PlacefileManager::placefile(const std::string& name) { @@ -189,6 +259,71 @@ void PlacefileManager::set_placefile_url(const std::string& name, } } +void PlacefileManager::Impl::InitializePlacefileSettings() +{ + std::string appDataPath { + QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + .toStdString()}; + + if (!std::filesystem::exists(appDataPath)) + { + if (!std::filesystem::create_directories(appDataPath)) + { + logger_->error("Unable to create application data directory: \"{}\"", + appDataPath); + } + } + + placefileSettingsPath_ = appDataPath + "/placefiles.json"; +} + +void PlacefileManager::Impl::ReadPlacefileSettings() +{ + logger_->info("Reading placefile settings"); + + boost::json::value placefileJson = nullptr; + + // Determine if placefile settings exists + if (std::filesystem::exists(placefileSettingsPath_)) + { + placefileJson = util::json::ReadJsonFile(placefileSettingsPath_); + } + + // If placefile settings was successfully read + if (placefileJson != nullptr && placefileJson.is_array()) + { + // For each placefile entry + auto& placefileArray = placefileJson.as_array(); + for (auto& placefileEntry : placefileArray) + { + try + { + // Convert placefile entry to a record + PlacefileRecord record = + boost::json::value_to(placefileEntry); + + self_->AddUrl(record.name_, + record.title_, + record.enabled_, + record.thresholded_); + } + catch (const std::exception& ex) + { + logger_->warn("Invalid placefile entry: {}", ex.what()); + } + } + } +} + +void PlacefileManager::Impl::WritePlacefileSettings() +{ + logger_->info("Saving placefile settings"); + + std::shared_lock lock {placefileRecordLock_}; + auto placefileJson = boost::json::value_from(placefileRecords_); + util::json::WriteJsonFile(placefileSettingsPath_, placefileJson); +} + void PlacefileManager::SetRadarSite( std::shared_ptr radarSite) { @@ -231,7 +366,10 @@ PlacefileManager::GetActivePlacefiles() return placefiles; } -void PlacefileManager::AddUrl(const std::string& urlString) +void PlacefileManager::AddUrl(const std::string& urlString, + const std::string& title, + bool enabled, + bool thresholded) { std::string normalizedUrl = util::network::NormalizeUrl(urlString); @@ -254,7 +392,7 @@ void PlacefileManager::AddUrl(const std::string& urlString) // Add an empty placefile record for the new URL auto& record = p->placefileRecords_.emplace_back(std::make_shared( - p.get(), normalizedUrl, nullptr, false)); + p.get(), normalizedUrl, nullptr, title, enabled, thresholded)); p->placefileRecordMap_.insert_or_assign(normalizedUrl, record); lock.unlock(); @@ -303,7 +441,7 @@ void PlacefileManager::LoadFile(const std::string& filename) // If this is a new placefile, add it auto& record = p->placefileRecords_.emplace_back( std::make_shared( - p.get(), placefileName, placefile)); + p.get(), placefileName, placefile, placefile->title(), true)); p->placefileRecordMap_.insert_or_assign(placefileName, record); lock.unlock(); @@ -434,6 +572,7 @@ void PlacefileManager::Impl::PlacefileRecord::Update() { // Update the placefile placefile_ = updatedPlacefile; + title_ = placefile_->title(); // Notify slots of the placefile update Q_EMIT p->self_->PlacefileUpdated(name); diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp index eaa9c21b..498ea9a1 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp @@ -20,8 +20,9 @@ public: explicit PlacefileManager(); ~PlacefileManager(); - bool placefile_enabled(const std::string& name); - bool placefile_thresholded(const std::string& name); + bool placefile_enabled(const std::string& name); + bool placefile_thresholded(const std::string& name); + std::string placefile_title(const std::string& name); std::shared_ptr placefile(const std::string& name); void set_placefile_enabled(const std::string& name, bool enabled); @@ -37,7 +38,10 @@ public: */ std::vector> GetActivePlacefiles(); - void AddUrl(const std::string& urlString); + void AddUrl(const std::string& urlString, + const std::string& title = {}, + bool enabled = false, + bool thresholded = false); void LoadFile(const std::string& filename); void RemoveUrl(const std::string& urlString); diff --git a/scwx-qt/source/scwx/qt/model/placefile_model.cpp b/scwx-qt/source/scwx/qt/model/placefile_model.cpp index 5ad286a1..21478936 100644 --- a/scwx-qt/source/scwx/qt/model/placefile_model.cpp +++ b/scwx-qt/source/scwx/qt/model/placefile_model.cpp @@ -159,14 +159,11 @@ QVariant PlacefileModel::data(const QModelIndex& index, int role) const role == Qt::ItemDataRole::ToolTipRole) { std::string description = placefileName; - auto placefile = p->placefileManager_->placefile(placefileName); - if (placefile != nullptr) + std::string title = + p->placefileManager_->placefile_title(placefileName); + if (!title.empty()) { - std::string title = placefile->title(); - if (!title.empty()) - { - description = title + '\n' + description; - } + description = title + '\n' + description; } return QString::fromStdString(description); From e7da934db14183135eb0b8759df2947b4ecc6fa9 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 23 Aug 2023 01:14:18 -0500 Subject: [PATCH 077/199] Need to wait for application initialization before reading placefiles, and emit placefile enabled when adding an enabled URL --- scwx-qt/source/scwx/qt/manager/placefile_manager.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index 45963124..2521d03f 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -131,8 +132,10 @@ PlacefileManager::PlacefileManager() : p(std::make_unique(this)) boost::asio::post(p->threadPool_, [this]() { - // Read placefile settings on startup p->InitializePlacefileSettings(); + + // Read placefile settings on startup + main::Application::WaitForInitialization(); p->ReadPlacefileSettings(); }); } @@ -397,6 +400,11 @@ void PlacefileManager::AddUrl(const std::string& urlString, lock.unlock(); + if (enabled) + { + Q_EMIT PlacefileEnabled(normalizedUrl, record->enabled_); + } + Q_EMIT PlacefileUpdated(normalizedUrl); // Queue a placefile update From 170e30ca6c27057511a07d759f8f1a6a7c907fb7 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 23 Aug 2023 07:50:25 -0500 Subject: [PATCH 078/199] Placefile GCC warning cleanup --- scwx-qt/source/scwx/qt/manager/placefile_manager.cpp | 2 +- scwx-qt/source/scwx/qt/model/placefile_model.cpp | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index 2521d03f..c32831c6 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -77,8 +77,8 @@ public: bool thresholded = false) : p {impl}, name_ {name}, - placefile_ {placefile}, title_ {title}, + placefile_ {placefile}, enabled_ {enabled}, thresholded_ {thresholded} { diff --git a/scwx-qt/source/scwx/qt/model/placefile_model.cpp b/scwx-qt/source/scwx/qt/model/placefile_model.cpp index 21478936..d1d25a4c 100644 --- a/scwx-qt/source/scwx/qt/model/placefile_model.cpp +++ b/scwx-qt/source/scwx/qt/model/placefile_model.cpp @@ -302,9 +302,7 @@ void PlacefileModel::HandlePlacefileRemoved(const std::string& name) if (it != p->placefileNames_.end()) { // Placefile exists, delete row - const int row = std::distance(p->placefileNames_.begin(), it); - QModelIndex topLeft = createIndex(row, kFirstColumn); - QModelIndex bottomRight = createIndex(row, kLastColumn); + const int row = std::distance(p->placefileNames_.begin(), it); beginRemoveRows(QModelIndex(), row, row); p->placefileNames_.erase(it); From 80c307c5fc1a2a24c0f65787a7ef934a87ff8516 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 23 Aug 2023 21:39:17 -0500 Subject: [PATCH 079/199] Enable placefile auto-refresh (no more frequent than every 15 seconds) --- .../scwx/qt/manager/placefile_manager.cpp | 129 ++++++++++++++++-- wxdata/include/scwx/gr/placefile.hpp | 2 + wxdata/source/scwx/gr/placefile.cpp | 5 + 3 files changed, 125 insertions(+), 11 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index c32831c6..96a3ce4a 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -22,6 +22,7 @@ #include #include #include +#include namespace scwx { @@ -85,10 +86,16 @@ public: } ~PlacefileRecord() { - std::unique_lock lock(refreshMutex_); + std::unique_lock refreshLock(refreshMutex_); + std::unique_lock timerLock(timerMutex_); refreshTimer_.cancel(); } + bool refresh_enabled() const; + std::chrono::seconds refresh_time() const; + + void CancelRefresh(); + void ScheduleRefresh(); void Update(); void UpdateAsync(); void UpdatePlacefile(const std::shared_ptr& placefile); @@ -125,6 +132,10 @@ public: boost::asio::thread_pool threadPool_ {1u}; boost::asio::steady_timer refreshTimer_ {threadPool_}; std::mutex refreshMutex_ {}; + std::mutex timerMutex_ {}; + + std::string lastRadarSite_ {}; + std::chrono::system_clock::time_point lastUpdateTime_ {}; }; PlacefileManager::PlacefileManager() : p(std::make_unique(this)) @@ -210,11 +221,26 @@ void PlacefileManager::set_placefile_enabled(const std::string& name, Q_EMIT PlacefileEnabled(name, enabled); + using namespace std::chrono_literals; + // Update the placefile - // TODO: Only update if it's out of date, or if the radar site has changed if (enabled) { - it->second->UpdateAsync(); + if (p->radarSite_ != nullptr && + record->lastRadarSite_ != p->radarSite_->id()) + { + // If the radar site has changed, update now + record->UpdateAsync(); + } + else + { + // Otherwise, schedule an update + record->ScheduleRefresh(); + } + } + else if (!enabled) + { + record->CancelRefresh(); } } } @@ -262,6 +288,31 @@ void PlacefileManager::set_placefile_url(const std::string& name, } } +bool PlacefileManager::Impl::PlacefileRecord::refresh_enabled() const +{ + if (placefile_ != nullptr) + { + using namespace std::chrono_literals; + return placefile_->refresh() > 0s; + } + + return false; +} + +std::chrono::seconds +PlacefileManager::Impl::PlacefileRecord::refresh_time() const +{ + using namespace std::chrono_literals; + + if (refresh_enabled()) + { + // Don't refresh more often than every 15 seconds + return std::max(placefile_->refresh(), 15s); + } + + return -1s; +} + void PlacefileManager::Impl::InitializePlacefileSettings() { std::string appDataPath { @@ -407,8 +458,11 @@ void PlacefileManager::AddUrl(const std::string& urlString, Q_EMIT PlacefileUpdated(normalizedUrl); - // Queue a placefile update - record->UpdateAsync(); + // Queue a placefile update, either if enabled, or if we don't know the title + if (enabled || title.empty()) + { + record->UpdateAsync(); + } } void PlacefileManager::LoadFile(const std::string& filename) @@ -491,6 +545,9 @@ void PlacefileManager::Impl::PlacefileRecord::Update() { logger_->debug("Update: {}", name_); + // Take unique lock before refreshing + std::unique_lock lock {refreshMutex_}; + // Make a copy of name in the event it changes. const std::string name {name_}; @@ -579,17 +636,66 @@ void PlacefileManager::Impl::PlacefileRecord::Update() if (name_ == name) { // Update the placefile - placefile_ = updatedPlacefile; - title_ = placefile_->title(); + placefile_ = updatedPlacefile; + title_ = placefile_->title(); + lastUpdateTime_ = std::chrono::system_clock::now(); + + if (p->radarSite_ != nullptr) + { + lastRadarSite_ = p->radarSite_->id(); + } // Notify slots of the placefile update Q_EMIT p->self_->PlacefileUpdated(name); } } - // TODO: Update refresh timer - // TODO: Can running this function out of sync with an existing refresh timer - // cause issues? + // Update refresh timer + ScheduleRefresh(); +} + +void PlacefileManager::Impl::PlacefileRecord::ScheduleRefresh() +{ + using namespace std::chrono_literals; + + if (!enabled_ || !refresh_enabled()) + { + // Refresh is disabled + return; + } + + std::unique_lock lock {timerMutex_}; + + auto nextUpdateTime = lastUpdateTime_ + refresh_time(); + auto timeUntilNextUpdate = nextUpdateTime - std::chrono::system_clock::now(); + + logger_->debug( + "Scheduled refresh in {:%M:%S} ({})", + std::chrono::duration_cast(timeUntilNextUpdate), + name_); + + refreshTimer_.expires_after(timeUntilNextUpdate); + refreshTimer_.async_wait( + [this](const boost::system::error_code& e) + { + if (e == boost::asio::error::operation_aborted) + { + logger_->debug("Refresh timer cancelled"); + } + else if (e != boost::system::errc::success) + { + logger_->warn("Refresh timer error: {}", e.message()); + } + else + { + Update(); + } + }); +} + +void PlacefileManager::Impl::PlacefileRecord::CancelRefresh() +{ + refreshTimer_.cancel(); } void PlacefileManager::Impl::PlacefileRecord::UpdateAsync() @@ -603,7 +709,8 @@ void PlacefileManager::Impl::PlacefileRecord::UpdatePlacefile( // Update placefile placefile_ = placefile; - // TODO: Update refresh timer + // Update refresh timer + ScheduleRefresh(); } std::shared_ptr PlacefileManager::Instance() diff --git a/wxdata/include/scwx/gr/placefile.hpp b/wxdata/include/scwx/gr/placefile.hpp index 10ea2802..d092ed3f 100644 --- a/wxdata/include/scwx/gr/placefile.hpp +++ b/wxdata/include/scwx/gr/placefile.hpp @@ -2,6 +2,7 @@ #include +#include #include #include #include @@ -174,6 +175,7 @@ public: std::string name() const; std::string title() const; + std::chrono::seconds refresh() const; std::unordered_map> fonts(); std::shared_ptr font(std::size_t i); diff --git a/wxdata/source/scwx/gr/placefile.cpp b/wxdata/source/scwx/gr/placefile.cpp index 5c86f319..27febd8c 100644 --- a/wxdata/source/scwx/gr/placefile.cpp +++ b/wxdata/source/scwx/gr/placefile.cpp @@ -113,6 +113,11 @@ std::string Placefile::title() const return p->title_; } +std::chrono::seconds Placefile::refresh() const +{ + return p->refresh_; +} + std::unordered_map> Placefile::fonts() { From c7487281adf59ca6edc8c6416b0b610684a2c4aa Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 26 Aug 2023 21:28:26 -0500 Subject: [PATCH 080/199] Fixing minor data race and memory issues in Placefile Manager --- scwx-qt/source/scwx/qt/manager/placefile_manager.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index 96a3ce4a..e125d591 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -277,7 +277,7 @@ void PlacefileManager::set_placefile_url(const std::string& name, placefileRecord->name_ = normalizedUrl; placefileRecord->placefile_ = nullptr; p->placefileRecordMap_.erase(it); - p->placefileRecordMap_.emplace(normalizedUrl, placefileRecord); + p->placefileRecordMap_.insert_or_assign(normalizedUrl, placefileRecord); lock.unlock(); @@ -413,7 +413,7 @@ PlacefileManager::GetActivePlacefiles() { if (record->enabled_ && record->placefile_ != nullptr) { - placefiles.emplace_back(record->placefile_); + placefiles.push_back(record->placefile_); } } @@ -695,6 +695,7 @@ void PlacefileManager::Impl::PlacefileRecord::ScheduleRefresh() void PlacefileManager::Impl::PlacefileRecord::CancelRefresh() { + std::unique_lock lock {timerMutex_}; refreshTimer_.cancel(); } From 38b56be7c482113bdd37f6a4bc73cf64d05e8461 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 27 Aug 2023 00:29:17 -0500 Subject: [PATCH 081/199] Mouse picking boilerplate --- scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp | 7 ++++++ scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp | 10 ++++++++ scwx-qt/source/scwx/qt/map/draw_layer.cpp | 20 +++++++++++++++ scwx-qt/source/scwx/qt/map/draw_layer.hpp | 10 +++++--- scwx-qt/source/scwx/qt/map/generic_layer.cpp | 7 ++++++ scwx-qt/source/scwx/qt/map/generic_layer.hpp | 10 ++++++++ scwx-qt/source/scwx/qt/map/map_context.cpp | 26 ++++++++++++++------ scwx-qt/source/scwx/qt/map/map_context.hpp | 17 +++++++------ scwx-qt/source/scwx/qt/map/map_widget.cpp | 23 +++++++++++++++++ scwx-qt/source/scwx/qt/map/overlay_layer.cpp | 2 ++ 10 files changed, 115 insertions(+), 17 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp b/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp index 583c7929..56d9b574 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp @@ -53,6 +53,13 @@ void DrawItem::Render(const QMapLibreGL::CustomLayerRenderParameters& params, Render(params); } +bool DrawItem::RunMousePicking( + const QMapLibreGL::CustomLayerRenderParameters& /* params */) +{ + // By default, the draw item is not picked + return false; +} + void DrawItem::UseDefaultProjection( const QMapLibreGL::CustomLayerRenderParameters& params, GLint uMVPMatrixLocation) diff --git a/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp b/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp index 4c3b7140..5e559d12 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp @@ -33,6 +33,16 @@ public: bool textureAtlasChanged); virtual void Deinitialize() = 0; + /** + * @brief Run mouse picking on the draw item. + * + * @param [in] params Custom layer render parameters + * + * @return true if the draw item was picked, otherwise false + */ + virtual bool + RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params); + protected: void UseDefaultProjection(const QMapLibreGL::CustomLayerRenderParameters& params, diff --git a/scwx-qt/source/scwx/qt/map/draw_layer.cpp b/scwx-qt/source/scwx/qt/map/draw_layer.cpp index d6bafa4d..2116a1bd 100644 --- a/scwx-qt/source/scwx/qt/map/draw_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/draw_layer.cpp @@ -76,6 +76,26 @@ void DrawLayer::Deinitialize() } } +bool DrawLayer::RunMousePicking( + const QMapLibreGL::CustomLayerRenderParameters& params) +{ + bool itemPicked = false; + + // For each draw item in the draw list in reverse + for (auto it = p->drawList_.rbegin(); it != p->drawList_.rend(); ++it) + { + // Run mouse picking on each draw item + if ((*it)->RunMousePicking(params)) + { + // If a draw item was picked, don't process additional items + itemPicked = true; + break; + } + } + + return itemPicked; +} + void DrawLayer::AddDrawItem(const std::shared_ptr& drawItem) { p->drawList_.push_back(drawItem); diff --git a/scwx-qt/source/scwx/qt/map/draw_layer.hpp b/scwx-qt/source/scwx/qt/map/draw_layer.hpp index 0ac3e03f..e2b3694c 100644 --- a/scwx-qt/source/scwx/qt/map/draw_layer.hpp +++ b/scwx-qt/source/scwx/qt/map/draw_layer.hpp @@ -18,9 +18,13 @@ public: explicit DrawLayer(const std::shared_ptr& context); virtual ~DrawLayer(); - virtual void Initialize(); - virtual void Render(const QMapLibreGL::CustomLayerRenderParameters&); - virtual void Deinitialize(); + virtual void Initialize() override; + virtual void + Render(const QMapLibreGL::CustomLayerRenderParameters&) override; + virtual void Deinitialize() override; + + virtual bool RunMousePicking( + const QMapLibreGL::CustomLayerRenderParameters& params) override; protected: void AddDrawItem(const std::shared_ptr& drawItem); diff --git a/scwx-qt/source/scwx/qt/map/generic_layer.cpp b/scwx-qt/source/scwx/qt/map/generic_layer.cpp index 7c5f6e34..3ec27513 100644 --- a/scwx-qt/source/scwx/qt/map/generic_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/generic_layer.cpp @@ -26,6 +26,13 @@ GenericLayer::GenericLayer(std::shared_ptr context) : } GenericLayer::~GenericLayer() = default; +bool GenericLayer::RunMousePicking( + const QMapLibreGL::CustomLayerRenderParameters& /* params */) +{ + // By default, the layer has nothing to pick + return false; +} + std::shared_ptr GenericLayer::context() const { return p->context_; diff --git a/scwx-qt/source/scwx/qt/map/generic_layer.hpp b/scwx-qt/source/scwx/qt/map/generic_layer.hpp index 86bf7a20..b44e38f3 100644 --- a/scwx-qt/source/scwx/qt/map/generic_layer.hpp +++ b/scwx-qt/source/scwx/qt/map/generic_layer.hpp @@ -28,6 +28,16 @@ public: virtual void Render(const QMapLibreGL::CustomLayerRenderParameters&) = 0; virtual void Deinitialize() = 0; + /** + * @brief Run mouse picking on the layer. + * + * @param [in] params Custom layer render parameters + * + * @return true if a draw item was picked, otherwise false + */ + virtual bool + RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params); + protected: std::shared_ptr context() const; diff --git a/scwx-qt/source/scwx/qt/map/map_context.cpp b/scwx-qt/source/scwx/qt/map/map_context.cpp index b141b838..79d9c719 100644 --- a/scwx-qt/source/scwx/qt/map/map_context.cpp +++ b/scwx-qt/source/scwx/qt/map/map_context.cpp @@ -23,13 +23,14 @@ public: ~Impl() {} - std::weak_ptr map_; - MapSettings settings_; - float pixelRatio_; - std::shared_ptr radarProductView_; - common::RadarProductGroup radarProductGroup_; - std::string radarProduct_; - int16_t radarProductCode_; + std::weak_ptr map_; + MapSettings settings_; + float pixelRatio_; + std::shared_ptr radarProductView_; + common::RadarProductGroup radarProductGroup_; + std::string radarProduct_; + int16_t radarProductCode_; + QMapLibreGL::CustomLayerRenderParameters renderParameters_ {}; }; MapContext::MapContext( @@ -77,6 +78,11 @@ int16_t MapContext::radar_product_code() const return p->radarProductCode_; } +QMapLibreGL::CustomLayerRenderParameters MapContext::render_parameters() const +{ + return p->renderParameters_; +} + void MapContext::set_map(std::shared_ptr map) { p->map_ = map; @@ -109,6 +115,12 @@ void MapContext::set_radar_product_code(int16_t radarProductCode) p->radarProductCode_ = radarProductCode; } +void MapContext::set_render_parameters( + const QMapLibreGL::CustomLayerRenderParameters& params) +{ + p->renderParameters_ = params; +} + } // namespace map } // namespace qt } // namespace scwx diff --git a/scwx-qt/source/scwx/qt/map/map_context.hpp b/scwx-qt/source/scwx/qt/map/map_context.hpp index b9659ad2..fc2b893e 100644 --- a/scwx-qt/source/scwx/qt/map/map_context.hpp +++ b/scwx-qt/source/scwx/qt/map/map_context.hpp @@ -26,13 +26,14 @@ public: MapContext(MapContext&&) noexcept; MapContext& operator=(MapContext&&) noexcept; - std::weak_ptr map() const; - MapSettings& settings(); - float pixel_ratio() const; - std::shared_ptr radar_product_view() const; - common::RadarProductGroup radar_product_group() const; - std::string radar_product() const; - int16_t radar_product_code() const; + std::weak_ptr map() const; + MapSettings& settings(); + float pixel_ratio() const; + std::shared_ptr radar_product_view() const; + common::RadarProductGroup radar_product_group() const; + std::string radar_product() const; + int16_t radar_product_code() const; + QMapLibreGL::CustomLayerRenderParameters render_parameters() const; void set_map(std::shared_ptr map); void set_pixel_ratio(float pixelRatio); @@ -41,6 +42,8 @@ public: void set_radar_product_group(common::RadarProductGroup radarProductGroup); void set_radar_product(const std::string& radarProduct); void set_radar_product_code(int16_t radarProductCode); + void set_render_parameters( + const QMapLibreGL::CustomLayerRenderParameters& params); private: class Impl; diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index bfb2284a..cb164277 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -126,6 +126,7 @@ public: void RadarProductViewConnect(); void RadarProductViewDisconnect(); void RemovePlacefileLayer(const std::string& placefileName); + void RunMousePicking(); void SetRadarSite(const std::string& radarSite); void UpdatePlacefileLayers(); bool UpdateStoredMapParameters(); @@ -1022,6 +1023,9 @@ void MapWidget::paintGL() size() * pixelRatio()); p->map_->render(); + // Perform mouse picking + p->RunMousePicking(); + // Render ImGui Frame ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); @@ -1030,6 +1034,25 @@ void MapWidget::paintGL() Q_EMIT WidgetPainted(); } +void MapWidgetImpl::RunMousePicking() +{ + const QMapLibreGL::CustomLayerRenderParameters params = + context_->render_parameters(); + + // For each layer in reverse + // TODO: All Generic Layers, not just Placefile Layers + for (auto it = placefileLayers_.rbegin(); it != placefileLayers_.rend(); + ++it) + { + // Run mouse picking for each layer + if ((*it)->RunMousePicking(params)) + { + // If a draw item was picked, don't process additional layers + break; + } + } +} + void MapWidget::mapChanged(QMapLibreGL::Map::MapChange mapChange) { switch (mapChange) diff --git a/scwx-qt/source/scwx/qt/map/overlay_layer.cpp b/scwx-qt/source/scwx/qt/map/overlay_layer.cpp index e7f4b4ae..d114573d 100644 --- a/scwx-qt/source/scwx/qt/map/overlay_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/overlay_layer.cpp @@ -95,6 +95,8 @@ void OverlayLayer::Render( auto& settings = context()->settings(); const float pixelRatio = context()->pixel_ratio(); + context()->set_render_parameters(params); + if (p->sweepTimeNeedsUpdate_ && radarProductView != nullptr) { const scwx::util::time_zone* currentZone; From 37d751774d237f7309ba303043cd5a76d20e790d Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 27 Aug 2023 08:51:04 -0500 Subject: [PATCH 082/199] Move placefile text hover to mouse picking pass --- .../source/scwx/qt/gl/draw/placefile_text.cpp | 30 +++++++++++++++---- .../source/scwx/qt/gl/draw/placefile_text.hpp | 3 ++ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp index 082ee88e..99de2e87 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp @@ -55,6 +55,7 @@ public: float halfWidth_ {}; float halfHeight_ {}; ImFont* monospaceFont_ {}; + std::string hoverText_ {}; units::length::nautical_miles mapDistance_ {}; @@ -94,6 +95,7 @@ void PlacefileText::Render( { // Reset text ID per frame p->textId_ = 0; + p->hoverText_.clear(); // Update map screen coordinate and scale information p->mapScreenCoordLocation_ = util::maplibre::LatLongToScreenCoordinate( @@ -189,14 +191,10 @@ void PlacefileText::Impl::RenderText( ImGui::TextUnformatted(text.c_str()); ImGui::PopStyleColor(); - // Create tooltip for hover text + // Store hover text for mouse picking pass if (!hoverText.empty() && ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushFont(monospaceFont_); - ImGui::TextUnformatted(hoverText.c_str()); - ImGui::PopFont(); - ImGui::EndTooltip(); + hoverText_ = hoverText; } // End window @@ -211,6 +209,26 @@ void PlacefileText::Deinitialize() p->textList_.clear(); } +bool PlacefileText::RunMousePicking( + const QMapLibreGL::CustomLayerRenderParameters& /* params */) +{ + bool itemPicked = false; + + // Create tooltip for hover text + if (!p->hoverText_.empty()) + { + itemPicked = true; + + ImGui::BeginTooltip(); + ImGui::PushFont(p->monospaceFont_); + ImGui::TextUnformatted(p->hoverText_.c_str()); + ImGui::PopFont(); + ImGui::EndTooltip(); + } + + return itemPicked; +} + void PlacefileText::StartText() { // Clear the new list diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp index 58e22929..65c05905 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp @@ -33,6 +33,9 @@ public: void Render(const QMapLibreGL::CustomLayerRenderParameters& params) override; void Deinitialize() override; + bool RunMousePicking( + const QMapLibreGL::CustomLayerRenderParameters& params) override; + /** * Resets and prepares the draw item for adding a new set of text. */ From 8dfb9f1105d1224b47528e97324c6d64eed21827 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 28 Aug 2023 00:15:57 -0500 Subject: [PATCH 083/199] Placefile line hover in-work --- scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp | 15 +- scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp | 5 +- .../scwx/qt/gl/draw/placefile_lines.cpp | 181 ++++++++++++++++-- .../scwx/qt/gl/draw/placefile_lines.hpp | 3 + .../source/scwx/qt/gl/draw/placefile_text.cpp | 3 +- .../source/scwx/qt/gl/draw/placefile_text.hpp | 4 +- scwx-qt/source/scwx/qt/map/draw_layer.cpp | 5 +- scwx-qt/source/scwx/qt/map/draw_layer.hpp | 5 +- scwx-qt/source/scwx/qt/map/generic_layer.cpp | 3 +- scwx-qt/source/scwx/qt/map/generic_layer.hpp | 5 +- scwx-qt/source/scwx/qt/map/map_widget.cpp | 23 ++- scwx-qt/source/scwx/qt/map/map_widget.hpp | 2 + scwx-qt/source/scwx/qt/util/maplibre.cpp | 23 +++ scwx-qt/source/scwx/qt/util/maplibre.hpp | 2 + 14 files changed, 243 insertions(+), 36 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp b/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp index 56d9b574..e1e45dee 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp @@ -54,7 +54,8 @@ void DrawItem::Render(const QMapLibreGL::CustomLayerRenderParameters& params, } bool DrawItem::RunMousePicking( - const QMapLibreGL::CustomLayerRenderParameters& /* params */) + const QMapLibreGL::CustomLayerRenderParameters& /* params */, + const glm::vec2& /* mousePos */) { // By default, the draw item is not picked return false; @@ -97,17 +98,7 @@ void DrawItem::UseMapProjection( { OpenGLFunctions& gl = p->gl_; - // TODO: Refactor to utility class - const float scale = std::pow(2.0, params.zoom) * 2.0f * - mbgl::util::tileSize_D / mbgl::util::DEGREES_MAX; - const float xScale = scale / params.width; - const float yScale = scale / params.height; - - glm::mat4 uMVPMatrix(1.0f); - uMVPMatrix = glm::scale(uMVPMatrix, glm::vec3(xScale, yScale, 1.0f)); - uMVPMatrix = glm::rotate(uMVPMatrix, - glm::radians(params.bearing), - glm::vec3(0.0f, 0.0f, 1.0f)); + const glm::mat4 uMVPMatrix = util::maplibre::GetMapMatrix(params); gl.glUniform2fv(uMapScreenCoordLocation, 1, diff --git a/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp b/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp index 5e559d12..50704506 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp @@ -5,6 +5,7 @@ #include #include +#include namespace scwx { @@ -37,11 +38,13 @@ public: * @brief Run mouse picking on the draw item. * * @param [in] params Custom layer render parameters + * @param [in] mousePos Mouse cursor location in map screen coordinates * * @return true if the draw item was picked, otherwise false */ virtual bool - RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params); + RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params, + const glm::vec2& mousePos); protected: void diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp index 6112d078..d94a0209 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp @@ -1,8 +1,12 @@ #include +#include +#include #include #include #include +#include + namespace scwx { namespace qt @@ -28,6 +32,15 @@ static const boost::gil::rgba8_pixel_t kBlack_ {0, 0, 0, 255}; class PlacefileLines::Impl { public: + struct LineHoverEntry + { + std::string hoverText_; + glm::vec2 p1_; + glm::vec2 p2_; + glm::mat2 rotate_; + float width_; + }; + explicit Impl(const std::shared_ptr& context) : context_ {context}, shaderProgram_ {nullptr}, @@ -46,9 +59,10 @@ public: void BufferLine(const gr::Placefile::LineDrawItem::Element& e1, const gr::Placefile::LineDrawItem::Element& e2, const float width, - const float angle, + const units::angle::degrees angle, const boost::gil::rgba8_pixel_t color, - const GLint threshold); + const GLint threshold, + const std::string& hoverText = {}); void UpdateBuffers(const std::shared_ptr& di); void Update(); @@ -68,6 +82,9 @@ public: std::vector newLinesBuffer_ {}; std::vector newThresholdBuffer_ {}; + std::vector currentHoverLines_ {}; + std::vector newHoverLines_ {}; + std::shared_ptr shaderProgram_; GLint uMVPMatrixLocation_; GLint uMapMatrixLocation_; @@ -213,6 +230,125 @@ void PlacefileLines::Deinitialize() p->currentLinesBuffer_.clear(); p->currentThresholdBuffer_.clear(); + p->currentHoverLines_.clear(); +} + +void DrawTooltip(const std::string& hoverText) +{ + // Get monospace font pointer + std::size_t fontSize = 16; + auto fontSizes = + manager::SettingsManager::general_settings().font_sizes().GetValue(); + if (fontSizes.size() > 1) + { + fontSize = fontSizes[1]; + } + else if (fontSizes.size() > 0) + { + fontSize = fontSizes[0]; + } + auto monospace = + manager::ResourceManager::Font(types::Font::Inconsolata_Regular); + auto monospaceFont = monospace->ImGuiFont(fontSize); + + ImGui::BeginTooltip(); + ImGui::PushFont(monospaceFont); + ImGui::TextUnformatted(hoverText.c_str()); + ImGui::PopFont(); + ImGui::EndTooltip(); +} + +bool IsPointInPolygon(const std::vector vertices, + const glm::vec2& point) +{ + bool inPolygon = true; + + // For each vertex, assume counterclockwise order + for (std::size_t i = 0; i < vertices.size(); ++i) + { + const auto& p1 = vertices[i]; + const auto& p2 = + (i == vertices.size() - 1) ? vertices[0] : vertices[i + 1]; + + // Test which side of edge point lies on + const float a = -(p2.y - p1.y); + const float b = p2.x - p1.x; + const float c = -(a * p1.x + b * p1.y); + const float d = a * point.x + b * point.y + c; + + // If d < 0, the point is on the right-hand side, and outside of the + // polygon + if (d < 0) + { + inPolygon = false; + break; + } + } + + return inPolygon; +} + +bool PlacefileLines::RunMousePicking( + const QMapLibreGL::CustomLayerRenderParameters& params, + const glm::vec2& mousePos) +{ + std::unique_lock lock {p->lineMutex_}; + + bool itemPicked = false; + + // Calculate map scale, remove width and height from original calculation + glm::vec2 scale = util::maplibre::GetMapScale(params); + scale = 1.0f / glm::vec2 {scale.x * params.width, scale.y * params.height}; + + // Scale and rotate the identity matrix to create the map matrix + glm::mat4 mapMatrix {1.0f}; + mapMatrix = glm::scale(mapMatrix, glm::vec3 {scale, 1.0f}); + mapMatrix = glm::rotate(mapMatrix, + glm::radians(params.bearing), + glm::vec3(0.0f, 0.0f, 1.0f)); + + // For each pickable line + for (auto& line : p->currentHoverLines_) + { + // Initialize vertices + glm::vec2 bl = line.p1_; + glm::vec2 br = bl; + glm::vec2 tl = line.p2_; + glm::vec2 tr = tl; + + // Calculate offsets + // - Offset is half the line width (pixels) in each direction + // - Rotate the offset at each vertex + // - Multiply the offset by the map matrix + const float hw = line.width_ * 0.5f; + const glm::vec2 otl = + mapMatrix * + glm::vec4 {line.rotate_ * glm::vec2 {-hw, -hw}, 0.0f, 1.0f}; + const glm::vec2 obl = + mapMatrix * glm::vec4 {line.rotate_ * glm::vec2 {-hw, hw}, 0.0f, 1.0f}; + const glm::vec2 obr = + mapMatrix * glm::vec4 {line.rotate_ * glm::vec2 {hw, hw}, 0.0f, 1.0f}; + const glm::vec2 otr = + mapMatrix * glm::vec4 {line.rotate_ * glm::vec2 {hw, -hw}, 0.0f, 1.0f}; + + // Offset vertices + tl += otl; + bl += obl; + br += obr; + tr += otr; + + // TODO: X/Y offsets + + // Test point against polygon bounds + if (IsPointInPolygon({tl, bl, br, tr}, mousePos)) + { + itemPicked = true; + DrawTooltip(line.hoverText_); + break; + } + } + + return itemPicked; } void PlacefileLines::StartLines() @@ -220,6 +356,7 @@ void PlacefileLines::StartLines() // Clear the new buffers p->newLinesBuffer_.clear(); p->newThresholdBuffer_.clear(); + p->newHoverLines_.clear(); p->newNumLines_ = 0u; } @@ -241,10 +378,12 @@ void PlacefileLines::FinishLines() // Swap buffers p->currentLinesBuffer_.swap(p->newLinesBuffer_); p->currentThresholdBuffer_.swap(p->newThresholdBuffer_); + p->currentHoverLines_.swap(p->newHoverLines_); // Clear the new buffers p->newLinesBuffer_.clear(); p->newThresholdBuffer_.clear(); + p->newHoverLines_.clear(); // Update the number of lines p->currentNumLines_ = p->newNumLines_; @@ -262,7 +401,7 @@ void PlacefileLines::Impl::UpdateBuffers( units::length::nautical_miles threshold = di->threshold_; GLint thresholdValue = static_cast(std::round(threshold.value())); - std::vector angles {}; + std::vector> angles {}; angles.reserve(di->elements_.size() - 1); // For each element pair inside a Line statement, render a black line @@ -277,26 +416,27 @@ void PlacefileLines::Impl::UpdateBuffers( // Calculate angle const units::angle::degrees angle = util::GeographicLib::GetAngle(lat1, lon1, lat2, lon2); - float angleValue = angle.value(); - angles.push_back(angleValue); + angles.push_back(angle); + // Buffer line with hover text BufferLine(di->elements_[i], di->elements_[i + 1], di->width_ + 2, - static_cast(angleValue), + angle, kBlack_, - thresholdValue); + thresholdValue, + di->hoverText_); } // For each element pair inside a Line statement, render a colored line for (std::size_t i = 0; i < di->elements_.size() - 1; ++i) { - float angleValue = angles[i]; + auto angle = angles[i]; BufferLine(di->elements_[i], di->elements_[i + 1], di->width_, - static_cast(angleValue), + angle, di->color_, thresholdValue); } @@ -306,9 +446,10 @@ void PlacefileLines::Impl::BufferLine( const gr::Placefile::LineDrawItem::Element& e1, const gr::Placefile::LineDrawItem::Element& e2, const float width, - const float angle, + const units::angle::degrees angle, const boost::gil::rgba8_pixel_t color, - const GLint threshold) + const GLint threshold, + const std::string& hoverText) { // Latitude and longitude coordinates in degrees const float lat1 = static_cast(e1.latitude_); @@ -323,7 +464,7 @@ void PlacefileLines::Impl::BufferLine( // const float y2 = static_cast(e2.y_); // Angle - const float a = angle; + const float a = static_cast(angle.value()); // Final X/Y offsets in pixels const float hw = width * 0.5f; @@ -352,6 +493,22 @@ void PlacefileLines::Impl::BufferLine( newThresholdBuffer_.insert( newThresholdBuffer_.end(), {threshold, threshold, threshold, threshold, threshold, threshold}); + + if (!hoverText.empty()) + { + const units::angle::radians radians = angle; + + const auto sc1 = util::maplibre::LatLongToScreenCoordinate({lat1, lon1}); + const auto sc2 = util::maplibre::LatLongToScreenCoordinate({lat2, lon2}); + + const float cosAngle = cosf(static_cast(radians.value())); + const float sinAngle = sinf(static_cast(radians.value())); + + const glm::mat2 rotate {cosAngle, -sinAngle, sinAngle, cosAngle}; + + newHoverLines_.emplace_back( + LineHoverEntry {hoverText, sc1, sc2, rotate, width}); + } } void PlacefileLines::Impl::Update() diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp index 4dbf518d..551b955c 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp @@ -31,6 +31,9 @@ public: void Render(const QMapLibreGL::CustomLayerRenderParameters& params) override; void Deinitialize() override; + bool RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params, + const glm::vec2& mousePos) override; + /** * Resets and prepares the draw item for adding a new set of lines. */ diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp index 99de2e87..ccd77f0c 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp @@ -210,7 +210,8 @@ void PlacefileText::Deinitialize() } bool PlacefileText::RunMousePicking( - const QMapLibreGL::CustomLayerRenderParameters& /* params */) + const QMapLibreGL::CustomLayerRenderParameters& /* params */, + const glm::vec2& /* mousePos */) { bool itemPicked = false; diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp index 65c05905..a4e51fec 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp @@ -33,8 +33,8 @@ public: void Render(const QMapLibreGL::CustomLayerRenderParameters& params) override; void Deinitialize() override; - bool RunMousePicking( - const QMapLibreGL::CustomLayerRenderParameters& params) override; + bool RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params, + const glm::vec2& mousePos) override; /** * Resets and prepares the draw item for adding a new set of text. diff --git a/scwx-qt/source/scwx/qt/map/draw_layer.cpp b/scwx-qt/source/scwx/qt/map/draw_layer.cpp index 2116a1bd..6a6895e7 100644 --- a/scwx-qt/source/scwx/qt/map/draw_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/draw_layer.cpp @@ -77,7 +77,8 @@ void DrawLayer::Deinitialize() } bool DrawLayer::RunMousePicking( - const QMapLibreGL::CustomLayerRenderParameters& params) + const QMapLibreGL::CustomLayerRenderParameters& params, + const glm::vec2& mousePos) { bool itemPicked = false; @@ -85,7 +86,7 @@ bool DrawLayer::RunMousePicking( for (auto it = p->drawList_.rbegin(); it != p->drawList_.rend(); ++it) { // Run mouse picking on each draw item - if ((*it)->RunMousePicking(params)) + if ((*it)->RunMousePicking(params, mousePos)) { // If a draw item was picked, don't process additional items itemPicked = true; diff --git a/scwx-qt/source/scwx/qt/map/draw_layer.hpp b/scwx-qt/source/scwx/qt/map/draw_layer.hpp index e2b3694c..2924a408 100644 --- a/scwx-qt/source/scwx/qt/map/draw_layer.hpp +++ b/scwx-qt/source/scwx/qt/map/draw_layer.hpp @@ -23,8 +23,9 @@ public: Render(const QMapLibreGL::CustomLayerRenderParameters&) override; virtual void Deinitialize() override; - virtual bool RunMousePicking( - const QMapLibreGL::CustomLayerRenderParameters& params) override; + virtual bool + RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params, + const glm::vec2& mousePos) override; protected: void AddDrawItem(const std::shared_ptr& drawItem); diff --git a/scwx-qt/source/scwx/qt/map/generic_layer.cpp b/scwx-qt/source/scwx/qt/map/generic_layer.cpp index 3ec27513..ce8673a4 100644 --- a/scwx-qt/source/scwx/qt/map/generic_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/generic_layer.cpp @@ -27,7 +27,8 @@ GenericLayer::GenericLayer(std::shared_ptr context) : GenericLayer::~GenericLayer() = default; bool GenericLayer::RunMousePicking( - const QMapLibreGL::CustomLayerRenderParameters& /* params */) + const QMapLibreGL::CustomLayerRenderParameters& /* params */, + const glm::vec2& /* mousePos */) { // By default, the layer has nothing to pick return false; diff --git a/scwx-qt/source/scwx/qt/map/generic_layer.hpp b/scwx-qt/source/scwx/qt/map/generic_layer.hpp index b44e38f3..48eb9ca0 100644 --- a/scwx-qt/source/scwx/qt/map/generic_layer.hpp +++ b/scwx-qt/source/scwx/qt/map/generic_layer.hpp @@ -6,6 +6,7 @@ #include #include +#include namespace scwx { @@ -32,11 +33,13 @@ public: * @brief Run mouse picking on the layer. * * @param [in] params Custom layer render parameters + * @param [in] mousePos Mouse cursor location in map screen coordinates * * @return true if a draw item was picked, otherwise false */ virtual bool - RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params); + RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params, + const glm::vec2& mousePos); protected: std::shared_ptr context() const; diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index cb164277..9d49bae8 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -172,6 +173,7 @@ public: common::Level2Product selectedLevel2Product_; + bool hasMouse_ {false}; QPointF lastPos_; std::size_t currentStyleIndex_; const MapStyle* currentStyle_; @@ -862,6 +864,16 @@ void MapWidgetImpl::AddLayer(const std::string& id, layerList_.push_back(id); } +void MapWidget::enterEvent(QEnterEvent* /* ev */) +{ + p->hasMouse_ = true; +} + +void MapWidget::leaveEvent(QEvent* /* ev */) +{ + p->hasMouse_ = false; +} + void MapWidget::keyPressEvent(QKeyEvent* ev) { switch (ev->key()) @@ -1024,7 +1036,10 @@ void MapWidget::paintGL() p->map_->render(); // Perform mouse picking - p->RunMousePicking(); + if (p->hasMouse_) + { + p->RunMousePicking(); + } // Render ImGui Frame ImGui::Render(); @@ -1039,13 +1054,17 @@ void MapWidgetImpl::RunMousePicking() const QMapLibreGL::CustomLayerRenderParameters params = context_->render_parameters(); + auto coordinate = map_->coordinateForPixel(lastPos_); + auto mouseScreenCoordinate = + util::maplibre::LatLongToScreenCoordinate(coordinate); + // For each layer in reverse // TODO: All Generic Layers, not just Placefile Layers for (auto it = placefileLayers_.rbegin(); it != placefileLayers_.rend(); ++it) { // Run mouse picking for each layer - if ((*it)->RunMousePicking(params)) + if ((*it)->RunMousePicking(params, mouseScreenCoordinate)) { // If a draw item was picked, don't process additional layers break; diff --git a/scwx-qt/source/scwx/qt/map/map_widget.hpp b/scwx-qt/source/scwx/qt/map/map_widget.hpp index e1c75a79..d18bec6e 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.hpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.hpp @@ -119,7 +119,9 @@ private: qreal pixelRatio(); // QWidget implementation. + void enterEvent(QEnterEvent* ev) override final; void keyPressEvent(QKeyEvent* ev) override final; + void leaveEvent(QEvent* ev) override final; void mousePressEvent(QMouseEvent* ev) override final; void mouseMoveEvent(QMouseEvent* ev) override final; void wheelEvent(QWheelEvent* ev) override final; diff --git a/scwx-qt/source/scwx/qt/util/maplibre.cpp b/scwx-qt/source/scwx/qt/util/maplibre.cpp index 8cde77ca..1093ce02 100644 --- a/scwx-qt/source/scwx/qt/util/maplibre.cpp +++ b/scwx-qt/source/scwx/qt/util/maplibre.cpp @@ -20,6 +20,29 @@ GetMapDistance(const QMapLibreGL::CustomLayerRenderParameters& params) (params.width + params.height) / 2.0); } +glm::mat4 GetMapMatrix(const QMapLibreGL::CustomLayerRenderParameters& params) +{ + glm::vec2 scale = GetMapScale(params); + + glm::mat4 mapMatrix(1.0f); + mapMatrix = glm::scale(mapMatrix, glm::vec3(scale, 1.0f)); + mapMatrix = glm::rotate(mapMatrix, + glm::radians(params.bearing), + glm::vec3(0.0f, 0.0f, 1.0f)); + + return mapMatrix; +} + +glm::vec2 GetMapScale(const QMapLibreGL::CustomLayerRenderParameters& params) +{ + const float scale = std::pow(2.0, params.zoom) * 2.0f * + mbgl::util::tileSize_D / mbgl::util::DEGREES_MAX; + const float xScale = scale / params.width; + const float yScale = scale / params.height; + + return glm::vec2 {xScale, yScale}; +} + glm::vec2 LatLongToScreenCoordinate(const QMapLibreGL::Coordinate& coordinate) { static constexpr double RAD2DEG_D = 180.0 / M_PI; diff --git a/scwx-qt/source/scwx/qt/util/maplibre.hpp b/scwx-qt/source/scwx/qt/util/maplibre.hpp index 37d205eb..d62fb878 100644 --- a/scwx-qt/source/scwx/qt/util/maplibre.hpp +++ b/scwx-qt/source/scwx/qt/util/maplibre.hpp @@ -15,6 +15,8 @@ namespace maplibre units::length::meters GetMapDistance(const QMapLibreGL::CustomLayerRenderParameters& params); +glm::mat4 GetMapMatrix(const QMapLibreGL::CustomLayerRenderParameters& params); +glm::vec2 GetMapScale(const QMapLibreGL::CustomLayerRenderParameters& params); glm::vec2 LatLongToScreenCoordinate(const QMapLibreGL::Coordinate& coordinate); } // namespace maplibre From 5bb5093579616d07e4ce694a5a2d0366cdbf54da Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 28 Aug 2023 23:09:05 -0500 Subject: [PATCH 084/199] Fixing placefile line hover offsets --- .../scwx/qt/gl/draw/placefile_lines.cpp | 84 ++++++++++--------- 1 file changed, 45 insertions(+), 39 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp index d94a0209..ec1ba7ac 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp @@ -34,11 +34,15 @@ class PlacefileLines::Impl public: struct LineHoverEntry { - std::string hoverText_; - glm::vec2 p1_; - glm::vec2 p2_; - glm::mat2 rotate_; - float width_; + std::shared_ptr di_; + + glm::vec2 p1_; + glm::vec2 p2_; + glm::vec2 otl_; + glm::vec2 otr_; + glm::vec2 obl_; + glm::vec2 obr_; + float width_; }; explicit Impl(const std::shared_ptr& context) : @@ -56,13 +60,14 @@ public: ~Impl() {} - void BufferLine(const gr::Placefile::LineDrawItem::Element& e1, - const gr::Placefile::LineDrawItem::Element& e2, - const float width, - const units::angle::degrees angle, - const boost::gil::rgba8_pixel_t color, - const GLint threshold, - const std::string& hoverText = {}); + void BufferLine(const std::shared_ptr& di, + const gr::Placefile::LineDrawItem::Element& e1, + const gr::Placefile::LineDrawItem::Element& e2, + const float width, + const units::angle::degrees angle, + const boost::gil::rgba8_pixel_t color, + const GLint threshold, + bool bufferHover = false); void UpdateBuffers(const std::shared_ptr& di); void Update(); @@ -298,7 +303,7 @@ bool PlacefileLines::RunMousePicking( // Calculate map scale, remove width and height from original calculation glm::vec2 scale = util::maplibre::GetMapScale(params); - scale = 1.0f / glm::vec2 {scale.x * params.width, scale.y * params.height}; + scale = 2.0f / glm::vec2 {scale.x * params.width, scale.y * params.height}; // Scale and rotate the identity matrix to create the map matrix glm::mat4 mapMatrix {1.0f}; @@ -317,19 +322,12 @@ bool PlacefileLines::RunMousePicking( glm::vec2 tr = tl; // Calculate offsets - // - Offset is half the line width (pixels) in each direction - // - Rotate the offset at each vertex - // - Multiply the offset by the map matrix - const float hw = line.width_ * 0.5f; - const glm::vec2 otl = - mapMatrix * - glm::vec4 {line.rotate_ * glm::vec2 {-hw, -hw}, 0.0f, 1.0f}; - const glm::vec2 obl = - mapMatrix * glm::vec4 {line.rotate_ * glm::vec2 {-hw, hw}, 0.0f, 1.0f}; - const glm::vec2 obr = - mapMatrix * glm::vec4 {line.rotate_ * glm::vec2 {hw, hw}, 0.0f, 1.0f}; - const glm::vec2 otr = - mapMatrix * glm::vec4 {line.rotate_ * glm::vec2 {hw, -hw}, 0.0f, 1.0f}; + // - Pre-rotated offset is half the line width (pixels) in each direction + // - Multiply the offset by the scaled and rotated map matrix + const glm::vec2 otl = mapMatrix * glm::vec4 {line.otl_, 0.0f, 1.0f}; + const glm::vec2 obl = mapMatrix * glm::vec4 {line.obl_, 0.0f, 1.0f}; + const glm::vec2 obr = mapMatrix * glm::vec4 {line.obr_, 0.0f, 1.0f}; + const glm::vec2 otr = mapMatrix * glm::vec4 {line.otr_, 0.0f, 1.0f}; // Offset vertices tl += otl; @@ -343,7 +341,7 @@ bool PlacefileLines::RunMousePicking( if (IsPointInPolygon({tl, bl, br, tr}, mousePos)) { itemPicked = true; - DrawTooltip(line.hoverText_); + DrawTooltip(line.di_->hoverText_); break; } } @@ -419,13 +417,14 @@ void PlacefileLines::Impl::UpdateBuffers( angles.push_back(angle); // Buffer line with hover text - BufferLine(di->elements_[i], + BufferLine(di, + di->elements_[i], di->elements_[i + 1], di->width_ + 2, angle, kBlack_, thresholdValue, - di->hoverText_); + true); } // For each element pair inside a Line statement, render a colored line @@ -433,7 +432,8 @@ void PlacefileLines::Impl::UpdateBuffers( { auto angle = angles[i]; - BufferLine(di->elements_[i], + BufferLine(di, + di->elements_[i], di->elements_[i + 1], di->width_, angle, @@ -443,13 +443,14 @@ void PlacefileLines::Impl::UpdateBuffers( } void PlacefileLines::Impl::BufferLine( - const gr::Placefile::LineDrawItem::Element& e1, - const gr::Placefile::LineDrawItem::Element& e2, - const float width, - const units::angle::degrees angle, - const boost::gil::rgba8_pixel_t color, - const GLint threshold, - const std::string& hoverText) + const std::shared_ptr& di, + const gr::Placefile::LineDrawItem::Element& e1, + const gr::Placefile::LineDrawItem::Element& e2, + const float width, + const units::angle::degrees angle, + const boost::gil::rgba8_pixel_t color, + const GLint threshold, + bool bufferHover) { // Latitude and longitude coordinates in degrees const float lat1 = static_cast(e1.latitude_); @@ -494,7 +495,7 @@ void PlacefileLines::Impl::BufferLine( newThresholdBuffer_.end(), {threshold, threshold, threshold, threshold, threshold, threshold}); - if (!hoverText.empty()) + if (bufferHover && !di->hoverText_.empty()) { const units::angle::radians radians = angle; @@ -506,8 +507,13 @@ void PlacefileLines::Impl::BufferLine( const glm::mat2 rotate {cosAngle, -sinAngle, sinAngle, cosAngle}; + const glm::vec2 otl = rotate * glm::vec2 {-hw, +hw}; + const glm::vec2 otr = rotate * glm::vec2 {+hw, +hw}; + const glm::vec2 obl = rotate * glm::vec2 {-hw, -hw}; + const glm::vec2 obr = rotate * glm::vec2 {+hw, -hw}; + newHoverLines_.emplace_back( - LineHoverEntry {hoverText, sc1, sc2, rotate, width}); + LineHoverEntry {di, sc1, sc2, otl, otr, obl, obr, width}); } } From 2c3de1a28ff3d14c95369203ab10ef6b44a8aeba Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 28 Aug 2023 23:43:07 -0500 Subject: [PATCH 085/199] Refactoring map and ImGui functions from hover text --- scwx-qt/scwx-qt.cmake | 2 + .../scwx/qt/gl/draw/placefile_lines.cpp | 68 +----------- .../source/scwx/qt/gl/draw/placefile_text.cpp | 27 +---- scwx-qt/source/scwx/qt/util/imgui.cpp | 105 ++++++++++++++++++ scwx-qt/source/scwx/qt/util/imgui.hpp | 37 ++++++ scwx-qt/source/scwx/qt/util/maplibre.cpp | 30 +++++ scwx-qt/source/scwx/qt/util/maplibre.hpp | 12 ++ 7 files changed, 192 insertions(+), 89 deletions(-) create mode 100644 scwx-qt/source/scwx/qt/util/imgui.cpp create mode 100644 scwx-qt/source/scwx/qt/util/imgui.hpp diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index fd8bd87a..7719e855 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -210,6 +210,7 @@ set(HDR_UTIL source/scwx/qt/util/color.hpp source/scwx/qt/util/font.hpp source/scwx/qt/util/font_buffer.hpp source/scwx/qt/util/geographic_lib.hpp + source/scwx/qt/util/imgui.hpp source/scwx/qt/util/json.hpp source/scwx/qt/util/maplibre.hpp source/scwx/qt/util/network.hpp @@ -223,6 +224,7 @@ set(SRC_UTIL source/scwx/qt/util/color.cpp source/scwx/qt/util/font.cpp source/scwx/qt/util/font_buffer.cpp source/scwx/qt/util/geographic_lib.cpp + source/scwx/qt/util/imgui.cpp source/scwx/qt/util/json.cpp source/scwx/qt/util/maplibre.cpp source/scwx/qt/util/network.cpp diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp index ec1ba7ac..29677233 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp @@ -1,12 +1,9 @@ #include -#include -#include #include +#include #include #include -#include - namespace scwx { namespace qt @@ -238,61 +235,6 @@ void PlacefileLines::Deinitialize() p->currentHoverLines_.clear(); } -void DrawTooltip(const std::string& hoverText) -{ - // Get monospace font pointer - std::size_t fontSize = 16; - auto fontSizes = - manager::SettingsManager::general_settings().font_sizes().GetValue(); - if (fontSizes.size() > 1) - { - fontSize = fontSizes[1]; - } - else if (fontSizes.size() > 0) - { - fontSize = fontSizes[0]; - } - auto monospace = - manager::ResourceManager::Font(types::Font::Inconsolata_Regular); - auto monospaceFont = monospace->ImGuiFont(fontSize); - - ImGui::BeginTooltip(); - ImGui::PushFont(monospaceFont); - ImGui::TextUnformatted(hoverText.c_str()); - ImGui::PopFont(); - ImGui::EndTooltip(); -} - -bool IsPointInPolygon(const std::vector vertices, - const glm::vec2& point) -{ - bool inPolygon = true; - - // For each vertex, assume counterclockwise order - for (std::size_t i = 0; i < vertices.size(); ++i) - { - const auto& p1 = vertices[i]; - const auto& p2 = - (i == vertices.size() - 1) ? vertices[0] : vertices[i + 1]; - - // Test which side of edge point lies on - const float a = -(p2.y - p1.y); - const float b = p2.x - p1.x; - const float c = -(a * p1.x + b * p1.y); - const float d = a * point.x + b * point.y + c; - - // If d < 0, the point is on the right-hand side, and outside of the - // polygon - if (d < 0) - { - inPolygon = false; - break; - } - } - - return inPolygon; -} - bool PlacefileLines::RunMousePicking( const QMapLibreGL::CustomLayerRenderParameters& params, const glm::vec2& mousePos) @@ -338,10 +280,10 @@ bool PlacefileLines::RunMousePicking( // TODO: X/Y offsets // Test point against polygon bounds - if (IsPointInPolygon({tl, bl, br, tr}, mousePos)) + if (util::maplibre::IsPointInPolygon({tl, bl, br, tr}, mousePos)) { itemPicked = true; - DrawTooltip(line.di_->hoverText_); + util::ImGui::Instance().DrawTooltip(line.di_->hoverText_); break; } } @@ -430,13 +372,11 @@ void PlacefileLines::Impl::UpdateBuffers( // For each element pair inside a Line statement, render a colored line for (std::size_t i = 0; i < di->elements_.size() - 1; ++i) { - auto angle = angles[i]; - BufferLine(di, di->elements_[i], di->elements_[i + 1], di->width_, - angle, + angles[i], di->color_, thresholdValue); } diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp index ccd77f0c..227af962 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp @@ -1,6 +1,5 @@ #include -#include -#include +#include #include #include @@ -54,7 +53,6 @@ public: float mapBearingSin_ {0.0f}; float halfWidth_ {}; float halfHeight_ {}; - ImFont* monospaceFont_ {}; std::string hoverText_ {}; units::length::nautical_miles mapDistance_ {}; @@ -108,22 +106,6 @@ void PlacefileText::Render( p->halfHeight_ = params.height * 0.5f; p->mapDistance_ = util::maplibre::GetMapDistance(params); - // Get monospace font pointer - std::size_t fontSize = 16; - auto fontSizes = - manager::SettingsManager::general_settings().font_sizes().GetValue(); - if (fontSizes.size() > 1) - { - fontSize = fontSizes[1]; - } - else if (fontSizes.size() > 0) - { - fontSize = fontSizes[0]; - } - auto monospace = - manager::ResourceManager::Font(types::Font::Inconsolata_Regular); - p->monospaceFont_ = monospace->ImGuiFont(fontSize); - for (auto& di : p->textList_) { p->RenderTextDrawItem(params, di); @@ -219,12 +201,7 @@ bool PlacefileText::RunMousePicking( if (!p->hoverText_.empty()) { itemPicked = true; - - ImGui::BeginTooltip(); - ImGui::PushFont(p->monospaceFont_); - ImGui::TextUnformatted(p->hoverText_.c_str()); - ImGui::PopFont(); - ImGui::EndTooltip(); + util::ImGui::Instance().DrawTooltip(p->hoverText_); } return itemPicked; diff --git a/scwx-qt/source/scwx/qt/util/imgui.cpp b/scwx-qt/source/scwx/qt/util/imgui.cpp new file mode 100644 index 00000000..9d1622ed --- /dev/null +++ b/scwx-qt/source/scwx/qt/util/imgui.cpp @@ -0,0 +1,105 @@ +#include +#include +#include +#include + +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace util +{ + +static const std::string logPrefix_ = "scwx::qt::util::imgui"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +class ImGui::Impl +{ +public: + explicit Impl() {} + ~Impl() {} + + void Initialize(); + void UpdateMonospaceFont(); + + bool initialized_ {false}; + + ImFont* monospaceFont_ {nullptr}; +}; + +ImGui::ImGui() : p(std::make_unique()) {} +ImGui::~ImGui() = default; + +ImGui::ImGui(ImGui&&) noexcept = default; +ImGui& ImGui::operator=(ImGui&&) noexcept = default; + +void ImGui::Impl::Initialize() +{ + if (initialized_) + { + return; + } + + logger_->debug("Initialize"); + + // Configure monospace font + UpdateMonospaceFont(); + manager::SettingsManager::general_settings() + .font_sizes() + .RegisterValueChangedCallback([this](const std::vector&) + { UpdateMonospaceFont(); }); + + initialized_ = true; +} + +void ImGui::Impl::UpdateMonospaceFont() +{ + // Get monospace font size + std::size_t fontSize = 16; + auto fontSizes = + manager::SettingsManager::general_settings().font_sizes().GetValue(); + if (fontSizes.size() > 1) + { + fontSize = fontSizes[1]; + } + else if (fontSizes.size() > 0) + { + fontSize = fontSizes[0]; + } + + // Get monospace font pointer + auto monospace = + manager::ResourceManager::Font(types::Font::Inconsolata_Regular); + auto monospaceFont = monospace->ImGuiFont(fontSize); + + // Store monospace font pointer if not null + if (monospaceFont != nullptr) + { + monospaceFont_ = monospace->ImGuiFont(fontSize); + } +} + +void ImGui::DrawTooltip(const std::string& hoverText) +{ + p->Initialize(); + + ::ImGui::BeginTooltip(); + ::ImGui::PushFont(p->monospaceFont_); + ::ImGui::TextUnformatted(hoverText.c_str()); + ::ImGui::PopFont(); + ::ImGui::EndTooltip(); +} + +ImGui& ImGui::Instance() +{ + static ImGui instance_ {}; + return instance_; +} + +} // namespace util +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/util/imgui.hpp b/scwx-qt/source/scwx/qt/util/imgui.hpp new file mode 100644 index 00000000..fce09a1e --- /dev/null +++ b/scwx-qt/source/scwx/qt/util/imgui.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace util +{ + +class ImGui +{ +public: + explicit ImGui(); + ~ImGui(); + + ImGui(const ImGui&) = delete; + ImGui& operator=(const ImGui&) = delete; + + ImGui(ImGui&&) noexcept; + ImGui& operator=(ImGui&&) noexcept; + + void DrawTooltip(const std::string& hoverText); + + static ImGui& Instance(); + +private: + class Impl; + + std::unique_ptr p; +}; + +} // namespace util +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/util/maplibre.cpp b/scwx-qt/source/scwx/qt/util/maplibre.cpp index 1093ce02..9cc092bb 100644 --- a/scwx-qt/source/scwx/qt/util/maplibre.cpp +++ b/scwx-qt/source/scwx/qt/util/maplibre.cpp @@ -43,6 +43,36 @@ glm::vec2 GetMapScale(const QMapLibreGL::CustomLayerRenderParameters& params) return glm::vec2 {xScale, yScale}; } +bool IsPointInPolygon(const std::vector& vertices, + const glm::vec2& point) +{ + bool inPolygon = true; + + // For each vertex, assume counterclockwise order + for (std::size_t i = 0; i < vertices.size(); ++i) + { + const auto& p1 = vertices[i]; + const auto& p2 = + (i == vertices.size() - 1) ? vertices[0] : vertices[i + 1]; + + // Test which side of edge point lies on + const float a = -(p2.y - p1.y); + const float b = p2.x - p1.x; + const float c = -(a * p1.x + b * p1.y); + const float d = a * point.x + b * point.y + c; + + // If d < 0, the point is on the right-hand side, and outside of the + // polygon + if (d < 0) + { + inPolygon = false; + break; + } + } + + return inPolygon; +} + glm::vec2 LatLongToScreenCoordinate(const QMapLibreGL::Coordinate& coordinate) { static constexpr double RAD2DEG_D = 180.0 / M_PI; diff --git a/scwx-qt/source/scwx/qt/util/maplibre.hpp b/scwx-qt/source/scwx/qt/util/maplibre.hpp index d62fb878..35989cbf 100644 --- a/scwx-qt/source/scwx/qt/util/maplibre.hpp +++ b/scwx-qt/source/scwx/qt/util/maplibre.hpp @@ -17,6 +17,18 @@ units::length::meters GetMapDistance(const QMapLibreGL::CustomLayerRenderParameters& params); glm::mat4 GetMapMatrix(const QMapLibreGL::CustomLayerRenderParameters& params); glm::vec2 GetMapScale(const QMapLibreGL::CustomLayerRenderParameters& params); + +/** + * @brief Determine whether a point lies within a polygon + * + * @param [in] vertices Counterclockwise vertices + * @param [in] point Point to test + * + * @return Whether the point lies within the polygon + */ +bool IsPointInPolygon(const std::vector& vertices, + const glm::vec2& point); + glm::vec2 LatLongToScreenCoordinate(const QMapLibreGL::Coordinate& coordinate); } // namespace maplibre From 24c919afb657849f2f8178fe31a6d74c7a730370 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 28 Aug 2023 23:51:24 -0500 Subject: [PATCH 086/199] Search for hovered lines in parallel --- .../scwx/qt/gl/draw/placefile_lines.cpp | 67 ++++++++++--------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp index 29677233..a2a8d38a 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp @@ -4,6 +4,8 @@ #include #include +#include + namespace scwx { namespace qt @@ -255,37 +257,42 @@ bool PlacefileLines::RunMousePicking( glm::vec3(0.0f, 0.0f, 1.0f)); // For each pickable line - for (auto& line : p->currentHoverLines_) - { - // Initialize vertices - glm::vec2 bl = line.p1_; - glm::vec2 br = bl; - glm::vec2 tl = line.p2_; - glm::vec2 tr = tl; - - // Calculate offsets - // - Pre-rotated offset is half the line width (pixels) in each direction - // - Multiply the offset by the scaled and rotated map matrix - const glm::vec2 otl = mapMatrix * glm::vec4 {line.otl_, 0.0f, 1.0f}; - const glm::vec2 obl = mapMatrix * glm::vec4 {line.obl_, 0.0f, 1.0f}; - const glm::vec2 obr = mapMatrix * glm::vec4 {line.obr_, 0.0f, 1.0f}; - const glm::vec2 otr = mapMatrix * glm::vec4 {line.otr_, 0.0f, 1.0f}; - - // Offset vertices - tl += otl; - bl += obl; - br += obr; - tr += otr; - - // TODO: X/Y offsets - - // Test point against polygon bounds - if (util::maplibre::IsPointInPolygon({tl, bl, br, tr}, mousePos)) + auto it = std::find_if( + std::execution::par_unseq, + p->currentHoverLines_.cbegin(), + p->currentHoverLines_.cend(), + [&mapMatrix, &mousePos](const auto& line) { - itemPicked = true; - util::ImGui::Instance().DrawTooltip(line.di_->hoverText_); - break; - } + // Initialize vertices + glm::vec2 bl = line.p1_; + glm::vec2 br = bl; + glm::vec2 tl = line.p2_; + glm::vec2 tr = tl; + + // Calculate offsets + // - Rotated offset is half the line width (pixels) in each direction + // - Multiply the offset by the scaled and rotated map matrix + const glm::vec2 otl = mapMatrix * glm::vec4 {line.otl_, 0.0f, 1.0f}; + const glm::vec2 obl = mapMatrix * glm::vec4 {line.obl_, 0.0f, 1.0f}; + const glm::vec2 obr = mapMatrix * glm::vec4 {line.obr_, 0.0f, 1.0f}; + const glm::vec2 otr = mapMatrix * glm::vec4 {line.otr_, 0.0f, 1.0f}; + + // Offset vertices + tl += otl; + bl += obl; + br += obr; + tr += otr; + + // TODO: X/Y offsets + + // Test point against polygon bounds + return util::maplibre::IsPointInPolygon({tl, bl, br, tr}, mousePos); + }); + + if (it != p->currentHoverLines_.cend()) + { + itemPicked = true; + util::ImGui::Instance().DrawTooltip(it->di_->hoverText_); } return itemPicked; From 23732cef48bda88e32a91aa5704c7b5d1faf5503 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 30 Aug 2023 22:43:44 -0500 Subject: [PATCH 087/199] Wrap hover text, default to 80 characters --- .gitmodules | 3 +++ ACKNOWLEDGEMENTS.md | 2 ++ external/CMakeLists.txt | 2 ++ external/textflowcpp | 1 + external/textflowcpp.cmake | 4 ++++ scwx-qt/scwx-qt.cmake | 3 ++- scwx-qt/source/scwx/qt/util/imgui.cpp | 8 +++++++- 7 files changed, 21 insertions(+), 2 deletions(-) create mode 160000 external/textflowcpp create mode 100644 external/textflowcpp.cmake diff --git a/.gitmodules b/.gitmodules index a0fe596c..d73393a6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -34,3 +34,6 @@ [submodule "external/units"] path = external/units url = https://github.com/nholthaus/units.git +[submodule "external/textflowcpp"] + path = external/textflowcpp + url = https://github.com/catchorg/textflowcpp.git diff --git a/ACKNOWLEDGEMENTS.md b/ACKNOWLEDGEMENTS.md index 87476473..1e70a3c7 100644 --- a/ACKNOWLEDGEMENTS.md +++ b/ACKNOWLEDGEMENTS.md @@ -36,6 +36,8 @@ Supercell Wx uses code from the following dependencies: | [spdlog](https://github.com/gabime/spdlog) | [MIT License](https://spdx.org/licenses/MIT.html) | | [SQLite](https://www.sqlite.org/) | Public Domain | | [stb](https://github.com/nothings/stb) | Public Domain | +| [TextFlowCpp](https://github.com/catchorg/textflowcpp) | [Boost Software License 1.0](https://spdx.org/licenses/BSL-1.0.html) | +| [Units](https://github.com/nholthaus/units) | [MIT License](https://spdx.org/licenses/MIT.html) | | [Vulkan SDK](https://www.vulkan.org/) | [Apache License 2.0](https://spdx.org/licenses/Apache-2.0.html) | | [zlib](https://zlib.net/) | [zlib License](https://spdx.org/licenses/Zlib.html) | diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 806b603b..3c0be1cf 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -11,6 +11,7 @@ set_property(DIRECTORY imgui.cmake mapbox-gl-native.cmake stb.cmake + textflowcpp.cmake units.cmake) include(aws-sdk-cpp.cmake) @@ -20,4 +21,5 @@ include(hsluv-c.cmake) include(imgui.cmake) include(mapbox-gl-native.cmake) include(stb.cmake) +include(textflowcpp.cmake) include(units.cmake) diff --git a/external/textflowcpp b/external/textflowcpp new file mode 160000 index 00000000..12010ddc --- /dev/null +++ b/external/textflowcpp @@ -0,0 +1 @@ +Subproject commit 12010ddc8d15538ceea20622d22977e7c5a25da5 diff --git a/external/textflowcpp.cmake b/external/textflowcpp.cmake new file mode 100644 index 00000000..1e36da18 --- /dev/null +++ b/external/textflowcpp.cmake @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 3.20) +set(PROJECT_NAME scwx-textflowcpp) + +set(TEXTFLOWCPP_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/textflowcpp PARENT_SCOPE) diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 7719e855..4b7e1d80 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -418,7 +418,8 @@ target_include_directories(scwx-qt PUBLIC ${scwx-qt_SOURCE_DIR}/source ${FTGL_INCLUDE_DIR} ${IMGUI_INCLUDE_DIRS} ${MBGL_INCLUDE_DIR} - ${STB_INCLUDE_DIR}) + ${STB_INCLUDE_DIR} + ${TEXTFLOWCPP_INCLUDE_DIR}) target_include_directories(supercell-wx PUBLIC ${scwx-qt_SOURCE_DIR}/source) diff --git a/scwx-qt/source/scwx/qt/util/imgui.cpp b/scwx-qt/source/scwx/qt/util/imgui.cpp index 9d1622ed..018e5000 100644 --- a/scwx-qt/source/scwx/qt/util/imgui.cpp +++ b/scwx-qt/source/scwx/qt/util/imgui.cpp @@ -5,6 +5,7 @@ #include +#include #include namespace scwx @@ -85,11 +86,16 @@ void ImGui::Impl::UpdateMonospaceFont() void ImGui::DrawTooltip(const std::string& hoverText) { + static constexpr std::size_t kDefaultWidth = 80u; + p->Initialize(); + auto wrappedText = + TextFlow::Column(hoverText).width(kDefaultWidth).toString(); + ::ImGui::BeginTooltip(); ::ImGui::PushFont(p->monospaceFont_); - ::ImGui::TextUnformatted(hoverText.c_str()); + ::ImGui::TextUnformatted(wrappedText.c_str()); ::ImGui::PopFont(); ::ImGui::EndTooltip(); } From fc6cdc729bb576e58c765893f2bb2ef7042d6c8f Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 30 Aug 2023 22:55:37 -0500 Subject: [PATCH 088/199] If placefile lines overlap, hover text should come from the top line --- scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp index a2a8d38a..a24fc0a0 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp @@ -259,8 +259,8 @@ bool PlacefileLines::RunMousePicking( // For each pickable line auto it = std::find_if( std::execution::par_unseq, - p->currentHoverLines_.cbegin(), - p->currentHoverLines_.cend(), + p->currentHoverLines_.crbegin(), + p->currentHoverLines_.crend(), [&mapMatrix, &mousePos](const auto& line) { // Initialize vertices @@ -289,7 +289,7 @@ bool PlacefileLines::RunMousePicking( return util::maplibre::IsPointInPolygon({tl, bl, br, tr}, mousePos); }); - if (it != p->currentHoverLines_.cend()) + if (it != p->currentHoverLines_.crend()) { itemPicked = true; util::ImGui::Instance().DrawTooltip(it->di_->hoverText_); From f9e69d15e464e240f272c401f5930cca744df350 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 31 Aug 2023 21:26:57 -0500 Subject: [PATCH 089/199] Add hover text wrap to settings --- .../res/icons/font-awesome-6/font-solid.svg | 1 + scwx-qt/scwx-qt.cmake | 2 + scwx-qt/scwx-qt.qrc | 1 + .../scwx/qt/manager/settings_manager.cpp | 4 + .../source/scwx/qt/settings/text_settings.cpp | 56 +++++++++++++ .../source/scwx/qt/settings/text_settings.hpp | 42 ++++++++++ scwx-qt/source/scwx/qt/ui/settings_dialog.cpp | 21 ++++- scwx-qt/source/scwx/qt/ui/settings_dialog.ui | 79 ++++++++++++++++++- scwx-qt/source/scwx/qt/util/imgui.cpp | 20 +++-- test/data | 2 +- 10 files changed, 218 insertions(+), 10 deletions(-) create mode 100644 scwx-qt/res/icons/font-awesome-6/font-solid.svg create mode 100644 scwx-qt/source/scwx/qt/settings/text_settings.cpp create mode 100644 scwx-qt/source/scwx/qt/settings/text_settings.hpp diff --git a/scwx-qt/res/icons/font-awesome-6/font-solid.svg b/scwx-qt/res/icons/font-awesome-6/font-solid.svg new file mode 100644 index 00000000..2fa4599c --- /dev/null +++ b/scwx-qt/res/icons/font-awesome-6/font-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 4b7e1d80..8c3282f8 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -137,6 +137,7 @@ set(HDR_SETTINGS source/scwx/qt/settings/general_settings.hpp source/scwx/qt/settings/settings_interface_base.hpp source/scwx/qt/settings/settings_variable.hpp source/scwx/qt/settings/settings_variable_base.hpp + source/scwx/qt/settings/text_settings.hpp source/scwx/qt/settings/ui_settings.hpp) set(SRC_SETTINGS source/scwx/qt/settings/general_settings.cpp source/scwx/qt/settings/map_settings.cpp @@ -147,6 +148,7 @@ set(SRC_SETTINGS source/scwx/qt/settings/general_settings.cpp source/scwx/qt/settings/settings_interface_base.cpp source/scwx/qt/settings/settings_variable.cpp source/scwx/qt/settings/settings_variable_base.cpp + source/scwx/qt/settings/text_settings.cpp source/scwx/qt/settings/ui_settings.cpp) set(HDR_TYPES source/scwx/qt/types/alert_types.hpp source/scwx/qt/types/font_types.hpp diff --git a/scwx-qt/scwx-qt.qrc b/scwx-qt/scwx-qt.qrc index 05a94725..5453b19d 100644 --- a/scwx-qt/scwx-qt.qrc +++ b/scwx-qt/scwx-qt.qrc @@ -25,6 +25,7 @@ res/icons/font-awesome-6/book-solid.svg res/icons/font-awesome-6/discord.svg res/icons/font-awesome-6/earth-americas-solid.svg + res/icons/font-awesome-6/font-solid.svg res/icons/font-awesome-6/forward-step-solid.svg res/icons/font-awesome-6/gears-solid.svg res/icons/font-awesome-6/github.svg diff --git a/scwx-qt/source/scwx/qt/manager/settings_manager.cpp b/scwx-qt/source/scwx/qt/manager/settings_manager.cpp index c504e15e..7c973ca4 100644 --- a/scwx-qt/source/scwx/qt/manager/settings_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/settings_manager.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -130,6 +131,7 @@ static boost::json::value ConvertSettingsToJson() general_settings().WriteJson(settingsJson); map_settings().WriteJson(settingsJson); palette_settings().WriteJson(settingsJson); + settings::TextSettings::Instance().WriteJson(settingsJson); settings::UiSettings::Instance().WriteJson(settingsJson); return settingsJson; @@ -142,6 +144,7 @@ static void GenerateDefaultSettings() general_settings().SetDefaults(); map_settings().SetDefaults(); palette_settings().SetDefaults(); + settings::TextSettings::Instance().SetDefaults(); settings::UiSettings::Instance().SetDefaults(); } @@ -154,6 +157,7 @@ static bool LoadSettings(const boost::json::object& settingsJson) jsonDirty |= !general_settings().ReadJson(settingsJson); jsonDirty |= !map_settings().ReadJson(settingsJson); jsonDirty |= !palette_settings().ReadJson(settingsJson); + jsonDirty |= !settings::TextSettings::Instance().ReadJson(settingsJson); jsonDirty |= !settings::UiSettings::Instance().ReadJson(settingsJson); return jsonDirty; diff --git a/scwx-qt/source/scwx/qt/settings/text_settings.cpp b/scwx-qt/source/scwx/qt/settings/text_settings.cpp new file mode 100644 index 00000000..48f612d7 --- /dev/null +++ b/scwx-qt/source/scwx/qt/settings/text_settings.cpp @@ -0,0 +1,56 @@ +#include + +namespace scwx +{ +namespace qt +{ +namespace settings +{ + +static const std::string logPrefix_ = "scwx::qt::settings::text_settings"; + +class TextSettings::Impl +{ +public: + explicit Impl() + { + hoverTextWrap_.SetDefault(80); + hoverTextWrap_.SetMinimum(0); + hoverTextWrap_.SetMaximum(999); + } + + ~Impl() {} + + SettingsVariable hoverTextWrap_ {"hover_text_wrap"}; +}; + +TextSettings::TextSettings() : + SettingsCategory("text"), p(std::make_unique()) +{ + RegisterVariables({&p->hoverTextWrap_}); + SetDefaults(); +} +TextSettings::~TextSettings() = default; + +TextSettings::TextSettings(TextSettings&&) noexcept = default; +TextSettings& TextSettings::operator=(TextSettings&&) noexcept = default; + +SettingsVariable& TextSettings::hover_text_wrap() const +{ + return p->hoverTextWrap_; +} + +TextSettings& TextSettings::Instance() +{ + static TextSettings TextSettings_; + return TextSettings_; +} + +bool operator==(const TextSettings& lhs, const TextSettings& rhs) +{ + return (lhs.p->hoverTextWrap_ == rhs.p->hoverTextWrap_); +} + +} // namespace settings +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/settings/text_settings.hpp b/scwx-qt/source/scwx/qt/settings/text_settings.hpp new file mode 100644 index 00000000..56c3eb8a --- /dev/null +++ b/scwx-qt/source/scwx/qt/settings/text_settings.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include +#include + +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace settings +{ + +class TextSettings : public SettingsCategory +{ +public: + explicit TextSettings(); + ~TextSettings(); + + TextSettings(const TextSettings&) = delete; + TextSettings& operator=(const TextSettings&) = delete; + + TextSettings(TextSettings&&) noexcept; + TextSettings& operator=(TextSettings&&) noexcept; + + SettingsVariable& hover_text_wrap() const; + + static TextSettings& Instance(); + + friend bool operator==(const TextSettings& lhs, const TextSettings& rhs); + +private: + class Impl; + + std::unique_ptr p; +}; + +} // namespace settings +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp index e3672c5f..c3fcae82 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -92,7 +93,8 @@ public: &mapTilerApiKey_, &defaultAlertAction_, &updateNotificationsEnabled_, - &debugEnabled_}} + &debugEnabled_, + &hoverTextWrap_}} { // Configure default alert phenomena colors auto& paletteSettings = manager::SettingsManager::palette_settings(); @@ -117,6 +119,7 @@ public: void SetupPalettesColorTablesTab(); void SetupPalettesAlertsTab(); void SetupPlacefilesTab(); + void SetupTextTab(); void ShowColorDialog(QLineEdit* lineEdit, QFrame* frame = nullptr); void UpdateRadarDialogLocation(const std::string& id); @@ -139,7 +142,7 @@ public: static void SetBackgroundColor(const std::string& value, QFrame* frame); SettingsDialog* self_; - PlacefileSettingsWidget* placefileSettingsWidget_; + PlacefileSettingsWidget* placefileSettingsWidget_ {nullptr}; RadarSiteDialog* radarSiteDialog_; settings::SettingsInterface defaultRadarSite_ {}; @@ -162,6 +165,8 @@ public: settings::SettingsInterface> inactiveAlertColors_ {}; + settings::SettingsInterface hoverTextWrap_ {}; + std::vector settings_; }; @@ -181,6 +186,9 @@ SettingsDialog::SettingsDialog(QWidget* parent) : // Palettes > Alerts p->SetupPalettesAlertsTab(); + // Text + p->SetupTextTab(); + // Placefiles p->SetupPlacefilesTab(); @@ -630,6 +638,15 @@ void SettingsDialogImpl::SetupPlacefilesTab() self_->ui->placefiles->layout()->addWidget(placefileSettingsWidget_); } +void SettingsDialogImpl::SetupTextTab() +{ + settings::TextSettings& textSettings = settings::TextSettings::Instance(); + + hoverTextWrap_.SetSettingsVariable(textSettings.hover_text_wrap()); + hoverTextWrap_.SetEditWidget(self_->ui->hoverTextWrapSpinBox); + hoverTextWrap_.SetResetButton(self_->ui->resetHoverTextWrapButton); +} + QImage SettingsDialogImpl::GenerateColorTableImage( std::shared_ptr colorTable, std::uint16_t min, diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui index 79a68f55..efa5dfd1 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui @@ -73,6 +73,15 @@ :/res/icons/font-awesome-6/palette-solid.svg:/res/icons/font-awesome-6/palette-solid.svg + + + Text + + + + :/res/icons/font-awesome-6/font-solid.svg:/res/icons/font-awesome-6/font-solid.svg + + Placefiles @@ -355,8 +364,8 @@ 0 0 - 66 - 18 + 481 + 382 @@ -427,6 +436,72 @@ + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Hover text character wrap (0 to disable) + + + + + + + 999 + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + diff --git a/scwx-qt/source/scwx/qt/util/imgui.cpp b/scwx-qt/source/scwx/qt/util/imgui.cpp index 018e5000..c31f22b0 100644 --- a/scwx-qt/source/scwx/qt/util/imgui.cpp +++ b/scwx-qt/source/scwx/qt/util/imgui.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -86,16 +87,25 @@ void ImGui::Impl::UpdateMonospaceFont() void ImGui::DrawTooltip(const std::string& hoverText) { - static constexpr std::size_t kDefaultWidth = 80u; - p->Initialize(); - auto wrappedText = - TextFlow::Column(hoverText).width(kDefaultWidth).toString(); + std::size_t textWidth = static_cast( + settings::TextSettings::Instance().hover_text_wrap().GetValue()); + + // Wrap text if enabled + std::string wrappedText {}; + if (textWidth > 0) + { + wrappedText = TextFlow::Column(hoverText).width(textWidth).toString(); + } + + // Display text is either wrapped or unwrapped text (do this to avoid copy + // when not wrapping) + const std::string& displayText = (textWidth > 0) ? wrappedText : hoverText; ::ImGui::BeginTooltip(); ::ImGui::PushFont(p->monospaceFont_); - ::ImGui::TextUnformatted(wrappedText.c_str()); + ::ImGui::TextUnformatted(displayText.c_str()); ::ImGui::PopFont(); ::ImGui::EndTooltip(); } diff --git a/test/data b/test/data index a87219c0..6d407be1 160000 --- a/test/data +++ b/test/data @@ -1 +1 @@ -Subproject commit a87219c010a26905fd893e68e17077144394b316 +Subproject commit 6d407be1b6f2e72490ef0d07da1e297994df8fe4 From 0b2a118c420a795e01aa4821b5b8fa833c6ca324 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 31 Aug 2023 23:33:27 -0500 Subject: [PATCH 090/199] Removing unused hover line parameter, moving mouse picking function to bottom (no change) --- .../scwx/qt/gl/draw/placefile_lines.cpp | 125 +++++++++--------- 1 file changed, 62 insertions(+), 63 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp index a24fc0a0..424d8940 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp @@ -41,7 +41,6 @@ public: glm::vec2 otr_; glm::vec2 obl_; glm::vec2 obr_; - float width_; }; explicit Impl(const std::shared_ptr& context) : @@ -237,67 +236,6 @@ void PlacefileLines::Deinitialize() p->currentHoverLines_.clear(); } -bool PlacefileLines::RunMousePicking( - const QMapLibreGL::CustomLayerRenderParameters& params, - const glm::vec2& mousePos) -{ - std::unique_lock lock {p->lineMutex_}; - - bool itemPicked = false; - - // Calculate map scale, remove width and height from original calculation - glm::vec2 scale = util::maplibre::GetMapScale(params); - scale = 2.0f / glm::vec2 {scale.x * params.width, scale.y * params.height}; - - // Scale and rotate the identity matrix to create the map matrix - glm::mat4 mapMatrix {1.0f}; - mapMatrix = glm::scale(mapMatrix, glm::vec3 {scale, 1.0f}); - mapMatrix = glm::rotate(mapMatrix, - glm::radians(params.bearing), - glm::vec3(0.0f, 0.0f, 1.0f)); - - // For each pickable line - auto it = std::find_if( - std::execution::par_unseq, - p->currentHoverLines_.crbegin(), - p->currentHoverLines_.crend(), - [&mapMatrix, &mousePos](const auto& line) - { - // Initialize vertices - glm::vec2 bl = line.p1_; - glm::vec2 br = bl; - glm::vec2 tl = line.p2_; - glm::vec2 tr = tl; - - // Calculate offsets - // - Rotated offset is half the line width (pixels) in each direction - // - Multiply the offset by the scaled and rotated map matrix - const glm::vec2 otl = mapMatrix * glm::vec4 {line.otl_, 0.0f, 1.0f}; - const glm::vec2 obl = mapMatrix * glm::vec4 {line.obl_, 0.0f, 1.0f}; - const glm::vec2 obr = mapMatrix * glm::vec4 {line.obr_, 0.0f, 1.0f}; - const glm::vec2 otr = mapMatrix * glm::vec4 {line.otr_, 0.0f, 1.0f}; - - // Offset vertices - tl += otl; - bl += obl; - br += obr; - tr += otr; - - // TODO: X/Y offsets - - // Test point against polygon bounds - return util::maplibre::IsPointInPolygon({tl, bl, br, tr}, mousePos); - }); - - if (it != p->currentHoverLines_.crend()) - { - itemPicked = true; - util::ImGui::Instance().DrawTooltip(it->di_->hoverText_); - } - - return itemPicked; -} - void PlacefileLines::StartLines() { // Clear the new buffers @@ -460,7 +398,7 @@ void PlacefileLines::Impl::BufferLine( const glm::vec2 obr = rotate * glm::vec2 {+hw, -hw}; newHoverLines_.emplace_back( - LineHoverEntry {di, sc1, sc2, otl, otr, obl, obr, width}); + LineHoverEntry {di, sc1, sc2, otl, otr, obl, obr}); } } @@ -489,6 +427,67 @@ void PlacefileLines::Impl::Update() dirty_ = false; } +bool PlacefileLines::RunMousePicking( + const QMapLibreGL::CustomLayerRenderParameters& params, + const glm::vec2& mousePos) +{ + std::unique_lock lock {p->lineMutex_}; + + bool itemPicked = false; + + // Calculate map scale, remove width and height from original calculation + glm::vec2 scale = util::maplibre::GetMapScale(params); + scale = 2.0f / glm::vec2 {scale.x * params.width, scale.y * params.height}; + + // Scale and rotate the identity matrix to create the map matrix + glm::mat4 mapMatrix {1.0f}; + mapMatrix = glm::scale(mapMatrix, glm::vec3 {scale, 1.0f}); + mapMatrix = glm::rotate(mapMatrix, + glm::radians(params.bearing), + glm::vec3(0.0f, 0.0f, 1.0f)); + + // For each pickable line + auto it = std::find_if( + std::execution::par_unseq, + p->currentHoverLines_.crbegin(), + p->currentHoverLines_.crend(), + [&mapMatrix, &mousePos](const auto& line) + { + // Initialize vertices + glm::vec2 bl = line.p1_; + glm::vec2 br = bl; + glm::vec2 tl = line.p2_; + glm::vec2 tr = tl; + + // Calculate offsets + // - Rotated offset is half the line width (pixels) in each direction + // - Multiply the offset by the scaled and rotated map matrix + const glm::vec2 otl = mapMatrix * glm::vec4 {line.otl_, 0.0f, 1.0f}; + const glm::vec2 obl = mapMatrix * glm::vec4 {line.obl_, 0.0f, 1.0f}; + const glm::vec2 obr = mapMatrix * glm::vec4 {line.obr_, 0.0f, 1.0f}; + const glm::vec2 otr = mapMatrix * glm::vec4 {line.otr_, 0.0f, 1.0f}; + + // Offset vertices + tl += otl; + bl += obl; + br += obr; + tr += otr; + + // TODO: X/Y offsets + + // Test point against polygon bounds + return util::maplibre::IsPointInPolygon({tl, bl, br, tr}, mousePos); + }); + + if (it != p->currentHoverLines_.crend()) + { + itemPicked = true; + util::ImGui::Instance().DrawTooltip(it->di_->hoverText_); + } + + return itemPicked; +} + } // namespace draw } // namespace gl } // namespace qt From 0421435e97203b8733d1bef23bca9733f78e1e98 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 31 Aug 2023 23:34:12 -0500 Subject: [PATCH 091/199] Placefile icon mouse picking --- .../scwx/qt/gl/draw/placefile_icons.cpp | 96 +++++++++++++++++++ .../scwx/qt/gl/draw/placefile_icons.hpp | 3 + 2 files changed, 99 insertions(+) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp index e03cc887..151feb6c 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp @@ -1,8 +1,11 @@ #include +#include #include #include #include +#include + #include #include @@ -54,6 +57,17 @@ struct PlacefileIconInfo class PlacefileIcons::Impl { public: + struct IconHoverEntry + { + std::shared_ptr di_; + + glm::vec2 p_; + glm::vec2 otl_; + glm::vec2 otr_; + glm::vec2 obl_; + glm::vec2 obr_; + }; + explicit Impl(const std::shared_ptr& context) : context_ {context}, shaderProgram_ {nullptr}, @@ -88,6 +102,8 @@ public: std::vector> newIconList_ {}; + std::vector hoverIcons_ {}; + std::vector iconBuffer_ {}; std::vector thresholdBuffer_ {}; @@ -250,6 +266,7 @@ void PlacefileIcons::Deinitialize() p->currentIconList_.clear(); p->currentIconFiles_.clear(); + p->hoverIcons_.clear(); p->iconBuffer_.clear(); p->thresholdBuffer_.clear(); } @@ -337,6 +354,7 @@ void PlacefileIcons::Impl::UpdateBuffers() iconBuffer_.reserve(currentIconList_.size() * kBufferLength); thresholdBuffer_.clear(); thresholdBuffer_.reserve(currentIconList_.size() * kVerticesPerRectangle); + hoverIcons_.clear(); numVertices_ = 0; for (auto& di : currentIconList_) @@ -425,6 +443,25 @@ void PlacefileIcons::Impl::UpdateBuffers() thresholdValue, thresholdValue, thresholdValue}); + + if (!di->hoverText_.empty()) + { + const units::angle::radians radians = angle; + + const auto sc = util::maplibre::LatLongToScreenCoordinate({lat, lon}); + + const float cosAngle = cosf(static_cast(radians.value())); + const float sinAngle = sinf(static_cast(radians.value())); + + const glm::mat2 rotate {cosAngle, -sinAngle, sinAngle, cosAngle}; + + const glm::vec2 otl = rotate * glm::vec2 {lx, ty}; + const glm::vec2 otr = rotate * glm::vec2 {rx, ty}; + const glm::vec2 obl = rotate * glm::vec2 {lx, by}; + const glm::vec2 obr = rotate * glm::vec2 {rx, by}; + + hoverIcons_.emplace_back(IconHoverEntry {di, sc, otl, otr, obl, obr}); + } } dirty_ = true; @@ -467,6 +504,65 @@ void PlacefileIcons::Impl::Update(bool textureAtlasChanged) dirty_ = false; } +bool PlacefileIcons::RunMousePicking( + const QMapLibreGL::CustomLayerRenderParameters& params, + const glm::vec2& mousePos) +{ + std::unique_lock lock {p->iconMutex_}; + + bool itemPicked = false; + + // Calculate map scale, remove width and height from original calculation + glm::vec2 scale = util::maplibre::GetMapScale(params); + scale = 2.0f / glm::vec2 {scale.x * params.width, scale.y * params.height}; + + // Scale and rotate the identity matrix to create the map matrix + glm::mat4 mapMatrix {1.0f}; + mapMatrix = glm::scale(mapMatrix, glm::vec3 {scale, 1.0f}); + mapMatrix = glm::rotate(mapMatrix, + glm::radians(params.bearing), + glm::vec3(0.0f, 0.0f, 1.0f)); + + // For each pickable icon + auto it = std::find_if( + std::execution::par_unseq, + p->hoverIcons_.crbegin(), + p->hoverIcons_.crend(), + [&mapMatrix, &mousePos](const auto& icon) + { + // Initialize vertices + glm::vec2 bl = icon.p_; + glm::vec2 br = bl; + glm::vec2 tl = br; + glm::vec2 tr = tl; + + // Calculate offsets + // - Rotated offset is based on final X/Y offsets (pixels) + // - Multiply the offset by the scaled and rotated map matrix + const glm::vec2 otl = mapMatrix * glm::vec4 {icon.otl_, 0.0f, 1.0f}; + const glm::vec2 obl = mapMatrix * glm::vec4 {icon.obl_, 0.0f, 1.0f}; + const glm::vec2 obr = mapMatrix * glm::vec4 {icon.obr_, 0.0f, 1.0f}; + const glm::vec2 otr = mapMatrix * glm::vec4 {icon.otr_, 0.0f, 1.0f}; + + // Offset vertices + tl += otl; + bl += obl; + br += obr; + tr += otr; + + // Test point against polygon bounds + return util::maplibre::IsPointInPolygon({tl, bl, br, tr}, mousePos); + }); + + if (it != p->hoverIcons_.crend()) + { + itemPicked = true; + util::ImGui::Instance().DrawTooltip(it->di_->hoverText_); + } + + return itemPicked; +} + } // namespace draw } // namespace gl } // namespace qt diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp index 418d774d..cc6b632d 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp @@ -34,6 +34,9 @@ public: bool textureAtlasChanged) override; void Deinitialize() override; + bool RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params, + const glm::vec2& mousePos) override; + /** * Resets and prepares the draw item for adding a new set of icons. */ From 26a326b450ce8df07318b3dd77e05a94cfdeb907 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 1 Sep 2023 00:28:32 -0500 Subject: [PATCH 092/199] Render placefile triangles --- scwx-qt/scwx-qt.cmake | 2 + .../scwx/qt/gl/draw/placefile_triangles.cpp | 306 ++++++++++++++++++ .../scwx/qt/gl/draw/placefile_triangles.hpp | 61 ++++ .../source/scwx/qt/map/placefile_layer.cpp | 24 +- 4 files changed, 388 insertions(+), 5 deletions(-) create mode 100644 scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.cpp create mode 100644 scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.hpp diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 8c3282f8..ab091fe2 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -61,6 +61,7 @@ set(HDR_GL_DRAW source/scwx/qt/gl/draw/draw_item.hpp source/scwx/qt/gl/draw/placefile_lines.hpp source/scwx/qt/gl/draw/placefile_polygons.hpp source/scwx/qt/gl/draw/placefile_text.hpp + source/scwx/qt/gl/draw/placefile_triangles.hpp source/scwx/qt/gl/draw/rectangle.hpp) set(SRC_GL_DRAW source/scwx/qt/gl/draw/draw_item.cpp source/scwx/qt/gl/draw/geo_line.cpp @@ -68,6 +69,7 @@ set(SRC_GL_DRAW source/scwx/qt/gl/draw/draw_item.cpp source/scwx/qt/gl/draw/placefile_lines.cpp source/scwx/qt/gl/draw/placefile_polygons.cpp source/scwx/qt/gl/draw/placefile_text.cpp + source/scwx/qt/gl/draw/placefile_triangles.cpp source/scwx/qt/gl/draw/rectangle.cpp) set(HDR_MANAGER source/scwx/qt/manager/placefile_manager.hpp source/scwx/qt/manager/radar_product_manager.hpp diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.cpp new file mode 100644 index 00000000..693e9cca --- /dev/null +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.cpp @@ -0,0 +1,306 @@ +#include +#include +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace gl +{ +namespace draw +{ + +static const std::string logPrefix_ = "scwx::qt::gl::draw::placefile_triangles"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +static constexpr std::size_t kVerticesPerTriangle = 3; +static constexpr std::size_t kPointsPerVertex = 8; + +class PlacefileTriangles::Impl +{ +public: + explicit Impl(const std::shared_ptr& context) : + context_ {context}, + shaderProgram_ {nullptr}, + uMVPMatrixLocation_(GL_INVALID_INDEX), + uMapMatrixLocation_(GL_INVALID_INDEX), + uMapScreenCoordLocation_(GL_INVALID_INDEX), + uMapDistanceLocation_(GL_INVALID_INDEX), + vao_ {GL_INVALID_INDEX}, + vbo_ {GL_INVALID_INDEX}, + numVertices_ {0} + { + } + + ~Impl() {} + + void UpdateBuffers( + const std::shared_ptr& di); + void Update(); + + std::shared_ptr context_; + + bool dirty_ {false}; + bool thresholded_ {false}; + + std::mutex bufferMutex_ {}; + + std::vector currentBuffer_ {}; + std::vector currentThresholdBuffer_ {}; + std::vector newBuffer_ {}; + std::vector newThresholdBuffer_ {}; + + std::shared_ptr shaderProgram_; + GLint uMVPMatrixLocation_; + GLint uMapMatrixLocation_; + GLint uMapScreenCoordLocation_; + GLint uMapDistanceLocation_; + + GLuint vao_; + std::array vbo_; + + GLsizei numVertices_; +}; + +PlacefileTriangles::PlacefileTriangles( + const std::shared_ptr& context) : + DrawItem(context->gl()), p(std::make_unique(context)) +{ +} +PlacefileTriangles::~PlacefileTriangles() = default; + +PlacefileTriangles::PlacefileTriangles(PlacefileTriangles&&) noexcept = default; +PlacefileTriangles& +PlacefileTriangles::operator=(PlacefileTriangles&&) noexcept = default; + +void PlacefileTriangles::set_thresholded(bool thresholded) +{ + p->thresholded_ = thresholded; +} + +void PlacefileTriangles::Initialize() +{ + gl::OpenGLFunctions& gl = p->context_->gl(); + + p->shaderProgram_ = p->context_->GetShaderProgram( + {{GL_VERTEX_SHADER, ":/gl/map_color.vert"}, + {GL_GEOMETRY_SHADER, ":/gl/threshold.geom"}, + {GL_FRAGMENT_SHADER, ":/gl/color.frag"}}); + + p->uMVPMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMVPMatrix"); + p->uMapMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMapMatrix"); + p->uMapScreenCoordLocation_ = + p->shaderProgram_->GetUniformLocation("uMapScreenCoord"); + p->uMapDistanceLocation_ = + p->shaderProgram_->GetUniformLocation("uMapDistance"); + + gl.glGenVertexArrays(1, &p->vao_); + gl.glGenBuffers(2, p->vbo_.data()); + + gl.glBindVertexArray(p->vao_); + gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); + gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + + // aScreenCoord + gl.glVertexAttribPointer(0, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + static_cast(0)); + gl.glEnableVertexAttribArray(0); + + // aXYOffset + gl.glVertexAttribPointer(1, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(2 * sizeof(float))); + gl.glEnableVertexAttribArray(1); + + // aColor + gl.glVertexAttribPointer(2, + 4, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(4 * sizeof(float))); + gl.glEnableVertexAttribArray(2); + + gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); + gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + + // aThreshold + gl.glVertexAttribIPointer(3, // + 1, + GL_INT, + 0, + static_cast(0)); + gl.glEnableVertexAttribArray(3); + + p->dirty_ = true; +} + +void PlacefileTriangles::Render( + const QMapLibreGL::CustomLayerRenderParameters& params) +{ + if (!p->currentBuffer_.empty()) + { + gl::OpenGLFunctions& gl = p->context_->gl(); + + gl.glBindVertexArray(p->vao_); + + p->Update(); + p->shaderProgram_->Use(); + UseRotationProjection(params, p->uMVPMatrixLocation_); + UseMapProjection( + params, p->uMapMatrixLocation_, p->uMapScreenCoordLocation_); + + if (p->thresholded_) + { + // If thresholding is enabled, set the map distance + units::length::nautical_miles mapDistance = + util::maplibre::GetMapDistance(params); + gl.glUniform1f(p->uMapDistanceLocation_, mapDistance.value()); + } + else + { + // If thresholding is disabled, set the map distance to 0 + gl.glUniform1f(p->uMapDistanceLocation_, 0.0f); + } + + // Draw icons + gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); + } +} + +void PlacefileTriangles::Deinitialize() +{ + gl::OpenGLFunctions& gl = p->context_->gl(); + + gl.glDeleteVertexArrays(1, &p->vao_); + gl.glDeleteBuffers(2, p->vbo_.data()); + + std::unique_lock lock {p->bufferMutex_}; + + // Clear the current buffers + p->currentBuffer_.clear(); + p->currentThresholdBuffer_.clear(); +} + +void PlacefileTriangles::StartTriangles() +{ + // Clear the new buffers + p->newBuffer_.clear(); + p->newThresholdBuffer_.clear(); +} + +void PlacefileTriangles::AddTriangles( + const std::shared_ptr& di) +{ + if (di != nullptr) + { + p->UpdateBuffers(di); + } +} + +void PlacefileTriangles::FinishTriangles() +{ + std::unique_lock lock {p->bufferMutex_}; + + // Swap buffers + p->currentBuffer_.swap(p->newBuffer_); + p->currentThresholdBuffer_.swap(p->newThresholdBuffer_); + + // Clear the new buffers + p->newBuffer_.clear(); + p->newThresholdBuffer_.clear(); + + // Mark the draw item dirty + p->dirty_ = true; +} + +void PlacefileTriangles::Impl::UpdateBuffers( + const std::shared_ptr& di) +{ + // Threshold value + units::length::nautical_miles threshold = di->threshold_; + GLint thresholdValue = static_cast(std::round(threshold.value())); + + // Default color to "Color" statement + boost::gil::rgba8_pixel_t lastColor = di->color_; + + // For each element inside a Triangles statement, add a vertex + for (auto& element : di->elements_) + { + // Calculate screen coordinate + auto screenCoordinate = util::maplibre::LatLongToScreenCoordinate( + {element.latitude_, element.longitude_}); + + // X/Y offset in pixels + const float x = static_cast(element.x_); + const float y = static_cast(element.y_); + + // Update the most recent color if specified + if (element.color_.has_value()) + { + lastColor = element.color_.value(); + } + + // Color value + const float r = lastColor[0] / 255.0f; + const float g = lastColor[1] / 255.0f; + const float b = lastColor[2] / 255.0f; + const float a = lastColor[3] / 255.0f; + + newBuffer_.insert( + newBuffer_.end(), + {screenCoordinate.x, screenCoordinate.y, x, y, r, g, b, a}); + newThresholdBuffer_.push_back(thresholdValue); + } + + // Remove extra vertices that don't correspond to a full triangle + while (newBuffer_.size() % kVerticesPerTriangle != 0) + { + newBuffer_.pop_back(); + newThresholdBuffer_.pop_back(); + } +} + +void PlacefileTriangles::Impl::Update() +{ + if (dirty_) + { + gl::OpenGLFunctions& gl = context_->gl(); + + std::unique_lock lock {bufferMutex_}; + + // Buffer vertex data + gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); + gl.glBufferData(GL_ARRAY_BUFFER, + sizeof(GLfloat) * currentBuffer_.size(), + currentBuffer_.data(), + GL_DYNAMIC_DRAW); + + // Buffer threshold data + gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); + gl.glBufferData(GL_ARRAY_BUFFER, + sizeof(GLint) * currentThresholdBuffer_.size(), + currentThresholdBuffer_.data(), + GL_DYNAMIC_DRAW); + + numVertices_ = + static_cast(currentBuffer_.size() / kPointsPerVertex); + + dirty_ = false; + } +} + +} // namespace draw +} // namespace gl +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.hpp new file mode 100644 index 00000000..bb98316f --- /dev/null +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace gl +{ +namespace draw +{ + +class PlacefileTriangles : public DrawItem +{ +public: + explicit PlacefileTriangles(const std::shared_ptr& context); + ~PlacefileTriangles(); + + PlacefileTriangles(const PlacefileTriangles&) = delete; + PlacefileTriangles& operator=(const PlacefileTriangles&) = delete; + + PlacefileTriangles(PlacefileTriangles&&) noexcept; + PlacefileTriangles& operator=(PlacefileTriangles&&) noexcept; + + void set_thresholded(bool thresholded); + + void Initialize() override; + void Render(const QMapLibreGL::CustomLayerRenderParameters& params) override; + void Deinitialize() override; + + /** + * Resets and prepares the draw item for adding a new set of triangles. + */ + void StartTriangles(); + + /** + * Adds placefile triangles to the internal draw list. + * + * @param [in] di Placefile triangles + */ + void + AddTriangles(const std::shared_ptr& di); + + /** + * Finalizes the draw item after adding new triangles. + */ + void FinishTriangles(); + +private: + class Impl; + + std::unique_ptr p; +}; + +} // namespace draw +} // namespace gl +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp index 2c1b66ab..b6f8e190 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +32,8 @@ public: placefileLines_ {std::make_shared(context)}, placefilePolygons_ { std::make_shared(context)}, + placefileTriangles_ { + std::make_shared(context)}, placefileText_ { std::make_shared(context, placefileName)} { @@ -47,10 +50,11 @@ public: std::string placefileName_; std::mutex dataMutex_ {}; - std::shared_ptr placefileIcons_; - std::shared_ptr placefileLines_; - std::shared_ptr placefilePolygons_; - std::shared_ptr placefileText_; + std::shared_ptr placefileIcons_; + std::shared_ptr placefileLines_; + std::shared_ptr placefilePolygons_; + std::shared_ptr placefileTriangles_; + std::shared_ptr placefileText_; }; PlacefileLayer::PlacefileLayer(const std::shared_ptr& context, @@ -59,8 +63,9 @@ PlacefileLayer::PlacefileLayer(const std::shared_ptr& context, p(std::make_unique(this, context, placefileName)) { AddDrawItem(p->placefileIcons_); - AddDrawItem(p->placefileLines_); AddDrawItem(p->placefilePolygons_); + AddDrawItem(p->placefileTriangles_); + AddDrawItem(p->placefileLines_); AddDrawItem(p->placefileText_); ReloadData(); @@ -125,6 +130,7 @@ void PlacefileLayer::Render( p->placefileIcons_->set_thresholded(thresholded); p->placefileLines_->set_thresholded(thresholded); p->placefilePolygons_->set_thresholded(thresholded); + p->placefileTriangles_->set_thresholded(thresholded); p->placefileText_->set_thresholded(thresholded); } @@ -163,6 +169,7 @@ void PlacefileLayer::ReloadData() p->placefileIcons_->StartIcons(); p->placefileLines_->StartLines(); p->placefilePolygons_->StartPolygons(); + p->placefileTriangles_->StartTriangles(); p->placefileText_->StartText(); p->placefileIcons_->SetIconFiles(placefile->icon_files(), @@ -196,6 +203,12 @@ void PlacefileLayer::ReloadData() drawItem)); break; + case gr::Placefile::ItemType::Triangles: + p->placefileTriangles_->AddTriangles( + std::static_pointer_cast( + drawItem)); + break; + default: break; } @@ -205,6 +218,7 @@ void PlacefileLayer::ReloadData() p->placefileIcons_->FinishIcons(); p->placefileLines_->FinishLines(); p->placefilePolygons_->FinishPolygons(); + p->placefileTriangles_->FinishTriangles(); p->placefileText_->FinishText(); Q_EMIT DataReloaded(); From ca6f55d712267fefdb0f209c5b86e30abdbf6559 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 1 Sep 2023 08:30:46 -0500 Subject: [PATCH 093/199] Placefile image statement --- wxdata/include/scwx/gr/placefile.hpp | 19 +++++++++ wxdata/source/scwx/gr/placefile.cpp | 64 +++++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 1 deletion(-) diff --git a/wxdata/include/scwx/gr/placefile.hpp b/wxdata/include/scwx/gr/placefile.hpp index d092ed3f..81b37daa 100644 --- a/wxdata/include/scwx/gr/placefile.hpp +++ b/wxdata/include/scwx/gr/placefile.hpp @@ -142,6 +142,25 @@ public: std::vector elements_ {}; }; + struct ImageDrawItem : DrawItem + { + ImageDrawItem() { itemType_ = ItemType::Image; } + + std::string imageFile_ {}; + + struct Element + { + double latitude_ {}; + double longitude_ {}; + double x_ {}; + double y_ {}; + double tu_ {}; + double tv_ {}; + }; + + std::vector elements_ {}; + }; + struct PolygonDrawItem : DrawItem { PolygonDrawItem() { itemType_ = ItemType::Polygon; } diff --git a/wxdata/source/scwx/gr/placefile.cpp b/wxdata/source/scwx/gr/placefile.cpp index 27febd8c..5d007647 100644 --- a/wxdata/source/scwx/gr/placefile.cpp +++ b/wxdata/source/scwx/gr/placefile.cpp @@ -570,9 +570,29 @@ void Placefile::Impl::ProcessLine(const std::string& line) // lat, lon, Tu [, Tv ] // ... // End: + std::vector tokenList = + util::ParseTokens(line, {" "}, imageKey_.size()); + currentStatement_ = DrawingStatement::Image; - // TODO + std::shared_ptr di = nullptr; + + if (tokenList.size() >= 1) + { + di = std::make_shared(); + + di->threshold_ = threshold_; + + TrimQuotes(tokenList[0]); + di->imageFile_.swap(tokenList[0]); + + currentDrawItem_ = di; + drawItems_.emplace_back(std::move(di)); + } + else + { + logger_->warn("Image statement malformed: {}", line); + } } else if (boost::istarts_with(line, polygonKey_)) { @@ -664,6 +684,48 @@ void Placefile::Impl::ProcessElement(const std::string& line) logger_->warn("Triangles sub-statement malformed: {}", line); } } + else if (currentStatement_ == DrawingStatement::Image) + { + // Image: image_file + // lat, lon, Tu [, Tv ] + // ... + // End: + std::vector tokenList = + util::ParseTokens(line, {",", ",", ",", ","}); + + ImageDrawItem::Element element; + + if (tokenList.size() >= 3) + { + ParseLocation(tokenList[0], + tokenList[1], + element.latitude_, + element.longitude_, + element.x_, + element.y_); + + element.tu_ = std::stod(tokenList[2]); + } + + if (tokenList.size() >= 4) + { + element.tv_ = std::stod(tokenList[3]); + } + else + { + element.tv_ = element.tu_; + } + + if (tokenList.size() >= 3) + { + std::static_pointer_cast(currentDrawItem_) + ->elements_.emplace_back(std::move(element)); + } + else + { + logger_->warn("Image sub-statement malformed: {}", line); + } + } else if (currentStatement_ == DrawingStatement::Polygon) { // Polygon: From 96421bba40164c40e97f67af0fed7dc6bab1b357 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 1 Sep 2023 08:59:42 -0500 Subject: [PATCH 094/199] Placefile TimeRange statement --- wxdata/include/scwx/gr/placefile.hpp | 6 ++- wxdata/source/scwx/gr/placefile.cpp | 74 ++++++++++++++++++++++++++-- 2 files changed, 73 insertions(+), 7 deletions(-) diff --git a/wxdata/include/scwx/gr/placefile.hpp b/wxdata/include/scwx/gr/placefile.hpp index 81b37daa..671af535 100644 --- a/wxdata/include/scwx/gr/placefile.hpp +++ b/wxdata/include/scwx/gr/placefile.hpp @@ -71,8 +71,10 @@ public: struct DrawItem { - ItemType itemType_ {ItemType::Unknown}; - units::length::nautical_miles threshold_ {}; + ItemType itemType_ {ItemType::Unknown}; + units::length::nautical_miles threshold_ {}; + std::chrono::sys_time startTime_ {}; + std::chrono::sys_time endTime_ {}; }; struct IconDrawItem : DrawItem diff --git a/wxdata/source/scwx/gr/placefile.cpp b/wxdata/source/scwx/gr/placefile.cpp index 5d007647..8ef9ba2d 100644 --- a/wxdata/source/scwx/gr/placefile.cpp +++ b/wxdata/source/scwx/gr/placefile.cpp @@ -1,3 +1,8 @@ +// Enable chrono formatters +#ifndef __cpp_lib_format +# define __cpp_lib_format 202110L +#endif + #include #include #include @@ -10,6 +15,10 @@ #include +#if !defined(_MSC_VER) +# include +#endif + using namespace units::literals; namespace scwx @@ -59,10 +68,13 @@ public: std::chrono::seconds refresh_ {-1}; // Parsing state - units::length::nautical_miles threshold_ {999.0_nmi}; - boost::gil::rgba8_pixel_t color_ {255, 255, 255, 255}; - ColorMode colorMode_ {ColorMode::RGBA}; - std::vector objectStack_ {}; + units::length::nautical_miles threshold_ {999.0_nmi}; + boost::gil::rgba8_pixel_t color_ {255, 255, 255, 255}; + ColorMode colorMode_ {ColorMode::RGBA}; + std::chrono::sys_time startTime_ {}; + std::chrono::sys_time endTime_ {}; + + std::vector objectStack_ {}; DrawingStatement currentStatement_ {DrawingStatement::Standard}; std::shared_ptr currentDrawItem_ {nullptr}; std::vector currentPolygonContour_ {}; @@ -256,8 +268,46 @@ void Placefile::Impl::ProcessLine(const std::string& line) else if (boost::istarts_with(line, timeRangeKey_)) { // TimeRange: start_time end_time + // (YYYY-MM-DDThh:mm:ss) + std::vector tokenList = + util::ParseTokens(line, {" ", " "}, timeRangeKey_.size()); - // TODO + if (tokenList.size() >= 2) + { + using namespace std::chrono; + +#if !defined(_MSC_VER) + using namespace date; +#endif + + static const std::string dateTimeFormat {"%Y-%m-%dT%H:%M:%S"}; + + std::istringstream ssStartTime {tokenList[0]}; + std::istringstream ssEndTime {tokenList[1]}; + + std::chrono::sys_time startTime; + std::chrono::sys_time endTime; + + ssStartTime >> parse(dateTimeFormat, startTime); + ssEndTime >> parse(dateTimeFormat, endTime); + + if (!ssStartTime.fail() && !ssEndTime.fail()) + { + startTime_ = startTime; + endTime_ = endTime; + } + else + { + startTime_ = {}; + endTime_ = {}; + + logger_->warn("TimeRange statement parse error: {}", line); + } + } + else + { + logger_->warn("TimeRange statement malformed: {}", line); + } } else if (boost::istarts_with(line, hsluvKey_)) { @@ -322,6 +372,8 @@ void Placefile::Impl::ProcessLine(const std::string& line) di->threshold_ = threshold_; di->color_ = color_; + di->startTime_ = startTime_; + di->endTime_ = endTime_; ParseLocation(tokenList[0], tokenList[1], @@ -379,6 +431,8 @@ void Placefile::Impl::ProcessLine(const std::string& line) di = std::make_shared(); di->threshold_ = threshold_; + di->startTime_ = startTime_; + di->endTime_ = endTime_; ParseLocation(tokenList[0], tokenList[1], @@ -446,6 +500,8 @@ void Placefile::Impl::ProcessLine(const std::string& line) di->threshold_ = threshold_; di->color_ = color_; + di->startTime_ = startTime_; + di->endTime_ = endTime_; ParseLocation(tokenList[0], tokenList[1], @@ -526,6 +582,8 @@ void Placefile::Impl::ProcessLine(const std::string& line) di->threshold_ = threshold_; di->color_ = color_; + di->startTime_ = startTime_; + di->endTime_ = endTime_; di->width_ = std::stoul(tokenList[0]); di->flags_ = std::stoul(tokenList[1]); @@ -560,6 +618,8 @@ void Placefile::Impl::ProcessLine(const std::string& line) di->threshold_ = threshold_; di->color_ = color_; + di->startTime_ = startTime_; + di->endTime_ = endTime_; currentDrawItem_ = di; drawItems_.emplace_back(std::move(di)); @@ -582,6 +642,8 @@ void Placefile::Impl::ProcessLine(const std::string& line) di = std::make_shared(); di->threshold_ = threshold_; + di->startTime_ = startTime_; + di->endTime_ = endTime_; TrimQuotes(tokenList[0]); di->imageFile_.swap(tokenList[0]); @@ -612,6 +674,8 @@ void Placefile::Impl::ProcessLine(const std::string& line) di->threshold_ = threshold_; di->color_ = color_; + di->startTime_ = startTime_; + di->endTime_ = endTime_; currentDrawItem_ = di; drawItems_.emplace_back(std::move(di)); From 854d4a43db226480c211ed80e7680b0c5da50a35 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 1 Sep 2023 22:23:41 -0500 Subject: [PATCH 095/199] Placefile thresholds should apply to mouse picking --- .../scwx/qt/gl/draw/placefile_icons.cpp | 22 ++++++++++++++++++- .../scwx/qt/gl/draw/placefile_lines.cpp | 22 ++++++++++++++++++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp index 151feb6c..9432e408 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp @@ -523,13 +523,33 @@ bool PlacefileIcons::RunMousePicking( glm::radians(params.bearing), glm::vec3(0.0f, 0.0f, 1.0f)); + units::length::meters mapDistance = + (p->thresholded_) ? util::maplibre::GetMapDistance(params) : + units::length::meters {0.0}; + // For each pickable icon auto it = std::find_if( std::execution::par_unseq, p->hoverIcons_.crbegin(), p->hoverIcons_.crend(), - [&mapMatrix, &mousePos](const auto& icon) + [&mapDistance, &mapMatrix, &mousePos](const auto& icon) { + if ( + // Placefile is thresholded + mapDistance > units::length::meters {0.0} && + + // Placefile threshold is < 999 nmi + static_cast(std::round( + units::length::nautical_miles {icon.di_->threshold_} + .value())) < 999 && + + // Map distance is beyond the threshold + icon.di_->threshold_ < mapDistance) + { + // Icon is not pickable + return false; + } + // Initialize vertices glm::vec2 bl = icon.p_; glm::vec2 br = bl; diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp index 424d8940..8041ff63 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp @@ -446,13 +446,33 @@ bool PlacefileLines::RunMousePicking( glm::radians(params.bearing), glm::vec3(0.0f, 0.0f, 1.0f)); + units::length::meters mapDistance = + (p->thresholded_) ? util::maplibre::GetMapDistance(params) : + units::length::meters {0.0}; + // For each pickable line auto it = std::find_if( std::execution::par_unseq, p->currentHoverLines_.crbegin(), p->currentHoverLines_.crend(), - [&mapMatrix, &mousePos](const auto& line) + [&mapDistance, &mapMatrix, &mousePos](const auto& line) { + if ( + // Placefile is thresholded + mapDistance > units::length::meters {0.0} && + + // Placefile threshold is < 999 nmi + static_cast(std::round( + units::length::nautical_miles {line.di_->threshold_} + .value())) < 999 && + + // Map distance is beyond the threshold + line.di_->threshold_ < mapDistance) + { + // Line is not pickable + return false; + } + // Initialize vertices glm::vec2 bl = line.p1_; glm::vec2 br = bl; From 44030fdf871c7719ee10db23bfdd8692e1477823 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 1 Sep 2023 23:35:30 -0500 Subject: [PATCH 096/199] For placefile icons, only buffer texture data on texture atlas update --- .../scwx/qt/gl/draw/placefile_icons.cpp | 276 +++++++++++++----- .../source/scwx/qt/map/placefile_layer.cpp | 2 +- 2 files changed, 200 insertions(+), 78 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp index 9432e408..1a74f4e6 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp @@ -25,9 +25,12 @@ static constexpr std::size_t kNumRectangles = 1; static constexpr std::size_t kNumTriangles = kNumRectangles * 2; static constexpr std::size_t kVerticesPerTriangle = 3; static constexpr std::size_t kVerticesPerRectangle = kVerticesPerTriangle * 2; -static constexpr std::size_t kPointsPerVertex = 11; -static constexpr std::size_t kBufferLength = +static constexpr std::size_t kPointsPerVertex = 9; +static constexpr std::size_t kPointsPerTexCoord = 2; +static constexpr std::size_t kIconBufferLength = kNumTriangles * kVerticesPerTriangle * kPointsPerVertex; +static constexpr std::size_t kTextureBufferLength = + kNumTriangles * kVerticesPerTriangle * kPointsPerTexCoord; struct PlacefileIconInfo { @@ -84,6 +87,7 @@ public: ~Impl() {} void UpdateBuffers(); + void UpdateTextureBuffer(); void Update(bool textureAtlasChanged); std::shared_ptr context_; @@ -101,11 +105,18 @@ public: currentIconList_ {}; std::vector> newIconList_ {}; + std::vector> + newValidIconList_ {}; - std::vector hoverIcons_ {}; + std::vector currentIconBuffer_ {}; + std::vector currentThresholdBuffer_ {}; + std::vector newIconBuffer_ {}; + std::vector newThresholdBuffer_ {}; - std::vector iconBuffer_ {}; - std::vector thresholdBuffer_ {}; + std::vector textureBuffer_ {}; + + std::vector currentHoverIcons_ {}; + std::vector newHoverIcons_ {}; std::shared_ptr shaderProgram_; GLint uMVPMatrixLocation_; @@ -114,7 +125,7 @@ public: GLint uMapDistanceLocation_; GLuint vao_; - std::array vbo_; + std::array vbo_; GLsizei numVertices_; }; @@ -150,7 +161,7 @@ void PlacefileIcons::Initialize() p->shaderProgram_->GetUniformLocation("uMapDistance"); gl.glGenVertexArrays(1, &p->vao_); - gl.glGenBuffers(2, p->vbo_.data()); + gl.glGenBuffers(static_cast(p->vbo_.size()), p->vbo_.data()); gl.glBindVertexArray(p->vao_); gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); @@ -174,22 +185,13 @@ void PlacefileIcons::Initialize() reinterpret_cast(2 * sizeof(float))); gl.glEnableVertexAttribArray(1); - // aTexCoord - gl.glVertexAttribPointer(2, - 2, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - reinterpret_cast(4 * sizeof(float))); - gl.glEnableVertexAttribArray(2); - // aModulate gl.glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, kPointsPerVertex * sizeof(float), - reinterpret_cast(6 * sizeof(float))); + reinterpret_cast(4 * sizeof(float))); gl.glEnableVertexAttribArray(3); // aAngle @@ -198,12 +200,24 @@ void PlacefileIcons::Initialize() GL_FLOAT, GL_FALSE, kPointsPerVertex * sizeof(float), - reinterpret_cast(10 * sizeof(float))); + reinterpret_cast(8 * sizeof(float))); gl.glEnableVertexAttribArray(4); gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + // aTexCoord + gl.glVertexAttribPointer(2, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerTexCoord * sizeof(float), + static_cast(0)); + gl.glEnableVertexAttribArray(2); + + gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[2]); + gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + // aThreshold gl.glVertexAttribIPointer(5, // 1, @@ -260,15 +274,16 @@ void PlacefileIcons::Deinitialize() gl::OpenGLFunctions& gl = p->context_->gl(); gl.glDeleteVertexArrays(1, &p->vao_); - gl.glDeleteBuffers(2, p->vbo_.data()); + gl.glDeleteBuffers(static_cast(p->vbo_.size()), p->vbo_.data()); std::unique_lock lock {p->iconMutex_}; p->currentIconList_.clear(); p->currentIconFiles_.clear(); - p->hoverIcons_.clear(); - p->iconBuffer_.clear(); - p->thresholdBuffer_.clear(); + p->currentHoverIcons_.clear(); + p->currentIconBuffer_.clear(); + p->currentThresholdBuffer_.clear(); + p->textureBuffer_.clear(); } void PlacefileIconInfo::UpdateTextureInfo() @@ -306,7 +321,11 @@ void PlacefileIcons::StartIcons() { // Clear the new buffer p->newIconList_.clear(); + p->newValidIconList_.clear(); p->newIconFiles_.clear(); + p->newIconBuffer_.clear(); + p->newThresholdBuffer_.clear(); + p->newHoverIcons_.clear(); } void PlacefileIcons::SetIconFiles( @@ -334,15 +353,31 @@ void PlacefileIcons::AddIcon( void PlacefileIcons::FinishIcons() { + // Update icon files + for (auto& iconFile : p->newIconFiles_) + { + iconFile.second.UpdateTextureInfo(); + } + + // Update buffers + p->UpdateBuffers(); + std::unique_lock lock {p->iconMutex_}; // Swap buffers - p->currentIconList_.swap(p->newIconList_); + p->currentIconList_.swap(p->newValidIconList_); p->currentIconFiles_.swap(p->newIconFiles_); + p->currentIconBuffer_.swap(p->newIconBuffer_); + p->currentThresholdBuffer_.swap(p->newThresholdBuffer_); + p->currentHoverIcons_.swap(p->newHoverIcons_); // Clear the new buffers p->newIconList_.clear(); + p->newValidIconList_.clear(); p->newIconFiles_.clear(); + p->newIconBuffer_.clear(); + p->newThresholdBuffer_.clear(); + p->newHoverIcons_.clear(); // Mark the draw item dirty p->dirty_ = true; @@ -350,17 +385,15 @@ void PlacefileIcons::FinishIcons() void PlacefileIcons::Impl::UpdateBuffers() { - iconBuffer_.clear(); - iconBuffer_.reserve(currentIconList_.size() * kBufferLength); - thresholdBuffer_.clear(); - thresholdBuffer_.reserve(currentIconList_.size() * kVerticesPerRectangle); - hoverIcons_.clear(); - numVertices_ = 0; + newIconBuffer_.clear(); + newIconBuffer_.reserve(newIconList_.size() * kIconBufferLength); + newThresholdBuffer_.clear(); + newThresholdBuffer_.reserve(newIconList_.size() * kVerticesPerRectangle); - for (auto& di : currentIconList_) + for (auto& di : newIconList_) { - auto it = currentIconFiles_.find(di->fileNumber_); - if (it == currentIconFiles_.cend()) + auto it = newIconFiles_.find(di->fileNumber_); + if (it == newIconFiles_.cend()) { // No file found logger_->trace("Could not find file number: {}", di->fileNumber_); @@ -377,6 +410,9 @@ void PlacefileIcons::Impl::UpdateBuffers() continue; } + // Icon is valid, add to valid icon list + newValidIconList_.push_back(di); + // Threshold value units::length::nautical_miles threshold = di->threshold_; GLint thresholdValue = static_cast(std::round(threshold.value())); @@ -407,42 +443,29 @@ void PlacefileIcons::Impl::UpdateBuffers() units::angle::degrees angle = di->angle_; const float a = angle.value(); - // Texture coordinates - const std::size_t iconRow = (di->iconNumber_ - 1) / icon.columns_; - const std::size_t iconColumn = (di->iconNumber_ - 1) % icon.columns_; - - const float iconX = iconColumn * icon.scaledWidth_; - const float iconY = iconRow * icon.scaledHeight_; - - const float ls = icon.texture_.sLeft_ + iconX; - const float rs = ls + icon.scaledWidth_; - const float tt = icon.texture_.tTop_ + iconY; - const float bt = tt + icon.scaledHeight_; - // Fixed modulate color const float mc0 = 1.0f; const float mc1 = 1.0f; const float mc2 = 1.0f; const float mc3 = 1.0f; - iconBuffer_.insert( - iconBuffer_.end(), - { - // Icon - lat, lon, lx, by, ls, bt, mc0, mc1, mc2, mc3, a, // BL - lat, lon, lx, ty, ls, tt, mc0, mc1, mc2, mc3, a, // TL - lat, lon, rx, by, rs, bt, mc0, mc1, mc2, mc3, a, // BR - lat, lon, rx, by, rs, bt, mc0, mc1, mc2, mc3, a, // BR - lat, lon, rx, ty, rs, tt, mc0, mc1, mc2, mc3, a, // TR - lat, lon, lx, ty, ls, tt, mc0, mc1, mc2, mc3, a // TL - }); - thresholdBuffer_.insert(thresholdBuffer_.end(), - {thresholdValue, // - thresholdValue, - thresholdValue, - thresholdValue, - thresholdValue, - thresholdValue}); + newIconBuffer_.insert(newIconBuffer_.end(), + { + // Icon + lat, lon, lx, by, mc0, mc1, mc2, mc3, a, // BL + lat, lon, lx, ty, mc0, mc1, mc2, mc3, a, // TL + lat, lon, rx, by, mc0, mc1, mc2, mc3, a, // BR + lat, lon, rx, by, mc0, mc1, mc2, mc3, a, // BR + lat, lon, rx, ty, mc0, mc1, mc2, mc3, a, // TR + lat, lon, lx, ty, mc0, mc1, mc2, mc3, a // TL + }); + newThresholdBuffer_.insert(newThresholdBuffer_.end(), + {thresholdValue, // + thresholdValue, + thresholdValue, + thresholdValue, + thresholdValue, + thresholdValue}); if (!di->hoverText_.empty()) { @@ -460,15 +483,105 @@ void PlacefileIcons::Impl::UpdateBuffers() const glm::vec2 obl = rotate * glm::vec2 {lx, by}; const glm::vec2 obr = rotate * glm::vec2 {rx, by}; - hoverIcons_.emplace_back(IconHoverEntry {di, sc, otl, otr, obl, obr}); + newHoverIcons_.emplace_back( + IconHoverEntry {di, sc, otl, otr, obl, obr}); } } +} - dirty_ = true; +void PlacefileIcons::Impl::UpdateTextureBuffer() +{ + textureBuffer_.clear(); + textureBuffer_.reserve(currentIconList_.size() * kTextureBufferLength); + + for (auto& di : currentIconList_) + { + auto it = currentIconFiles_.find(di->fileNumber_); + if (it == currentIconFiles_.cend()) + { + // No file found + logger_->trace("Could not find file number: {}", di->fileNumber_); + + // Should not get here, but insert empty data to match up with data + // already buffered + + // clang-format off + textureBuffer_.insert( + textureBuffer_.end(), + { + // Icon + 0.0f, 0.0f, // BL + 0.0f, 0.0f, // TL + 0.0f, 0.0f, // BR + 0.0f, 0.0f, // BR + 0.0f, 0.0f, // TR + 0.0f, 0.0f // TL + }); + // clang-format on + + continue; + } + + auto& icon = it->second; + + // Validate icon + if (di->iconNumber_ == 0 || di->iconNumber_ > icon.numIcons_) + { + // No icon found + logger_->trace("Invalid icon number: {}", di->iconNumber_); + + // Will get here if a texture changes, and the texture shrunk such that + // the icon is no longer found + + // clang-format off + textureBuffer_.insert( + textureBuffer_.end(), + { + // Icon + 0.0f, 0.0f, // BL + 0.0f, 0.0f, // TL + 0.0f, 0.0f, // BR + 0.0f, 0.0f, // BR + 0.0f, 0.0f, // TR + 0.0f, 0.0f // TL + }); + // clang-format on + + continue; + } + + // Texture coordinates + const std::size_t iconRow = (di->iconNumber_ - 1) / icon.columns_; + const std::size_t iconColumn = (di->iconNumber_ - 1) % icon.columns_; + + const float iconX = iconColumn * icon.scaledWidth_; + const float iconY = iconRow * icon.scaledHeight_; + + const float ls = icon.texture_.sLeft_ + iconX; + const float rs = ls + icon.scaledWidth_; + const float tt = icon.texture_.tTop_ + iconY; + const float bt = tt + icon.scaledHeight_; + + // clang-format off + textureBuffer_.insert( + textureBuffer_.end(), + { + // Icon + ls, bt, // BL + ls, tt, // TL + rs, bt, // BR + rs, bt, // BR + rs, tt, // TR + ls, tt // TL + }); + // clang-format on + } } void PlacefileIcons::Impl::Update(bool textureAtlasChanged) { + gl::OpenGLFunctions& gl = context_->gl(); + // If the texture atlas has changed if (dirty_ || textureAtlasChanged) { @@ -478,27 +591,36 @@ void PlacefileIcons::Impl::Update(bool textureAtlasChanged) iconFile.second.UpdateTextureInfo(); } - // Update OpenGL buffer data - UpdateBuffers(); + // Update OpenGL texture buffer data + UpdateTextureBuffer(); - gl::OpenGLFunctions& gl = context_->gl(); + // Buffer texture data + gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); + gl.glBufferData(GL_ARRAY_BUFFER, + sizeof(float) * textureBuffer_.size(), + textureBuffer_.data(), + GL_DYNAMIC_DRAW); + } + // If buffers need updating + if (dirty_) + { // Buffer vertex data gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(float) * iconBuffer_.size(), - iconBuffer_.data(), + sizeof(float) * currentIconBuffer_.size(), + currentIconBuffer_.data(), GL_DYNAMIC_DRAW); // Buffer threshold data - gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); + gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[2]); gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(GLint) * thresholdBuffer_.size(), - thresholdBuffer_.data(), + sizeof(GLint) * currentThresholdBuffer_.size(), + currentThresholdBuffer_.data(), GL_DYNAMIC_DRAW); - numVertices_ = - static_cast(iconBuffer_.size() / kVerticesPerRectangle); + numVertices_ = static_cast(currentIconBuffer_.size() / + kVerticesPerRectangle); } dirty_ = false; @@ -530,8 +652,8 @@ bool PlacefileIcons::RunMousePicking( // For each pickable icon auto it = std::find_if( std::execution::par_unseq, - p->hoverIcons_.crbegin(), - p->hoverIcons_.crend(), + p->currentHoverIcons_.crbegin(), + p->currentHoverIcons_.crend(), [&mapDistance, &mapMatrix, &mousePos](const auto& icon) { if ( @@ -574,7 +696,7 @@ bool PlacefileIcons::RunMousePicking( return util::maplibre::IsPointInPolygon({tl, bl, br, tr}, mousePos); }); - if (it != p->hoverIcons_.crend()) + if (it != p->currentHoverIcons_.crend()) { itemPicked = true; util::ImGui::Instance().DrawTooltip(it->di_->hoverText_); diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp index b6f8e190..03f52b36 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp @@ -62,10 +62,10 @@ PlacefileLayer::PlacefileLayer(const std::shared_ptr& context, DrawLayer(context), p(std::make_unique(this, context, placefileName)) { - AddDrawItem(p->placefileIcons_); AddDrawItem(p->placefilePolygons_); AddDrawItem(p->placefileTriangles_); AddDrawItem(p->placefileLines_); + AddDrawItem(p->placefileIcons_); AddDrawItem(p->placefileText_); ReloadData(); From 00c297094e65c45f46791d02a574d7e85d745e83 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 2 Sep 2023 00:58:52 -0500 Subject: [PATCH 097/199] Placefile image rendering --- scwx-qt/scwx-qt.cmake | 2 + .../scwx/qt/gl/draw/placefile_images.cpp | 438 ++++++++++++++++++ .../scwx/qt/gl/draw/placefile_images.hpp | 61 +++ .../scwx/qt/manager/placefile_manager.cpp | 32 +- .../source/scwx/qt/map/placefile_layer.cpp | 13 + 5 files changed, 545 insertions(+), 1 deletion(-) create mode 100644 scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp create mode 100644 scwx-qt/source/scwx/qt/gl/draw/placefile_images.hpp diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index ab091fe2..6f3513dd 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -58,6 +58,7 @@ set(SRC_GL source/scwx/qt/gl/gl_context.cpp set(HDR_GL_DRAW source/scwx/qt/gl/draw/draw_item.hpp source/scwx/qt/gl/draw/geo_line.hpp source/scwx/qt/gl/draw/placefile_icons.hpp + source/scwx/qt/gl/draw/placefile_images.hpp source/scwx/qt/gl/draw/placefile_lines.hpp source/scwx/qt/gl/draw/placefile_polygons.hpp source/scwx/qt/gl/draw/placefile_text.hpp @@ -66,6 +67,7 @@ set(HDR_GL_DRAW source/scwx/qt/gl/draw/draw_item.hpp set(SRC_GL_DRAW source/scwx/qt/gl/draw/draw_item.cpp source/scwx/qt/gl/draw/geo_line.cpp source/scwx/qt/gl/draw/placefile_icons.cpp + source/scwx/qt/gl/draw/placefile_images.cpp source/scwx/qt/gl/draw/placefile_lines.cpp source/scwx/qt/gl/draw/placefile_polygons.cpp source/scwx/qt/gl/draw/placefile_text.cpp diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp new file mode 100644 index 00000000..092fdbf3 --- /dev/null +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp @@ -0,0 +1,438 @@ +#include +#include +#include +#include + +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace gl +{ +namespace draw +{ + +static const std::string logPrefix_ = "scwx::qt::gl::draw::placefile_images"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +static constexpr std::size_t kNumRectangles = 1; +static constexpr std::size_t kNumTriangles = kNumRectangles * 2; +static constexpr std::size_t kVerticesPerTriangle = 3; +static constexpr std::size_t kVerticesPerRectangle = kVerticesPerTriangle * 2; +static constexpr std::size_t kPointsPerVertex = 8; +static constexpr std::size_t kPointsPerTexCoord = 2; +static constexpr std::size_t kImageBufferLength = + kNumTriangles * kVerticesPerTriangle * kPointsPerVertex; +static constexpr std::size_t kTextureBufferLength = + kNumTriangles * kVerticesPerTriangle * kPointsPerTexCoord; + +struct PlacefileImageInfo +{ + PlacefileImageInfo(const std::string& imageFile, + const std::string& baseUrlString) + { + // Resolve using base URL + auto baseUrl = QUrl::fromUserInput(QString::fromStdString(baseUrlString)); + auto relativeUrl = QUrl(QString::fromStdString(imageFile)); + resolvedUrl_ = baseUrl.resolved(relativeUrl).toString().toStdString(); + } + + void UpdateTextureInfo(); + + std::string resolvedUrl_; + util::TextureAttributes texture_ {}; + float scaledWidth_ {}; + float scaledHeight_ {}; +}; + +class PlacefileImages::Impl +{ +public: + explicit Impl(const std::shared_ptr& context) : + context_ {context}, + shaderProgram_ {nullptr}, + uMVPMatrixLocation_(GL_INVALID_INDEX), + uMapMatrixLocation_(GL_INVALID_INDEX), + uMapScreenCoordLocation_(GL_INVALID_INDEX), + uMapDistanceLocation_(GL_INVALID_INDEX), + vao_ {GL_INVALID_INDEX}, + vbo_ {GL_INVALID_INDEX}, + numVertices_ {0} + { + } + + ~Impl() {} + + void UpdateBuffers(); + void UpdateTextureBuffer(); + void Update(bool textureAtlasChanged); + + std::shared_ptr context_; + + std::string baseUrl_ {}; + + bool dirty_ {false}; + bool thresholded_ {false}; + + std::mutex imageMutex_; + + boost::unordered_flat_map + currentImageFiles_ {}; + boost::unordered_flat_map newImageFiles_ {}; + + std::vector> + currentImageList_ {}; + std::vector> + newImageList_ {}; + + std::vector currentImageBuffer_ {}; + std::vector currentThresholdBuffer_ {}; + std::vector newImageBuffer_ {}; + std::vector newThresholdBuffer_ {}; + + std::vector textureBuffer_ {}; + + std::shared_ptr shaderProgram_; + GLint uMVPMatrixLocation_; + GLint uMapMatrixLocation_; + GLint uMapScreenCoordLocation_; + GLint uMapDistanceLocation_; + + GLuint vao_; + std::array vbo_; + + GLsizei numVertices_; +}; + +PlacefileImages::PlacefileImages(const std::shared_ptr& context) : + DrawItem(context->gl()), p(std::make_unique(context)) +{ +} +PlacefileImages::~PlacefileImages() = default; + +PlacefileImages::PlacefileImages(PlacefileImages&&) noexcept = default; +PlacefileImages& +PlacefileImages::operator=(PlacefileImages&&) noexcept = default; + +void PlacefileImages::set_thresholded(bool thresholded) +{ + p->thresholded_ = thresholded; +} + +void PlacefileImages::Initialize() +{ + gl::OpenGLFunctions& gl = p->context_->gl(); + + p->shaderProgram_ = p->context_->GetShaderProgram( + {{GL_VERTEX_SHADER, ":/gl/geo_texture2d.vert"}, + {GL_GEOMETRY_SHADER, ":/gl/threshold.geom"}, + {GL_FRAGMENT_SHADER, ":/gl/texture2d.frag"}}); + + p->uMVPMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMVPMatrix"); + p->uMapMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMapMatrix"); + p->uMapScreenCoordLocation_ = + p->shaderProgram_->GetUniformLocation("uMapScreenCoord"); + p->uMapDistanceLocation_ = + p->shaderProgram_->GetUniformLocation("uMapDistance"); + + gl.glGenVertexArrays(1, &p->vao_); + gl.glGenBuffers(static_cast(p->vbo_.size()), p->vbo_.data()); + + gl.glBindVertexArray(p->vao_); + gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); + gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + + // aLatLong + gl.glVertexAttribPointer(0, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + static_cast(0)); + gl.glEnableVertexAttribArray(0); + + // aXYOffset + gl.glVertexAttribPointer(1, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(2 * sizeof(float))); + gl.glEnableVertexAttribArray(1); + + // aModulate + gl.glVertexAttribPointer(3, + 4, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(4 * sizeof(float))); + gl.glEnableVertexAttribArray(3); + + gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); + gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + + // aTexCoord + gl.glVertexAttribPointer(2, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerTexCoord * sizeof(float), + static_cast(0)); + gl.glEnableVertexAttribArray(2); + + gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[2]); + gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + + // aThreshold + gl.glVertexAttribIPointer(5, // + 1, + GL_INT, + 0, + static_cast(0)); + gl.glEnableVertexAttribArray(5); + + p->dirty_ = true; +} + +void PlacefileImages::Render( + const QMapLibreGL::CustomLayerRenderParameters& params, + bool textureAtlasChanged) +{ + std::unique_lock lock {p->imageMutex_}; + + if (!p->currentImageList_.empty()) + { + gl::OpenGLFunctions& gl = p->context_->gl(); + + gl.glBindVertexArray(p->vao_); + + p->Update(textureAtlasChanged); + p->shaderProgram_->Use(); + UseRotationProjection(params, p->uMVPMatrixLocation_); + UseMapProjection( + params, p->uMapMatrixLocation_, p->uMapScreenCoordLocation_); + + if (p->thresholded_) + { + // If thresholding is enabled, set the map distance + units::length::nautical_miles mapDistance = + util::maplibre::GetMapDistance(params); + gl.glUniform1f(p->uMapDistanceLocation_, mapDistance.value()); + } + else + { + // If thresholding is disabled, set the map distance to 0 + gl.glUniform1f(p->uMapDistanceLocation_, 0.0f); + } + + // Interpolate texture coordinates + gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // Draw images + gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); + } +} + +void PlacefileImages::Deinitialize() +{ + gl::OpenGLFunctions& gl = p->context_->gl(); + + gl.glDeleteVertexArrays(1, &p->vao_); + gl.glDeleteBuffers(static_cast(p->vbo_.size()), p->vbo_.data()); + + std::unique_lock lock {p->imageMutex_}; + + p->currentImageList_.clear(); + p->currentImageFiles_.clear(); + p->currentImageBuffer_.clear(); + p->currentThresholdBuffer_.clear(); + p->textureBuffer_.clear(); +} + +void PlacefileImageInfo::UpdateTextureInfo() +{ + texture_ = util::TextureAtlas::Instance().GetTextureAttributes(resolvedUrl_); + + scaledWidth_ = texture_.sRight_ - texture_.sLeft_; + scaledHeight_ = texture_.tBottom_ - texture_.tTop_; +} + +void PlacefileImages::StartImages(const std::string& baseUrl) +{ + p->baseUrl_ = baseUrl; + + // Clear the new buffer + p->newImageList_.clear(); + p->newImageFiles_.clear(); + p->newImageBuffer_.clear(); + p->newThresholdBuffer_.clear(); +} + +void PlacefileImages::AddImage( + const std::shared_ptr& di) +{ + if (di != nullptr) + { + p->newImageList_.emplace_back(di); + } +} + +void PlacefileImages::FinishImages() +{ + // Update buffers + p->UpdateBuffers(); + + std::unique_lock lock {p->imageMutex_}; + + // Swap buffers + p->currentImageList_.swap(p->newImageList_); + p->currentImageFiles_.swap(p->newImageFiles_); + p->currentImageBuffer_.swap(p->newImageBuffer_); + p->currentThresholdBuffer_.swap(p->newThresholdBuffer_); + + // Clear the new buffers + p->newImageList_.clear(); + p->newImageFiles_.clear(); + p->newImageBuffer_.clear(); + p->newThresholdBuffer_.clear(); + + // Mark the draw item dirty + p->dirty_ = true; +} + +void PlacefileImages::Impl::UpdateBuffers() +{ + newImageBuffer_.clear(); + newImageBuffer_.reserve(newImageList_.size() * kImageBufferLength); + newThresholdBuffer_.clear(); + newThresholdBuffer_.reserve(newImageList_.size() * kVerticesPerRectangle); + newImageFiles_.clear(); + + // Fixed modulate color + static const float mc0 = 1.0f; + static const float mc1 = 1.0f; + static const float mc2 = 1.0f; + static const float mc3 = 1.0f; + + for (auto& di : newImageList_) + { + // Populate image file map + newImageFiles_.emplace( + std::piecewise_construct, + std::tuple {di->imageFile_}, + std::forward_as_tuple(PlacefileImageInfo {di->imageFile_, baseUrl_})); + + // Threshold value + units::length::nautical_miles threshold = di->threshold_; + GLint thresholdValue = static_cast(std::round(threshold.value())); + + // Limit processing to groups of 3 (triangles) + std::size_t numElements = di->elements_.size() - di->elements_.size() % 3; + for (std::size_t i = 0; i < numElements; ++i) + { + auto& element = di->elements_[i]; + + // Latitude and longitude coordinates in degrees + const float lat = static_cast(element.latitude_); + const float lon = static_cast(element.longitude_); + + // Base X/Y offsets in pixels + const float x = static_cast(element.x_); + const float y = static_cast(element.y_); + + newImageBuffer_.insert(newImageBuffer_.end(), + {lat, lon, x, y, mc0, mc1, mc2, mc3}); + newThresholdBuffer_.insert(newThresholdBuffer_.end(), + {thresholdValue}); + } + } +} + +void PlacefileImages::Impl::UpdateTextureBuffer() +{ + textureBuffer_.clear(); + textureBuffer_.reserve(currentImageList_.size() * kTextureBufferLength); + + for (auto& di : currentImageList_) + { + // Get placefile image info. The key should always be found in the map, as + // it is populated when the placefile is updated. + auto it = currentImageFiles_.find(di->imageFile_); + const PlacefileImageInfo& image = (it == currentImageFiles_.cend()) ? + currentImageFiles_.cbegin()->second : + it->second; + + // Limit processing to groups of 3 (triangles) + std::size_t numElements = di->elements_.size() - di->elements_.size() % 3; + for (std::size_t i = 0; i < numElements; ++i) + { + auto& element = di->elements_[i]; + + // Texture coordinates + const float s = + image.texture_.sLeft_ + (image.scaledWidth_ * element.tu_); + const float t = + image.texture_.tTop_ + (image.scaledHeight_ * element.tv_); + + textureBuffer_.insert(textureBuffer_.end(), {s, t}); + } + } +} + +void PlacefileImages::Impl::Update(bool textureAtlasChanged) +{ + gl::OpenGLFunctions& gl = context_->gl(); + + // If the texture atlas has changed + if (dirty_ || textureAtlasChanged) + { + // Update texture coordinates + for (auto& imageFile : currentImageFiles_) + { + imageFile.second.UpdateTextureInfo(); + } + + // Update OpenGL texture buffer data + UpdateTextureBuffer(); + + // Buffer texture data + gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); + gl.glBufferData(GL_ARRAY_BUFFER, + sizeof(float) * textureBuffer_.size(), + textureBuffer_.data(), + GL_DYNAMIC_DRAW); + } + + // If buffers need updating + if (dirty_) + { + // Buffer vertex data + gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); + gl.glBufferData(GL_ARRAY_BUFFER, + sizeof(float) * currentImageBuffer_.size(), + currentImageBuffer_.data(), + GL_DYNAMIC_DRAW); + + // Buffer threshold data + gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[2]); + gl.glBufferData(GL_ARRAY_BUFFER, + sizeof(GLint) * currentThresholdBuffer_.size(), + currentThresholdBuffer_.data(), + GL_DYNAMIC_DRAW); + + numVertices_ = static_cast(currentImageBuffer_.size() / + kVerticesPerRectangle); + } + + dirty_ = false; +} + +} // namespace draw +} // namespace gl +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_images.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_images.hpp new file mode 100644 index 00000000..8048fc3a --- /dev/null +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_images.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace gl +{ +namespace draw +{ + +class PlacefileImages : public DrawItem +{ +public: + explicit PlacefileImages(const std::shared_ptr& context); + ~PlacefileImages(); + + PlacefileImages(const PlacefileImages&) = delete; + PlacefileImages& operator=(const PlacefileImages&) = delete; + + PlacefileImages(PlacefileImages&&) noexcept; + PlacefileImages& operator=(PlacefileImages&&) noexcept; + + void set_thresholded(bool thresholded); + + void Initialize() override; + void Render(const QMapLibreGL::CustomLayerRenderParameters& params, + bool textureAtlasChanged) override; + void Deinitialize() override; + + /** + * Resets and prepares the draw item for adding a new set of images. + */ + void StartImages(const std::string& baseUrl); + + /** + * Adds a placefile image to the internal draw list. + * + * @param [in] di Placefile image + */ + void AddImage(const std::shared_ptr& di); + + /** + * Finalizes the draw item after adding new images. + */ + void FinishImages(); + +private: + class Impl; + + std::unique_ptr p; +}; + +} // namespace draw +} // namespace gl +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index e125d591..ddeaec87 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -737,13 +737,15 @@ void PlacefileManager::Impl::LoadResources( const std::shared_ptr& placefile) { const auto iconFiles = placefile->icon_files(); + const auto drawItems = placefile->GetDrawItems(); const QUrl baseUrl = QUrl::fromUserInput(QString::fromStdString(placefile->name())); - std::vector urlStrings; + std::vector urlStrings {}; urlStrings.reserve(iconFiles.size()); + // Resolve Icon Files std::transform(iconFiles.cbegin(), iconFiles.cend(), std::back_inserter(urlStrings), @@ -757,6 +759,34 @@ void PlacefileManager::Impl::LoadResources( return resolvedUrl.toString().toStdString(); }); + // Resolve Image Files + for (auto& di : drawItems) + { + switch (di->itemType_) + { + case gr::Placefile::ItemType::Image: + { + const std::string& imageFile = + std::static_pointer_cast(di) + ->imageFile_; + + QUrl fileUrl = QUrl(QString::fromStdString(imageFile)); + QUrl resolvedUrl = baseUrl.resolved(fileUrl); + std::string urlString = resolvedUrl.toString().toStdString(); + + if (std::find(urlStrings.cbegin(), urlStrings.cend(), urlString) == + urlStrings.cend()) + { + urlStrings.push_back(urlString); + } + break; + } + + default: + break; + } + } + ResourceManager::LoadImageResources(urlStrings); } diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp index 03f52b36..cc688a23 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -29,6 +30,7 @@ public: self_ {self}, placefileName_ {placefileName}, placefileIcons_ {std::make_shared(context)}, + placefileImages_ {std::make_shared(context)}, placefileLines_ {std::make_shared(context)}, placefilePolygons_ { std::make_shared(context)}, @@ -51,6 +53,7 @@ public: std::mutex dataMutex_ {}; std::shared_ptr placefileIcons_; + std::shared_ptr placefileImages_; std::shared_ptr placefileLines_; std::shared_ptr placefilePolygons_; std::shared_ptr placefileTriangles_; @@ -62,6 +65,7 @@ PlacefileLayer::PlacefileLayer(const std::shared_ptr& context, DrawLayer(context), p(std::make_unique(this, context, placefileName)) { + AddDrawItem(p->placefileImages_); AddDrawItem(p->placefilePolygons_); AddDrawItem(p->placefileTriangles_); AddDrawItem(p->placefileLines_); @@ -128,6 +132,7 @@ void PlacefileLayer::Render( bool thresholded = placefileManager->placefile_thresholded(placefile->name()); p->placefileIcons_->set_thresholded(thresholded); + p->placefileImages_->set_thresholded(thresholded); p->placefileLines_->set_thresholded(thresholded); p->placefilePolygons_->set_thresholded(thresholded); p->placefileTriangles_->set_thresholded(thresholded); @@ -167,6 +172,7 @@ void PlacefileLayer::ReloadData() // Start draw items p->placefileIcons_->StartIcons(); + p->placefileImages_->StartImages(placefile->name()); p->placefileLines_->StartLines(); p->placefilePolygons_->StartPolygons(); p->placefileTriangles_->StartTriangles(); @@ -203,6 +209,12 @@ void PlacefileLayer::ReloadData() drawItem)); break; + case gr::Placefile::ItemType::Image: + p->placefileImages_->AddImage( + std::static_pointer_cast( + drawItem)); + break; + case gr::Placefile::ItemType::Triangles: p->placefileTriangles_->AddTriangles( std::static_pointer_cast( @@ -216,6 +228,7 @@ void PlacefileLayer::ReloadData() // Finish draw items p->placefileIcons_->FinishIcons(); + p->placefileImages_->FinishImages(); p->placefileLines_->FinishLines(); p->placefilePolygons_->FinishPolygons(); p->placefileTriangles_->FinishTriangles(); From 922e875b075967ed5ac690d8431ff837c71c6a71 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 2 Sep 2023 00:59:25 -0500 Subject: [PATCH 098/199] Placefile icon render error logging --- scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp index 1a74f4e6..fce5495d 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp @@ -499,11 +499,9 @@ void PlacefileIcons::Impl::UpdateTextureBuffer() auto it = currentIconFiles_.find(di->fileNumber_); if (it == currentIconFiles_.cend()) { - // No file found - logger_->trace("Could not find file number: {}", di->fileNumber_); - - // Should not get here, but insert empty data to match up with data - // already buffered + // No file found. Should not get here, but insert empty data to match + // up with data already buffered + logger_->error("Could not find file number: {}", di->fileNumber_); // clang-format off textureBuffer_.insert( From 7198d1c7afee8d7043f122e152623a174dbb3d3f Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 2 Sep 2023 13:44:56 -0500 Subject: [PATCH 099/199] Use GL_TEXTURE_2D_ARRAY to enable multiple texture atlases --- scwx-qt/gl/texture2d_array.frag | 16 ++++++++++++++++ scwx-qt/scwx-qt.cmake | 1 + scwx-qt/scwx-qt.qrc | 1 + scwx-qt/source/scwx/qt/gl/draw/geo_line.cpp | 4 ++-- .../source/scwx/qt/gl/draw/placefile_icons.cpp | 6 +++--- .../source/scwx/qt/gl/draw/placefile_images.cpp | 6 +++--- scwx-qt/source/scwx/qt/map/draw_layer.cpp | 2 +- scwx-qt/source/scwx/qt/util/texture_atlas.cpp | 15 +++++++++------ 8 files changed, 36 insertions(+), 15 deletions(-) create mode 100644 scwx-qt/gl/texture2d_array.frag diff --git a/scwx-qt/gl/texture2d_array.frag b/scwx-qt/gl/texture2d_array.frag new file mode 100644 index 00000000..c345769a --- /dev/null +++ b/scwx-qt/gl/texture2d_array.frag @@ -0,0 +1,16 @@ +#version 330 core + +// Lower the default precision to medium +precision mediump float; + +uniform sampler2DArray uTexture; + +smooth in vec2 texCoord; +smooth in vec4 color; + +layout (location = 0) out vec4 fragColor; + +void main() +{ + fragColor = texture(uTexture, vec3(texCoord, 0.0)) * color; +} diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 6f3513dd..6557e6ca 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -265,6 +265,7 @@ set(SHADER_FILES gl/color.frag gl/texture1d.frag gl/texture1d.vert gl/texture2d.frag + gl/texture2d_array.frag gl/threshold.geom) set(CMAKE_FILES scwx-qt.cmake) diff --git a/scwx-qt/scwx-qt.qrc b/scwx-qt/scwx-qt.qrc index 5453b19d..47e49c4c 100644 --- a/scwx-qt/scwx-qt.qrc +++ b/scwx-qt/scwx-qt.qrc @@ -12,6 +12,7 @@ gl/texture1d.frag gl/texture1d.vert gl/texture2d.frag + gl/texture2d_array.frag gl/threshold.geom res/config/radar_sites.json res/fonts/din1451alt.ttf diff --git a/scwx-qt/source/scwx/qt/gl/draw/geo_line.cpp b/scwx-qt/source/scwx/qt/gl/draw/geo_line.cpp index 2b939293..912a5022 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/geo_line.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/geo_line.cpp @@ -92,8 +92,8 @@ void GeoLine::Initialize() { gl::OpenGLFunctions& gl = p->context_->gl(); - p->shaderProgram_ = p->context_->GetShaderProgram(":/gl/geo_line.vert", - ":/gl/texture2d.frag"); + p->shaderProgram_ = p->context_->GetShaderProgram( + ":/gl/geo_line.vert", ":/gl/texture2d_array.frag"); p->uMVPMatrixLocation_ = gl.glGetUniformLocation(p->shaderProgram_->id(), "uMVPMatrix"); diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp index fce5495d..22e94bb8 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp @@ -151,7 +151,7 @@ void PlacefileIcons::Initialize() p->shaderProgram_ = p->context_->GetShaderProgram( {{GL_VERTEX_SHADER, ":/gl/geo_texture2d.vert"}, {GL_GEOMETRY_SHADER, ":/gl/threshold.geom"}, - {GL_FRAGMENT_SHADER, ":/gl/texture2d.frag"}}); + {GL_FRAGMENT_SHADER, ":/gl/texture2d_array.frag"}}); p->uMVPMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMVPMatrix"); p->uMapMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMapMatrix"); @@ -261,8 +261,8 @@ void PlacefileIcons::Render( } // Interpolate texture coordinates - gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Draw icons gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp index 092fdbf3..e796ed04 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp @@ -129,7 +129,7 @@ void PlacefileImages::Initialize() p->shaderProgram_ = p->context_->GetShaderProgram( {{GL_VERTEX_SHADER, ":/gl/geo_texture2d.vert"}, {GL_GEOMETRY_SHADER, ":/gl/threshold.geom"}, - {GL_FRAGMENT_SHADER, ":/gl/texture2d.frag"}}); + {GL_FRAGMENT_SHADER, ":/gl/texture2d_array.frag"}}); p->uMVPMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMVPMatrix"); p->uMapMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMapMatrix"); @@ -230,8 +230,8 @@ void PlacefileImages::Render( } // Interpolate texture coordinates - gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Draw images gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); diff --git a/scwx-qt/source/scwx/qt/map/draw_layer.cpp b/scwx-qt/source/scwx/qt/map/draw_layer.cpp index 6a6895e7..4ecf2fb3 100644 --- a/scwx-qt/source/scwx/qt/map/draw_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/draw_layer.cpp @@ -56,7 +56,7 @@ void DrawLayer::Render(const QMapLibreGL::CustomLayerRenderParameters& params) newTextureAtlasBuildCount != p->textureAtlasBuildCount_; gl.glActiveTexture(GL_TEXTURE0); - gl.glBindTexture(GL_TEXTURE_2D, p->textureAtlas_); + gl.glBindTexture(GL_TEXTURE_2D_ARRAY, p->textureAtlas_); for (auto& item : p->drawList_) { diff --git a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp index 274ea8fb..7cb4d067 100644 --- a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp +++ b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp @@ -296,18 +296,21 @@ GLuint TextureAtlas::BufferAtlas(gl::OpenGLFunctions& gl) lock.unlock(); gl.glGenTextures(1, &texture); - gl.glBindTexture(GL_TEXTURE_2D, texture); + gl.glBindTexture(GL_TEXTURE_2D_ARRAY, texture); - gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + gl.glTexParameteri( + GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + gl.glTexParameteri( + GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - gl.glTexImage2D(GL_TEXTURE_2D, + gl.glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, view.width(), view.height(), + 1u, 0, GL_RGBA, GL_UNSIGNED_BYTE, From 9766e02f32cbe309674d8fa51331b58c7af41c59 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 2 Sep 2023 23:37:45 -0500 Subject: [PATCH 100/199] Generate multiple texture atlases when first atlas is full --- scwx-qt/gl/geo_line.vert | 4 +- scwx-qt/gl/geo_texture2d.vert | 6 +- scwx-qt/gl/map_color.vert | 2 +- scwx-qt/gl/texture2d_array.frag | 4 +- scwx-qt/gl/threshold.geom | 4 +- scwx-qt/source/scwx/qt/gl/draw/geo_line.cpp | 20 +- .../scwx/qt/gl/draw/placefile_icons.cpp | 41 +-- .../scwx/qt/gl/draw/placefile_images.cpp | 8 +- scwx-qt/source/scwx/qt/util/texture_atlas.cpp | 249 +++++++++++------- scwx-qt/source/scwx/qt/util/texture_atlas.hpp | 8 +- 10 files changed, 205 insertions(+), 141 deletions(-) diff --git a/scwx-qt/gl/geo_line.vert b/scwx-qt/gl/geo_line.vert index 490fc3eb..487b8041 100644 --- a/scwx-qt/gl/geo_line.vert +++ b/scwx-qt/gl/geo_line.vert @@ -8,14 +8,14 @@ layout (location = 0) in vec2 aLatLong; layout (location = 1) in vec2 aXYOffset; -layout (location = 2) in vec2 aTexCoord; +layout (location = 2) in vec3 aTexCoord; layout (location = 3) in vec4 aModulate; uniform mat4 uMVPMatrix; uniform mat4 uMapMatrix; uniform vec2 uMapScreenCoord; -smooth out vec2 texCoord; +smooth out vec3 texCoord; smooth out vec4 color; vec2 latLngToScreenCoordinate(in vec2 latLng) diff --git a/scwx-qt/gl/geo_texture2d.vert b/scwx-qt/gl/geo_texture2d.vert index 05a8c5d0..ee93a12a 100644 --- a/scwx-qt/gl/geo_texture2d.vert +++ b/scwx-qt/gl/geo_texture2d.vert @@ -9,7 +9,7 @@ layout (location = 0) in vec2 aLatLong; layout (location = 1) in vec2 aXYOffset; -layout (location = 2) in vec2 aTexCoord; +layout (location = 2) in vec3 aTexCoord; layout (location = 3) in vec4 aModulate; layout (location = 4) in float aAngleDeg; layout (location = 5) in int aThreshold; @@ -21,11 +21,11 @@ uniform vec2 uMapScreenCoord; out VertexData { int threshold; - vec2 texCoord; + vec3 texCoord; vec4 color; } vsOut; -smooth out vec2 texCoord; +smooth out vec3 texCoord; smooth out vec4 color; vec2 latLngToScreenCoordinate(in vec2 latLng) diff --git a/scwx-qt/gl/map_color.vert b/scwx-qt/gl/map_color.vert index cc19f8a4..b2749a02 100644 --- a/scwx-qt/gl/map_color.vert +++ b/scwx-qt/gl/map_color.vert @@ -12,7 +12,7 @@ uniform vec2 uMapScreenCoord; out VertexData { int threshold; - vec2 texCoord; + vec3 texCoord; vec4 color; } vsOut; diff --git a/scwx-qt/gl/texture2d_array.frag b/scwx-qt/gl/texture2d_array.frag index c345769a..b73bfda8 100644 --- a/scwx-qt/gl/texture2d_array.frag +++ b/scwx-qt/gl/texture2d_array.frag @@ -5,12 +5,12 @@ precision mediump float; uniform sampler2DArray uTexture; -smooth in vec2 texCoord; +smooth in vec3 texCoord; smooth in vec4 color; layout (location = 0) out vec4 fragColor; void main() { - fragColor = texture(uTexture, vec3(texCoord, 0.0)) * color; + fragColor = texture(uTexture, texCoord) * color; } diff --git a/scwx-qt/gl/threshold.geom b/scwx-qt/gl/threshold.geom index 4d9d3b53..1f64b368 100644 --- a/scwx-qt/gl/threshold.geom +++ b/scwx-qt/gl/threshold.geom @@ -8,11 +8,11 @@ uniform float uMapDistance; in VertexData { int threshold; - vec2 texCoord; + vec3 texCoord; vec4 color; } gsIn[]; -smooth out vec2 texCoord; +smooth out vec3 texCoord; smooth out vec4 color; void main() diff --git a/scwx-qt/source/scwx/qt/gl/draw/geo_line.cpp b/scwx-qt/source/scwx/qt/gl/draw/geo_line.cpp index 912a5022..17d84a79 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/geo_line.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/geo_line.cpp @@ -23,7 +23,7 @@ static constexpr size_t kNumRectangles = 1; static constexpr size_t kNumTriangles = kNumRectangles * 2; static constexpr size_t kVerticesPerTriangle = 3; static constexpr size_t kVerticesPerRectangle = kVerticesPerTriangle * 2; -static constexpr size_t kPointsPerVertex = 10; +static constexpr size_t kPointsPerVertex = 11; static constexpr size_t kBufferLength = kNumTriangles * kVerticesPerTriangle * kPointsPerVertex; @@ -147,7 +147,7 @@ void GeoLine::Initialize() // aTexCoord gl.glVertexAttribPointer(2, - 2, + 3, GL_FLOAT, GL_FALSE, kPointsPerVertex * sizeof(float), @@ -160,7 +160,7 @@ void GeoLine::Initialize() GL_FLOAT, GL_FALSE, kPointsPerVertex * sizeof(float), - reinterpret_cast(6 * sizeof(float))); + reinterpret_cast(7 * sizeof(float))); gl.glEnableVertexAttribArray(3); p->dirty_ = true; @@ -264,6 +264,8 @@ void GeoLine::Impl::Update() const float oy = width_ * 0.5f * sinf(angle_); // Texture coordinates + static constexpr float r = 0.0f; + const float ls = texture_.sLeft_; const float rs = texture_.sRight_; const float tt = texture_.tTop_; @@ -289,12 +291,12 @@ void GeoLine::Impl::Update() { // // Line { - {lx, by, -ox, -oy, ls, bt, mc0, mc1, mc2, mc3}, // BL - {lx, by, +ox, +oy, ls, tt, mc0, mc1, mc2, mc3}, // TL - {rx, ty, -ox, -oy, rs, bt, mc0, mc1, mc2, mc3}, // BR - {rx, ty, -ox, -oy, rs, bt, mc0, mc1, mc2, mc3}, // BR - {rx, ty, +ox, +oy, rs, tt, mc0, mc1, mc2, mc3}, // TR - {lx, by, +ox, +oy, ls, tt, mc0, mc1, mc2, mc3} // TL + {lx, by, -ox, -oy, ls, bt, r, mc0, mc1, mc2, mc3}, // BL + {lx, by, +ox, +oy, ls, tt, r, mc0, mc1, mc2, mc3}, // TL + {rx, ty, -ox, -oy, rs, bt, r, mc0, mc1, mc2, mc3}, // BR + {rx, ty, -ox, -oy, rs, bt, r, mc0, mc1, mc2, mc3}, // BR + {rx, ty, +ox, +oy, rs, tt, r, mc0, mc1, mc2, mc3}, // TR + {lx, by, +ox, +oy, ls, tt, r, mc0, mc1, mc2, mc3} // TL }}; gl.glBufferData(GL_ARRAY_BUFFER, diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp index 22e94bb8..03a66c61 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp @@ -26,7 +26,7 @@ static constexpr std::size_t kNumTriangles = kNumRectangles * 2; static constexpr std::size_t kVerticesPerTriangle = 3; static constexpr std::size_t kVerticesPerRectangle = kVerticesPerTriangle * 2; static constexpr std::size_t kPointsPerVertex = 9; -static constexpr std::size_t kPointsPerTexCoord = 2; +static constexpr std::size_t kPointsPerTexCoord = 3; static constexpr std::size_t kIconBufferLength = kNumTriangles * kVerticesPerTriangle * kPointsPerVertex; static constexpr std::size_t kTextureBufferLength = @@ -208,7 +208,7 @@ void PlacefileIcons::Initialize() // aTexCoord gl.glVertexAttribPointer(2, - 2, + 3, GL_FLOAT, GL_FALSE, kPointsPerTexCoord * sizeof(float), @@ -508,12 +508,12 @@ void PlacefileIcons::Impl::UpdateTextureBuffer() textureBuffer_.end(), { // Icon - 0.0f, 0.0f, // BL - 0.0f, 0.0f, // TL - 0.0f, 0.0f, // BR - 0.0f, 0.0f, // BR - 0.0f, 0.0f, // TR - 0.0f, 0.0f // TL + 0.0f, 0.0f, 0.0f, // BL + 0.0f, 0.0f, 0.0f, // TL + 0.0f, 0.0f, 0.0f, // BR + 0.0f, 0.0f, 0.0f, // BR + 0.0f, 0.0f, 0.0f, // TR + 0.0f, 0.0f, 0.0f // TL }); // clang-format on @@ -536,12 +536,12 @@ void PlacefileIcons::Impl::UpdateTextureBuffer() textureBuffer_.end(), { // Icon - 0.0f, 0.0f, // BL - 0.0f, 0.0f, // TL - 0.0f, 0.0f, // BR - 0.0f, 0.0f, // BR - 0.0f, 0.0f, // TR - 0.0f, 0.0f // TL + 0.0f, 0.0f, 0.0f, // BL + 0.0f, 0.0f, 0.0f, // TL + 0.0f, 0.0f, 0.0f, // BR + 0.0f, 0.0f, 0.0f, // BR + 0.0f, 0.0f, 0.0f, // TR + 0.0f, 0.0f, 0.0f // TL }); // clang-format on @@ -559,18 +559,19 @@ void PlacefileIcons::Impl::UpdateTextureBuffer() const float rs = ls + icon.scaledWidth_; const float tt = icon.texture_.tTop_ + iconY; const float bt = tt + icon.scaledHeight_; + const float r = static_cast(icon.texture_.layerId_); // clang-format off textureBuffer_.insert( textureBuffer_.end(), { // Icon - ls, bt, // BL - ls, tt, // TL - rs, bt, // BR - rs, bt, // BR - rs, tt, // TR - ls, tt // TL + ls, bt, r, // BL + ls, tt, r, // TL + rs, bt, r, // BR + rs, bt, r, // BR + rs, tt, r, // TR + ls, tt, r // TL }); // clang-format on } diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp index e796ed04..019c2b21 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp @@ -23,7 +23,7 @@ static constexpr std::size_t kNumTriangles = kNumRectangles * 2; static constexpr std::size_t kVerticesPerTriangle = 3; static constexpr std::size_t kVerticesPerRectangle = kVerticesPerTriangle * 2; static constexpr std::size_t kPointsPerVertex = 8; -static constexpr std::size_t kPointsPerTexCoord = 2; +static constexpr std::size_t kPointsPerTexCoord = 3; static constexpr std::size_t kImageBufferLength = kNumTriangles * kVerticesPerTriangle * kPointsPerVertex; static constexpr std::size_t kTextureBufferLength = @@ -177,7 +177,7 @@ void PlacefileImages::Initialize() // aTexCoord gl.glVertexAttribPointer(2, - 2, + 3, GL_FLOAT, GL_FALSE, kPointsPerTexCoord * sizeof(float), @@ -367,6 +367,8 @@ void PlacefileImages::Impl::UpdateTextureBuffer() currentImageFiles_.cbegin()->second : it->second; + const float r = static_cast(image.texture_.layerId_); + // Limit processing to groups of 3 (triangles) std::size_t numElements = di->elements_.size() - di->elements_.size() % 3; for (std::size_t i = 0; i < numElements; ++i) @@ -379,7 +381,7 @@ void PlacefileImages::Impl::UpdateTextureBuffer() const float t = image.texture_.tTop_ + (image.scaledHeight_ * element.tv_); - textureBuffer_.insert(textureBuffer_.end(), {s, t}); + textureBuffer_.insert(textureBuffer_.end(), {s, t, r}); } } } diff --git a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp index 7cb4d067..40f521eb 100644 --- a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp +++ b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp @@ -50,7 +50,7 @@ public: std::shared_mutex textureCacheMutex_ {}; std::unordered_map textureCache_ {}; - boost::gil::rgba8_image_t atlas_ {}; + std::vector atlasArray_ {}; std::unordered_map atlasMap_ {}; std::shared_mutex atlasMutex_ {}; @@ -95,7 +95,7 @@ bool TextureAtlas::CacheTexture(const std::string& name, return false; } -void TextureAtlas::BuildAtlas(size_t width, size_t height) +void TextureAtlas::BuildAtlas(std::size_t width, std::size_t height) { logger_->debug("Building {}x{} texture atlas", width, height); @@ -105,11 +105,13 @@ void TextureAtlas::BuildAtlas(size_t width, size_t height) return; } - std::vector>> - images; - std::vector stbrpRects; + ImageVector; + + ImageVector images {}; + std::vector stbrpRects {}; // Load images { @@ -171,104 +173,143 @@ void TextureAtlas::BuildAtlas(size_t width, size_t height) } } - // Pack images - { - logger_->trace("Packing {} images", images.size()); - - // Optimal number of nodes = width - stbrp_context stbrpContext; - std::vector stbrpNodes(width); - - stbrp_init_target(&stbrpContext, - static_cast(width), - static_cast(height), - stbrpNodes.data(), - static_cast(stbrpNodes.size())); - - // Pack loaded textures - stbrp_pack_rects( - &stbrpContext, stbrpRects.data(), static_cast(stbrpRects.size())); - } - - // Lock atlas - std::unique_lock lock(p->atlasMutex_); - - // Clear index - p->atlasMap_.clear(); - - // Clear atlas - p->atlas_.recreate(width, height); - boost::gil::rgba8_view_t atlasView = boost::gil::view(p->atlas_); - boost::gil::fill_pixels(atlasView, - boost::gil::rgba8_pixel_t {255, 0, 255, 255}); - - // Populate atlas - logger_->trace("Populating atlas"); + // GL_MAX_ARRAY_TEXTURE_LAYERS is guaranteed to be at least 256 in OpenGL 3.3 + constexpr std::size_t kMaxLayers = 256u; const float xStep = 1.0f / width; const float yStep = 1.0f / height; const float xMin = xStep * 0.5f; const float yMin = yStep * 0.5f; - for (size_t i = 0; i < images.size(); i++) + // Optimal number of nodes = width + stbrp_context stbrpContext; + std::vector stbrpNodes(width); + ImageVector unpackedImages {}; + std::vector unpackedRects {}; + + std::vector newAtlasArray {}; + std::unordered_map newAtlasMap {}; + + for (std::size_t layer = 0; layer < kMaxLayers; ++layer) { - // If the image was packed successfully - if (stbrpRects[i].was_packed != 0) + logger_->trace("Processing layer {}", layer); + + // Pack images { - // Populate the atlas - boost::gil::rgba8c_view_t imageView; + logger_->trace("Packing {} images", images.size()); - // Retrieve the image - if (std::holds_alternative( - images[i].second)) + stbrp_init_target(&stbrpContext, + static_cast(width), + static_cast(height), + stbrpNodes.data(), + static_cast(stbrpNodes.size())); + + // Pack loaded textures + stbrp_pack_rects(&stbrpContext, + stbrpRects.data(), + static_cast(stbrpRects.size())); + } + + // Clear atlas + auto& atlas = + newAtlasArray.emplace_back(boost::gil::rgba8_image_t(width, height)); + boost::gil::rgba8_view_t atlasView = boost::gil::view(atlas); + boost::gil::fill_pixels(atlasView, + boost::gil::rgba8_pixel_t {255, 0, 255, 255}); + + // Populate atlas + logger_->trace("Populating atlas"); + + for (std::size_t i = 0; i < images.size(); ++i) + { + // If the image was packed successfully + if (stbrpRects[i].was_packed != 0) { - imageView = boost::gil::const_view( - std::get(images[i].second)); + // Populate the atlas + boost::gil::rgba8c_view_t imageView; + + // Retrieve the image + if (std::holds_alternative( + images[i].second)) + { + imageView = boost::gil::const_view( + std::get(images[i].second)); + } + else if (std::holds_alternative( + images[i].second)) + { + imageView = boost::gil::const_view( + *std::get(images[i].second)); + } + + boost::gil::rgba8_view_t atlasSubView = + boost::gil::subimage_view(atlasView, + stbrpRects[i].x, + stbrpRects[i].y, + imageView.width(), + imageView.height()); + + boost::gil::copy_pixels(imageView, atlasSubView); + + // Add texture image to the index + const stbrp_coord x = stbrpRects[i].x; + const stbrp_coord y = stbrpRects[i].y; + + const float sLeft = x * xStep + xMin; + const float sRight = + sLeft + static_cast(imageView.width() - 1) / width; + const float tTop = y * yStep + yMin; + const float tBottom = + tTop + static_cast(imageView.height() - 1) / height; + + newAtlasMap.emplace( + std::piecewise_construct, + std::forward_as_tuple(images[i].first), + std::forward_as_tuple( + layer, + boost::gil::point_t {x, y}, + boost::gil::point_t {imageView.width(), imageView.height()}, + sLeft, + sRight, + tTop, + tBottom)); } - else if (std::holds_alternative( - images[i].second)) + else { - imageView = boost::gil::const_view( - *std::get(images[i].second)); + unpackedImages.push_back(std::move(images[i])); + unpackedRects.push_back(stbrpRects[i]); } + } - boost::gil::rgba8_view_t atlasSubView = - boost::gil::subimage_view(atlasView, - stbrpRects[i].x, - stbrpRects[i].y, - imageView.width(), - imageView.height()); - - boost::gil::copy_pixels(imageView, atlasSubView); - - // Add texture image to the index - const stbrp_coord x = stbrpRects[i].x; - const stbrp_coord y = stbrpRects[i].y; - - const float sLeft = x * xStep + xMin; - const float sRight = - sLeft + static_cast(imageView.width() - 1) / width; - const float tTop = y * yStep + yMin; - const float tBottom = - tTop + static_cast(imageView.height() - 1) / height; - - p->atlasMap_.emplace( - std::piecewise_construct, - std::forward_as_tuple(images[i].first), - std::forward_as_tuple( - boost::gil::point_t {x, y}, - boost::gil::point_t {imageView.width(), imageView.height()}, - sLeft, - sRight, - tTop, - tBottom)); + if (unpackedImages.empty()) + { + // All images have been packed into the texture atlas + break; + } + else if (layer == kMaxLayers - 1u) + { + // Some images were unable to be packed into the texture atlas + for (auto& image : unpackedImages) + { + logger_->warn("Unable to pack texture: {}", image.first); + } } else { - logger_->warn("Unable to pack texture: {}", images[i].first); + // Swap in unpacked images for processing the next atlas layer + images.swap(unpackedImages); + stbrpRects.swap(unpackedRects); + unpackedImages.clear(); + unpackedRects.clear(); } } + // Lock atlas + std::unique_lock lock(p->atlasMutex_); + + p->atlasArray_.swap(newAtlasArray); + p->atlasMap_.swap(newAtlasMap); + // Mark the need to buffer the atlas ++p->buildCount_; } @@ -279,19 +320,28 @@ GLuint TextureAtlas::BufferAtlas(gl::OpenGLFunctions& gl) std::shared_lock lock(p->atlasMutex_); - if (p->atlas_.width() > 0u && p->atlas_.height() > 0u) + if (p->atlasArray_.size() > 0u && p->atlasArray_[0].width() > 0 && + p->atlasArray_[0].height() > 0) { - boost::gil::rgba8_view_t view = boost::gil::view(p->atlas_); - std::vector pixelData(view.width() * - view.height()); + const std::size_t numLayers = p->atlasArray_.size(); + const std::size_t width = p->atlasArray_[0].width(); + const std::size_t height = p->atlasArray_[0].height(); + const std::size_t layerSize = width * height; - boost::gil::copy_pixels( - view, - boost::gil::interleaved_view(view.width(), - view.height(), - pixelData.data(), - view.width() * - sizeof(boost::gil::rgba8_pixel_t))); + std::vector pixelData {layerSize * numLayers}; + + for (std::size_t i = 0; i < numLayers; ++i) + { + boost::gil::rgba8_view_t view = boost::gil::view(p->atlasArray_[i]); + + boost::gil::copy_pixels( + view, + boost::gil::interleaved_view(view.width(), + view.height(), + pixelData.data() + (i * layerSize), + view.width() * + sizeof(boost::gil::rgba8_pixel_t))); + } lock.unlock(); @@ -308,9 +358,9 @@ GLuint TextureAtlas::BufferAtlas(gl::OpenGLFunctions& gl) gl.glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, - view.width(), - view.height(), - 1u, + static_cast(width), + static_cast(height), + static_cast(numLayers), 0, GL_RGBA, GL_UNSIGNED_BYTE, @@ -424,6 +474,11 @@ TextureAtlas::Impl::LoadImage(const std::string& imagePath) }); } + boost::gil::write_view( + fmt::format("gil-{}.png", url.fileName().toStdString()), + image._view, + boost::gil::png_tag()); + stbi_image_free(pixelData); } else if (response.status_code == 0) diff --git a/scwx-qt/source/scwx/qt/util/texture_atlas.hpp b/scwx-qt/source/scwx/qt/util/texture_atlas.hpp index 5aae76b4..5cc77889 100644 --- a/scwx-qt/source/scwx/qt/util/texture_atlas.hpp +++ b/scwx-qt/source/scwx/qt/util/texture_atlas.hpp @@ -18,6 +18,7 @@ struct TextureAttributes { TextureAttributes() : valid_ {false}, + layerId_ {}, position_ {}, size_ {}, sLeft_ {}, @@ -27,13 +28,15 @@ struct TextureAttributes { } - TextureAttributes(boost::gil::point_t position, + TextureAttributes(std::size_t layerId, + boost::gil::point_t position, boost::gil::point_t size, float sLeft, float sRight, float tTop, float tBottom) : valid_ {true}, + layerId_ {layerId}, position_ {position}, size_ {size}, sLeft_ {sLeft}, @@ -44,6 +47,7 @@ struct TextureAttributes } bool valid_; + std::size_t layerId_; boost::gil::point_t position_; boost::gil::point_t size_; float sLeft_; @@ -70,7 +74,7 @@ public: void RegisterTexture(const std::string& name, const std::string& path); bool CacheTexture(const std::string& name, const std::string& path); - void BuildAtlas(size_t width, size_t height); + void BuildAtlas(std::size_t width, std::size_t height); GLuint BufferAtlas(gl::OpenGLFunctions& gl); TextureAttributes GetTextureAttributes(const std::string& name); From bce274f5acd9639ed5e2a16f529595203bb9c9b5 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 3 Sep 2023 11:09:30 -0500 Subject: [PATCH 101/199] Placefile line flags are not always specified --- wxdata/source/scwx/gr/placefile.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/wxdata/source/scwx/gr/placefile.cpp b/wxdata/source/scwx/gr/placefile.cpp index 8ef9ba2d..b92fe1b2 100644 --- a/wxdata/source/scwx/gr/placefile.cpp +++ b/wxdata/source/scwx/gr/placefile.cpp @@ -586,7 +586,11 @@ void Placefile::Impl::ProcessLine(const std::string& line) di->endTime_ = endTime_; di->width_ = std::stoul(tokenList[0]); - di->flags_ = std::stoul(tokenList[1]); + + if (!tokenList[1].empty()) + { + di->flags_ = std::stoul(tokenList[1]); + } } if (tokenList.size() >= 3) { From 94a58987554cbe6cb784e6c60f7db110c629e438 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 3 Sep 2023 18:59:34 -0500 Subject: [PATCH 102/199] Remove debug writing of texture atlas images --- scwx-qt/source/scwx/qt/util/texture_atlas.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp index 40f521eb..1ae34287 100644 --- a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp +++ b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp @@ -474,11 +474,6 @@ TextureAtlas::Impl::LoadImage(const std::string& imagePath) }); } - boost::gil::write_view( - fmt::format("gil-{}.png", url.fileName().toStdString()), - image._view, - boost::gil::png_tag()); - stbi_image_free(pixelData); } else if (response.status_code == 0) From e013b9a77fd2f648c492d68e2623c0f66dc55fad Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 3 Sep 2023 19:04:47 -0500 Subject: [PATCH 103/199] If no images are able to be packed into a texture atlas layer, don't create more --- scwx-qt/source/scwx/qt/util/texture_atlas.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp index 1ae34287..ad4502db 100644 --- a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp +++ b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp @@ -211,15 +211,16 @@ void TextureAtlas::BuildAtlas(std::size_t width, std::size_t height) } // Clear atlas - auto& atlas = - newAtlasArray.emplace_back(boost::gil::rgba8_image_t(width, height)); - boost::gil::rgba8_view_t atlasView = boost::gil::view(atlas); + boost::gil::rgba8_image_t atlas(width, height); + boost::gil::rgba8_view_t atlasView = boost::gil::view(atlas); boost::gil::fill_pixels(atlasView, boost::gil::rgba8_pixel_t {255, 0, 255, 255}); // Populate atlas logger_->trace("Populating atlas"); + std::size_t numPackedImages = 0u; + for (std::size_t i = 0; i < images.size(); ++i) { // If the image was packed successfully @@ -273,6 +274,8 @@ void TextureAtlas::BuildAtlas(std::size_t width, std::size_t height) sRight, tTop, tBottom)); + + numPackedImages++; } else { @@ -281,12 +284,18 @@ void TextureAtlas::BuildAtlas(std::size_t width, std::size_t height) } } + if (numPackedImages > 0u) + { + // The new atlas layer has images that were able to be packed + newAtlasArray.emplace_back(std::move(atlas)); + } + if (unpackedImages.empty()) { // All images have been packed into the texture atlas break; } - else if (layer == kMaxLayers - 1u) + else if (layer == kMaxLayers - 1u || numPackedImages == 0u) { // Some images were unable to be packed into the texture atlas for (auto& image : unpackedImages) From 4cc1a2b3106463162ce56a1ca976b9bec6ae246e Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 4 Sep 2023 21:56:59 -0500 Subject: [PATCH 104/199] Placefile text animation support --- .../source/scwx/qt/gl/draw/placefile_icons.cpp | 8 ++++++++ .../source/scwx/qt/gl/draw/placefile_icons.hpp | 1 + .../scwx/qt/gl/draw/placefile_images.cpp | 8 ++++++++ .../scwx/qt/gl/draw/placefile_images.hpp | 1 + .../source/scwx/qt/gl/draw/placefile_lines.cpp | 8 ++++++++ .../source/scwx/qt/gl/draw/placefile_lines.hpp | 1 + .../scwx/qt/gl/draw/placefile_polygons.cpp | 8 ++++++++ .../scwx/qt/gl/draw/placefile_polygons.hpp | 1 + .../source/scwx/qt/gl/draw/placefile_text.cpp | 18 +++++++++++++++++- .../source/scwx/qt/gl/draw/placefile_text.hpp | 1 + .../scwx/qt/gl/draw/placefile_triangles.cpp | 8 ++++++++ .../scwx/qt/gl/draw/placefile_triangles.hpp | 1 + scwx-qt/source/scwx/qt/main/main_window.cpp | 9 +++++++++ scwx-qt/source/scwx/qt/map/placefile_layer.cpp | 17 +++++++++++++++++ 14 files changed, 89 insertions(+), 1 deletion(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp index 03a66c61..b9e68983 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp @@ -95,6 +95,8 @@ public: bool dirty_ {false}; bool thresholded_ {false}; + std::chrono::system_clock::time_point selectedTime_ {}; + std::mutex iconMutex_; boost::unordered_flat_map @@ -139,6 +141,12 @@ PlacefileIcons::~PlacefileIcons() = default; PlacefileIcons::PlacefileIcons(PlacefileIcons&&) noexcept = default; PlacefileIcons& PlacefileIcons::operator=(PlacefileIcons&&) noexcept = default; +void PlacefileIcons::set_selected_time( + std::chrono::system_clock::time_point selectedTime) +{ + p->selectedTime_ = selectedTime; +} + void PlacefileIcons::set_thresholded(bool thresholded) { p->thresholded_ = thresholded; diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp index cc6b632d..71bf684a 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp @@ -27,6 +27,7 @@ public: PlacefileIcons(PlacefileIcons&&) noexcept; PlacefileIcons& operator=(PlacefileIcons&&) noexcept; + void set_selected_time(std::chrono::system_clock::time_point selectedTime); void set_thresholded(bool thresholded); void Initialize() override; diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp index 019c2b21..0b55f8b1 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp @@ -77,6 +77,8 @@ public: bool dirty_ {false}; bool thresholded_ {false}; + std::chrono::system_clock::time_point selectedTime_ {}; + std::mutex imageMutex_; boost::unordered_flat_map @@ -117,6 +119,12 @@ PlacefileImages::PlacefileImages(PlacefileImages&&) noexcept = default; PlacefileImages& PlacefileImages::operator=(PlacefileImages&&) noexcept = default; +void PlacefileImages::set_selected_time( + std::chrono::system_clock::time_point selectedTime) +{ + p->selectedTime_ = selectedTime; +} + void PlacefileImages::set_thresholded(bool thresholded) { p->thresholded_ = thresholded; diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_images.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_images.hpp index 8048fc3a..d99c43a0 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_images.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_images.hpp @@ -25,6 +25,7 @@ public: PlacefileImages(PlacefileImages&&) noexcept; PlacefileImages& operator=(PlacefileImages&&) noexcept; + void set_selected_time(std::chrono::system_clock::time_point selectedTime); void set_thresholded(bool thresholded); void Initialize() override; diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp index 8041ff63..8b68009b 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp @@ -75,6 +75,8 @@ public: bool dirty_ {false}; bool thresholded_ {false}; + std::chrono::system_clock::time_point selectedTime_ {}; + std::mutex lineMutex_ {}; std::size_t currentNumLines_ {}; @@ -109,6 +111,12 @@ PlacefileLines::~PlacefileLines() = default; PlacefileLines::PlacefileLines(PlacefileLines&&) noexcept = default; PlacefileLines& PlacefileLines::operator=(PlacefileLines&&) noexcept = default; +void PlacefileLines::set_selected_time( + std::chrono::system_clock::time_point selectedTime) +{ + p->selectedTime_ = selectedTime; +} + void PlacefileLines::set_thresholded(bool thresholded) { p->thresholded_ = thresholded; diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp index 551b955c..7005933c 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp @@ -25,6 +25,7 @@ public: PlacefileLines(PlacefileLines&&) noexcept; PlacefileLines& operator=(PlacefileLines&&) noexcept; + void set_selected_time(std::chrono::system_clock::time_point selectedTime); void set_thresholded(bool thresholded); void Initialize() override; diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp index db1851ac..5fa0c6ac 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp @@ -91,6 +91,8 @@ public: bool dirty_ {false}; bool thresholded_ {false}; + std::chrono::system_clock::time_point selectedTime_ {}; + boost::container::stable_vector tessCombineBuffer_ {}; std::mutex bufferMutex_ {}; @@ -126,6 +128,12 @@ PlacefilePolygons::PlacefilePolygons(PlacefilePolygons&&) noexcept = default; PlacefilePolygons& PlacefilePolygons::operator=(PlacefilePolygons&&) noexcept = default; +void PlacefilePolygons::set_selected_time( + std::chrono::system_clock::time_point selectedTime) +{ + p->selectedTime_ = selectedTime; +} + void PlacefilePolygons::set_thresholded(bool thresholded) { p->thresholded_ = thresholded; diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.hpp index 75c21793..3c607b72 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.hpp @@ -27,6 +27,7 @@ public: PlacefilePolygons(PlacefilePolygons&&) noexcept; PlacefilePolygons& operator=(PlacefilePolygons&&) noexcept; + void set_selected_time(std::chrono::system_clock::time_point selectedTime); void set_thresholded(bool thresholded); void Initialize() override; diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp index 227af962..3df8bd38 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp @@ -46,6 +46,8 @@ public: bool thresholded_ {false}; + std::chrono::system_clock::time_point selectedTime_ {}; + std::uint32_t textId_ {}; glm::vec2 mapScreenCoordLocation_ {}; float mapScale_ {1.0f}; @@ -77,6 +79,12 @@ void PlacefileText::set_placefile_name(const std::string& placefileName) p->placefileName_ = placefileName; } +void PlacefileText::set_selected_time( + std::chrono::system_clock::time_point selectedTime) +{ + p->selectedTime_ = selectedTime; +} + void PlacefileText::set_thresholded(bool thresholded) { p->thresholded_ = thresholded; @@ -117,7 +125,15 @@ void PlacefileText::Impl::RenderTextDrawItem( const QMapLibreGL::CustomLayerRenderParameters& params, const std::shared_ptr& di) { - if (!thresholded_ || mapDistance_ <= di->threshold_) + // If no time has been selected, use the current time + std::chrono::system_clock::time_point selectedTime = + (selectedTime_ == std::chrono::system_clock::time_point {}) ? + std::chrono::system_clock::now() : + selectedTime_; + + if ((!thresholded_ || mapDistance_ <= di->threshold_) && + (di->startTime_ == std::chrono::system_clock::time_point {} || + (di->startTime_ <= selectedTime && selectedTime < di->endTime_))) { const auto screenCoordinates = (util::maplibre::LatLongToScreenCoordinate( {di->latitude_, di->longitude_}) - diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp index a4e51fec..db6af3b2 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp @@ -27,6 +27,7 @@ public: PlacefileText& operator=(PlacefileText&&) noexcept; void set_placefile_name(const std::string& placefileName); + void set_selected_time(std::chrono::system_clock::time_point selectedTime); void set_thresholded(bool thresholded); void Initialize() override; diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.cpp index 693e9cca..b0965a8c 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.cpp @@ -46,6 +46,8 @@ public: bool dirty_ {false}; bool thresholded_ {false}; + std::chrono::system_clock::time_point selectedTime_ {}; + std::mutex bufferMutex_ {}; std::vector currentBuffer_ {}; @@ -76,6 +78,12 @@ PlacefileTriangles::PlacefileTriangles(PlacefileTriangles&&) noexcept = default; PlacefileTriangles& PlacefileTriangles::operator=(PlacefileTriangles&&) noexcept = default; +void PlacefileTriangles::set_selected_time( + std::chrono::system_clock::time_point selectedTime) +{ + p->selectedTime_ = selectedTime; +} + void PlacefileTriangles::set_thresholded(bool thresholded) { p->thresholded_ = thresholded; diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.hpp index bb98316f..daacc120 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.hpp @@ -25,6 +25,7 @@ public: PlacefileTriangles(PlacefileTriangles&&) noexcept; PlacefileTriangles& operator=(PlacefileTriangles&&) noexcept; + void set_selected_time(std::chrono::system_clock::time_point selectedTime); void set_thresholded(bool thresholded); void Initialize() override; diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp index 11ae2877..c72db8a0 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -839,6 +839,15 @@ void MainWindowImpl::ConnectAnimationSignals() timelineManager_.get(), &manager::TimelineManager::AnimationStepEnd); + connect(timelineManager_.get(), + &manager::TimelineManager::SelectedTimeUpdated, + [this]() + { + for (auto map : maps_) + { + map->update(); + } + }); connect(timelineManager_.get(), &manager::TimelineManager::VolumeTimeUpdated, [this](std::chrono::system_clock::time_point dateTime) diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp index cc688a23..7e9303f4 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -58,6 +59,8 @@ public: std::shared_ptr placefilePolygons_; std::shared_ptr placefileTriangles_; std::shared_ptr placefileText_; + + std::chrono::system_clock::time_point selectedTime_ {}; }; PlacefileLayer::PlacefileLayer(const std::shared_ptr& context, @@ -80,6 +83,7 @@ PlacefileLayer::~PlacefileLayer() = default; void PlacefileLayer::Impl::ConnectSignals() { auto placefileManager = manager::PlacefileManager::Instance(); + auto timelineManager = manager::TimelineManager::Instance(); QObject::connect(placefileManager.get(), &manager::PlacefileManager::PlacefileUpdated, @@ -91,6 +95,12 @@ void PlacefileLayer::Impl::ConnectSignals() self_->ReloadData(); } }); + + QObject::connect(timelineManager.get(), + &manager::TimelineManager::SelectedTimeUpdated, + self_, + [this](std::chrono::system_clock::time_point dateTime) + { selectedTime_ = dateTime; }); } std::string PlacefileLayer::placefile_name() const @@ -137,6 +147,13 @@ void PlacefileLayer::Render( p->placefilePolygons_->set_thresholded(thresholded); p->placefileTriangles_->set_thresholded(thresholded); p->placefileText_->set_thresholded(thresholded); + + p->placefileIcons_->set_selected_time(p->selectedTime_); + p->placefileImages_->set_selected_time(p->selectedTime_); + p->placefileLines_->set_selected_time(p->selectedTime_); + p->placefilePolygons_->set_selected_time(p->selectedTime_); + p->placefileTriangles_->set_selected_time(p->selectedTime_); + p->placefileText_->set_selected_time(p->selectedTime_); } DrawLayer::Render(params); From e5d18ecc4ddbff0000c63e400425aed646feed52 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 4 Sep 2023 23:48:36 -0500 Subject: [PATCH 105/199] Update to actions/checkout@v4 Something broke. See actions/checkout#949, actions/checkout#1448 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b12ca16..b86a47e6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,7 +66,7 @@ jobs: run: git config --global core.longpaths true - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: source submodules: recursive From 1a4b0642144ebe20acc80a6c4a2e6aa9c502c76f Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 5 Sep 2023 23:32:22 -0500 Subject: [PATCH 106/199] Placefile polygon looping --- scwx-qt/gl/geo_texture2d.vert | 11 ++-- scwx-qt/gl/map_color.vert | 19 +++--- scwx-qt/gl/threshold.geom | 17 +++-- .../scwx/qt/gl/draw/placefile_polygons.cpp | 65 +++++++++++++++---- 4 files changed, 82 insertions(+), 30 deletions(-) diff --git a/scwx-qt/gl/geo_texture2d.vert b/scwx-qt/gl/geo_texture2d.vert index ee93a12a..7977af95 100644 --- a/scwx-qt/gl/geo_texture2d.vert +++ b/scwx-qt/gl/geo_texture2d.vert @@ -13,6 +13,7 @@ layout (location = 2) in vec3 aTexCoord; layout (location = 3) in vec4 aModulate; layout (location = 4) in float aAngleDeg; layout (location = 5) in int aThreshold; +layout (location = 6) in ivec2 aTimeRange; uniform mat4 uMVPMatrix; uniform mat4 uMapMatrix; @@ -20,9 +21,10 @@ uniform vec2 uMapScreenCoord; out VertexData { - int threshold; - vec3 texCoord; - vec4 color; + int threshold; + vec3 texCoord; + vec4 color; + ivec2 timeRange; } vsOut; smooth out vec3 texCoord; @@ -39,8 +41,9 @@ vec2 latLngToScreenCoordinate(in vec2 latLng) void main() { - // Pass the threshold to the geometry shader + // Pass the threshold and time range to the geometry shader vsOut.threshold = aThreshold; + vsOut.timeRange = aTimeRange; // Pass the texture coordinate and color modulate to the geometry and // fragment shaders diff --git a/scwx-qt/gl/map_color.vert b/scwx-qt/gl/map_color.vert index b2749a02..4319310f 100644 --- a/scwx-qt/gl/map_color.vert +++ b/scwx-qt/gl/map_color.vert @@ -1,9 +1,10 @@ #version 330 core -layout (location = 0) in vec2 aScreenCoord; -layout (location = 1) in vec2 aXYOffset; -layout (location = 2) in vec4 aColor; -layout (location = 3) in int aThreshold; +layout (location = 0) in vec2 aScreenCoord; +layout (location = 1) in vec2 aXYOffset; +layout (location = 2) in vec4 aColor; +layout (location = 3) in int aThreshold; +layout (location = 4) in ivec2 aTimeRange; uniform mat4 uMVPMatrix; uniform mat4 uMapMatrix; @@ -11,17 +12,19 @@ uniform vec2 uMapScreenCoord; out VertexData { - int threshold; - vec3 texCoord; - vec4 color; + int threshold; + vec3 texCoord; + vec4 color; + ivec2 timeRange; } vsOut; smooth out vec4 color; void main() { - // Pass the threshold and color to the geometry shader + // Pass the threshold and time range to the geometry shader vsOut.threshold = aThreshold; + vsOut.timeRange = aTimeRange; // Pass the color to the geometry and fragment shaders vsOut.color = aColor; diff --git a/scwx-qt/gl/threshold.geom b/scwx-qt/gl/threshold.geom index 1f64b368..dec09b01 100644 --- a/scwx-qt/gl/threshold.geom +++ b/scwx-qt/gl/threshold.geom @@ -4,12 +4,14 @@ layout (triangles) in; layout (triangle_strip, max_vertices = 3) out; uniform float uMapDistance; +uniform int uSelectedTime; in VertexData { - int threshold; - vec3 texCoord; - vec4 color; + int threshold; + vec3 texCoord; + vec4 color; + ivec2 timeRange; } gsIn[]; smooth out vec3 texCoord; @@ -17,9 +19,12 @@ smooth out vec4 color; void main() { - if (gsIn[0].threshold <= 0 || // If Threshold: 0 was specified, no threshold - gsIn[0].threshold >= uMapDistance || // If Threshold is above current map distance - gsIn[0].threshold >= 999) // If Threshold: 999 was specified (or greater), no threshold + if ((gsIn[0].threshold <= 0 || // If Threshold: 0 was specified, no threshold + gsIn[0].threshold >= uMapDistance || // If Threshold is above current map distance + gsIn[0].threshold >= 999) && // If Threshold: 999 was specified (or greater), no threshold + (gsIn[0].timeRange[0] == 0 || // If there is no start time specified + (gsIn[0].timeRange[0] <= uSelectedTime && // If the selected time is after the start time + uSelectedTime < gsIn[0].timeRange[1]))) // If the selected time is before the end time { gl_Position = gl_in[0].gl_Position; texCoord = gsIn[0].texCoord; diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp index 5fa0c6ac..24a96fe6 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp @@ -26,6 +26,9 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_); static constexpr std::size_t kVerticesPerTriangle = 3; static constexpr std::size_t kPointsPerVertex = 8; +// Threshold, start time, end time +static constexpr std::size_t kIntegersPerVertex_ = 3; + static constexpr std::size_t kTessVertexScreenX_ = 0; static constexpr std::size_t kTessVertexScreenY_ = 1; static constexpr std::size_t kTessVertexScreenZ_ = 2; @@ -49,6 +52,7 @@ public: uMapMatrixLocation_(GL_INVALID_INDEX), uMapScreenCoordLocation_(GL_INVALID_INDEX), uMapDistanceLocation_(GL_INVALID_INDEX), + uSelectedTimeLocation_(GL_INVALID_INDEX), vao_ {GL_INVALID_INDEX}, vbo_ {GL_INVALID_INDEX}, numVertices_ {0} @@ -97,9 +101,9 @@ public: std::mutex bufferMutex_ {}; std::vector currentBuffer_ {}; - std::vector currentThresholdBuffer_ {}; + std::vector currentIntegerBuffer_ {}; std::vector newBuffer_ {}; - std::vector newThresholdBuffer_ {}; + std::vector newIntegerBuffer_ {}; GLUtesselator* tessellator_; @@ -108,13 +112,16 @@ public: GLint uMapMatrixLocation_; GLint uMapScreenCoordLocation_; GLint uMapDistanceLocation_; + GLint uSelectedTimeLocation_; GLuint vao_; std::array vbo_; GLsizei numVertices_; - GLint currentThreshold_; + GLint currentThreshold_ {}; + GLint currentStartTime_ {}; + GLint currentEndTime_ {}; }; PlacefilePolygons::PlacefilePolygons( @@ -154,6 +161,8 @@ void PlacefilePolygons::Initialize() p->shaderProgram_->GetUniformLocation("uMapScreenCoord"); p->uMapDistanceLocation_ = p->shaderProgram_->GetUniformLocation("uMapDistance"); + p->uSelectedTimeLocation_ = + p->shaderProgram_->GetUniformLocation("uSelectedTime"); gl.glGenVertexArrays(1, &p->vao_); gl.glGenBuffers(2, p->vbo_.data()); @@ -196,10 +205,18 @@ void PlacefilePolygons::Initialize() gl.glVertexAttribIPointer(3, // 1, GL_INT, - 0, + kIntegersPerVertex_ * sizeof(GLint), static_cast(0)); gl.glEnableVertexAttribArray(3); + // aTimeRange + gl.glVertexAttribIPointer(4, // + 2, + GL_INT, + kIntegersPerVertex_ * sizeof(GLint), + reinterpret_cast(1 * sizeof(GLint))); + gl.glEnableVertexAttribArray(4); + p->dirty_ = true; } @@ -231,6 +248,17 @@ void PlacefilePolygons::Render( gl.glUniform1f(p->uMapDistanceLocation_, 0.0f); } + // Selected time + std::chrono::system_clock::time_point selectedTime = + (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? + std::chrono::system_clock::now() : + p->selectedTime_; + gl.glUniform1i( + p->uSelectedTimeLocation_, + static_cast(std::chrono::duration_cast( + selectedTime.time_since_epoch()) + .count())); + // Draw icons gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); } @@ -247,14 +275,14 @@ void PlacefilePolygons::Deinitialize() // Clear the current buffers p->currentBuffer_.clear(); - p->currentThresholdBuffer_.clear(); + p->currentIntegerBuffer_.clear(); } void PlacefilePolygons::StartPolygons() { // Clear the new buffers p->newBuffer_.clear(); - p->newThresholdBuffer_.clear(); + p->newIntegerBuffer_.clear(); } void PlacefilePolygons::AddPolygon( @@ -272,11 +300,11 @@ void PlacefilePolygons::FinishPolygons() // Swap buffers p->currentBuffer_.swap(p->newBuffer_); - p->currentThresholdBuffer_.swap(p->newThresholdBuffer_); + p->currentIntegerBuffer_.swap(p->newIntegerBuffer_); // Clear the new buffers p->newBuffer_.clear(); - p->newThresholdBuffer_.clear(); + p->newIntegerBuffer_.clear(); // Mark the draw item dirty p->dirty_ = true; @@ -300,8 +328,8 @@ void PlacefilePolygons::Impl::Update() // Buffer threshold data gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(GLint) * currentThresholdBuffer_.size(), - currentThresholdBuffer_.data(), + sizeof(GLint) * currentIntegerBuffer_.size(), + currentIntegerBuffer_.data(), GL_DYNAMIC_DRAW); numVertices_ = @@ -324,6 +352,16 @@ void PlacefilePolygons::Impl::Tessellate( units::length::nautical_miles threshold = di->threshold_; currentThreshold_ = static_cast(std::round(threshold.value())); + // Start and end time + currentStartTime_ = + static_cast(std::chrono::duration_cast( + di->startTime_.time_since_epoch()) + .count()); + currentEndTime_ = + static_cast(std::chrono::duration_cast( + di->endTime_.time_since_epoch()) + .count()); + gluTessBeginPolygon(tessellator_, this); for (auto& contour : di->contours_) @@ -370,7 +408,7 @@ void PlacefilePolygons::Impl::Tessellate( while (newBuffer_.size() % kVerticesPerTriangle != 0) { newBuffer_.pop_back(); - newThresholdBuffer_.pop_back(); + newIntegerBuffer_.pop_back(); } } @@ -431,7 +469,10 @@ void PlacefilePolygons::Impl::TessellateVertexCallback(void* vertexData, static_cast(data[kTessVertexG_]), static_cast(data[kTessVertexB_]), static_cast(data[kTessVertexA_])}); - self->newThresholdBuffer_.push_back(self->currentThreshold_); + self->newIntegerBuffer_.insert(self->newIntegerBuffer_.end(), + {self->currentThreshold_, + self->currentStartTime_, + self->currentEndTime_}); } void PlacefilePolygons::Impl::TessellateErrorCallback(GLenum errorCode) From 5c77bff2c63ee09816f0135e635043e6f966d256 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 5 Sep 2023 23:43:35 -0500 Subject: [PATCH 107/199] Placefile triangle looping --- .../scwx/qt/gl/draw/placefile_triangles.cpp | 59 +++++++++++++++---- 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.cpp index b0965a8c..dc0bc781 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.cpp @@ -19,6 +19,9 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_); static constexpr std::size_t kVerticesPerTriangle = 3; static constexpr std::size_t kPointsPerVertex = 8; +// Threshold, start time, end time +static constexpr std::size_t kIntegersPerVertex_ = 3; + class PlacefileTriangles::Impl { public: @@ -29,6 +32,7 @@ public: uMapMatrixLocation_(GL_INVALID_INDEX), uMapScreenCoordLocation_(GL_INVALID_INDEX), uMapDistanceLocation_(GL_INVALID_INDEX), + uSelectedTimeLocation_(GL_INVALID_INDEX), vao_ {GL_INVALID_INDEX}, vbo_ {GL_INVALID_INDEX}, numVertices_ {0} @@ -51,15 +55,16 @@ public: std::mutex bufferMutex_ {}; std::vector currentBuffer_ {}; - std::vector currentThresholdBuffer_ {}; + std::vector currentIntegerBuffer_ {}; std::vector newBuffer_ {}; - std::vector newThresholdBuffer_ {}; + std::vector newIntegerBuffer_ {}; std::shared_ptr shaderProgram_; GLint uMVPMatrixLocation_; GLint uMapMatrixLocation_; GLint uMapScreenCoordLocation_; GLint uMapDistanceLocation_; + GLint uSelectedTimeLocation_; GLuint vao_; std::array vbo_; @@ -104,6 +109,8 @@ void PlacefileTriangles::Initialize() p->shaderProgram_->GetUniformLocation("uMapScreenCoord"); p->uMapDistanceLocation_ = p->shaderProgram_->GetUniformLocation("uMapDistance"); + p->uSelectedTimeLocation_ = + p->shaderProgram_->GetUniformLocation("uSelectedTime"); gl.glGenVertexArrays(1, &p->vao_); gl.glGenBuffers(2, p->vbo_.data()); @@ -146,10 +153,18 @@ void PlacefileTriangles::Initialize() gl.glVertexAttribIPointer(3, // 1, GL_INT, - 0, + kIntegersPerVertex_ * sizeof(GLint), static_cast(0)); gl.glEnableVertexAttribArray(3); + // aTimeRange + gl.glVertexAttribIPointer(4, // + 2, + GL_INT, + kIntegersPerVertex_ * sizeof(GLint), + reinterpret_cast(1 * sizeof(GLint))); + gl.glEnableVertexAttribArray(4); + p->dirty_ = true; } @@ -181,6 +196,17 @@ void PlacefileTriangles::Render( gl.glUniform1f(p->uMapDistanceLocation_, 0.0f); } + // Selected time + std::chrono::system_clock::time_point selectedTime = + (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? + std::chrono::system_clock::now() : + p->selectedTime_; + gl.glUniform1i( + p->uSelectedTimeLocation_, + static_cast(std::chrono::duration_cast( + selectedTime.time_since_epoch()) + .count())); + // Draw icons gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); } @@ -197,14 +223,14 @@ void PlacefileTriangles::Deinitialize() // Clear the current buffers p->currentBuffer_.clear(); - p->currentThresholdBuffer_.clear(); + p->currentIntegerBuffer_.clear(); } void PlacefileTriangles::StartTriangles() { // Clear the new buffers p->newBuffer_.clear(); - p->newThresholdBuffer_.clear(); + p->newIntegerBuffer_.clear(); } void PlacefileTriangles::AddTriangles( @@ -222,11 +248,11 @@ void PlacefileTriangles::FinishTriangles() // Swap buffers p->currentBuffer_.swap(p->newBuffer_); - p->currentThresholdBuffer_.swap(p->newThresholdBuffer_); + p->currentIntegerBuffer_.swap(p->newIntegerBuffer_); // Clear the new buffers p->newBuffer_.clear(); - p->newThresholdBuffer_.clear(); + p->newIntegerBuffer_.clear(); // Mark the draw item dirty p->dirty_ = true; @@ -239,6 +265,16 @@ void PlacefileTriangles::Impl::UpdateBuffers( units::length::nautical_miles threshold = di->threshold_; GLint thresholdValue = static_cast(std::round(threshold.value())); + // Start and end time + GLint startTime = + static_cast(std::chrono::duration_cast( + di->startTime_.time_since_epoch()) + .count()); + GLint endTime = + static_cast(std::chrono::duration_cast( + di->endTime_.time_since_epoch()) + .count()); + // Default color to "Color" statement boost::gil::rgba8_pixel_t lastColor = di->color_; @@ -268,14 +304,15 @@ void PlacefileTriangles::Impl::UpdateBuffers( newBuffer_.insert( newBuffer_.end(), {screenCoordinate.x, screenCoordinate.y, x, y, r, g, b, a}); - newThresholdBuffer_.push_back(thresholdValue); + newIntegerBuffer_.insert(newIntegerBuffer_.end(), + {thresholdValue, startTime, endTime}); } // Remove extra vertices that don't correspond to a full triangle while (newBuffer_.size() % kVerticesPerTriangle != 0) { newBuffer_.pop_back(); - newThresholdBuffer_.pop_back(); + newIntegerBuffer_.pop_back(); } } @@ -297,8 +334,8 @@ void PlacefileTriangles::Impl::Update() // Buffer threshold data gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(GLint) * currentThresholdBuffer_.size(), - currentThresholdBuffer_.data(), + sizeof(GLint) * currentIntegerBuffer_.size(), + currentIntegerBuffer_.data(), GL_DYNAMIC_DRAW); numVertices_ = From d160eb6f9439d626c5dab681c6ee1cc69b4dbd64 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 5 Sep 2023 23:57:33 -0500 Subject: [PATCH 108/199] Placefile lines looping --- .../scwx/qt/gl/draw/placefile_lines.cpp | 123 ++++++++++++++---- 1 file changed, 100 insertions(+), 23 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp index 8b68009b..18f30db9 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp @@ -26,6 +26,9 @@ static constexpr std::size_t kPointsPerVertex = 9; static constexpr std::size_t kBufferLength = kNumTriangles * kVerticesPerTriangle * kPointsPerVertex; +// Threshold, start time, end time +static constexpr std::size_t kIntegersPerVertex_ = 3; + static const boost::gil::rgba8_pixel_t kBlack_ {0, 0, 0, 255}; class PlacefileLines::Impl @@ -50,6 +53,7 @@ public: uMapMatrixLocation_(GL_INVALID_INDEX), uMapScreenCoordLocation_(GL_INVALID_INDEX), uMapDistanceLocation_(GL_INVALID_INDEX), + uSelectedTimeLocation_(GL_INVALID_INDEX), vao_ {GL_INVALID_INDEX}, vbo_ {GL_INVALID_INDEX}, numVertices_ {0} @@ -65,6 +69,8 @@ public: const units::angle::degrees angle, const boost::gil::rgba8_pixel_t color, const GLint threshold, + const GLint startTime, + const GLint endTime, bool bufferHover = false); void UpdateBuffers(const std::shared_ptr& di); @@ -83,9 +89,9 @@ public: std::size_t newNumLines_ {}; std::vector currentLinesBuffer_ {}; - std::vector currentThresholdBuffer_ {}; + std::vector currentIntegerBuffer_ {}; std::vector newLinesBuffer_ {}; - std::vector newThresholdBuffer_ {}; + std::vector newIntegerBuffer_ {}; std::vector currentHoverLines_ {}; std::vector newHoverLines_ {}; @@ -95,6 +101,7 @@ public: GLint uMapMatrixLocation_; GLint uMapScreenCoordLocation_; GLint uMapDistanceLocation_; + GLint uSelectedTimeLocation_; GLuint vao_; std::array vbo_; @@ -137,6 +144,8 @@ void PlacefileLines::Initialize() p->shaderProgram_->GetUniformLocation("uMapScreenCoord"); p->uMapDistanceLocation_ = p->shaderProgram_->GetUniformLocation("uMapDistance"); + p->uSelectedTimeLocation_ = + p->shaderProgram_->GetUniformLocation("uSelectedTime"); gl.glGenVertexArrays(1, &p->vao_); gl.glGenBuffers(2, p->vbo_.data()); @@ -188,10 +197,18 @@ void PlacefileLines::Initialize() gl.glVertexAttribIPointer(5, // 1, GL_INT, - 0, + kIntegersPerVertex_ * sizeof(GLint), static_cast(0)); gl.glEnableVertexAttribArray(5); + // aTimeRange + gl.glVertexAttribIPointer(6, // + 2, + GL_INT, + kIntegersPerVertex_ * sizeof(GLint), + reinterpret_cast(1 * sizeof(GLint))); + gl.glEnableVertexAttribArray(6); + p->dirty_ = true; } @@ -225,6 +242,17 @@ void PlacefileLines::Render( gl.glUniform1f(p->uMapDistanceLocation_, 0.0f); } + // Selected time + std::chrono::system_clock::time_point selectedTime = + (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? + std::chrono::system_clock::now() : + p->selectedTime_; + gl.glUniform1i( + p->uSelectedTimeLocation_, + static_cast(std::chrono::duration_cast( + selectedTime.time_since_epoch()) + .count())); + // Draw icons gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); } @@ -240,7 +268,7 @@ void PlacefileLines::Deinitialize() std::unique_lock lock {p->lineMutex_}; p->currentLinesBuffer_.clear(); - p->currentThresholdBuffer_.clear(); + p->currentIntegerBuffer_.clear(); p->currentHoverLines_.clear(); } @@ -248,7 +276,7 @@ void PlacefileLines::StartLines() { // Clear the new buffers p->newLinesBuffer_.clear(); - p->newThresholdBuffer_.clear(); + p->newIntegerBuffer_.clear(); p->newHoverLines_.clear(); p->newNumLines_ = 0u; @@ -270,12 +298,12 @@ void PlacefileLines::FinishLines() // Swap buffers p->currentLinesBuffer_.swap(p->newLinesBuffer_); - p->currentThresholdBuffer_.swap(p->newThresholdBuffer_); + p->currentIntegerBuffer_.swap(p->newIntegerBuffer_); p->currentHoverLines_.swap(p->newHoverLines_); // Clear the new buffers p->newLinesBuffer_.clear(); - p->newThresholdBuffer_.clear(); + p->newIntegerBuffer_.clear(); p->newHoverLines_.clear(); // Update the number of lines @@ -294,6 +322,16 @@ void PlacefileLines::Impl::UpdateBuffers( units::length::nautical_miles threshold = di->threshold_; GLint thresholdValue = static_cast(std::round(threshold.value())); + // Start and end time + GLint startTime = + static_cast(std::chrono::duration_cast( + di->startTime_.time_since_epoch()) + .count()); + GLint endTime = + static_cast(std::chrono::duration_cast( + di->endTime_.time_since_epoch()) + .count()); + std::vector> angles {}; angles.reserve(di->elements_.size() - 1); @@ -319,6 +357,8 @@ void PlacefileLines::Impl::UpdateBuffers( angle, kBlack_, thresholdValue, + startTime, + endTime, true); } @@ -331,7 +371,9 @@ void PlacefileLines::Impl::UpdateBuffers( di->width_, angles[i], di->color_, - thresholdValue); + thresholdValue, + startTime, + endTime); } } @@ -343,6 +385,8 @@ void PlacefileLines::Impl::BufferLine( const units::angle::degrees angle, const boost::gil::rgba8_pixel_t color, const GLint threshold, + const GLint startTime, + const GLint endTime, bool bufferHover) { // Latitude and longitude coordinates in degrees @@ -384,9 +428,25 @@ void PlacefileLines::Impl::BufferLine( lat2, lon2, rx, ty, mc0, mc1, mc2, mc3, a, // TR lat2, lon2, lx, ty, mc0, mc1, mc2, mc3, a // TL }); - newThresholdBuffer_.insert( - newThresholdBuffer_.end(), - {threshold, threshold, threshold, threshold, threshold, threshold}); + newIntegerBuffer_.insert(newIntegerBuffer_.end(), + {threshold, + startTime, + endTime, + threshold, + startTime, + endTime, + threshold, + startTime, + endTime, + threshold, + startTime, + endTime, + threshold, + startTime, + endTime, + threshold, + startTime, + endTime}); if (bufferHover && !di->hoverText_.empty()) { @@ -427,8 +487,8 @@ void PlacefileLines::Impl::Update() // Buffer threshold data gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(GLint) * currentThresholdBuffer_.size(), - currentThresholdBuffer_.data(), + sizeof(GLint) * currentIntegerBuffer_.size(), + currentIntegerBuffer_.data(), GL_DYNAMIC_DRAW); } @@ -458,24 +518,41 @@ bool PlacefileLines::RunMousePicking( (p->thresholded_) ? util::maplibre::GetMapDistance(params) : units::length::meters {0.0}; + // If no time has been selected, use the current time + std::chrono::system_clock::time_point selectedTime = + (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? + std::chrono::system_clock::now() : + p->selectedTime_; + // For each pickable line auto it = std::find_if( std::execution::par_unseq, p->currentHoverLines_.crbegin(), p->currentHoverLines_.crend(), - [&mapDistance, &mapMatrix, &mousePos](const auto& line) + [&mapDistance, &selectedTime, &mapMatrix, &mousePos](const auto& line) { - if ( - // Placefile is thresholded - mapDistance > units::length::meters {0.0} && + if (( + // Placefile is thresholded + mapDistance > units::length::meters {0.0} && - // Placefile threshold is < 999 nmi - static_cast(std::round( - units::length::nautical_miles {line.di_->threshold_} - .value())) < 999 && + // Placefile threshold is < 999 nmi + static_cast(std::round( + units::length::nautical_miles {line.di_->threshold_} + .value())) < 999 && - // Map distance is beyond the threshold - line.di_->threshold_ < mapDistance) + // Map distance is beyond the threshold + line.di_->threshold_ < mapDistance) || + + ( + // Line has a start time + line.di_->startTime_ != + std::chrono::system_clock::time_point {} && + + // The time range has not yet started + (selectedTime < line.di_->startTime_ || + + // The time range has ended + line.di_->endTime_ <= selectedTime))) { // Line is not pickable return false; From 4feff1cb6ee1b6f9bc3877fbb2a825075acc48ad Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 6 Sep 2023 21:51:37 -0500 Subject: [PATCH 109/199] Placefile icons looping --- .../scwx/qt/gl/draw/placefile_icons.cpp | 120 ++++++++++++++---- 1 file changed, 93 insertions(+), 27 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp index b9e68983..150a8985 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp @@ -32,6 +32,9 @@ static constexpr std::size_t kIconBufferLength = static constexpr std::size_t kTextureBufferLength = kNumTriangles * kVerticesPerTriangle * kPointsPerTexCoord; +// Threshold, start time, end time +static constexpr std::size_t kIntegersPerVertex_ = 3; + struct PlacefileIconInfo { PlacefileIconInfo( @@ -78,6 +81,7 @@ public: uMapMatrixLocation_(GL_INVALID_INDEX), uMapScreenCoordLocation_(GL_INVALID_INDEX), uMapDistanceLocation_(GL_INVALID_INDEX), + uSelectedTimeLocation_(GL_INVALID_INDEX), vao_ {GL_INVALID_INDEX}, vbo_ {GL_INVALID_INDEX}, numVertices_ {0} @@ -111,9 +115,9 @@ public: newValidIconList_ {}; std::vector currentIconBuffer_ {}; - std::vector currentThresholdBuffer_ {}; + std::vector currentIntegerBuffer_ {}; std::vector newIconBuffer_ {}; - std::vector newThresholdBuffer_ {}; + std::vector newIntegerBuffer_ {}; std::vector textureBuffer_ {}; @@ -125,6 +129,7 @@ public: GLint uMapMatrixLocation_; GLint uMapScreenCoordLocation_; GLint uMapDistanceLocation_; + GLint uSelectedTimeLocation_; GLuint vao_; std::array vbo_; @@ -167,6 +172,8 @@ void PlacefileIcons::Initialize() p->shaderProgram_->GetUniformLocation("uMapScreenCoord"); p->uMapDistanceLocation_ = p->shaderProgram_->GetUniformLocation("uMapDistance"); + p->uSelectedTimeLocation_ = + p->shaderProgram_->GetUniformLocation("uSelectedTime"); gl.glGenVertexArrays(1, &p->vao_); gl.glGenBuffers(static_cast(p->vbo_.size()), p->vbo_.data()); @@ -234,6 +241,14 @@ void PlacefileIcons::Initialize() static_cast(0)); gl.glEnableVertexAttribArray(5); + // aTimeRange + gl.glVertexAttribIPointer(6, // + 2, + GL_INT, + kIntegersPerVertex_ * sizeof(GLint), + reinterpret_cast(1 * sizeof(GLint))); + gl.glEnableVertexAttribArray(6); + p->dirty_ = true; } @@ -268,6 +283,17 @@ void PlacefileIcons::Render( gl.glUniform1f(p->uMapDistanceLocation_, 0.0f); } + // Selected time + std::chrono::system_clock::time_point selectedTime = + (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? + std::chrono::system_clock::now() : + p->selectedTime_; + gl.glUniform1i( + p->uSelectedTimeLocation_, + static_cast(std::chrono::duration_cast( + selectedTime.time_since_epoch()) + .count())); + // Interpolate texture coordinates gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); @@ -290,7 +316,7 @@ void PlacefileIcons::Deinitialize() p->currentIconFiles_.clear(); p->currentHoverIcons_.clear(); p->currentIconBuffer_.clear(); - p->currentThresholdBuffer_.clear(); + p->currentIntegerBuffer_.clear(); p->textureBuffer_.clear(); } @@ -332,7 +358,7 @@ void PlacefileIcons::StartIcons() p->newValidIconList_.clear(); p->newIconFiles_.clear(); p->newIconBuffer_.clear(); - p->newThresholdBuffer_.clear(); + p->newIntegerBuffer_.clear(); p->newHoverIcons_.clear(); } @@ -376,7 +402,7 @@ void PlacefileIcons::FinishIcons() p->currentIconList_.swap(p->newValidIconList_); p->currentIconFiles_.swap(p->newIconFiles_); p->currentIconBuffer_.swap(p->newIconBuffer_); - p->currentThresholdBuffer_.swap(p->newThresholdBuffer_); + p->currentIntegerBuffer_.swap(p->newIntegerBuffer_); p->currentHoverIcons_.swap(p->newHoverIcons_); // Clear the new buffers @@ -384,7 +410,7 @@ void PlacefileIcons::FinishIcons() p->newValidIconList_.clear(); p->newIconFiles_.clear(); p->newIconBuffer_.clear(); - p->newThresholdBuffer_.clear(); + p->newIntegerBuffer_.clear(); p->newHoverIcons_.clear(); // Mark the draw item dirty @@ -395,8 +421,9 @@ void PlacefileIcons::Impl::UpdateBuffers() { newIconBuffer_.clear(); newIconBuffer_.reserve(newIconList_.size() * kIconBufferLength); - newThresholdBuffer_.clear(); - newThresholdBuffer_.reserve(newIconList_.size() * kVerticesPerRectangle); + newIntegerBuffer_.clear(); + newIntegerBuffer_.reserve(newIconList_.size() * kVerticesPerRectangle * + kIntegersPerVertex_); for (auto& di : newIconList_) { @@ -425,6 +452,16 @@ void PlacefileIcons::Impl::UpdateBuffers() units::length::nautical_miles threshold = di->threshold_; GLint thresholdValue = static_cast(std::round(threshold.value())); + // Start and end time + GLint startTime = + static_cast(std::chrono::duration_cast( + di->startTime_.time_since_epoch()) + .count()); + GLint endTime = + static_cast(std::chrono::duration_cast( + di->endTime_.time_since_epoch()) + .count()); + // Latitude and longitude coordinates in degrees const float lat = static_cast(di->latitude_); const float lon = static_cast(di->longitude_); @@ -467,13 +504,25 @@ void PlacefileIcons::Impl::UpdateBuffers() lat, lon, rx, ty, mc0, mc1, mc2, mc3, a, // TR lat, lon, lx, ty, mc0, mc1, mc2, mc3, a // TL }); - newThresholdBuffer_.insert(newThresholdBuffer_.end(), - {thresholdValue, // - thresholdValue, - thresholdValue, - thresholdValue, - thresholdValue, - thresholdValue}); + newIntegerBuffer_.insert(newIntegerBuffer_.end(), + {thresholdValue, + startTime, + endTime, + thresholdValue, + startTime, + endTime, + thresholdValue, + startTime, + endTime, + thresholdValue, + startTime, + endTime, + thresholdValue, + startTime, + endTime, + thresholdValue, + startTime, + endTime}); if (!di->hoverText_.empty()) { @@ -622,8 +671,8 @@ void PlacefileIcons::Impl::Update(bool textureAtlasChanged) // Buffer threshold data gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[2]); gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(GLint) * currentThresholdBuffer_.size(), - currentThresholdBuffer_.data(), + sizeof(GLint) * currentIntegerBuffer_.size(), + currentIntegerBuffer_.data(), GL_DYNAMIC_DRAW); numVertices_ = static_cast(currentIconBuffer_.size() / @@ -656,24 +705,41 @@ bool PlacefileIcons::RunMousePicking( (p->thresholded_) ? util::maplibre::GetMapDistance(params) : units::length::meters {0.0}; + // If no time has been selected, use the current time + std::chrono::system_clock::time_point selectedTime = + (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? + std::chrono::system_clock::now() : + p->selectedTime_; + // For each pickable icon auto it = std::find_if( std::execution::par_unseq, p->currentHoverIcons_.crbegin(), p->currentHoverIcons_.crend(), - [&mapDistance, &mapMatrix, &mousePos](const auto& icon) + [&mapDistance, &selectedTime, &mapMatrix, &mousePos](const auto& icon) { - if ( - // Placefile is thresholded - mapDistance > units::length::meters {0.0} && + if (( + // Placefile is thresholded + mapDistance > units::length::meters {0.0} && - // Placefile threshold is < 999 nmi - static_cast(std::round( - units::length::nautical_miles {icon.di_->threshold_} - .value())) < 999 && + // Placefile threshold is < 999 nmi + static_cast(std::round( + units::length::nautical_miles {icon.di_->threshold_} + .value())) < 999 && - // Map distance is beyond the threshold - icon.di_->threshold_ < mapDistance) + // Map distance is beyond the threshold + icon.di_->threshold_ < mapDistance) || + + ( + // Line has a start time + icon.di_->startTime_ != + std::chrono::system_clock::time_point {} && + + // The time range has not yet started + (selectedTime < icon.di_->startTime_ || + + // The time range has ended + icon.di_->endTime_ <= selectedTime))) { // Icon is not pickable return false; From f158c4174c5d7593948e3188af5f924ee6648059 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 6 Sep 2023 22:00:52 -0500 Subject: [PATCH 110/199] Placefile images looping --- .../scwx/qt/gl/draw/placefile_images.cpp | 63 +++++++++++++++---- 1 file changed, 50 insertions(+), 13 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp index 0b55f8b1..f7ba1737 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp @@ -29,6 +29,9 @@ static constexpr std::size_t kImageBufferLength = static constexpr std::size_t kTextureBufferLength = kNumTriangles * kVerticesPerTriangle * kPointsPerTexCoord; +// Threshold, start time, end time +static constexpr std::size_t kIntegersPerVertex_ = 3; + struct PlacefileImageInfo { PlacefileImageInfo(const std::string& imageFile, @@ -58,6 +61,7 @@ public: uMapMatrixLocation_(GL_INVALID_INDEX), uMapScreenCoordLocation_(GL_INVALID_INDEX), uMapDistanceLocation_(GL_INVALID_INDEX), + uSelectedTimeLocation_(GL_INVALID_INDEX), vao_ {GL_INVALID_INDEX}, vbo_ {GL_INVALID_INDEX}, numVertices_ {0} @@ -91,9 +95,9 @@ public: newImageList_ {}; std::vector currentImageBuffer_ {}; - std::vector currentThresholdBuffer_ {}; + std::vector currentIntegerBuffer_ {}; std::vector newImageBuffer_ {}; - std::vector newThresholdBuffer_ {}; + std::vector newIntegerBuffer_ {}; std::vector textureBuffer_ {}; @@ -102,6 +106,7 @@ public: GLint uMapMatrixLocation_; GLint uMapScreenCoordLocation_; GLint uMapDistanceLocation_; + GLint uSelectedTimeLocation_; GLuint vao_; std::array vbo_; @@ -145,6 +150,8 @@ void PlacefileImages::Initialize() p->shaderProgram_->GetUniformLocation("uMapScreenCoord"); p->uMapDistanceLocation_ = p->shaderProgram_->GetUniformLocation("uMapDistance"); + p->uSelectedTimeLocation_ = + p->shaderProgram_->GetUniformLocation("uSelectedTime"); gl.glGenVertexArrays(1, &p->vao_); gl.glGenBuffers(static_cast(p->vbo_.size()), p->vbo_.data()); @@ -199,10 +206,18 @@ void PlacefileImages::Initialize() gl.glVertexAttribIPointer(5, // 1, GL_INT, - 0, + kIntegersPerVertex_ * sizeof(GLint), static_cast(0)); gl.glEnableVertexAttribArray(5); + // aTimeRange + gl.glVertexAttribIPointer(6, // + 2, + GL_INT, + kIntegersPerVertex_ * sizeof(GLint), + reinterpret_cast(1 * sizeof(GLint))); + gl.glEnableVertexAttribArray(6); + p->dirty_ = true; } @@ -237,6 +252,17 @@ void PlacefileImages::Render( gl.glUniform1f(p->uMapDistanceLocation_, 0.0f); } + // Selected time + std::chrono::system_clock::time_point selectedTime = + (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? + std::chrono::system_clock::now() : + p->selectedTime_; + gl.glUniform1i( + p->uSelectedTimeLocation_, + static_cast(std::chrono::duration_cast( + selectedTime.time_since_epoch()) + .count())); + // Interpolate texture coordinates gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); @@ -258,7 +284,7 @@ void PlacefileImages::Deinitialize() p->currentImageList_.clear(); p->currentImageFiles_.clear(); p->currentImageBuffer_.clear(); - p->currentThresholdBuffer_.clear(); + p->currentIntegerBuffer_.clear(); p->textureBuffer_.clear(); } @@ -278,7 +304,7 @@ void PlacefileImages::StartImages(const std::string& baseUrl) p->newImageList_.clear(); p->newImageFiles_.clear(); p->newImageBuffer_.clear(); - p->newThresholdBuffer_.clear(); + p->newIntegerBuffer_.clear(); } void PlacefileImages::AddImage( @@ -301,13 +327,13 @@ void PlacefileImages::FinishImages() p->currentImageList_.swap(p->newImageList_); p->currentImageFiles_.swap(p->newImageFiles_); p->currentImageBuffer_.swap(p->newImageBuffer_); - p->currentThresholdBuffer_.swap(p->newThresholdBuffer_); + p->currentIntegerBuffer_.swap(p->newIntegerBuffer_); // Clear the new buffers p->newImageList_.clear(); p->newImageFiles_.clear(); p->newImageBuffer_.clear(); - p->newThresholdBuffer_.clear(); + p->newIntegerBuffer_.clear(); // Mark the draw item dirty p->dirty_ = true; @@ -317,8 +343,9 @@ void PlacefileImages::Impl::UpdateBuffers() { newImageBuffer_.clear(); newImageBuffer_.reserve(newImageList_.size() * kImageBufferLength); - newThresholdBuffer_.clear(); - newThresholdBuffer_.reserve(newImageList_.size() * kVerticesPerRectangle); + newIntegerBuffer_.clear(); + newIntegerBuffer_.reserve(newImageList_.size() * kVerticesPerRectangle * + kIntegersPerVertex_); newImageFiles_.clear(); // Fixed modulate color @@ -339,6 +366,16 @@ void PlacefileImages::Impl::UpdateBuffers() units::length::nautical_miles threshold = di->threshold_; GLint thresholdValue = static_cast(std::round(threshold.value())); + // Start and end time + GLint startTime = + static_cast(std::chrono::duration_cast( + di->startTime_.time_since_epoch()) + .count()); + GLint endTime = + static_cast(std::chrono::duration_cast( + di->endTime_.time_since_epoch()) + .count()); + // Limit processing to groups of 3 (triangles) std::size_t numElements = di->elements_.size() - di->elements_.size() % 3; for (std::size_t i = 0; i < numElements; ++i) @@ -355,8 +392,8 @@ void PlacefileImages::Impl::UpdateBuffers() newImageBuffer_.insert(newImageBuffer_.end(), {lat, lon, x, y, mc0, mc1, mc2, mc3}); - newThresholdBuffer_.insert(newThresholdBuffer_.end(), - {thresholdValue}); + newIntegerBuffer_.insert(newIntegerBuffer_.end(), + {thresholdValue, startTime, endTime}); } } } @@ -431,8 +468,8 @@ void PlacefileImages::Impl::Update(bool textureAtlasChanged) // Buffer threshold data gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[2]); gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(GLint) * currentThresholdBuffer_.size(), - currentThresholdBuffer_.data(), + sizeof(GLint) * currentIntegerBuffer_.size(), + currentIntegerBuffer_.data(), GL_DYNAMIC_DRAW); numVertices_ = static_cast(currentImageBuffer_.size() / From 9ebc859756426288fd27382a9ad93cf34689cc9e Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 8 Sep 2023 22:57:30 -0500 Subject: [PATCH 111/199] Add cursor position (screen coordinates) to mouse picking --- scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp | 4 +++- scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp | 8 ++++++-- scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp | 8 +++++--- scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp | 4 +++- scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp | 8 +++++--- scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp | 4 +++- scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp | 4 +++- scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp | 4 +++- scwx-qt/source/scwx/qt/map/draw_layer.cpp | 7 +++++-- scwx-qt/source/scwx/qt/map/draw_layer.hpp | 4 +++- scwx-qt/source/scwx/qt/map/generic_layer.cpp | 2 ++ scwx-qt/source/scwx/qt/map/generic_layer.hpp | 8 ++++++-- scwx-qt/source/scwx/qt/map/map_widget.cpp | 13 ++++++++----- 13 files changed, 55 insertions(+), 23 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp b/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp index e1e45dee..21dab7b0 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp @@ -55,7 +55,9 @@ void DrawItem::Render(const QMapLibreGL::CustomLayerRenderParameters& params, bool DrawItem::RunMousePicking( const QMapLibreGL::CustomLayerRenderParameters& /* params */, - const glm::vec2& /* mousePos */) + const QPointF& /* mouseLocalPos */, + const QPointF& /* mouseGlobalPos */, + const glm::vec2& /* mouseCoords */) { // By default, the draw item is not picked return false; diff --git a/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp b/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp index 50704506..b4d7d4ce 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp @@ -38,13 +38,17 @@ public: * @brief Run mouse picking on the draw item. * * @param [in] params Custom layer render parameters - * @param [in] mousePos Mouse cursor location in map screen coordinates + * @param [in] mouseLocalPos Mouse cursor widget position + * @param [in] mouseGlobalPos Mouse cursor screen position + * @param [in] mouseCoords Mouse cursor location in map screen coordinates * * @return true if the draw item was picked, otherwise false */ virtual bool RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params, - const glm::vec2& mousePos); + const QPointF& mouseLocalPos, + const QPointF& mouseGlobalPos, + const glm::vec2& mouseCoords); protected: void diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp index 150a8985..937c2e43 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp @@ -684,7 +684,9 @@ void PlacefileIcons::Impl::Update(bool textureAtlasChanged) bool PlacefileIcons::RunMousePicking( const QMapLibreGL::CustomLayerRenderParameters& params, - const glm::vec2& mousePos) + const QPointF& /* mouseLocalPos */, + const QPointF& /* mouseGlobalPos */, + const glm::vec2& mouseCoords) { std::unique_lock lock {p->iconMutex_}; @@ -716,7 +718,7 @@ bool PlacefileIcons::RunMousePicking( std::execution::par_unseq, p->currentHoverIcons_.crbegin(), p->currentHoverIcons_.crend(), - [&mapDistance, &selectedTime, &mapMatrix, &mousePos](const auto& icon) + [&mapDistance, &selectedTime, &mapMatrix, &mouseCoords](const auto& icon) { if (( // Placefile is thresholded @@ -766,7 +768,7 @@ bool PlacefileIcons::RunMousePicking( tr += otr; // Test point against polygon bounds - return util::maplibre::IsPointInPolygon({tl, bl, br, tr}, mousePos); + return util::maplibre::IsPointInPolygon({tl, bl, br, tr}, mouseCoords); }); if (it != p->currentHoverIcons_.crend()) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp index 71bf684a..16ac08cd 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp @@ -36,7 +36,9 @@ public: void Deinitialize() override; bool RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params, - const glm::vec2& mousePos) override; + const QPointF& mouseLocalPos, + const QPointF& mouseGlobalPos, + const glm::vec2& mouseCoords) override; /** * Resets and prepares the draw item for adding a new set of icons. diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp index 18f30db9..e1a16481 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp @@ -497,7 +497,9 @@ void PlacefileLines::Impl::Update() bool PlacefileLines::RunMousePicking( const QMapLibreGL::CustomLayerRenderParameters& params, - const glm::vec2& mousePos) + const QPointF& /* mouseLocalPos */, + const QPointF& /* mouseGlobalPos */, + const glm::vec2& mouseCoords) { std::unique_lock lock {p->lineMutex_}; @@ -529,7 +531,7 @@ bool PlacefileLines::RunMousePicking( std::execution::par_unseq, p->currentHoverLines_.crbegin(), p->currentHoverLines_.crend(), - [&mapDistance, &selectedTime, &mapMatrix, &mousePos](const auto& line) + [&mapDistance, &selectedTime, &mapMatrix, &mouseCoords](const auto& line) { if (( // Placefile is thresholded @@ -581,7 +583,7 @@ bool PlacefileLines::RunMousePicking( // TODO: X/Y offsets // Test point against polygon bounds - return util::maplibre::IsPointInPolygon({tl, bl, br, tr}, mousePos); + return util::maplibre::IsPointInPolygon({tl, bl, br, tr}, mouseCoords); }); if (it != p->currentHoverLines_.crend()) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp index 7005933c..c4f9c7ad 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp @@ -33,7 +33,9 @@ public: void Deinitialize() override; bool RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params, - const glm::vec2& mousePos) override; + const QPointF& mouseLocalPos, + const QPointF& mouseGlobalPos, + const glm::vec2& mouseCoords) override; /** * Resets and prepares the draw item for adding a new set of lines. diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp index 3df8bd38..d8340575 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp @@ -209,7 +209,9 @@ void PlacefileText::Deinitialize() bool PlacefileText::RunMousePicking( const QMapLibreGL::CustomLayerRenderParameters& /* params */, - const glm::vec2& /* mousePos */) + const QPointF& /* mouseLocalPos */, + const QPointF& /* mouseGlobalPos */, + const glm::vec2& /* mouseCoords */) { bool itemPicked = false; diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp index db6af3b2..979a802a 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp @@ -35,7 +35,9 @@ public: void Deinitialize() override; bool RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params, - const glm::vec2& mousePos) override; + const QPointF& mouseLocalPos, + const QPointF& mouseGlobalPos, + const glm::vec2& mouseCoords) override; /** * Resets and prepares the draw item for adding a new set of text. diff --git a/scwx-qt/source/scwx/qt/map/draw_layer.cpp b/scwx-qt/source/scwx/qt/map/draw_layer.cpp index 4ecf2fb3..1cbcd849 100644 --- a/scwx-qt/source/scwx/qt/map/draw_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/draw_layer.cpp @@ -78,7 +78,9 @@ void DrawLayer::Deinitialize() bool DrawLayer::RunMousePicking( const QMapLibreGL::CustomLayerRenderParameters& params, - const glm::vec2& mousePos) + const QPointF& mouseLocalPos, + const QPointF& mouseGlobalPos, + const glm::vec2& mouseCoords) { bool itemPicked = false; @@ -86,7 +88,8 @@ bool DrawLayer::RunMousePicking( for (auto it = p->drawList_.rbegin(); it != p->drawList_.rend(); ++it) { // Run mouse picking on each draw item - if ((*it)->RunMousePicking(params, mousePos)) + if ((*it)->RunMousePicking( + params, mouseLocalPos, mouseGlobalPos, mouseCoords)) { // If a draw item was picked, don't process additional items itemPicked = true; diff --git a/scwx-qt/source/scwx/qt/map/draw_layer.hpp b/scwx-qt/source/scwx/qt/map/draw_layer.hpp index 2924a408..912657fa 100644 --- a/scwx-qt/source/scwx/qt/map/draw_layer.hpp +++ b/scwx-qt/source/scwx/qt/map/draw_layer.hpp @@ -25,7 +25,9 @@ public: virtual bool RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params, - const glm::vec2& mousePos) override; + const QPointF& mouseLocalPos, + const QPointF& mouseGlobalPos, + const glm::vec2& mouseCoords) override; protected: void AddDrawItem(const std::shared_ptr& drawItem); diff --git a/scwx-qt/source/scwx/qt/map/generic_layer.cpp b/scwx-qt/source/scwx/qt/map/generic_layer.cpp index ce8673a4..2c7ae6f2 100644 --- a/scwx-qt/source/scwx/qt/map/generic_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/generic_layer.cpp @@ -28,6 +28,8 @@ GenericLayer::~GenericLayer() = default; bool GenericLayer::RunMousePicking( const QMapLibreGL::CustomLayerRenderParameters& /* params */, + const QPointF& /* mouseLocalPos */, + const QPointF& /* mouseGlobalPos */, const glm::vec2& /* mousePos */) { // By default, the layer has nothing to pick diff --git a/scwx-qt/source/scwx/qt/map/generic_layer.hpp b/scwx-qt/source/scwx/qt/map/generic_layer.hpp index 48eb9ca0..239d271b 100644 --- a/scwx-qt/source/scwx/qt/map/generic_layer.hpp +++ b/scwx-qt/source/scwx/qt/map/generic_layer.hpp @@ -33,13 +33,17 @@ public: * @brief Run mouse picking on the layer. * * @param [in] params Custom layer render parameters - * @param [in] mousePos Mouse cursor location in map screen coordinates + * @param [in] mouseLocalPos Mouse cursor widget position + * @param [in] mouseGlobalPos Mouse cursor screen position + * @param [in] mouseCoords Mouse cursor location in map screen coordinates * * @return true if a draw item was picked, otherwise false */ virtual bool RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params, - const glm::vec2& mousePos); + const QPointF& mouseLocalPos, + const QPointF& mouseGlobalPos, + const glm::vec2& mouseCoords); protected: std::shared_ptr context() const; diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index 9d49bae8..6b0b46a0 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -70,7 +70,6 @@ public: autoRefreshEnabled_ {true}, autoUpdateEnabled_ {true}, selectedLevel2Product_ {common::Level2Product::Unknown}, - lastPos_(), currentStyleIndex_ {0}, currentStyle_ {nullptr}, frameDraws_(0), @@ -174,7 +173,8 @@ public: common::Level2Product selectedLevel2Product_; bool hasMouse_ {false}; - QPointF lastPos_; + QPointF lastPos_ {}; + QPointF lastGlobalPos_ {}; std::size_t currentStyleIndex_; const MapStyle* currentStyle_; std::string initialStyleName_ {}; @@ -898,7 +898,8 @@ void MapWidget::keyPressEvent(QKeyEvent* ev) void MapWidget::mousePressEvent(QMouseEvent* ev) { - p->lastPos_ = ev->position(); + p->lastPos_ = ev->position(); + p->lastGlobalPos_ = ev->globalPosition(); if (ev->type() == QEvent::MouseButtonPress) { @@ -944,7 +945,8 @@ void MapWidget::mouseMoveEvent(QMouseEvent* ev) } } - p->lastPos_ = ev->position(); + p->lastPos_ = ev->position(); + p->lastGlobalPos_ = ev->globalPosition(); ev->accept(); } @@ -1064,7 +1066,8 @@ void MapWidgetImpl::RunMousePicking() ++it) { // Run mouse picking for each layer - if ((*it)->RunMousePicking(params, mouseScreenCoordinate)) + if ((*it)->RunMousePicking( + params, lastPos_, lastGlobalPos_, mouseScreenCoordinate)) { // If a draw item was picked, don't process additional layers break; From 0badf01a923f4472d949ee3ba66cf2deaa7fc2be Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 9 Sep 2023 00:49:23 -0500 Subject: [PATCH 112/199] Refactor tooltip display --- scwx-qt/scwx-qt.cmake | 6 ++- .../scwx/qt/gl/draw/placefile_icons.cpp | 6 +-- .../scwx/qt/gl/draw/placefile_lines.cpp | 6 +-- .../source/scwx/qt/gl/draw/placefile_text.cpp | 6 +-- scwx-qt/source/scwx/qt/map/map_widget.cpp | 9 ++++ scwx-qt/source/scwx/qt/util/imgui.cpp | 18 +------- scwx-qt/source/scwx/qt/util/tooltip.cpp | 42 +++++++++++++++++++ scwx-qt/source/scwx/qt/util/tooltip.hpp | 22 ++++++++++ 8 files changed, 87 insertions(+), 28 deletions(-) create mode 100644 scwx-qt/source/scwx/qt/util/tooltip.cpp create mode 100644 scwx-qt/source/scwx/qt/util/tooltip.hpp diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 6557e6ca..6975be3f 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -224,7 +224,8 @@ set(HDR_UTIL source/scwx/qt/util/color.hpp source/scwx/qt/util/texture_atlas.hpp source/scwx/qt/util/q_file_buffer.hpp source/scwx/qt/util/q_file_input_stream.hpp - source/scwx/qt/util/time.hpp) + source/scwx/qt/util/time.hpp + source/scwx/qt/util/tooltip.hpp) set(SRC_UTIL source/scwx/qt/util/color.cpp source/scwx/qt/util/file.cpp source/scwx/qt/util/font.cpp @@ -237,7 +238,8 @@ set(SRC_UTIL source/scwx/qt/util/color.cpp source/scwx/qt/util/texture_atlas.cpp source/scwx/qt/util/q_file_buffer.cpp source/scwx/qt/util/q_file_input_stream.cpp - source/scwx/qt/util/time.cpp) + source/scwx/qt/util/time.cpp + source/scwx/qt/util/tooltip.cpp) set(HDR_VIEW source/scwx/qt/view/level2_product_view.hpp source/scwx/qt/view/level3_product_view.hpp source/scwx/qt/view/level3_radial_view.hpp diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp index 937c2e43..7c29a771 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp @@ -1,7 +1,7 @@ #include -#include #include #include +#include #include #include @@ -685,7 +685,7 @@ void PlacefileIcons::Impl::Update(bool textureAtlasChanged) bool PlacefileIcons::RunMousePicking( const QMapLibreGL::CustomLayerRenderParameters& params, const QPointF& /* mouseLocalPos */, - const QPointF& /* mouseGlobalPos */, + const QPointF& mouseGlobalPos, const glm::vec2& mouseCoords) { std::unique_lock lock {p->iconMutex_}; @@ -774,7 +774,7 @@ bool PlacefileIcons::RunMousePicking( if (it != p->currentHoverIcons_.crend()) { itemPicked = true; - util::ImGui::Instance().DrawTooltip(it->di_->hoverText_); + util::tooltip::Show(it->di_->hoverText_, mouseGlobalPos); } return itemPicked; diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp index e1a16481..df0eff48 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp @@ -1,7 +1,7 @@ #include #include -#include #include +#include #include #include @@ -498,7 +498,7 @@ void PlacefileLines::Impl::Update() bool PlacefileLines::RunMousePicking( const QMapLibreGL::CustomLayerRenderParameters& params, const QPointF& /* mouseLocalPos */, - const QPointF& /* mouseGlobalPos */, + const QPointF& mouseGlobalPos, const glm::vec2& mouseCoords) { std::unique_lock lock {p->lineMutex_}; @@ -589,7 +589,7 @@ bool PlacefileLines::RunMousePicking( if (it != p->currentHoverLines_.crend()) { itemPicked = true; - util::ImGui::Instance().DrawTooltip(it->di_->hoverText_); + util::tooltip::Show(it->di_->hoverText_, mouseGlobalPos); } return itemPicked; diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp index d8340575..18f2efee 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp @@ -1,6 +1,6 @@ #include -#include #include +#include #include #include @@ -210,7 +210,7 @@ void PlacefileText::Deinitialize() bool PlacefileText::RunMousePicking( const QMapLibreGL::CustomLayerRenderParameters& /* params */, const QPointF& /* mouseLocalPos */, - const QPointF& /* mouseGlobalPos */, + const QPointF& mouseGlobalPos, const glm::vec2& /* mouseCoords */) { bool itemPicked = false; @@ -219,7 +219,7 @@ bool PlacefileText::RunMousePicking( if (!p->hoverText_.empty()) { itemPicked = true; - util::ImGui::Instance().DrawTooltip(p->hoverText_); + util::tooltip::Show(p->hoverText_, mouseGlobalPos); } return itemPicked; diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index 6b0b46a0..a1cb05aa 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -1062,6 +1063,7 @@ void MapWidgetImpl::RunMousePicking() // For each layer in reverse // TODO: All Generic Layers, not just Placefile Layers + bool itemPicked = false; for (auto it = placefileLayers_.rbegin(); it != placefileLayers_.rend(); ++it) { @@ -1070,9 +1072,16 @@ void MapWidgetImpl::RunMousePicking() params, lastPos_, lastGlobalPos_, mouseScreenCoordinate)) { // If a draw item was picked, don't process additional layers + itemPicked = true; break; } } + + // If no draw item was picked, hide the tooltip + if (!itemPicked) + { + util::tooltip::Hide(); + } } void MapWidget::mapChanged(QMapLibreGL::Map::MapChange mapChange) diff --git a/scwx-qt/source/scwx/qt/util/imgui.cpp b/scwx-qt/source/scwx/qt/util/imgui.cpp index c31f22b0..9d1622ed 100644 --- a/scwx-qt/source/scwx/qt/util/imgui.cpp +++ b/scwx-qt/source/scwx/qt/util/imgui.cpp @@ -1,12 +1,10 @@ #include #include #include -#include #include #include -#include #include namespace scwx @@ -89,23 +87,9 @@ void ImGui::DrawTooltip(const std::string& hoverText) { p->Initialize(); - std::size_t textWidth = static_cast( - settings::TextSettings::Instance().hover_text_wrap().GetValue()); - - // Wrap text if enabled - std::string wrappedText {}; - if (textWidth > 0) - { - wrappedText = TextFlow::Column(hoverText).width(textWidth).toString(); - } - - // Display text is either wrapped or unwrapped text (do this to avoid copy - // when not wrapping) - const std::string& displayText = (textWidth > 0) ? wrappedText : hoverText; - ::ImGui::BeginTooltip(); ::ImGui::PushFont(p->monospaceFont_); - ::ImGui::TextUnformatted(displayText.c_str()); + ::ImGui::TextUnformatted(hoverText.c_str()); ::ImGui::PopFont(); ::ImGui::EndTooltip(); } diff --git a/scwx-qt/source/scwx/qt/util/tooltip.cpp b/scwx-qt/source/scwx/qt/util/tooltip.cpp new file mode 100644 index 00000000..fd1b7b0d --- /dev/null +++ b/scwx-qt/source/scwx/qt/util/tooltip.cpp @@ -0,0 +1,42 @@ +#include +#include +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace util +{ +namespace tooltip +{ + +static const std::string logPrefix_ = "scwx::qt::util::tooltip"; + +void Show(const std::string& text, const QPointF& /* mouseGlobalPos */) +{ + std::size_t textWidth = static_cast( + settings::TextSettings::Instance().hover_text_wrap().GetValue()); + + // Wrap text if enabled + std::string wrappedText {}; + if (textWidth > 0) + { + wrappedText = TextFlow::Column(text).width(textWidth).toString(); + } + + // Display text is either wrapped or unwrapped text (do this to avoid copy + // when not wrapping) + const std::string& displayText = (textWidth > 0) ? wrappedText : text; + + util::ImGui::Instance().DrawTooltip(displayText); +} + +void Hide() {} + +} // namespace tooltip +} // namespace util +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/util/tooltip.hpp b/scwx-qt/source/scwx/qt/util/tooltip.hpp new file mode 100644 index 00000000..83c5b146 --- /dev/null +++ b/scwx-qt/source/scwx/qt/util/tooltip.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace util +{ +namespace tooltip +{ + +void Show(const std::string& text, const QPointF& mouseGlobalPos); +void Hide(); + +} // namespace tooltip +} // namespace util +} // namespace qt +} // namespace scwx From 1a9e87ba079a724c9211e3bde2c1330b0f4abb22 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 9 Sep 2023 12:20:17 -0500 Subject: [PATCH 113/199] Don't display a disabled placefile when renaming --- scwx-qt/source/scwx/qt/map/map_widget.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index a1cb05aa..12db4616 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -255,7 +255,8 @@ void MapWidgetImpl::ConnectSignals() enabledPlacefiles_.erase(oldName); RemovePlacefileLayer(oldName); } - if (!enabledPlacefiles_.contains(newName)) + if (!enabledPlacefiles_.contains(newName) && + placefileManager_->placefile_enabled(newName)) { // Add new placefile layer enabledPlacefiles_.emplace(newName); From 2f41993d7b965610223e71a8c8eaf01c29eec576 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 9 Sep 2023 16:00:19 -0500 Subject: [PATCH 114/199] Support placefile icon modulate with "scwx-ModulateIcon:" statement --- .../scwx/qt/gl/draw/placefile_icons.cpp | 10 ++++---- wxdata/include/scwx/gr/placefile.hpp | 17 ++++++------- wxdata/source/scwx/gr/placefile.cpp | 24 +++++++++++++++---- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp index 7c29a771..784aa6b0 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp @@ -488,11 +488,11 @@ void PlacefileIcons::Impl::UpdateBuffers() units::angle::degrees angle = di->angle_; const float a = angle.value(); - // Fixed modulate color - const float mc0 = 1.0f; - const float mc1 = 1.0f; - const float mc2 = 1.0f; - const float mc3 = 1.0f; + // Modulate color + const float mc0 = di->modulate_[0] / 255.0f; + const float mc1 = di->modulate_[1] / 255.0f; + const float mc2 = di->modulate_[2] / 255.0f; + const float mc3 = di->modulate_[3] / 255.0f; newIconBuffer_.insert(newIconBuffer_.end(), { diff --git a/wxdata/include/scwx/gr/placefile.hpp b/wxdata/include/scwx/gr/placefile.hpp index 671af535..017cc112 100644 --- a/wxdata/include/scwx/gr/placefile.hpp +++ b/wxdata/include/scwx/gr/placefile.hpp @@ -81,14 +81,15 @@ public: { IconDrawItem() { itemType_ = ItemType::Icon; } - double latitude_ {}; - double longitude_ {}; - double x_ {}; - double y_ {}; - units::degrees angle_ {}; - std::size_t fileNumber_ {0u}; - std::size_t iconNumber_ {0u}; - std::string hoverText_ {}; + boost::gil::rgba8_pixel_t modulate_ {}; + double latitude_ {}; + double longitude_ {}; + double x_ {}; + double y_ {}; + units::degrees angle_ {}; + std::size_t fileNumber_ {0u}; + std::size_t iconNumber_ {0u}; + std::string hoverText_ {}; }; struct TextDrawItem : DrawItem diff --git a/wxdata/source/scwx/gr/placefile.cpp b/wxdata/source/scwx/gr/placefile.cpp index b92fe1b2..a7584fc3 100644 --- a/wxdata/source/scwx/gr/placefile.cpp +++ b/wxdata/source/scwx/gr/placefile.cpp @@ -68,9 +68,10 @@ public: std::chrono::seconds refresh_ {-1}; // Parsing state - units::length::nautical_miles threshold_ {999.0_nmi}; - boost::gil::rgba8_pixel_t color_ {255, 255, 255, 255}; - ColorMode colorMode_ {ColorMode::RGBA}; + units::length::nautical_miles threshold_ {999.0_nmi}; + boost::gil::rgba8_pixel_t color_ {255, 255, 255, 255}; + boost::gil::rgba8_pixel_t iconModulate_ {255, 255, 255, 255}; + ColorMode colorMode_ {ColorMode::RGBA}; std::chrono::sys_time startTime_ {}; std::chrono::sys_time endTime_ {}; @@ -242,6 +243,8 @@ void Placefile::Impl::ProcessLine(const std::string& line) static const std::string imageKey_ {"Image:"}; static const std::string polygonKey_ {"Polygon:"}; + static const std::string scwxModulateIconKey_ {"scwx-ModulateIcon:"}; + currentStatement_ = DrawingStatement::Standard; // When tokenizing, add one additional delimiter to discard unexpected @@ -338,6 +341,18 @@ void Placefile::Impl::ProcessLine(const std::string& line) color_ = ParseColor(tokenList, 0, colorMode_); } } + else if (boost::istarts_with(line, scwxModulateIconKey_)) + { + // Supercell Wx Extension + // scwx-ModulateIcon: red green blue [alpha] + std::vector tokenList = util::ParseTokens( + line, {" ", " ", " ", " "}, scwxModulateIconKey_.size()); + + if (tokenList.size() >= 3) + { + iconModulate_ = ParseColor(tokenList, 0, colorMode_); + } + } else if (boost::istarts_with(line, refreshKey_)) { // Refresh: minutes @@ -433,6 +448,7 @@ void Placefile::Impl::ProcessLine(const std::string& line) di->threshold_ = threshold_; di->startTime_ = startTime_; di->endTime_ = endTime_; + di->modulate_ = iconModulate_; ParseLocation(tokenList[0], tokenList[1], @@ -686,7 +702,7 @@ void Placefile::Impl::ProcessLine(const std::string& line) } else { - logger_->warn("Unknown statement: {}", line); + logger_->trace("Unknown statement: {}", line); } } From 477d9309a9e8ab1a3218f43c9e710632c1a039ef Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 9 Sep 2023 22:39:47 -0500 Subject: [PATCH 115/199] Fix placefile image/icon vertex count calculation --- scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp | 4 ++-- scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp index 784aa6b0..84ffb4b2 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp @@ -675,8 +675,8 @@ void PlacefileIcons::Impl::Update(bool textureAtlasChanged) currentIntegerBuffer_.data(), GL_DYNAMIC_DRAW); - numVertices_ = static_cast(currentIconBuffer_.size() / - kVerticesPerRectangle); + numVertices_ = + static_cast(currentIconBuffer_.size() / kPointsPerVertex); } dirty_ = false; diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp index f7ba1737..3a5809c9 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp @@ -472,8 +472,8 @@ void PlacefileImages::Impl::Update(bool textureAtlasChanged) currentIntegerBuffer_.data(), GL_DYNAMIC_DRAW); - numVertices_ = static_cast(currentImageBuffer_.size() / - kVerticesPerRectangle); + numVertices_ = + static_cast(currentImageBuffer_.size() / kPointsPerVertex); } dirty_ = false; From bb5acf3d1e405859a29e1912c012b4e6208fc447 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 9 Sep 2023 22:40:26 -0500 Subject: [PATCH 116/199] Placefile TimeRange is supported in version 1.5 - Fixes AllisonHouse looping --- scwx-qt/source/scwx/qt/manager/placefile_manager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index ddeaec87..f5b71a6f 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -577,7 +577,7 @@ void PlacefileManager::Impl::PlacefileRecord::Update() // Specify parameters auto parameters = cpr::Parameters { - {"version", "1.2"}, // Placefile Version Supported + {"version", "1.5"}, // Placefile Version Supported {"dpi", fmt::format("{:0.0f}", dpi)}, {"lat", fmt::format("{:0.3f}", p->radarSite_->latitude())}, {"lon", fmt::format("{:0.3f}", p->radarSite_->longitude())}}; From 339674919d656a54bdcf7757f28de2d237e1f05f Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 10 Sep 2023 00:38:56 -0500 Subject: [PATCH 117/199] Don't specify MSVC toolset, use latest/default --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b86a47e6..a1c48f4c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,6 @@ jobs: env_cxx: '' compiler: msvc msvc_arch: x64 - msvc_toolset: 14.36 msvc_version: 2022 qt_version: 6.5.0 qt_arch: win64_msvc2019_64 @@ -84,7 +83,6 @@ jobs: uses: ilammy/msvc-dev-cmd@v1 with: arch: ${{ matrix.msvc_arch }} - toolset: ${{ matrix.msvc_toolset }} vsversion: ${{ matrix.msvc_version }} - name: Setup Ubuntu Environment From d78e650368abd1606e8c4a99d0ebeed6b34b5454 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 10 Sep 2023 21:26:09 -0500 Subject: [PATCH 118/199] URL query parameters should be pretty decoded, not fully decoded --- scwx-qt/source/scwx/qt/manager/placefile_manager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index f5b71a6f..cc4e195e 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -585,7 +585,7 @@ void PlacefileManager::Impl::PlacefileRecord::Update() // Iterate through each query parameter in the URL if (url.hasQuery()) { - auto query = url.query(QUrl::ComponentFormattingOption::FullyEncoded) + auto query = url.query(QUrl::ComponentFormattingOption::PrettyDecoded) .toStdString(); boost::char_separator delimiter("&"); From 555fbf479ac9af356446e71bb53187981b865167 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 10 Sep 2023 21:44:19 -0500 Subject: [PATCH 119/199] Don't call glGenTextures every time the texture atlas updates --- scwx-qt/source/scwx/qt/gl/gl_context.cpp | 23 ++++++++++++++++--- scwx-qt/source/scwx/qt/util/texture_atlas.cpp | 7 +----- scwx-qt/source/scwx/qt/util/texture_atlas.hpp | 8 +++---- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/gl_context.cpp b/scwx-qt/source/scwx/qt/gl/gl_context.cpp index 64e2df46..592d12cb 100644 --- a/scwx-qt/source/scwx/qt/gl/gl_context.cpp +++ b/scwx-qt/source/scwx/qt/gl/gl_context.cpp @@ -26,11 +26,15 @@ public: } ~Impl() {} + void InitializeGL(); + static std::size_t GetShaderKey(std::initializer_list> shaders); gl::OpenGLFunctions gl_; + bool glInitialized_ {false}; + std::unordered_map> shaderProgramMap_; std::mutex shaderProgramMutex_; @@ -57,6 +61,18 @@ std::uint64_t GlContext::texture_buffer_count() const return p->textureBufferCount_; } +void GlContext::Impl::InitializeGL() +{ + if (glInitialized_) + { + return; + } + + gl_.glGenTextures(1, &textureAtlas_); + + glInitialized_ = true; +} + std::shared_ptr GlContext::GetShaderProgram(const std::string& vertexPath, const std::string& fragmentPath) @@ -91,15 +107,16 @@ std::shared_ptr GlContext::GetShaderProgram( GLuint GlContext::GetTextureAtlas() { + p->InitializeGL(); + std::unique_lock lock(p->textureMutex_); auto& textureAtlas = util::TextureAtlas::Instance(); - if (p->textureAtlas_ == GL_INVALID_INDEX || - p->textureBufferCount_ != textureAtlas.BuildCount()) + if (p->textureBufferCount_ != textureAtlas.BuildCount()) { p->textureBufferCount_ = textureAtlas.BuildCount(); - p->textureAtlas_ = textureAtlas.BufferAtlas(p->gl_); + textureAtlas.BufferAtlas(p->gl_, p->textureAtlas_); } return p->textureAtlas_; diff --git a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp index ad4502db..c6a29d00 100644 --- a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp +++ b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp @@ -323,10 +323,8 @@ void TextureAtlas::BuildAtlas(std::size_t width, std::size_t height) ++p->buildCount_; } -GLuint TextureAtlas::BufferAtlas(gl::OpenGLFunctions& gl) +void TextureAtlas::BufferAtlas(gl::OpenGLFunctions& gl, GLuint texture) { - GLuint texture = GL_INVALID_INDEX; - std::shared_lock lock(p->atlasMutex_); if (p->atlasArray_.size() > 0u && p->atlasArray_[0].width() > 0 && @@ -354,7 +352,6 @@ GLuint TextureAtlas::BufferAtlas(gl::OpenGLFunctions& gl) lock.unlock(); - gl.glGenTextures(1, &texture); gl.glBindTexture(GL_TEXTURE_2D_ARRAY, texture); gl.glTexParameteri( @@ -375,8 +372,6 @@ GLuint TextureAtlas::BufferAtlas(gl::OpenGLFunctions& gl) GL_UNSIGNED_BYTE, pixelData.data()); } - - return texture; } TextureAttributes TextureAtlas::GetTextureAttributes(const std::string& name) diff --git a/scwx-qt/source/scwx/qt/util/texture_atlas.hpp b/scwx-qt/source/scwx/qt/util/texture_atlas.hpp index 5cc77889..813962d2 100644 --- a/scwx-qt/source/scwx/qt/util/texture_atlas.hpp +++ b/scwx-qt/source/scwx/qt/util/texture_atlas.hpp @@ -72,10 +72,10 @@ public: std::uint64_t BuildCount() const; - void RegisterTexture(const std::string& name, const std::string& path); - bool CacheTexture(const std::string& name, const std::string& path); - void BuildAtlas(std::size_t width, std::size_t height); - GLuint BufferAtlas(gl::OpenGLFunctions& gl); + void RegisterTexture(const std::string& name, const std::string& path); + bool CacheTexture(const std::string& name, const std::string& path); + void BuildAtlas(std::size_t width, std::size_t height); + void BufferAtlas(gl::OpenGLFunctions& gl, GLuint texture); TextureAttributes GetTextureAttributes(const std::string& name); From e14db6cd219928b5c7a1abb68045a4ea96973bd9 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 11 Sep 2023 00:24:53 -0500 Subject: [PATCH 120/199] Add tooltip display methods --- scwx-qt/source/scwx/qt/util/tooltip.cpp | 143 +++++++++++++++++++++++- 1 file changed, 140 insertions(+), 3 deletions(-) diff --git a/scwx-qt/source/scwx/qt/util/tooltip.cpp b/scwx-qt/source/scwx/qt/util/tooltip.cpp index fd1b7b0d..efe7c3dc 100644 --- a/scwx-qt/source/scwx/qt/util/tooltip.cpp +++ b/scwx-qt/source/scwx/qt/util/tooltip.cpp @@ -1,8 +1,13 @@ #include +#include #include #include #include +#include +#include +#include +#include namespace scwx { @@ -15,8 +20,42 @@ namespace tooltip static const std::string logPrefix_ = "scwx::qt::util::tooltip"; -void Show(const std::string& text, const QPointF& /* mouseGlobalPos */) +enum class TooltipMethod { + ImGui, + QToolTip, + QLabel +}; + +static TooltipMethod tooltipMethod_ = TooltipMethod::ImGui; + +static std::unique_ptr labelTooltip_ = nullptr; + +void Initialize() +{ + static bool initialized = false; + + if (initialized) + { + return; + } + + labelTooltip_ = std::make_unique(); + labelTooltip_->setWindowFlag(Qt::ToolTip); + labelTooltip_->setMargin(6); + labelTooltip_->setAttribute(Qt::WidgetAttribute::WA_TranslucentBackground); + labelTooltip_->setStyleSheet( + "background-color: rgba(15, 15, 15, 191);" + "border: 1px solid rgba(110, 110, 128, 128);" + "color: rgba(255, 255, 255, 204);"); + + initialized = true; +} + +void Show(const std::string& text, const QPointF& mouseGlobalPos) +{ + Initialize(); + std::size_t textWidth = static_cast( settings::TextSettings::Instance().hover_text_wrap().GetValue()); @@ -31,10 +70,108 @@ void Show(const std::string& text, const QPointF& /* mouseGlobalPos */) // when not wrapping) const std::string& displayText = (textWidth > 0) ? wrappedText : text; - util::ImGui::Instance().DrawTooltip(displayText); + if (tooltipMethod_ == TooltipMethod::ImGui) + { + util::ImGui::Instance().DrawTooltip(displayText); + } + else if (tooltipMethod_ == TooltipMethod::QToolTip) + { + static std::size_t id = 0; + QToolTip::showText( + mouseGlobalPos.toPoint(), + QString("%3") + .arg(++id) + .arg("Inconsolata") + .arg(QString::fromStdString(displayText).replace("\n", "
")), + nullptr, + {}, + std::numeric_limits::max()); + } + else if (tooltipMethod_ == TooltipMethod::QLabel) + { + // Get monospace font size + std::size_t fontSize = 16; + auto fontSizes = + manager::SettingsManager::general_settings().font_sizes().GetValue(); + if (fontSizes.size() > 1) + { + fontSize = fontSizes[1]; + } + else if (fontSizes.size() > 0) + { + fontSize = fontSizes[0]; + } + + // Configure the label + labelTooltip_->setFont( + QFont("Inconsolata", static_cast(std::round(fontSize * 0.72)))); + labelTooltip_->setText(QString::fromStdString(displayText)); + + // Get the screen the label will be displayed on + QScreen* screen = QGuiApplication::screenAt(mouseGlobalPos.toPoint()); + if (screen == nullptr) + { + screen = QGuiApplication::primaryScreen(); + } + + // Default offset for label + const QPoint offset {2, 24}; + + // Get starting label position (below and to the right) + QPoint p = mouseGlobalPos.toPoint() + offset; + + // Adjust position if necessary + const QRect r = screen->geometry(); + if (p.x() + labelTooltip_->width() > r.x() + r.width()) + { + // If the label extends beyond the right of the screen, move it left + p.rx() -= 4 + labelTooltip_->width(); + } + if (p.y() + labelTooltip_->height() > r.y() + r.height()) + { + // If the label extends beyond the bottom of the screen, move it up + p.ry() -= 24 + labelTooltip_->height(); + } + + // Clamp the label within the screen + if (p.y() < r.y()) + { + p.setY(r.y()); + } + if (p.x() + labelTooltip_->width() > r.x() + r.width()) + { + p.setX(r.x() + r.width() - labelTooltip_->width()); + } + if (p.x() < r.x()) + { + p.setX(r.x()); + } + if (p.y() + labelTooltip_->height() > r.y() + r.height()) + { + p.setY(r.y() + r.height() - labelTooltip_->height()); + } + + // Move the label to the calculated offset + labelTooltip_->move(p); + + // Show the label + if (labelTooltip_->isHidden()) + { + labelTooltip_->show(); + } + } } -void Hide() {} +void Hide() +{ + Initialize(); + + // TooltipMethod::QToolTip + QToolTip::hideText(); + + // TooltipMethod::QLabel + labelTooltip_->hide(); +} } // namespace tooltip } // namespace util From df4500478c9520c93fd897852186da9508392ea7 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 11 Sep 2023 22:19:04 -0500 Subject: [PATCH 121/199] Don't allow placefiles with an empty name --- .../scwx/qt/manager/placefile_manager.cpp | 11 ++++++---- .../source/scwx/qt/model/placefile_model.cpp | 22 ++++++++++++++----- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index cc4e195e..f3b5ecbc 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -356,10 +356,13 @@ void PlacefileManager::Impl::ReadPlacefileSettings() PlacefileRecord record = boost::json::value_to(placefileEntry); - self_->AddUrl(record.name_, - record.title_, - record.enabled_, - record.thresholded_); + if (!record.name_.empty()) + { + self_->AddUrl(record.name_, + record.title_, + record.enabled_, + record.thresholded_); + } } catch (const std::exception& ex) { diff --git a/scwx-qt/source/scwx/qt/model/placefile_model.cpp b/scwx-qt/source/scwx/qt/model/placefile_model.cpp index d1d25a4c..9f08e0ec 100644 --- a/scwx-qt/source/scwx/qt/model/placefile_model.cpp +++ b/scwx-qt/source/scwx/qt/model/placefile_model.cpp @@ -257,6 +257,7 @@ bool PlacefileModel::setData(const QModelIndex& index, } const auto& placefileName = p->placefileNames_.at(index.row()); + bool result = false; switch (index.column()) { @@ -265,7 +266,7 @@ bool PlacefileModel::setData(const QModelIndex& index, { p->placefileManager_->set_placefile_enabled(placefileName, value.toBool()); - return true; + result = true; } break; @@ -274,16 +275,20 @@ bool PlacefileModel::setData(const QModelIndex& index, { p->placefileManager_->set_placefile_thresholded(placefileName, value.toBool()); - return true; + result = true; } break; case static_cast(Column::Placefile): if (role == Qt::ItemDataRole::EditRole) { - p->placefileManager_->set_placefile_url( - placefileName, value.toString().toStdString()); - return true; + QString str = value.toString(); + if (!str.isEmpty()) + { + p->placefileManager_->set_placefile_url(placefileName, + str.toStdString()); + result = true; + } } break; @@ -291,7 +296,12 @@ bool PlacefileModel::setData(const QModelIndex& index, break; } - return true; + if (result) + { + Q_EMIT dataChanged(index, index); + } + + return result; } void PlacefileModel::HandlePlacefileRemoved(const std::string& name) From 10a1755056b6b3dce9378053afde307fd745acd4 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 12 Sep 2023 22:58:17 -0500 Subject: [PATCH 122/199] Tooltip styling updates --- scwx-qt/source/scwx/qt/util/tooltip.cpp | 60 +++++++++++++++---------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/scwx-qt/source/scwx/qt/util/tooltip.cpp b/scwx-qt/source/scwx/qt/util/tooltip.cpp index efe7c3dc..cc5d0ff1 100644 --- a/scwx-qt/source/scwx/qt/util/tooltip.cpp +++ b/scwx-qt/source/scwx/qt/util/tooltip.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -19,6 +20,7 @@ namespace tooltip { static const std::string logPrefix_ = "scwx::qt::util::tooltip"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); enum class TooltipMethod { @@ -29,7 +31,8 @@ enum class TooltipMethod static TooltipMethod tooltipMethod_ = TooltipMethod::ImGui; -static std::unique_ptr labelTooltip_ = nullptr; +static std::unique_ptr tooltipLabel_ = nullptr; +static std::unique_ptr tooltipParent_ = nullptr; void Initialize() { @@ -40,11 +43,19 @@ void Initialize() return; } - labelTooltip_ = std::make_unique(); - labelTooltip_->setWindowFlag(Qt::ToolTip); - labelTooltip_->setMargin(6); - labelTooltip_->setAttribute(Qt::WidgetAttribute::WA_TranslucentBackground); - labelTooltip_->setStyleSheet( + tooltipParent_ = std::make_unique(); + tooltipParent_->setStyleSheet( + "QToolTip" + "{" + "background-color: rgba(15, 15, 15, 191);" + "border: 1px solid rgba(110, 110, 128, 128);" + "color: rgba(255, 255, 255, 204);" + "}"); + + tooltipLabel_ = std::make_unique(); + tooltipLabel_->setWindowFlag(Qt::ToolTip); + tooltipLabel_->setContentsMargins(6, 4, 6, 4); + tooltipLabel_->setStyleSheet( "background-color: rgba(15, 15, 15, 191);" "border: 1px solid rgba(110, 110, 128, 128);" "color: rgba(255, 255, 255, 204);"); @@ -83,7 +94,7 @@ void Show(const std::string& text, const QPointF& mouseGlobalPos) .arg(++id) .arg("Inconsolata") .arg(QString::fromStdString(displayText).replace("\n", "
")), - nullptr, + tooltipParent_.get(), {}, std::numeric_limits::max()); } @@ -103,9 +114,9 @@ void Show(const std::string& text, const QPointF& mouseGlobalPos) } // Configure the label - labelTooltip_->setFont( + tooltipLabel_->setFont( QFont("Inconsolata", static_cast(std::round(fontSize * 0.72)))); - labelTooltip_->setText(QString::fromStdString(displayText)); + tooltipLabel_->setText(QString::fromStdString(displayText)); // Get the screen the label will be displayed on QScreen* screen = QGuiApplication::screenAt(mouseGlobalPos.toPoint()); @@ -115,22 +126,23 @@ void Show(const std::string& text, const QPointF& mouseGlobalPos) } // Default offset for label - const QPoint offset {2, 24}; + const QPoint offset {25, 0}; // Get starting label position (below and to the right) QPoint p = mouseGlobalPos.toPoint() + offset; // Adjust position if necessary const QRect r = screen->geometry(); - if (p.x() + labelTooltip_->width() > r.x() + r.width()) + if (p.x() + tooltipLabel_->width() > r.x() + r.width()) { // If the label extends beyond the right of the screen, move it left - p.rx() -= 4 + labelTooltip_->width(); + p.rx() -= offset.x() * 2 + tooltipLabel_->width(); } - if (p.y() + labelTooltip_->height() > r.y() + r.height()) + if (p.y() + tooltipLabel_->height() > r.y() + r.height()) { // If the label extends beyond the bottom of the screen, move it up - p.ry() -= 24 + labelTooltip_->height(); + // p.ry() -= offset.y() * 2 + tooltipLabel_->height(); + // Don't, let it fall through and clamp instead } // Clamp the label within the screen @@ -138,26 +150,26 @@ void Show(const std::string& text, const QPointF& mouseGlobalPos) { p.setY(r.y()); } - if (p.x() + labelTooltip_->width() > r.x() + r.width()) + if (p.x() + tooltipLabel_->width() > r.x() + r.width()) { - p.setX(r.x() + r.width() - labelTooltip_->width()); + p.setX(r.x() + r.width() - tooltipLabel_->width()); } if (p.x() < r.x()) { p.setX(r.x()); } - if (p.y() + labelTooltip_->height() > r.y() + r.height()) + if (p.y() + tooltipLabel_->height() > r.y() + r.height()) { - p.setY(r.y() + r.height() - labelTooltip_->height()); + p.setY(r.y() + r.height() - tooltipLabel_->height()); } - // Move the label to the calculated offset - labelTooltip_->move(p); + // Move the tooltip to the calculated offset + tooltipLabel_->move(p); - // Show the label - if (labelTooltip_->isHidden()) + // Show the tooltip + if (tooltipLabel_->isHidden()) { - labelTooltip_->show(); + tooltipLabel_->show(); } } } @@ -170,7 +182,7 @@ void Hide() QToolTip::hideText(); // TooltipMethod::QLabel - labelTooltip_->hide(); + tooltipLabel_->hide(); } } // namespace tooltip From 9ea3ed47a68150907e229b96df8d5025ea41b9f1 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 12 Sep 2023 23:55:00 -0500 Subject: [PATCH 123/199] Use unit library to perform font size conversions --- scwx-qt/source/scwx/qt/main/main.cpp | 2 ++ scwx-qt/source/scwx/qt/types/font_types.hpp | 26 +++++++++++++++++++++ scwx-qt/source/scwx/qt/util/tooltip.cpp | 14 ++++++----- 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/scwx-qt/source/scwx/qt/main/main.cpp b/scwx-qt/source/scwx/qt/main/main.cpp index 274c13ca..b841b846 100644 --- a/scwx-qt/source/scwx/qt/main/main.cpp +++ b/scwx-qt/source/scwx/qt/main/main.cpp @@ -1,3 +1,5 @@ +#define NOMINMAX + #include #include #include diff --git a/scwx-qt/source/scwx/qt/types/font_types.hpp b/scwx-qt/source/scwx/qt/types/font_types.hpp index c6f42506..6fc8c506 100644 --- a/scwx-qt/source/scwx/qt/types/font_types.hpp +++ b/scwx-qt/source/scwx/qt/types/font_types.hpp @@ -1,5 +1,31 @@ #pragma once +#include + +namespace units +{ + +namespace dimension +{ + +struct font_size_tag +{ + static constexpr const char* const name = "font size"; + static constexpr const char* const abbreviation = "px"; +}; + +using font_size = make_dimension; + +} // namespace dimension + +UNIT_ADD(font_size, + pixels, + px, + conversion_factor, dimension::font_size>) +UNIT_ADD(font_size, points, pt, conversion_factor, pixels<>>) + +} // namespace units + namespace scwx { namespace qt diff --git a/scwx-qt/source/scwx/qt/util/tooltip.cpp b/scwx-qt/source/scwx/qt/util/tooltip.cpp index cc5d0ff1..6145225a 100644 --- a/scwx-qt/source/scwx/qt/util/tooltip.cpp +++ b/scwx-qt/source/scwx/qt/util/tooltip.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -101,21 +102,22 @@ void Show(const std::string& text, const QPointF& mouseGlobalPos) else if (tooltipMethod_ == TooltipMethod::QLabel) { // Get monospace font size - std::size_t fontSize = 16; - auto fontSizes = + units::font_size::pixels fontSize {16}; + auto fontSizes = manager::SettingsManager::general_settings().font_sizes().GetValue(); if (fontSizes.size() > 1) { - fontSize = fontSizes[1]; + fontSize = units::font_size::pixels {fontSizes[1]}; } else if (fontSizes.size() > 0) { - fontSize = fontSizes[0]; + fontSize = units::font_size::pixels {fontSizes[0]}; } // Configure the label - tooltipLabel_->setFont( - QFont("Inconsolata", static_cast(std::round(fontSize * 0.72)))); + QFont font("Inconsolata"); + font.setPointSizeF(units::font_size::points(fontSize).value()); + tooltipLabel_->setFont(font); tooltipLabel_->setText(QString::fromStdString(displayText)); // Get the screen the label will be displayed on From 23740d260135222ca3de5b5d9296f590eb95b13e Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 14 Sep 2023 23:53:11 -0500 Subject: [PATCH 124/199] Add tooltip method to settings --- scwx-qt/scwx-qt.cmake | 6 ++- .../source/scwx/qt/settings/text_settings.cpp | 41 ++++++++++++++++- .../source/scwx/qt/settings/text_settings.hpp | 1 + scwx-qt/source/scwx/qt/types/text_types.cpp | 45 +++++++++++++++++++ scwx-qt/source/scwx/qt/types/text_types.hpp | 30 +++++++++++++ scwx-qt/source/scwx/qt/ui/settings_dialog.cpp | 41 ++++++++++++++++- scwx-qt/source/scwx/qt/ui/settings_dialog.ui | 27 +++++++++-- scwx-qt/source/scwx/qt/util/tooltip.cpp | 24 +++++----- test/data | 2 +- 9 files changed, 194 insertions(+), 23 deletions(-) create mode 100644 scwx-qt/source/scwx/qt/types/text_types.cpp create mode 100644 scwx-qt/source/scwx/qt/types/text_types.hpp diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 6975be3f..b3b07707 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -160,12 +160,14 @@ set(HDR_TYPES source/scwx/qt/types/alert_types.hpp source/scwx/qt/types/map_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) + source/scwx/qt/types/text_event_key.hpp + source/scwx/qt/types/text_types.hpp) set(SRC_TYPES source/scwx/qt/types/alert_types.cpp source/scwx/qt/types/github_types.cpp source/scwx/qt/types/map_types.cpp source/scwx/qt/types/radar_product_record.cpp - source/scwx/qt/types/text_event_key.cpp) + source/scwx/qt/types/text_event_key.cpp + source/scwx/qt/types/text_types.cpp) set(HDR_UI source/scwx/qt/ui/about_dialog.hpp source/scwx/qt/ui/alert_dialog.hpp source/scwx/qt/ui/alert_dock_widget.hpp diff --git a/scwx-qt/source/scwx/qt/settings/text_settings.cpp b/scwx-qt/source/scwx/qt/settings/text_settings.cpp index 48f612d7..b96f94f4 100644 --- a/scwx-qt/source/scwx/qt/settings/text_settings.cpp +++ b/scwx-qt/source/scwx/qt/settings/text_settings.cpp @@ -1,4 +1,7 @@ #include +#include + +#include namespace scwx { @@ -14,20 +17,48 @@ class TextSettings::Impl public: explicit Impl() { + std::string defaultTooltipMethodValue = + types::GetTooltipMethodName(types::TooltipMethod::ImGui); + + boost::to_lower(defaultTooltipMethodValue); + hoverTextWrap_.SetDefault(80); hoverTextWrap_.SetMinimum(0); hoverTextWrap_.SetMaximum(999); + tooltipMethod_.SetDefault(defaultTooltipMethodValue); + + tooltipMethod_.SetValidator( + [](const std::string& value) + { + for (types::TooltipMethod tooltipMethod : + types::TooltipMethodIterator()) + { + // If the value is equal to a lower case alert action name + std::string tooltipMethodName = + types::GetTooltipMethodName(tooltipMethod); + boost::to_lower(tooltipMethodName); + if (value == tooltipMethodName) + { + // Regard as a match, valid + return true; + } + } + + // No match found, invalid + return false; + }); } ~Impl() {} SettingsVariable hoverTextWrap_ {"hover_text_wrap"}; + SettingsVariable tooltipMethod_ {"tooltip_method"}; }; TextSettings::TextSettings() : SettingsCategory("text"), p(std::make_unique()) { - RegisterVariables({&p->hoverTextWrap_}); + RegisterVariables({&p->hoverTextWrap_, &p->tooltipMethod_}); SetDefaults(); } TextSettings::~TextSettings() = default; @@ -40,6 +71,11 @@ SettingsVariable& TextSettings::hover_text_wrap() const return p->hoverTextWrap_; } +SettingsVariable& TextSettings::tooltip_method() const +{ + return p->tooltipMethod_; +} + TextSettings& TextSettings::Instance() { static TextSettings TextSettings_; @@ -48,7 +84,8 @@ TextSettings& TextSettings::Instance() bool operator==(const TextSettings& lhs, const TextSettings& rhs) { - return (lhs.p->hoverTextWrap_ == rhs.p->hoverTextWrap_); + return (lhs.p->hoverTextWrap_ == rhs.p->hoverTextWrap_ && + lhs.p->tooltipMethod_ == rhs.p->tooltipMethod_); } } // namespace settings diff --git a/scwx-qt/source/scwx/qt/settings/text_settings.hpp b/scwx-qt/source/scwx/qt/settings/text_settings.hpp index 56c3eb8a..42399f99 100644 --- a/scwx-qt/source/scwx/qt/settings/text_settings.hpp +++ b/scwx-qt/source/scwx/qt/settings/text_settings.hpp @@ -26,6 +26,7 @@ public: TextSettings& operator=(TextSettings&&) noexcept; SettingsVariable& hover_text_wrap() const; + SettingsVariable& tooltip_method() const; static TextSettings& Instance(); diff --git a/scwx-qt/source/scwx/qt/types/text_types.cpp b/scwx-qt/source/scwx/qt/types/text_types.cpp new file mode 100644 index 00000000..e708cf63 --- /dev/null +++ b/scwx-qt/source/scwx/qt/types/text_types.cpp @@ -0,0 +1,45 @@ +#include + +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace types +{ + +static const std::unordered_map tooltipMethodName_ { + {TooltipMethod::ImGui, "ImGui"}, + {TooltipMethod::QToolTip, "Native Tooltip"}, + {TooltipMethod::QLabel, "Floating Label"}, + {TooltipMethod::Unknown, "?"}}; + +TooltipMethod GetTooltipMethod(const std::string& name) +{ + auto result = std::find_if( + tooltipMethodName_.cbegin(), + tooltipMethodName_.cend(), + [&](const std::pair& pair) -> bool + { return boost::iequals(pair.second, name); }); + + if (result != tooltipMethodName_.cend()) + { + return result->first; + } + else + { + return TooltipMethod::Unknown; + } +} + +std::string GetTooltipMethodName(TooltipMethod tooltipMethod) +{ + return tooltipMethodName_.at(tooltipMethod); +} + +} // namespace types +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/types/text_types.hpp b/scwx-qt/source/scwx/qt/types/text_types.hpp new file mode 100644 index 00000000..98025a86 --- /dev/null +++ b/scwx-qt/source/scwx/qt/types/text_types.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace types +{ + +enum class TooltipMethod +{ + ImGui, + QToolTip, + QLabel, + Unknown +}; +typedef scwx::util:: + Iterator + TooltipMethodIterator; + +TooltipMethod GetTooltipMethod(const std::string& name); +std::string GetTooltipMethodName(TooltipMethod tooltipMethod); + +} // namespace types +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp index c3fcae82..9bb52e00 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -94,7 +95,8 @@ public: &defaultAlertAction_, &updateNotificationsEnabled_, &debugEnabled_, - &hoverTextWrap_}} + &hoverTextWrap_, + &tooltipMethod_}} { // Configure default alert phenomena colors auto& paletteSettings = manager::SettingsManager::palette_settings(); @@ -166,6 +168,7 @@ public: inactiveAlertColors_ {}; settings::SettingsInterface hoverTextWrap_ {}; + settings::SettingsInterface tooltipMethod_ {}; std::vector settings_; }; @@ -645,6 +648,42 @@ void SettingsDialogImpl::SetupTextTab() hoverTextWrap_.SetSettingsVariable(textSettings.hover_text_wrap()); hoverTextWrap_.SetEditWidget(self_->ui->hoverTextWrapSpinBox); hoverTextWrap_.SetResetButton(self_->ui->resetHoverTextWrapButton); + + for (const auto& tooltipMethod : types::TooltipMethodIterator()) + { + self_->ui->tooltipMethodComboBox->addItem( + QString::fromStdString(types::GetTooltipMethodName(tooltipMethod))); + } + + tooltipMethod_.SetSettingsVariable(textSettings.tooltip_method()); + tooltipMethod_.SetMapFromValueFunction( + [](const std::string& text) -> std::string + { + for (types::TooltipMethod tooltipMethod : + types::TooltipMethodIterator()) + { + const std::string tooltipMethodName = + types::GetTooltipMethodName(tooltipMethod); + + if (boost::iequals(text, tooltipMethodName)) + { + // Return tooltip method label + return tooltipMethodName; + } + } + + // Tooltip method label not found, return unknown + return "?"; + }); + tooltipMethod_.SetMapToValueFunction( + [](std::string text) -> std::string + { + // Convert label to lower case and return + boost::to_lower(text); + return text; + }); + tooltipMethod_.SetEditWidget(self_->ui->tooltipMethodComboBox); + tooltipMethod_.SetResetButton(self_->ui->resetTooltipMethodButton); } QImage SettingsDialogImpl::GenerateColorTableImage( diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui index efa5dfd1..15d5d1ec 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui @@ -459,21 +459,21 @@ 0 - + Hover text character wrap (0 to disable) - + 999 - + ... @@ -484,6 +484,27 @@ + + + + Tooltip Method + + + + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + diff --git a/scwx-qt/source/scwx/qt/util/tooltip.cpp b/scwx-qt/source/scwx/qt/util/tooltip.cpp index 6145225a..5d7b4c6c 100644 --- a/scwx-qt/source/scwx/qt/util/tooltip.cpp +++ b/scwx-qt/source/scwx/qt/util/tooltip.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -23,15 +24,6 @@ namespace tooltip static const std::string logPrefix_ = "scwx::qt::util::tooltip"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); -enum class TooltipMethod -{ - ImGui, - QToolTip, - QLabel -}; - -static TooltipMethod tooltipMethod_ = TooltipMethod::ImGui; - static std::unique_ptr tooltipLabel_ = nullptr; static std::unique_ptr tooltipParent_ = nullptr; @@ -68,8 +60,12 @@ void Show(const std::string& text, const QPointF& mouseGlobalPos) { Initialize(); - std::size_t textWidth = static_cast( - settings::TextSettings::Instance().hover_text_wrap().GetValue()); + auto& textSettings = settings::TextSettings::Instance(); + + std::size_t textWidth = + static_cast(textSettings.hover_text_wrap().GetValue()); + types::TooltipMethod tooltipMethod = + types::GetTooltipMethod(textSettings.tooltip_method().GetValue()); // Wrap text if enabled std::string wrappedText {}; @@ -82,11 +78,11 @@ void Show(const std::string& text, const QPointF& mouseGlobalPos) // when not wrapping) const std::string& displayText = (textWidth > 0) ? wrappedText : text; - if (tooltipMethod_ == TooltipMethod::ImGui) + if (tooltipMethod == types::TooltipMethod::ImGui) { util::ImGui::Instance().DrawTooltip(displayText); } - else if (tooltipMethod_ == TooltipMethod::QToolTip) + else if (tooltipMethod == types::TooltipMethod::QToolTip) { static std::size_t id = 0; QToolTip::showText( @@ -99,7 +95,7 @@ void Show(const std::string& text, const QPointF& mouseGlobalPos) {}, std::numeric_limits::max()); } - else if (tooltipMethod_ == TooltipMethod::QLabel) + else if (tooltipMethod == types::TooltipMethod::QLabel) { // Get monospace font size units::font_size::pixels fontSize {16}; diff --git a/test/data b/test/data index 6d407be1..33caca18 160000 --- a/test/data +++ b/test/data @@ -1 +1 @@ -Subproject commit 6d407be1b6f2e72490ef0d07da1e297994df8fe4 +Subproject commit 33caca188b1007c643db75afa560fdfe348c0ee5 From adbcc24aa9cdfcdf6c904c2936a8f7fe7b22110b Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 15 Sep 2023 00:01:00 -0500 Subject: [PATCH 125/199] Hide the tooltip when losing focus --- scwx-qt/source/scwx/qt/map/map_widget.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index 12db4616..25ecac14 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -174,6 +174,7 @@ public: common::Level2Product selectedLevel2Product_; bool hasMouse_ {false}; + bool lastItemPicked_ {false}; QPointF lastPos_ {}; QPointF lastGlobalPos_ {}; std::size_t currentStyleIndex_; @@ -1044,6 +1045,13 @@ void MapWidget::paintGL() { p->RunMousePicking(); } + else if (p->lastItemPicked_) + { + // Hide the tooltip when losing focus + util::tooltip::Hide(); + + p->lastItemPicked_ = false; + } // Render ImGui Frame ImGui::Render(); @@ -1083,6 +1091,8 @@ void MapWidgetImpl::RunMousePicking() { util::tooltip::Hide(); } + + lastItemPicked_ = itemPicked; } void MapWidget::mapChanged(QMapLibreGL::Map::MapChange mapChange) From 6774c16cbbf44600ebe1a7db9125ef084cacd468 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 16 Sep 2023 08:52:31 -0500 Subject: [PATCH 126/199] Fixing thread lifetime issues in Radar Product Manager --- scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp b/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp index 29bbc724..cbeb0fb0 100644 --- a/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp @@ -102,7 +102,7 @@ public: self, &RadarProductManager::NewDataAvailable); } - ~ProviderManager() = default; + ~ProviderManager() { threadPool_.join(); }; std::string name() const; @@ -179,6 +179,8 @@ public: // Lock other mutexes before destroying, ensure loading is complete std::unique_lock loadLevel2DataLock {loadLevel2DataMutex_}; std::unique_lock loadLevel3DataLock {loadLevel3DataMutex_}; + + threadPool_.join(); } RadarProductManager* self_; From da267c4c22e03e7dfc9ccd2b49ea99c78d7db0d9 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 16 Sep 2023 23:18:19 -0500 Subject: [PATCH 127/199] Load texture atlas images into shared pointers --- scwx-qt/source/scwx/qt/util/texture_atlas.cpp | 115 ++++++++---------- 1 file changed, 54 insertions(+), 61 deletions(-) diff --git a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp index c6a29d00..335bd70b 100644 --- a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp +++ b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #if defined(_MSC_VER) # pragma warning(push, 0) @@ -26,6 +25,10 @@ # pragma warning(pop) #endif +#if defined(LoadImage) +# undef LoadImage +#endif + namespace scwx { namespace qt @@ -42,13 +45,15 @@ public: explicit Impl() {} ~Impl() {} - static boost::gil::rgba8_image_t LoadImage(const std::string& imagePath); + static std::shared_ptr + LoadImage(const std::string& imagePath); std::unordered_map texturePathMap_ {}; std::shared_mutex texturePathMutex_ {}; std::shared_mutex textureCacheMutex_ {}; - std::unordered_map textureCache_ {}; + std::unordered_map> + textureCache_ {}; std::vector atlasArray_ {}; std::unordered_map atlasMap_ {}; @@ -79,10 +84,11 @@ bool TextureAtlas::CacheTexture(const std::string& name, const std::string& path) { // Attempt to load the image - boost::gil::rgba8_image_t image = TextureAtlas::Impl::LoadImage(path); + std::shared_ptr image = + TextureAtlas::Impl::LoadImage(path); // If the image is valid - if (image.width() > 0 && image.height() > 0) + if (image != nullptr && image->width() > 0 && image->height() > 0) { // Store it in the texture cache std::unique_lock lock(p->textureCacheMutex_); @@ -105,9 +111,8 @@ void TextureAtlas::BuildAtlas(std::size_t width, std::size_t height) return; } - typedef std::vector>> + typedef std::vector< + std::pair>> ImageVector; ImageVector images {}; @@ -119,56 +124,56 @@ void TextureAtlas::BuildAtlas(std::size_t width, std::size_t height) std::shared_lock lock(p->texturePathMutex_); // For each registered texture - std::for_each(p->texturePathMap_.cbegin(), - p->texturePathMap_.cend(), - [&](const auto& pair) - { - // Load texture image - boost::gil::rgba8_image_t image = - Impl::LoadImage(pair.second); + std::for_each( + p->texturePathMap_.cbegin(), + p->texturePathMap_.cend(), + [&](const auto& pair) + { + // Load texture image + std::shared_ptr image = + Impl::LoadImage(pair.second); - if (image.width() > 0u && image.height() > 0u) - { - // Store STB rectangle pack data in a vector - stbrpRects.push_back(stbrp_rect { - 0, - static_cast(image.width()), - static_cast(image.height()), - 0, - 0, - 0}); + if (image != nullptr && image->width() > 0u && image->height() > 0u) + { + // Store STB rectangle pack data in a vector + stbrpRects.push_back( + stbrp_rect {0, + static_cast(image->width()), + static_cast(image->height()), + 0, + 0, + 0}); - // Store image data in a vector - images.emplace_back(pair.first, std::move(image)); - } - }); + // Store image data in a vector + images.emplace_back(pair.first, std::move(image)); + } + }); } // Cached images - - // Take a read lock on the texture cache map. The read lock must persist - // through atlas population, as a pointer to the image is taken and used. - std::shared_lock textureCacheLock(p->textureCacheMutex_); - { + // Take a read lock on the texture cache map while adding textures images + // to the atlas vector. + std::shared_lock textureCacheLock(p->textureCacheMutex_); + // For each cached texture for (auto& texture : p->textureCache_) { auto& image = texture.second; - if (image.width() > 0u && image.height() > 0u) + if (image != nullptr && image->width() > 0u && image->height() > 0u) { // Store STB rectangle pack data in a vector stbrpRects.push_back( stbrp_rect {0, - static_cast(image.width()), - static_cast(image.height()), + static_cast(image->width()), + static_cast(image->height()), 0, 0, 0}); // Store image data in a vector - images.emplace_back(texture.first, &image); + images.push_back({texture.first, image}); } } } @@ -227,21 +232,8 @@ void TextureAtlas::BuildAtlas(std::size_t width, std::size_t height) if (stbrpRects[i].was_packed != 0) { // Populate the atlas - boost::gil::rgba8c_view_t imageView; - - // Retrieve the image - if (std::holds_alternative( - images[i].second)) - { - imageView = boost::gil::const_view( - std::get(images[i].second)); - } - else if (std::holds_alternative( - images[i].second)) - { - imageView = boost::gil::const_view( - *std::get(images[i].second)); - } + boost::gil::rgba8c_view_t imageView = + boost::gil::const_view(*images[i].second); boost::gil::rgba8_view_t atlasSubView = boost::gil::subimage_view(atlasView, @@ -388,12 +380,13 @@ TextureAttributes TextureAtlas::GetTextureAttributes(const std::string& name) return attr; } -boost::gil::rgba8_image_t +std::shared_ptr TextureAtlas::Impl::LoadImage(const std::string& imagePath) { logger_->debug("Loading image: {}", imagePath); - boost::gil::rgba8_image_t image; + std::shared_ptr image = + std::make_shared(); QUrl url = QUrl::fromUserInput(QString::fromStdString(imagePath)); @@ -406,7 +399,7 @@ TextureAtlas::Impl::LoadImage(const std::string& imagePath) if (!imageFile.isOpen()) { logger_->error("Could not open image: {}", imagePath); - return image; + return nullptr; } boost::iostreams::stream dataStream(imageFile); @@ -414,12 +407,12 @@ TextureAtlas::Impl::LoadImage(const std::string& imagePath) try { boost::gil::read_and_convert_image( - dataStream, image, boost::gil::png_tag()); + dataStream, *image, boost::gil::png_tag()); } catch (const std::exception& ex) { logger_->error("Error reading image: {}", ex.what()); - return image; + return nullptr; } } else @@ -447,7 +440,7 @@ TextureAtlas::Impl::LoadImage(const std::string& imagePath) if (pixelData == nullptr) { logger_->error("Error loading image: {}", stbi_failure_reason()); - return image; + return nullptr; } // Create a view pointing to the STB image data @@ -458,8 +451,8 @@ TextureAtlas::Impl::LoadImage(const std::string& imagePath) width * desiredChannels); // Copy the view to the destination image - image = boost::gil::rgba8_image_t(stbView); - auto& view = boost::gil::view(image); + *image = boost::gil::rgba8_image_t(stbView); + auto& view = boost::gil::view(*image); // If no alpha channel, replace black with transparent if (numChannels == 3) From 9165d66a3307bc1942a5a788198dd2ad897787e1 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 16 Sep 2023 23:27:43 -0500 Subject: [PATCH 128/199] Log a debug entry when the radar sweep monitor times out --- scwx-qt/source/scwx/qt/manager/timeline_manager.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp index 667fa4dd..8e79a552 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp @@ -281,7 +281,12 @@ void TimelineManager::Impl::RadarSweepMonitorReset() void TimelineManager::Impl::RadarSweepMonitorWait( std::unique_lock& lock) { - radarSweepMonitorCondition_.wait_for(lock, kRadarSweepMonitorTimeout_); + std::cv_status status = + radarSweepMonitorCondition_.wait_for(lock, kRadarSweepMonitorTimeout_); + if (status == std::cv_status::timeout) + { + logger_->debug("Radar sweep monitor timed out"); + } radarSweepMonitorActive_ = false; } From bbaae5d1bae01e1d992f5766ebb4c701a8bfb7fd Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 17 Sep 2023 00:28:54 -0500 Subject: [PATCH 129/199] Hold a reference to current images in the placefile manager, removing old, unused images --- .../scwx/qt/manager/placefile_manager.cpp | 17 +++- .../scwx/qt/manager/resource_manager.cpp | 20 +++-- .../scwx/qt/manager/resource_manager.hpp | 10 ++- scwx-qt/source/scwx/qt/util/texture_atlas.cpp | 80 ++++++++----------- scwx-qt/source/scwx/qt/util/texture_atlas.hpp | 4 +- 5 files changed, 68 insertions(+), 63 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index f3b5ecbc..2a2dcd16 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -51,7 +51,8 @@ public: void ReadPlacefileSettings(); void WritePlacefileSettings(); - static void LoadResources(const std::shared_ptr& placefile); + static std::vector> + LoadResources(const std::shared_ptr& placefile); boost::asio::thread_pool threadPool_ {1u}; @@ -134,6 +135,8 @@ public: std::mutex refreshMutex_ {}; std::mutex timerMutex_ {}; + std::vector> images_ {}; + std::string lastRadarSite_ {}; std::chrono::system_clock::time_point lastUpdateTime_ {}; }; @@ -276,6 +279,7 @@ void PlacefileManager::set_placefile_url(const std::string& name, auto placefileRecord = it->second; placefileRecord->name_ = normalizedUrl; placefileRecord->placefile_ = nullptr; + placefileRecord->images_.clear(); p->placefileRecordMap_.erase(it); p->placefileRecordMap_.insert_or_assign(normalizedUrl, placefileRecord); @@ -633,7 +637,7 @@ void PlacefileManager::Impl::PlacefileRecord::Update() if (updatedPlacefile != nullptr) { // Load placefile resources - Impl::LoadResources(updatedPlacefile); + auto newImages = Impl::LoadResources(updatedPlacefile); // Check the name matches, in case the name updated if (name_ == name) @@ -643,6 +647,10 @@ void PlacefileManager::Impl::PlacefileRecord::Update() title_ = placefile_->title(); lastUpdateTime_ = std::chrono::system_clock::now(); + // Update image resources + images_.swap(newImages); + newImages.clear(); + if (p->radarSite_ != nullptr) { lastRadarSite_ = p->radarSite_->id(); @@ -736,7 +744,8 @@ std::shared_ptr PlacefileManager::Instance() return placefileManager; } -void PlacefileManager::Impl::LoadResources( +std::vector> +PlacefileManager::Impl::LoadResources( const std::shared_ptr& placefile) { const auto iconFiles = placefile->icon_files(); @@ -790,7 +799,7 @@ void PlacefileManager::Impl::LoadResources( } } - ResourceManager::LoadImageResources(urlStrings); + return ResourceManager::LoadImageResources(urlStrings); } } // namespace manager diff --git a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp index aba8c918..1e694905 100644 --- a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp @@ -64,36 +64,40 @@ std::shared_ptr Font(types::Font font) return nullptr; } -bool LoadImageResource(const std::string& urlString) +std::shared_ptr +LoadImageResource(const std::string& urlString) { util::TextureAtlas& textureAtlas = util::TextureAtlas::Instance(); return textureAtlas.CacheTexture(urlString, urlString); } -void LoadImageResources(const std::vector& urlStrings) +std::vector> +LoadImageResources(const std::vector& urlStrings) { - std::mutex m {}; - bool textureCached = false; + std::mutex m {}; + std::vector> images {}; std::for_each(std::execution::par_unseq, urlStrings.begin(), urlStrings.end(), [&](auto& urlString) { - bool value = LoadImageResource(urlString); + auto image = LoadImageResource(urlString); - if (value) + if (image != nullptr) { std::unique_lock lock {m}; - textureCached = true; + images.emplace_back(std::move(image)); } }); - if (textureCached) + if (!images.empty()) { util::TextureAtlas& textureAtlas = util::TextureAtlas::Instance(); textureAtlas.BuildAtlas(2048, 2048); } + + return images; } static void LoadFonts() diff --git a/scwx-qt/source/scwx/qt/manager/resource_manager.hpp b/scwx-qt/source/scwx/qt/manager/resource_manager.hpp index 17b00b44..7b8003c9 100644 --- a/scwx-qt/source/scwx/qt/manager/resource_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/resource_manager.hpp @@ -3,6 +3,10 @@ #include #include +#include + +#include + namespace scwx { namespace qt @@ -18,8 +22,10 @@ void Shutdown(); int FontId(types::Font font); std::shared_ptr Font(types::Font font); -bool LoadImageResource(const std::string& urlString); -void LoadImageResources(const std::vector& urlStrings); +std::shared_ptr +LoadImageResource(const std::string& urlString); +std::vector> +LoadImageResources(const std::vector& urlStrings); } // namespace ResourceManager } // namespace manager diff --git a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp index 335bd70b..64ecc9e4 100644 --- a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp +++ b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp @@ -48,11 +48,12 @@ public: static std::shared_ptr LoadImage(const std::string& imagePath); - std::unordered_map texturePathMap_ {}; - std::shared_mutex texturePathMutex_ {}; + std::vector> + registeredTextures_ {}; + std::shared_mutex registeredTextureMutex_ {}; std::shared_mutex textureCacheMutex_ {}; - std::unordered_map> + std::unordered_map> textureCache_ {}; std::vector atlasArray_ {}; @@ -76,12 +77,14 @@ std::uint64_t TextureAtlas::BuildCount() const void TextureAtlas::RegisterTexture(const std::string& name, const std::string& path) { - std::unique_lock lock(p->texturePathMutex_); - p->texturePathMap_.insert_or_assign(name, path); + std::unique_lock lock(p->registeredTextureMutex_); + + std::shared_ptr image = CacheTexture(name, path); + p->registeredTextures_.emplace_back(std::move(image)); } -bool TextureAtlas::CacheTexture(const std::string& name, - const std::string& path) +std::shared_ptr +TextureAtlas::CacheTexture(const std::string& name, const std::string& path) { // Attempt to load the image std::shared_ptr image = @@ -93,12 +96,12 @@ bool TextureAtlas::CacheTexture(const std::string& name, // Store it in the texture cache std::unique_lock lock(p->textureCacheMutex_); - p->textureCache_.insert_or_assign(name, std::move(image)); + p->textureCache_.insert_or_assign(name, image); - return true; + return image; } - return false; + return nullptr; } void TextureAtlas::BuildAtlas(std::size_t width, std::size_t height) @@ -118,50 +121,28 @@ void TextureAtlas::BuildAtlas(std::size_t width, std::size_t height) ImageVector images {}; std::vector stbrpRects {}; - // Load images - { - // Take a read lock on the texture path map - std::shared_lock lock(p->texturePathMutex_); - - // For each registered texture - std::for_each( - p->texturePathMap_.cbegin(), - p->texturePathMap_.cend(), - [&](const auto& pair) - { - // Load texture image - std::shared_ptr image = - Impl::LoadImage(pair.second); - - if (image != nullptr && image->width() > 0u && image->height() > 0u) - { - // Store STB rectangle pack data in a vector - stbrpRects.push_back( - stbrp_rect {0, - static_cast(image->width()), - static_cast(image->height()), - 0, - 0, - 0}); - - // Store image data in a vector - images.emplace_back(pair.first, std::move(image)); - } - }); - } - // Cached images { - // Take a read lock on the texture cache map while adding textures images - // to the atlas vector. - std::shared_lock textureCacheLock(p->textureCacheMutex_); + // Take a lock on the texture cache map while adding textures images to + // the atlas vector. + std::unique_lock textureCacheLock(p->textureCacheMutex_); // For each cached texture - for (auto& texture : p->textureCache_) + for (auto it = p->textureCache_.begin(); it != p->textureCache_.end();) { - auto& image = texture.second; + auto& texture = *it; + auto image = texture.second.lock(); - if (image != nullptr && image->width() > 0u && image->height() > 0u) + if (image == nullptr) + { + logger_->trace("Removing texture from the cache: {}", + texture.first); + + // If the image is no longer cached, erase the iterator and continue + it = p->textureCache_.erase(it); + continue; + } + else if (image->width() > 0u && image->height() > 0u) { // Store STB rectangle pack data in a vector stbrpRects.push_back( @@ -175,6 +156,9 @@ void TextureAtlas::BuildAtlas(std::size_t width, std::size_t height) // Store image data in a vector images.push_back({texture.first, image}); } + + // Increment iterator + ++it; } } diff --git a/scwx-qt/source/scwx/qt/util/texture_atlas.hpp b/scwx-qt/source/scwx/qt/util/texture_atlas.hpp index 813962d2..64c5a2d7 100644 --- a/scwx-qt/source/scwx/qt/util/texture_atlas.hpp +++ b/scwx-qt/source/scwx/qt/util/texture_atlas.hpp @@ -6,6 +6,7 @@ #include #include +#include namespace scwx { @@ -73,7 +74,8 @@ public: std::uint64_t BuildCount() const; void RegisterTexture(const std::string& name, const std::string& path); - bool CacheTexture(const std::string& name, const std::string& path); + std::shared_ptr + CacheTexture(const std::string& name, const std::string& path); void BuildAtlas(std::size_t width, std::size_t height); void BufferAtlas(gl::OpenGLFunctions& gl, GLuint texture); From 2c0991cebcf713421dac7b37117dc02b0983b9de Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 17 Sep 2023 00:45:34 -0500 Subject: [PATCH 130/199] Add texture atlas build timing --- scwx-qt/source/scwx/qt/util/texture_atlas.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp index 64ecc9e4..fbb4f187 100644 --- a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp +++ b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -108,6 +109,9 @@ void TextureAtlas::BuildAtlas(std::size_t width, std::size_t height) { logger_->debug("Building {}x{} texture atlas", width, height); + boost::timer::cpu_timer timer {}; + timer.start(); + if (width > INT_MAX || height > INT_MAX) { logger_->error("Cannot build texture atlas of size {}x{}", width, height); @@ -297,6 +301,9 @@ void TextureAtlas::BuildAtlas(std::size_t width, std::size_t height) // Mark the need to buffer the atlas ++p->buildCount_; + + timer.stop(); + logger_->debug("Texture atlas built in {}", timer.format(6, "%ws")); } void TextureAtlas::BufferAtlas(gl::OpenGLFunctions& gl, GLuint texture) From 4f16d92ba37f881988dc2b72cdd4f3958bb6cc0d Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 17 Sep 2023 00:54:40 -0500 Subject: [PATCH 131/199] Update stb to 5736b15 (Jan 29 2023) --- external/stb | 2 +- scwx-qt/source/scwx/qt/external/stb_image.cpp | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/external/stb b/external/stb index 8b5f1f37..5736b15f 160000 --- a/external/stb +++ b/external/stb @@ -1 +1 @@ -Subproject commit 8b5f1f37b5b75829fc72d38e7b5d4bcbf8a26d55 +Subproject commit 5736b15f7ea0ffb08dd38af21067c314d6a3aae9 diff --git a/scwx-qt/source/scwx/qt/external/stb_image.cpp b/scwx-qt/source/scwx/qt/external/stb_image.cpp index b2c73443..f611b488 100644 --- a/scwx-qt/source/scwx/qt/external/stb_image.cpp +++ b/scwx-qt/source/scwx/qt/external/stb_image.cpp @@ -7,8 +7,16 @@ # pragma GCC diagnostic ignored "-Wunused-but-set-variable" #endif +#if defined(_MSC_VER) +# pragma warning(push, 0) +#endif + #include #if defined(__GNUC__) # pragma GCC diagnostic pop #endif + +#if defined(_MSC_VER) +# pragma warning(pop) +#endif From 47c1bce993598fceb268705c040a88623da5e1eb Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 17 Sep 2023 00:59:33 -0500 Subject: [PATCH 132/199] Remove File > Open for Placefiles --- scwx-qt/source/scwx/qt/main/main_window.cpp | 22 ------- scwx-qt/source/scwx/qt/main/main_window.hpp | 1 - scwx-qt/source/scwx/qt/main/main_window.ui | 6 -- .../scwx/qt/manager/placefile_manager.cpp | 60 ------------------- .../scwx/qt/manager/placefile_manager.hpp | 1 - 5 files changed, 90 deletions(-) diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp index c72db8a0..5befa67d 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -401,28 +401,6 @@ void MainWindow::on_actionOpenNexrad_triggered() dialog->open(); } -void MainWindow::on_actionOpenPlacefile_triggered() -{ - static const std::string placefileFilter = "Placefiles (*)"; - - QFileDialog* dialog = new QFileDialog(this); - - dialog->setFileMode(QFileDialog::ExistingFile); - dialog->setNameFilter(tr(placefileFilter.c_str())); - dialog->setAttribute(Qt::WA_DeleteOnClose); - - connect(dialog, - &QFileDialog::fileSelected, - this, - [this](const QString& file) - { - logger_->info("Selected: {}", file.toStdString()); - p->placefileManager_->LoadFile(file.toStdString()); - }); - - dialog->open(); -} - void MainWindow::on_actionOpenTextEvent_triggered() { static const std::string textFilter = "Text Event Products (*.txt)"; diff --git a/scwx-qt/source/scwx/qt/main/main_window.hpp b/scwx-qt/source/scwx/qt/main/main_window.hpp index 3261355e..6ed96aba 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.hpp +++ b/scwx-qt/source/scwx/qt/main/main_window.hpp @@ -33,7 +33,6 @@ signals: private slots: void on_actionOpenNexrad_triggered(); - void on_actionOpenPlacefile_triggered(); void on_actionOpenTextEvent_triggered(); void on_actionSettings_triggered(); void on_actionExit_triggered(); diff --git a/scwx-qt/source/scwx/qt/main/main_window.ui b/scwx-qt/source/scwx/qt/main/main_window.ui index 8cf8ce5b..369c8d28 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.ui +++ b/scwx-qt/source/scwx/qt/main/main_window.ui @@ -51,7 +51,6 @@ &Open - @@ -416,11 +415,6 @@ &Check for Updates - - - &Placefile... - - diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index 2a2dcd16..16aefcdd 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -99,7 +99,6 @@ public: void ScheduleRefresh(); void Update(); void UpdateAsync(); - void UpdatePlacefile(const std::shared_ptr& placefile); friend void tag_invoke(boost::json::value_from_tag, boost::json::value& jv, @@ -472,55 +471,6 @@ void PlacefileManager::AddUrl(const std::string& urlString, } } -void PlacefileManager::LoadFile(const std::string& filename) -{ - const std::string placefileName = - QDir::toNativeSeparators(QString::fromStdString(filename)).toStdString(); - - logger_->debug("LoadFile: {}", placefileName); - - boost::asio::post( - p->threadPool_, - [placefileName, this]() - { - // Load file - std::shared_ptr placefile = - gr::Placefile::Load(placefileName); - - if (placefile == nullptr) - { - return; - } - - std::unique_lock lock(p->placefileRecordLock_); - - // Determine if the placefile has been loaded previously - auto it = p->placefileRecordMap_.find(placefileName); - if (it != p->placefileRecordMap_.end()) - { - // If the placefile has been loaded previously, update it - it->second->UpdatePlacefile(placefile); - - lock.unlock(); - - Q_EMIT PlacefileUpdated(placefileName); - } - else - { - // If this is a new placefile, add it - auto& record = p->placefileRecords_.emplace_back( - std::make_shared( - p.get(), placefileName, placefile, placefile->title(), true)); - p->placefileRecordMap_.insert_or_assign(placefileName, record); - - lock.unlock(); - - Q_EMIT PlacefileEnabled(placefileName, record->enabled_); - Q_EMIT PlacefileUpdated(placefileName); - } - }); -} - void PlacefileManager::RemoveUrl(const std::string& urlString) { std::unique_lock lock(p->placefileRecordLock_); @@ -715,16 +665,6 @@ void PlacefileManager::Impl::PlacefileRecord::UpdateAsync() boost::asio::post(threadPool_, [this]() { Update(); }); } -void PlacefileManager::Impl::PlacefileRecord::UpdatePlacefile( - const std::shared_ptr& placefile) -{ - // Update placefile - placefile_ = placefile; - - // Update refresh timer - ScheduleRefresh(); -} - std::shared_ptr PlacefileManager::Instance() { static std::weak_ptr placefileManagerReference_ {}; diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp index 498ea9a1..0567265f 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp @@ -42,7 +42,6 @@ public: const std::string& title = {}, bool enabled = false, bool thresholded = false); - void LoadFile(const std::string& filename); void RemoveUrl(const std::string& urlString); static std::shared_ptr Instance(); From 827a0eacf5278c04c297c96956e70ac52c181831 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 17 Sep 2023 23:08:16 -0500 Subject: [PATCH 133/199] Don't destroy PlacefileLayer or PlacefileManager until background tasks have completed --- scwx-qt/source/scwx/qt/manager/placefile_manager.cpp | 2 +- scwx-qt/source/scwx/qt/map/placefile_layer.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index 16aefcdd..77344524 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -45,7 +45,7 @@ public: class PlacefileRecord; explicit Impl(PlacefileManager* self) : self_ {self} {} - ~Impl() {} + ~Impl() { threadPool_.join(); } void InitializePlacefileSettings(); void ReadPlacefileSettings(); diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp index 7e9303f4..f29d63e4 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp @@ -42,7 +42,7 @@ public: { ConnectSignals(); } - ~Impl() = default; + ~Impl() { threadPool_.join(); } void ConnectSignals(); From 7d021f72f3334793859e09937a68800962177cff Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 18 Sep 2023 22:22:28 -0500 Subject: [PATCH 134/199] Fixing additional thread lifetime issues --- scwx-qt/source/scwx/qt/main/main_window.cpp | 2 +- scwx-qt/source/scwx/qt/manager/text_event_manager.cpp | 3 +++ scwx-qt/source/scwx/qt/map/map_widget.cpp | 2 ++ scwx-qt/source/scwx/qt/view/radar_product_view.cpp | 2 +- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp index 5befa67d..2db1f2b0 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -119,7 +119,7 @@ public: settings_.setCacheDatabasePath(QString {cacheDbPath.c_str()}); settings_.setCacheDatabaseMaximumSize(20 * 1024 * 1024); } - ~MainWindowImpl() = default; + ~MainWindowImpl() { threadPool_.join(); } void AsyncSetup(); void ConfigureMapLayout(); diff --git a/scwx-qt/source/scwx/qt/manager/text_event_manager.cpp b/scwx-qt/source/scwx/qt/manager/text_event_manager.cpp index d44d4fb0..0ad34b9f 100644 --- a/scwx-qt/source/scwx/qt/manager/text_event_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/text_event_manager.cpp @@ -48,6 +48,9 @@ public: { std::unique_lock lock(refreshMutex_); refreshTimer_.cancel(); + lock.unlock(); + + threadPool_.join(); } void HandleMessage(std::shared_ptr message); diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index 25ecac14..4f279ad3 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -115,6 +115,8 @@ public: // Destroy ImGui Context model::ImGuiContextModel::Instance().DestroyContext(imGuiContextName_); + + threadPool_.join(); } void AddLayer(const std::string& id, diff --git a/scwx-qt/source/scwx/qt/view/radar_product_view.cpp b/scwx-qt/source/scwx/qt/view/radar_product_view.cpp index 634efed3..934922e3 100644 --- a/scwx-qt/source/scwx/qt/view/radar_product_view.cpp +++ b/scwx-qt/source/scwx/qt/view/radar_product_view.cpp @@ -35,7 +35,7 @@ public: radarProductManager_ {radarProductManager} { } - ~RadarProductViewImpl() = default; + ~RadarProductViewImpl() { threadPool_.join(); } boost::asio::thread_pool threadPool_ {1}; From 11c01e8538843b4a8a8d6d52009df57876e48432 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 18 Sep 2023 22:29:46 -0500 Subject: [PATCH 135/199] Add fontconfig dependency --- ACKNOWLEDGEMENTS.md | 1 + conanfile.py | 1 + scwx-qt/scwx-qt.cmake | 2 ++ 3 files changed, 4 insertions(+) diff --git a/ACKNOWLEDGEMENTS.md b/ACKNOWLEDGEMENTS.md index 1e70a3c7..4e6fffa0 100644 --- a/ACKNOWLEDGEMENTS.md +++ b/ACKNOWLEDGEMENTS.md @@ -18,6 +18,7 @@ Supercell Wx uses code from the following dependencies: | [CSS Color Parser](https://github.com/deanm/css-color-parser-js) | [MIT License](https://spdx.org/licenses/MIT.html) | Ported to C++ for MapLibre Native | | [Date](https://github.com/HowardHinnant/date) | [MIT License](https://spdx.org/licenses/MIT.html) | | [Dear ImGui](https://github.com/ocornut/imgui) | [MIT License](https://spdx.org/licenses/MIT.html) | +| [fontconfig](http://fontconfig.org/) | [MIT License](https://spdx.org/licenses/MIT.html) | | [FreeType](https://freetype.org/) | [Freetype Project License](https://spdx.org/licenses/FTL.html) | | [FreeType GL](https://github.com/rougier/freetype-gl) | [BSD 2-Clause with views sentence](https://spdx.org/licenses/BSD-2-Clause-Views.html) | | [GeographicLib](https://geographiclib.sourceforge.io/) | [MIT License](https://spdx.org/licenses/MIT.html) | diff --git a/conanfile.py b/conanfile.py index 4a803580..af9b5b90 100644 --- a/conanfile.py +++ b/conanfile.py @@ -4,6 +4,7 @@ class SupercellWxConan(ConanFile): settings = ("os", "compiler", "build_type", "arch") requires = ("boost/1.81.0", "cpr/1.9.3", + "fontconfig/2.14.2", "freetype/2.12.1", "geographiclib/1.52", "glew/2.2.0", diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index b3d73964..98647767 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -12,6 +12,7 @@ set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Boost) +find_package(Fontconfig) find_package(Freetype) find_package(geographiclib) find_package(glm) @@ -476,6 +477,7 @@ target_link_libraries(scwx-qt PUBLIC Qt${QT_VERSION_MAJOR}::Widgets Boost::timer qmaplibregl $<$:opengl32> + Fontconfig::Fontconfig freetype-gl GeographicLib::GeographicLib glm::glm From 3202476267838f151e2f3635dcf8ac1a3a8a6ca4 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 19 Sep 2023 23:15:02 -0500 Subject: [PATCH 136/199] Load bundled fonts into application font set --- .../scwx/qt/manager/resource_manager.cpp | 98 ++++++++++++++++++- 1 file changed, 97 insertions(+), 1 deletion(-) diff --git a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp index 1e694905..fb747c44 100644 --- a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp @@ -6,9 +6,14 @@ #include #include +#include #include +#include +#include #include +#include +#include #include namespace scwx @@ -23,6 +28,9 @@ namespace ResourceManager static const std::string logPrefix_ = "scwx::qt::manager::resource_manager"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); +static void InitializeFonts(); +static void InitializeFontCache(); +static void LoadFcApplicationFont(const std::string& fontFilename); static void LoadFonts(); static void LoadTextures(); @@ -31,6 +39,8 @@ static const std::unordered_map fontNames_ { {types::Font::din1451alt_g, ":/res/fonts/din1451alt_g.ttf"}, {types::Font::Inconsolata_Regular, ":/res/fonts/Inconsolata-Regular.ttf"}}; +static std::string fontCachePath_ {}; + static std::unordered_map fontIds_ {}; static std::unordered_map> fonts_ {}; @@ -38,11 +48,17 @@ void Initialize() { config::CountyDatabase::Initialize(); + InitializeFonts(); + LoadFonts(); LoadTextures(); } -void Shutdown() {} +void Shutdown() +{ + // Finalize Fontconfig + FcFini(); +} int FontId(types::Font font) { @@ -100,6 +116,38 @@ LoadImageResources(const std::vector& urlStrings) return images; } +static void InitializeFonts() +{ + // Create a directory to store local fonts + InitializeFontCache(); + + // Initialize Fontconfig + FcConfig* fcConfig = FcInitLoadConfigAndFonts(); + FcConfigSetCurrent(fcConfig); +} + +static void InitializeFontCache() +{ + std::string cachePath { + QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + .toStdString() + + "/fonts"}; + + fontCachePath_ = cachePath + "/"; + + if (!std::filesystem::exists(cachePath)) + { + std::error_code error; + if (!std::filesystem::create_directories(cachePath, error)) + { + logger_->error("Unable to create font cache directory: \"{}\" ({})", + cachePath, + error.message()); + fontCachePath_.clear(); + } + } +} + static void LoadFonts() { for (auto& fontName : fontNames_) @@ -108,6 +156,8 @@ static void LoadFonts() QString::fromStdString(fontName.second)); fontIds_.emplace(fontName.first, fontId); + LoadFcApplicationFont(fontName.second); + auto font = util::Font::Create(fontName.second); fonts_.emplace(fontName.first, font); } @@ -116,6 +166,52 @@ static void LoadFonts() fontAtlas->AddFontDefault(); } +static void LoadFcApplicationFont(const std::string& fontFilename) +{ + // If the font cache failed to create, don't attempt to cache any fonts + if (fontCachePath_.empty()) + { + return; + } + + // Make a copy of the font in the cache (if it doesn't exist) + QFile fontFile(QString::fromStdString(fontFilename)); + QFileInfo fontFileInfo(fontFile); + + QFile cacheFile(QString::fromStdString(fontCachePath_) + + fontFileInfo.fileName()); + QFileInfo cacheFileInfo(cacheFile); + std::string cacheFilename = cacheFile.fileName().toStdString(); + + if (fontFile.exists()) + { + // If the file has not been cached, or the font file size has changed + if (!cacheFile.exists() || fontFileInfo.size() != cacheFileInfo.size()) + { + logger_->info("Caching font: {}", fontFilename); + if (!fontFile.copy(cacheFile.fileName())) + { + logger_->error("Could not cache font: {}", fontFilename); + return; + } + } + } + else + { + logger_->error("Font does not exist: {}", fontFilename); + return; + } + + // Load the file into fontconfig (FcConfigAppFontAddFile) + FcBool result = FcConfigAppFontAddFile( + nullptr, reinterpret_cast(cacheFilename.c_str())); + if (!result) + { + logger_->error("Could not load font into fontconfig database", + fontFilename); + } +} + static void LoadTextures() { util::TextureAtlas& textureAtlas = util::TextureAtlas::Instance(); From 7edd512ff7b2d76696e89ed8e263713b02d7775c Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 21 Sep 2023 23:35:26 -0500 Subject: [PATCH 137/199] Use a vector to store bundled font names to preserve initialization order --- scwx-qt/source/scwx/qt/manager/resource_manager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp index fb747c44..21af6e43 100644 --- a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp @@ -34,7 +34,7 @@ static void LoadFcApplicationFont(const std::string& fontFilename); static void LoadFonts(); static void LoadTextures(); -static const std::unordered_map fontNames_ { +static const std::vector> fontNames_ { {types::Font::din1451alt, ":/res/fonts/din1451alt.ttf"}, {types::Font::din1451alt_g, ":/res/fonts/din1451alt_g.ttf"}, {types::Font::Inconsolata_Regular, ":/res/fonts/Inconsolata-Regular.ttf"}}; From d1a478ad12ffeb2ee2135298c3d4229fc49659f1 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 23 Sep 2023 00:44:35 -0500 Subject: [PATCH 138/199] Perform font matching from placefile --- .../scwx/qt/manager/placefile_manager.cpp | 32 ++++++++- .../scwx/qt/manager/resource_manager.cpp | 71 +++++++++++++++++++ .../scwx/qt/manager/resource_manager.hpp | 3 + wxdata/include/scwx/gr/placefile.hpp | 3 + 4 files changed, 106 insertions(+), 3 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index 77344524..9f10a714 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -51,8 +51,10 @@ public: void ReadPlacefileSettings(); void WritePlacefileSettings(); + static void + LoadFontResources(const std::shared_ptr& placefile); static std::vector> - LoadResources(const std::shared_ptr& placefile); + LoadImageResources(const std::shared_ptr& placefile); boost::asio::thread_pool threadPool_ {1u}; @@ -587,7 +589,8 @@ void PlacefileManager::Impl::PlacefileRecord::Update() if (updatedPlacefile != nullptr) { // Load placefile resources - auto newImages = Impl::LoadResources(updatedPlacefile); + Impl::LoadFontResources(updatedPlacefile); + auto newImages = Impl::LoadImageResources(updatedPlacefile); // Check the name matches, in case the name updated if (name_ == name) @@ -684,8 +687,31 @@ std::shared_ptr PlacefileManager::Instance() return placefileManager; } +void PlacefileManager::Impl::LoadFontResources( + const std::shared_ptr& placefile) +{ + auto fonts = placefile->fonts(); + + for (auto& font : fonts) + { + units::font_size::pixels size {font.second->pixels_}; + std::vector styles {}; + + if (font.second->IsBold()) + { + styles.push_back("bold"); + } + if (font.second->IsItalic()) + { + styles.push_back("italic"); + } + + ResourceManager::LoadFontResource(font.second->face_, styles, size); + } +} + std::vector> -PlacefileManager::Impl::LoadResources( +PlacefileManager::Impl::LoadImageResources( const std::shared_ptr& placefile) { const auto iconFiles = placefile->icon_files(); diff --git a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp index 21af6e43..10ad2557 100644 --- a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp @@ -34,6 +34,8 @@ static void LoadFcApplicationFont(const std::string& fontFilename); static void LoadFonts(); static void LoadTextures(); +static const std::string kFcTrueType_ {"TrueType"}; + static const std::vector> fontNames_ { {types::Font::din1451alt, ":/res/fonts/din1451alt.ttf"}, {types::Font::din1451alt_g, ":/res/fonts/din1451alt_g.ttf"}, @@ -44,6 +46,9 @@ static std::string fontCachePath_ {}; static std::unordered_map fontIds_ {}; static std::unordered_map> fonts_ {}; +static FcFontSet* fcFontSet_ {nullptr}; +static FcObjectSet* fcObjectSet_ {nullptr}; + void Initialize() { config::CountyDatabase::Initialize(); @@ -80,6 +85,72 @@ std::shared_ptr Font(types::Font font) return nullptr; } +void LoadFontResource(const std::string& family, + const std::vector& styles, + units::font_size::points size) +{ + const std::string styleString = fmt::format("{}", fmt::join(styles, " ")); + const std::string fontString = + fmt::format("{}-{}:{}", family, size.value(), styleString); + + logger_->debug("LoadFontResource: {}", fontString); + + // Build fontconfig pattern + FcPattern* pattern = FcPatternCreate(); + + FcPatternAddString( + pattern, FC_FAMILY, reinterpret_cast(family.c_str())); + FcPatternAddDouble(pattern, FC_SIZE, size.value()); + FcPatternAddString(pattern, + FC_FONTFORMAT, + reinterpret_cast(kFcTrueType_.c_str())); + + if (!styles.empty()) + { + FcPatternAddString(pattern, + FC_STYLE, + reinterpret_cast(styleString.c_str())); + } + + // Perform font pattern match substitution + FcConfigSubstitute(nullptr, pattern, FcMatchPattern); + FcDefaultSubstitute(pattern); + + // Find matching font + FcResult result; + FcPattern* match = FcFontMatch(nullptr, pattern, &result); + std::string fontFile {}; + + if (match != nullptr) + { + FcChar8* fcFamily; + FcChar8* fcStyle; + FcChar8* fcFile; + + // Match was found, get properties + if (FcPatternGetString(match, FC_FAMILY, 0, &fcFamily) == FcResultMatch && + FcPatternGetString(match, FC_STYLE, 0, &fcStyle) == FcResultMatch && + FcPatternGetString(match, FC_FILE, 0, &fcFile) == FcResultMatch) + { + fontFile = reinterpret_cast(fcFile); + + logger_->debug("Found matching font: {}:{} ({})", + reinterpret_cast(fcFamily), + reinterpret_cast(fcStyle), + fontFile); + } + } + + if (fontFile.empty()) + { + logger_->warn("Could not find matching font: {}", fontString); + } + + // Cleanup + FcPatternDestroy(match); + FcPatternDestroy(pattern); +} + std::shared_ptr LoadImageResource(const std::string& urlString) { diff --git a/scwx-qt/source/scwx/qt/manager/resource_manager.hpp b/scwx-qt/source/scwx/qt/manager/resource_manager.hpp index 7b8003c9..45115fd7 100644 --- a/scwx-qt/source/scwx/qt/manager/resource_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/resource_manager.hpp @@ -22,6 +22,9 @@ void Shutdown(); int FontId(types::Font font); std::shared_ptr Font(types::Font font); +void LoadFontResource(const std::string& family, + const std::vector& styles, + units::font_size::points size); std::shared_ptr LoadImageResource(const std::string& urlString); std::vector> diff --git a/wxdata/include/scwx/gr/placefile.hpp b/wxdata/include/scwx/gr/placefile.hpp index 017cc112..e9b381fe 100644 --- a/wxdata/include/scwx/gr/placefile.hpp +++ b/wxdata/include/scwx/gr/placefile.hpp @@ -67,6 +67,9 @@ public: std::size_t pixels_ {}; std::int32_t flags_ {}; std::string face_ {}; + + bool IsBold() { return flags_ & 1; } + bool IsItalic() { return flags_ & 2; } }; struct DrawItem From 190bd95781fca7088fe8e7af25fa82b168a511df Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 24 Sep 2023 00:36:27 -0500 Subject: [PATCH 139/199] Add ImGui Font class and dedicated Font Manager --- scwx-qt/scwx-qt.cmake | 8 +- .../source/scwx/qt/manager/font_manager.cpp | 45 ++++++++++ .../source/scwx/qt/manager/font_manager.hpp | 31 +++++++ scwx-qt/source/scwx/qt/types/imgui_font.cpp | 83 +++++++++++++++++++ scwx-qt/source/scwx/qt/types/imgui_font.hpp | 40 +++++++++ 5 files changed, 205 insertions(+), 2 deletions(-) create mode 100644 scwx-qt/source/scwx/qt/manager/font_manager.cpp create mode 100644 scwx-qt/source/scwx/qt/manager/font_manager.hpp create mode 100644 scwx-qt/source/scwx/qt/types/imgui_font.cpp create mode 100644 scwx-qt/source/scwx/qt/types/imgui_font.hpp diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 98647767..4b8cadda 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -74,7 +74,8 @@ set(SRC_GL_DRAW source/scwx/qt/gl/draw/draw_item.cpp source/scwx/qt/gl/draw/placefile_text.cpp source/scwx/qt/gl/draw/placefile_triangles.cpp source/scwx/qt/gl/draw/rectangle.cpp) -set(HDR_MANAGER source/scwx/qt/manager/placefile_manager.hpp +set(HDR_MANAGER source/scwx/qt/manager/font_manager.hpp + source/scwx/qt/manager/placefile_manager.hpp source/scwx/qt/manager/radar_product_manager.hpp source/scwx/qt/manager/radar_product_manager_notifier.hpp source/scwx/qt/manager/resource_manager.hpp @@ -82,7 +83,8 @@ set(HDR_MANAGER source/scwx/qt/manager/placefile_manager.hpp source/scwx/qt/manager/text_event_manager.hpp source/scwx/qt/manager/timeline_manager.hpp source/scwx/qt/manager/update_manager.hpp) -set(SRC_MANAGER source/scwx/qt/manager/placefile_manager.cpp +set(SRC_MANAGER source/scwx/qt/manager/font_manager.cpp + source/scwx/qt/manager/placefile_manager.cpp source/scwx/qt/manager/radar_product_manager.cpp source/scwx/qt/manager/radar_product_manager_notifier.cpp source/scwx/qt/manager/resource_manager.cpp @@ -158,6 +160,7 @@ set(SRC_SETTINGS source/scwx/qt/settings/general_settings.cpp set(HDR_TYPES source/scwx/qt/types/alert_types.hpp source/scwx/qt/types/font_types.hpp source/scwx/qt/types/github_types.hpp + source/scwx/qt/types/imgui_font.hpp source/scwx/qt/types/map_types.hpp source/scwx/qt/types/qt_types.hpp source/scwx/qt/types/radar_product_record.hpp @@ -165,6 +168,7 @@ set(HDR_TYPES source/scwx/qt/types/alert_types.hpp source/scwx/qt/types/text_types.hpp) set(SRC_TYPES source/scwx/qt/types/alert_types.cpp source/scwx/qt/types/github_types.cpp + source/scwx/qt/types/imgui_font.cpp source/scwx/qt/types/map_types.cpp source/scwx/qt/types/radar_product_record.cpp source/scwx/qt/types/text_event_key.cpp diff --git a/scwx-qt/source/scwx/qt/manager/font_manager.cpp b/scwx-qt/source/scwx/qt/manager/font_manager.cpp new file mode 100644 index 00000000..77dbf149 --- /dev/null +++ b/scwx-qt/source/scwx/qt/manager/font_manager.cpp @@ -0,0 +1,45 @@ +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace manager +{ + +static const std::string logPrefix_ = "scwx::qt::manager::font_manager"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +class FontManager::Impl +{ +public: + explicit Impl() {} + ~Impl() {} +}; + +FontManager::FontManager() : p(std::make_unique()) {} + +FontManager::~FontManager() {}; + +std::shared_ptr FontManager::Instance() +{ + static std::weak_ptr fontManagerReference_ {}; + static std::mutex instanceMutex_ {}; + + std::unique_lock lock(instanceMutex_); + + std::shared_ptr fontManager = fontManagerReference_.lock(); + + if (fontManager == nullptr) + { + fontManager = std::make_shared(); + fontManagerReference_ = fontManager; + } + + return fontManager; +} + +} // namespace manager +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/manager/font_manager.hpp b/scwx-qt/source/scwx/qt/manager/font_manager.hpp new file mode 100644 index 00000000..4699a4b5 --- /dev/null +++ b/scwx-qt/source/scwx/qt/manager/font_manager.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace manager +{ + +class FontManager : public QObject +{ + Q_OBJECT + +public: + explicit FontManager(); + ~FontManager(); + + static std::shared_ptr Instance(); + +private: + class Impl; + std::unique_ptr p; +}; + +} // namespace manager +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/types/imgui_font.cpp b/scwx-qt/source/scwx/qt/types/imgui_font.cpp new file mode 100644 index 00000000..b2b42acd --- /dev/null +++ b/scwx-qt/source/scwx/qt/types/imgui_font.cpp @@ -0,0 +1,83 @@ +// Disable strncpy warning +#define _CRT_SECURE_NO_WARNINGS + +#include +#include +#include + +#include +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace types +{ + +static const std::string logPrefix_ = "scwx::qt::types::imgui_font"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +class ImGuiFont::Impl +{ +public: + explicit Impl(const std::string& fontName, + const std::string& fontData, + units::pixels size) : + fontName_ {fontName}, size_ {size} + { + CreateImGuiFont(fontData); + } + + ~Impl() {} + + void CreateImGuiFont(const std::string& fontData); + + const std::string fontName_; + const units::pixels size_; + + ImFont* imFont_ {nullptr}; +}; + +ImGuiFont::ImGuiFont(const std::string& fontName, + const std::string& fontData, + units::pixels size) : + p(std::make_unique(fontName, fontData, size)) +{ +} +ImGuiFont::~ImGuiFont() = default; + +void ImGuiFont::Impl::CreateImGuiFont(const std::string& fontData) +{ + logger_->debug("Creating Font: {}", fontName_); + + ImFontAtlas* fontAtlas = model::ImGuiContextModel::Instance().font_atlas(); + ImFontConfig fontConfig {}; + + const float sizePixels = static_cast(size_.value()); + + // Do not transfer ownership of font data to ImGui, makes const_cast safe + fontConfig.FontDataOwnedByAtlas = false; + + // Assign name to font + strncpy(fontConfig.Name, fontName_.c_str(), sizeof(fontConfig.Name) - 1); + fontConfig.Name[sizeof(fontConfig.Name) - 1] = 0; + + imFont_ = fontAtlas->AddFontFromMemoryTTF( + const_cast(static_cast(fontData.c_str())), + static_cast(std::clamp( + fontData.size(), 0, std::numeric_limits::max())), + sizePixels, + &fontConfig); +} + +ImFont* ImGuiFont::font() +{ + return p->imFont_; +} + +} // namespace types +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/types/imgui_font.hpp b/scwx-qt/source/scwx/qt/types/imgui_font.hpp new file mode 100644 index 00000000..8c07cd95 --- /dev/null +++ b/scwx-qt/source/scwx/qt/types/imgui_font.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +#include + +struct ImFont; + +namespace scwx +{ +namespace qt +{ +namespace types +{ + +class ImGuiFont +{ +public: + explicit ImGuiFont(const std::string& fontName, + const std::string& fontData, + units::pixels size); + ~ImGuiFont(); + + ImGuiFont(const ImGuiFont&) = delete; + ImGuiFont& operator=(const ImGuiFont&) = delete; + + ImGuiFont(ImGuiFont&&) = delete; + ImGuiFont& operator=(ImGuiFont&&) = delete; + + ImFont* font(); + +private: + class Impl; + std::unique_ptr p; +}; + +} // namespace types +} // namespace qt +} // namespace scwx From c807188b2b3f22b44434e5e6a32dd93446951635 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 24 Sep 2023 01:11:04 -0500 Subject: [PATCH 140/199] Refactoring fontconfig from Resource Manager to Font Manager --- .../source/scwx/qt/manager/font_manager.cpp | 202 +++++++++++++++++- .../source/scwx/qt/manager/font_manager.hpp | 9 +- .../scwx/qt/manager/placefile_manager.cpp | 3 +- .../scwx/qt/manager/resource_manager.cpp | 172 +-------------- .../scwx/qt/manager/resource_manager.hpp | 3 - 5 files changed, 206 insertions(+), 183 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/font_manager.cpp b/scwx-qt/source/scwx/qt/manager/font_manager.cpp index 77dbf149..64b93d78 100644 --- a/scwx-qt/source/scwx/qt/manager/font_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/font_manager.cpp @@ -1,6 +1,13 @@ #include #include +#include + +#include +#include +#include +#include + namespace scwx { namespace qt @@ -11,33 +18,206 @@ namespace manager static const std::string logPrefix_ = "scwx::qt::manager::font_manager"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); +static const std::string kFcTrueType_ {"TrueType"}; + +struct FontRecord +{ + std::string family_ {}; + std::string style_ {}; + std::string filename_ {}; +}; + class FontManager::Impl { public: - explicit Impl() {} - ~Impl() {} + explicit Impl() + { + InitializeFontCache(); + InitializeFontconfig(); + } + ~Impl() { FinalizeFontconfig(); } + + void FinalizeFontconfig(); + void InitializeFontCache(); + void InitializeFontconfig(); + + static FontRecord MatchFontFile(const std::string& family, + const std::vector& styles); + + std::string fontCachePath_ {}; }; FontManager::FontManager() : p(std::make_unique()) {} FontManager::~FontManager() {}; -std::shared_ptr FontManager::Instance() +std::shared_ptr +FontManager::GetImGuiFont(const std::string& family, + const std::vector& styles, + units::font_size::points size) { - static std::weak_ptr fontManagerReference_ {}; - static std::mutex instanceMutex_ {}; + const std::string styleString = fmt::format("{}", fmt::join(styles, " ")); + const std::string fontString = + fmt::format("{}-{}:{}", family, size.value(), styleString); - std::unique_lock lock(instanceMutex_); + logger_->debug("LoadFontResource: {}", fontString); - std::shared_ptr fontManager = fontManagerReference_.lock(); + FontRecord fontRecord = Impl::MatchFontFile(family, styles); - if (fontManager == nullptr) + // TODO: + Q_UNUSED(fontRecord); + + return nullptr; +} + +void FontManager::LoadApplicationFont(const std::string& filename) +{ + // If the font cache failed to create, don't attempt to cache any fonts + if (p->fontCachePath_.empty()) { - fontManager = std::make_shared(); - fontManagerReference_ = fontManager; + return; } - return fontManager; + // Make a copy of the font in the cache (if it doesn't exist) + QFile fontFile(QString::fromStdString(filename)); + QFileInfo fontFileInfo(fontFile); + + QFile cacheFile(QString::fromStdString(p->fontCachePath_) + + fontFileInfo.fileName()); + QFileInfo cacheFileInfo(cacheFile); + std::string cacheFilename = cacheFile.fileName().toStdString(); + + if (fontFile.exists()) + { + // If the file has not been cached, or the font file size has changed + if (!cacheFile.exists() || fontFileInfo.size() != cacheFileInfo.size()) + { + logger_->info("Caching font: {}", filename); + if (!fontFile.copy(cacheFile.fileName())) + { + logger_->error("Could not cache font: {}", filename); + return; + } + } + } + else + { + logger_->error("Font does not exist: {}", filename); + return; + } + + // Load the file into fontconfig + FcBool result = FcConfigAppFontAddFile( + nullptr, reinterpret_cast(cacheFilename.c_str())); + if (!result) + { + logger_->error("Could not load font into fontconfig database", filename); + } +} + +void FontManager::Impl::InitializeFontCache() +{ + std::string cachePath { + QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + .toStdString() + + "/fonts"}; + + fontCachePath_ = cachePath + "/"; + + if (!std::filesystem::exists(cachePath)) + { + std::error_code error; + if (!std::filesystem::create_directories(cachePath, error)) + { + logger_->error("Unable to create font cache directory: \"{}\" ({})", + cachePath, + error.message()); + fontCachePath_.clear(); + } + } +} + +void FontManager::Impl::InitializeFontconfig() +{ + FcConfig* fcConfig = FcInitLoadConfigAndFonts(); + FcConfigSetCurrent(fcConfig); +} + +void FontManager::Impl::FinalizeFontconfig() +{ + FcFini(); +} + +FontRecord +FontManager::Impl::MatchFontFile(const std::string& family, + const std::vector& styles) +{ + const std::string styleString = fmt::format("{}", fmt::join(styles, " ")); + const std::string fontString = fmt::format("{}:{}", family, styleString); + + // Build fontconfig pattern + FcPattern* pattern = FcPatternCreate(); + + FcPatternAddString( + pattern, FC_FAMILY, reinterpret_cast(family.c_str())); + FcPatternAddString(pattern, + FC_FONTFORMAT, + reinterpret_cast(kFcTrueType_.c_str())); + + if (!styles.empty()) + { + FcPatternAddString(pattern, + FC_STYLE, + reinterpret_cast(styleString.c_str())); + } + + // Perform font pattern match substitution + FcConfigSubstitute(nullptr, pattern, FcMatchPattern); + FcDefaultSubstitute(pattern); + + // Find matching font + FcResult result; + FcPattern* match = FcFontMatch(nullptr, pattern, &result); + FontRecord record {}; + + if (match != nullptr) + { + FcChar8* fcFamily; + FcChar8* fcStyle; + FcChar8* fcFile; + + // Match was found, get properties + if (FcPatternGetString(match, FC_FAMILY, 0, &fcFamily) == FcResultMatch && + FcPatternGetString(match, FC_STYLE, 0, &fcStyle) == FcResultMatch && + FcPatternGetString(match, FC_FILE, 0, &fcFile) == FcResultMatch) + { + record.family_ = reinterpret_cast(fcFamily); + record.style_ = reinterpret_cast(fcStyle); + record.filename_ = reinterpret_cast(fcFile); + + logger_->debug("Found matching font: {}:{} ({})", + record.family_, + record.style_, + record.filename_); + } + } + + if (record.filename_.empty()) + { + logger_->warn("Could not find matching font: {}", fontString); + } + + // Cleanup + FcPatternDestroy(match); + FcPatternDestroy(pattern); + + return record; +} + +FontManager& FontManager::Instance() +{ + static FontManager instance_ {}; + return instance_; } } // namespace manager diff --git a/scwx-qt/source/scwx/qt/manager/font_manager.hpp b/scwx-qt/source/scwx/qt/manager/font_manager.hpp index 4699a4b5..0a4a812e 100644 --- a/scwx-qt/source/scwx/qt/manager/font_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/font_manager.hpp @@ -19,7 +19,14 @@ public: explicit FontManager(); ~FontManager(); - static std::shared_ptr Instance(); + std::shared_ptr + GetImGuiFont(const std::string& family, + const std::vector& styles, + units::font_size::points size); + + void LoadApplicationFont(const std::string& filename); + + static FontManager& Instance(); private: class Impl; diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index 9f10a714..d721e5cb 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -706,7 +707,7 @@ void PlacefileManager::Impl::LoadFontResources( styles.push_back("italic"); } - ResourceManager::LoadFontResource(font.second->face_, styles, size); + FontManager::Instance().GetImGuiFont(font.second->face_, styles, size); } } diff --git a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp index 10ad2557..a622ea48 100644 --- a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -6,14 +7,9 @@ #include #include -#include #include -#include -#include #include -#include -#include #include namespace scwx @@ -28,42 +24,26 @@ namespace ResourceManager static const std::string logPrefix_ = "scwx::qt::manager::resource_manager"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); -static void InitializeFonts(); -static void InitializeFontCache(); -static void LoadFcApplicationFont(const std::string& fontFilename); static void LoadFonts(); static void LoadTextures(); -static const std::string kFcTrueType_ {"TrueType"}; - static const std::vector> fontNames_ { {types::Font::din1451alt, ":/res/fonts/din1451alt.ttf"}, {types::Font::din1451alt_g, ":/res/fonts/din1451alt_g.ttf"}, {types::Font::Inconsolata_Regular, ":/res/fonts/Inconsolata-Regular.ttf"}}; -static std::string fontCachePath_ {}; - static std::unordered_map fontIds_ {}; static std::unordered_map> fonts_ {}; -static FcFontSet* fcFontSet_ {nullptr}; -static FcObjectSet* fcObjectSet_ {nullptr}; - void Initialize() { config::CountyDatabase::Initialize(); - InitializeFonts(); - LoadFonts(); LoadTextures(); } -void Shutdown() -{ - // Finalize Fontconfig - FcFini(); -} +void Shutdown() {} int FontId(types::Font font) { @@ -85,72 +65,6 @@ std::shared_ptr Font(types::Font font) return nullptr; } -void LoadFontResource(const std::string& family, - const std::vector& styles, - units::font_size::points size) -{ - const std::string styleString = fmt::format("{}", fmt::join(styles, " ")); - const std::string fontString = - fmt::format("{}-{}:{}", family, size.value(), styleString); - - logger_->debug("LoadFontResource: {}", fontString); - - // Build fontconfig pattern - FcPattern* pattern = FcPatternCreate(); - - FcPatternAddString( - pattern, FC_FAMILY, reinterpret_cast(family.c_str())); - FcPatternAddDouble(pattern, FC_SIZE, size.value()); - FcPatternAddString(pattern, - FC_FONTFORMAT, - reinterpret_cast(kFcTrueType_.c_str())); - - if (!styles.empty()) - { - FcPatternAddString(pattern, - FC_STYLE, - reinterpret_cast(styleString.c_str())); - } - - // Perform font pattern match substitution - FcConfigSubstitute(nullptr, pattern, FcMatchPattern); - FcDefaultSubstitute(pattern); - - // Find matching font - FcResult result; - FcPattern* match = FcFontMatch(nullptr, pattern, &result); - std::string fontFile {}; - - if (match != nullptr) - { - FcChar8* fcFamily; - FcChar8* fcStyle; - FcChar8* fcFile; - - // Match was found, get properties - if (FcPatternGetString(match, FC_FAMILY, 0, &fcFamily) == FcResultMatch && - FcPatternGetString(match, FC_STYLE, 0, &fcStyle) == FcResultMatch && - FcPatternGetString(match, FC_FILE, 0, &fcFile) == FcResultMatch) - { - fontFile = reinterpret_cast(fcFile); - - logger_->debug("Found matching font: {}:{} ({})", - reinterpret_cast(fcFamily), - reinterpret_cast(fcStyle), - fontFile); - } - } - - if (fontFile.empty()) - { - logger_->warn("Could not find matching font: {}", fontString); - } - - // Cleanup - FcPatternDestroy(match); - FcPatternDestroy(pattern); -} - std::shared_ptr LoadImageResource(const std::string& urlString) { @@ -187,47 +101,17 @@ LoadImageResources(const std::vector& urlStrings) return images; } -static void InitializeFonts() -{ - // Create a directory to store local fonts - InitializeFontCache(); - - // Initialize Fontconfig - FcConfig* fcConfig = FcInitLoadConfigAndFonts(); - FcConfigSetCurrent(fcConfig); -} - -static void InitializeFontCache() -{ - std::string cachePath { - QStandardPaths::writableLocation(QStandardPaths::CacheLocation) - .toStdString() + - "/fonts"}; - - fontCachePath_ = cachePath + "/"; - - if (!std::filesystem::exists(cachePath)) - { - std::error_code error; - if (!std::filesystem::create_directories(cachePath, error)) - { - logger_->error("Unable to create font cache directory: \"{}\" ({})", - cachePath, - error.message()); - fontCachePath_.clear(); - } - } -} - static void LoadFonts() { + auto& fontManager = FontManager::Instance(); + for (auto& fontName : fontNames_) { int fontId = QFontDatabase::addApplicationFont( QString::fromStdString(fontName.second)); fontIds_.emplace(fontName.first, fontId); - LoadFcApplicationFont(fontName.second); + fontManager.LoadApplicationFont(fontName.second); auto font = util::Font::Create(fontName.second); fonts_.emplace(fontName.first, font); @@ -237,52 +121,6 @@ static void LoadFonts() fontAtlas->AddFontDefault(); } -static void LoadFcApplicationFont(const std::string& fontFilename) -{ - // If the font cache failed to create, don't attempt to cache any fonts - if (fontCachePath_.empty()) - { - return; - } - - // Make a copy of the font in the cache (if it doesn't exist) - QFile fontFile(QString::fromStdString(fontFilename)); - QFileInfo fontFileInfo(fontFile); - - QFile cacheFile(QString::fromStdString(fontCachePath_) + - fontFileInfo.fileName()); - QFileInfo cacheFileInfo(cacheFile); - std::string cacheFilename = cacheFile.fileName().toStdString(); - - if (fontFile.exists()) - { - // If the file has not been cached, or the font file size has changed - if (!cacheFile.exists() || fontFileInfo.size() != cacheFileInfo.size()) - { - logger_->info("Caching font: {}", fontFilename); - if (!fontFile.copy(cacheFile.fileName())) - { - logger_->error("Could not cache font: {}", fontFilename); - return; - } - } - } - else - { - logger_->error("Font does not exist: {}", fontFilename); - return; - } - - // Load the file into fontconfig (FcConfigAppFontAddFile) - FcBool result = FcConfigAppFontAddFile( - nullptr, reinterpret_cast(cacheFilename.c_str())); - if (!result) - { - logger_->error("Could not load font into fontconfig database", - fontFilename); - } -} - static void LoadTextures() { util::TextureAtlas& textureAtlas = util::TextureAtlas::Instance(); diff --git a/scwx-qt/source/scwx/qt/manager/resource_manager.hpp b/scwx-qt/source/scwx/qt/manager/resource_manager.hpp index 45115fd7..7b8003c9 100644 --- a/scwx-qt/source/scwx/qt/manager/resource_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/resource_manager.hpp @@ -22,9 +22,6 @@ void Shutdown(); int FontId(types::Font font); std::shared_ptr Font(types::Font font); -void LoadFontResource(const std::string& family, - const std::vector& styles, - units::font_size::points size); std::shared_ptr LoadImageResource(const std::string& urlString); std::vector> From e0aa327bb729e354a2372b0215b6eedec7d91044 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 25 Sep 2023 00:02:36 -0500 Subject: [PATCH 141/199] Add font selection skeleton --- scwx-qt/source/scwx/qt/ui/settings_dialog.cpp | 18 ++ scwx-qt/source/scwx/qt/ui/settings_dialog.ui | 208 ++++++++++++++++++ 2 files changed, 226 insertions(+) diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp index 9bb52e00..e53d033d 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include namespace scwx @@ -84,6 +85,7 @@ public: explicit SettingsDialogImpl(SettingsDialog* self) : self_ {self}, radarSiteDialog_ {new RadarSiteDialog(self)}, + fontDialog_ {new QFontDialog(self)}, settings_ {std::initializer_list { &defaultRadarSite_, &fontSizes_, @@ -113,6 +115,9 @@ public: QColor(QString::fromStdString( paletteSettings.alert_color(phenomenon, false).GetDefault()))); } + + // Configure font dialog + fontDialog_->setOptions(QFontDialog::FontDialogOption::ScalableFonts); } ~SettingsDialogImpl() = default; @@ -146,6 +151,7 @@ public: SettingsDialog* self_; PlacefileSettingsWidget* placefileSettingsWidget_ {nullptr}; RadarSiteDialog* radarSiteDialog_; + QFontDialog* fontDialog_; settings::SettingsInterface defaultRadarSite_ {}; settings::SettingsInterface> fontSizes_ {}; @@ -232,6 +238,18 @@ void SettingsDialogImpl::ConnectSignals() } }); + QObject::connect(self_->ui->fontSelectButton, + &QAbstractButton::clicked, + self_, + [this]() { fontDialog_->show(); }); + + QObject::connect( + fontDialog_, + &QFontDialog::fontSelected, + self_, + [this](const QFont& font) + { logger_->debug("Selected font: {}", font.toString().toStdString()); }); + // Update the Radar Site dialog "map" location with the currently selected // radar site auto& defaultRadarSite = *defaultRadarSite_.GetSettingsVariable(); diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui index 15d5d1ec..4e40cdb3 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui @@ -438,6 +438,214 @@ + + + + QFrame::StyledPanel + + + QFrame::Plain + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Reset All Fonts + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Display Item: + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Tornado Warning expires in 15 minutes + + + Qt::AlignCenter + + + true + + + + + + + + + + ... + + + + + + + [Style] + + + + + + + [Font Name] + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + [Size] + + + + + + + Font: + + + + + + + Style: + + + + + + + Size: + + + + + + + Preview: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + From ad1646d72517ebc441af59c0a4180b41ee092758 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 25 Sep 2023 23:35:38 -0500 Subject: [PATCH 142/199] Initial setup for font settings --- .../scwx/qt/settings/settings_interface.hpp | 1 + .../source/scwx/qt/settings/text_settings.cpp | 80 ++++++++++++++++++- .../source/scwx/qt/settings/text_settings.hpp | 10 ++- scwx-qt/source/scwx/qt/types/text_types.cpp | 28 +++++++ scwx-qt/source/scwx/qt/types/text_types.hpp | 12 +++ scwx-qt/source/scwx/qt/ui/settings_dialog.cpp | 44 ++++++++++ scwx-qt/source/scwx/qt/ui/settings_dialog.ui | 40 +++++----- 7 files changed, 192 insertions(+), 23 deletions(-) diff --git a/scwx-qt/source/scwx/qt/settings/settings_interface.hpp b/scwx-qt/source/scwx/qt/settings/settings_interface.hpp index 030b8996..4ba1ea0e 100644 --- a/scwx-qt/source/scwx/qt/settings/settings_interface.hpp +++ b/scwx-qt/source/scwx/qt/settings/settings_interface.hpp @@ -103,6 +103,7 @@ private: #ifdef SETTINGS_INTERFACE_IMPLEMENTATION template class SettingsInterface; +template class SettingsInterface; template class SettingsInterface; template class SettingsInterface; diff --git a/scwx-qt/source/scwx/qt/settings/text_settings.cpp b/scwx-qt/source/scwx/qt/settings/text_settings.cpp index b96f94f4..6d4ceafa 100644 --- a/scwx-qt/source/scwx/qt/settings/text_settings.cpp +++ b/scwx-qt/source/scwx/qt/settings/text_settings.cpp @@ -12,10 +12,34 @@ namespace settings static const std::string logPrefix_ = "scwx::qt::settings::text_settings"; +static const std::string kAlteDIN1451Mittelscrhift_ { + "Alte DIN 1451 Mittelschrift"}; +static const std::string kInconsolata_ {"Inconsolata"}; + +static const std::string kRegular_ {"Regular"}; + +static const std::unordered_map + kDefaultFontFamily_ { + {types::FontCategory::Default, kAlteDIN1451Mittelscrhift_}, + {types::FontCategory::Tooltip, kInconsolata_}}; +static const std::unordered_map + kDefaultFontStyle_ {{types::FontCategory::Default, kRegular_}, + {types::FontCategory::Tooltip, kRegular_}}; +static const std::unordered_map + kDefaultFontPointSize_ {{types::FontCategory::Default, 12.0}, + {types::FontCategory::Tooltip, 10.5}}; + class TextSettings::Impl { public: - explicit Impl() + struct FontData + { + SettingsVariable fontFamily_ {"font_family"}; + SettingsVariable fontStyle_ {"font_style"}; + SettingsVariable fontPointSize_ {"font_point_size"}; + }; + + explicit Impl(TextSettings* self) : self_ {self} { std::string defaultTooltipMethodValue = types::GetTooltipMethodName(types::TooltipMethod::ImGui); @@ -47,16 +71,24 @@ public: // No match found, invalid return false; }); + + InitializeFontVariables(); } ~Impl() {} + void InitializeFontVariables(); + + TextSettings* self_; + + std::unordered_map fontData_ {}; + SettingsVariable hoverTextWrap_ {"hover_text_wrap"}; SettingsVariable tooltipMethod_ {"tooltip_method"}; }; TextSettings::TextSettings() : - SettingsCategory("text"), p(std::make_unique()) + SettingsCategory("text"), p(std::make_unique(this)) { RegisterVariables({&p->hoverTextWrap_, &p->tooltipMethod_}); SetDefaults(); @@ -66,6 +98,50 @@ TextSettings::~TextSettings() = default; TextSettings::TextSettings(TextSettings&&) noexcept = default; TextSettings& TextSettings::operator=(TextSettings&&) noexcept = default; +void TextSettings::Impl::InitializeFontVariables() +{ + for (auto fontCategory : types::FontCategoryIterator()) + { + auto result = fontData_.emplace(fontCategory, FontData {}); + auto& pair = *result.first; + auto& font = pair.second; + + font.fontFamily_.SetDefault(kDefaultFontFamily_.at(fontCategory)); + font.fontStyle_.SetDefault(kDefaultFontStyle_.at(fontCategory)); + font.fontPointSize_.SetDefault(kDefaultFontPointSize_.at(fontCategory)); + + // String values must not be empty + font.fontFamily_.SetValidator([](const std::string& value) + { return !value.empty(); }); + font.fontStyle_.SetValidator([](const std::string& value) + { return !value.empty(); }); + + // Font point size must be between 6 and 72 + font.fontPointSize_.SetMinimum(6.0); + font.fontPointSize_.SetMaximum(72.0); + + // TODO: Variable registration + } +} + +SettingsVariable& +TextSettings::font_family(types::FontCategory fontCategory) const +{ + return p->fontData_.at(fontCategory).fontFamily_; +} + +SettingsVariable& +TextSettings::font_style(types::FontCategory fontCategory) const +{ + return p->fontData_.at(fontCategory).fontStyle_; +} + +SettingsVariable& +TextSettings::font_point_size(types::FontCategory fontCategory) const +{ + return p->fontData_.at(fontCategory).fontPointSize_; +} + SettingsVariable& TextSettings::hover_text_wrap() const { return p->hoverTextWrap_; diff --git a/scwx-qt/source/scwx/qt/settings/text_settings.hpp b/scwx-qt/source/scwx/qt/settings/text_settings.hpp index 42399f99..a0347b4f 100644 --- a/scwx-qt/source/scwx/qt/settings/text_settings.hpp +++ b/scwx-qt/source/scwx/qt/settings/text_settings.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -25,8 +26,15 @@ public: TextSettings(TextSettings&&) noexcept; TextSettings& operator=(TextSettings&&) noexcept; + SettingsVariable& + font_family(types::FontCategory fontCategory) const; + SettingsVariable& + font_style(types::FontCategory fontCategory) const; + SettingsVariable& + font_point_size(types::FontCategory fontCategory) const; + SettingsVariable& hover_text_wrap() const; - SettingsVariable& tooltip_method() const; + SettingsVariable& tooltip_method() const; static TextSettings& Instance(); diff --git a/scwx-qt/source/scwx/qt/types/text_types.cpp b/scwx-qt/source/scwx/qt/types/text_types.cpp index e708cf63..f994dab9 100644 --- a/scwx-qt/source/scwx/qt/types/text_types.cpp +++ b/scwx-qt/source/scwx/qt/types/text_types.cpp @@ -11,12 +11,40 @@ namespace qt namespace types { +static const std::unordered_map fontCategoryName_ { + {FontCategory::Default, "Default"}, + {FontCategory::Tooltip, "Tooltip"}, + {FontCategory::Unknown, "?"}}; + static const std::unordered_map tooltipMethodName_ { {TooltipMethod::ImGui, "ImGui"}, {TooltipMethod::QToolTip, "Native Tooltip"}, {TooltipMethod::QLabel, "Floating Label"}, {TooltipMethod::Unknown, "?"}}; +FontCategory GetFontCategory(const std::string& name) +{ + auto result = + std::find_if(fontCategoryName_.cbegin(), + fontCategoryName_.cend(), + [&](const std::pair& pair) -> bool + { return boost::iequals(pair.second, name); }); + + if (result != fontCategoryName_.cend()) + { + return result->first; + } + else + { + return FontCategory::Unknown; + } +} + +std::string GetFontCategoryName(FontCategory fontCategory) +{ + return fontCategoryName_.at(fontCategory); +} + TooltipMethod GetTooltipMethod(const std::string& name) { auto result = std::find_if( diff --git a/scwx-qt/source/scwx/qt/types/text_types.hpp b/scwx-qt/source/scwx/qt/types/text_types.hpp index 98025a86..07c1ea00 100644 --- a/scwx-qt/source/scwx/qt/types/text_types.hpp +++ b/scwx-qt/source/scwx/qt/types/text_types.hpp @@ -11,6 +11,16 @@ namespace qt namespace types { +enum class FontCategory +{ + Default, + Tooltip, + Unknown +}; +typedef scwx::util:: + Iterator + FontCategoryIterator; + enum class TooltipMethod { ImGui, @@ -22,6 +32,8 @@ typedef scwx::util:: Iterator TooltipMethodIterator; +FontCategory GetFontCategory(const std::string& name); +std::string GetFontCategoryName(FontCategory fontCategory); TooltipMethod GetTooltipMethod(const std::string& name); std::string GetTooltipMethodName(TooltipMethod tooltipMethod); diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp index e53d033d..c2449e9f 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include namespace scwx @@ -86,6 +87,7 @@ public: self_ {self}, radarSiteDialog_ {new RadarSiteDialog(self)}, fontDialog_ {new QFontDialog(self)}, + fontCategoryModel_ {new QStandardItemModel(self)}, settings_ {std::initializer_list { &defaultRadarSite_, &fontSizes_, @@ -153,6 +155,8 @@ public: RadarSiteDialog* radarSiteDialog_; QFontDialog* fontDialog_; + QStandardItemModel* fontCategoryModel_; + settings::SettingsInterface defaultRadarSite_ {}; settings::SettingsInterface> fontSizes_ {}; settings::SettingsInterface gridWidth_ {}; @@ -173,6 +177,15 @@ public: settings::SettingsInterface> inactiveAlertColors_ {}; + std::unordered_map> + fontFamilies_ {}; + std::unordered_map> + fontStyles_ {}; + std::unordered_map> + fontPointSizes_ {}; + settings::SettingsInterface hoverTextWrap_ {}; settings::SettingsInterface tooltipMethod_ {}; @@ -663,6 +676,37 @@ void SettingsDialogImpl::SetupTextTab() { settings::TextSettings& textSettings = settings::TextSettings::Instance(); + self_->ui->fontListView->setModel(fontCategoryModel_); + for (const auto& fontCategory : types::FontCategoryIterator()) + { + // Add font category to list view + fontCategoryModel_->appendRow(new QStandardItem( + QString::fromStdString(types::GetFontCategoryName(fontCategory)))); + + // Create settings interface + auto fontFamilyResult = fontFamilies_.emplace( + fontCategory, settings::SettingsInterface {}); + auto fontStyleResult = fontStyles_.emplace( + fontCategory, settings::SettingsInterface {}); + auto fontSizeResult = fontPointSizes_.emplace( + fontCategory, settings::SettingsInterface {}); + + auto& fontFamily = (*fontFamilyResult.first).second; + auto& fontStyle = (*fontStyleResult.first).second; + auto& fontSize = (*fontSizeResult.first).second; + + // Add to settings list + settings_.push_back(&fontFamily); + settings_.push_back(&fontStyle); + settings_.push_back(&fontSize); + + // Set settings variables + fontFamily.SetSettingsVariable(textSettings.font_family(fontCategory)); + fontStyle.SetSettingsVariable(textSettings.font_style(fontCategory)); + fontSize.SetSettingsVariable(textSettings.font_point_size(fontCategory)); + } + self_->ui->fontListView->setCurrentIndex(fontCategoryModel_->index(0, 0)); + hoverTextWrap_.SetSettingsVariable(textSettings.hover_text_wrap()); hoverTextWrap_.SetEditWidget(self_->ui->hoverTextWrapSpinBox); hoverTextWrap_.SetResetButton(self_->ui->resetHoverTextWrapButton); diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui index 4e40cdb3..e921f38a 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui @@ -469,13 +469,6 @@ 0 - - - Reset All Fonts - - - - Qt::Horizontal @@ -488,10 +481,17 @@ - + + + + Reset All Fonts + + + + - + Display Item: @@ -581,17 +581,6 @@ - - - - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg - - - @@ -640,6 +629,17 @@ + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + From d82fb666f9bc4369631548e5d726854538b35bb6 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 26 Sep 2023 23:23:38 -0500 Subject: [PATCH 143/199] Register font subcategories with text settings --- .../scwx/qt/settings/settings_category.cpp | 85 ++++++++++++++++++- .../scwx/qt/settings/settings_category.hpp | 3 +- .../source/scwx/qt/settings/text_settings.cpp | 10 ++- test/data | 2 +- 4 files changed, 95 insertions(+), 5 deletions(-) diff --git a/scwx-qt/source/scwx/qt/settings/settings_category.cpp b/scwx-qt/source/scwx/qt/settings/settings_category.cpp index 75daf41d..e6c929f8 100644 --- a/scwx-qt/source/scwx/qt/settings/settings_category.cpp +++ b/scwx-qt/source/scwx/qt/settings/settings_category.cpp @@ -2,6 +2,8 @@ #include #include +#include + namespace scwx { namespace qt @@ -21,6 +23,8 @@ public: const std::string name_; + std::vector>> + subcategoryArrays_; std::vector variables_; }; @@ -41,6 +45,16 @@ std::string SettingsCategory::name() const void SettingsCategory::SetDefaults() { + // Set subcategory array defaults + for (auto& subcategoryArray : p->subcategoryArrays_) + { + for (auto& subcategory : subcategoryArray.second) + { + subcategory->SetDefaults(); + } + } + + // Set variable defaults for (auto& variable : p->variables_) { variable->SetValueToDefault(); @@ -57,6 +71,47 @@ bool SettingsCategory::ReadJson(const boost::json::object& json) { const boost::json::object& object = value->as_object(); + // Read subcategory arrays + for (auto& subcategoryArray : p->subcategoryArrays_) + { + const boost::json::value* arrayValue = + object.if_contains(subcategoryArray.first); + + if (arrayValue != nullptr && arrayValue->is_object()) + { + const boost::json::object& arrayObject = arrayValue->as_object(); + + for (auto& subcategory : subcategoryArray.second) + { + validated &= subcategory->ReadJson(arrayObject); + } + } + else + { + if (arrayValue == nullptr) + { + logger_->debug( + "Subcategory array key {} is not present, resetting to " + "defaults", + subcategoryArray.first); + } + else if (!arrayValue->is_object()) + { + logger_->warn( + "Invalid json for subcategory array key {}, resetting to " + "defaults", + p->name_); + } + + for (auto& subcategory : subcategoryArray.second) + { + subcategory->SetDefaults(); + } + validated = false; + } + } + + // Read variables for (auto& variable : p->variables_) { validated &= variable->ReadValue(object); @@ -66,8 +121,8 @@ bool SettingsCategory::ReadJson(const boost::json::object& json) { if (value == nullptr) { - logger_->warn("Key {} is not present, resetting to defaults", - p->name_); + logger_->debug("Key {} is not present, resetting to defaults", + p->name_); } else if (!value->is_object()) { @@ -86,6 +141,20 @@ void SettingsCategory::WriteJson(boost::json::object& json) const { boost::json::object object; + // Write subcategory arrays + for (auto& subcategoryArray : p->subcategoryArrays_) + { + boost::json::object arrayObject; + + for (auto& subcategory : subcategoryArray.second) + { + subcategory->WriteJson(arrayObject); + } + + object.insert_or_assign(subcategoryArray.first, arrayObject); + } + + // Write variables for (auto& variable : p->variables_) { variable->WriteValue(object); @@ -94,6 +163,18 @@ void SettingsCategory::WriteJson(boost::json::object& json) const json.insert_or_assign(p->name_, object); } +void SettingsCategory::RegisterSubcategoryArray( + const std::string& name, std::vector& subcategories) +{ + auto& newSubcategories = p->subcategoryArrays_.emplace_back( + name, std::vector {}); + + std::transform(subcategories.begin(), + subcategories.end(), + std::back_inserter(newSubcategories.second), + [](SettingsCategory& subcategory) { return &subcategory; }); +} + void SettingsCategory::RegisterVariables( std::initializer_list variables) { diff --git a/scwx-qt/source/scwx/qt/settings/settings_category.hpp b/scwx-qt/source/scwx/qt/settings/settings_category.hpp index cc07a7d7..d7c86abd 100644 --- a/scwx-qt/source/scwx/qt/settings/settings_category.hpp +++ b/scwx-qt/source/scwx/qt/settings/settings_category.hpp @@ -50,7 +50,8 @@ public: */ virtual void WriteJson(boost::json::object& json) const; -protected: + void RegisterSubcategoryArray(const std::string& name, + std::vector& subcategories); void RegisterVariables(std::initializer_list variables); void RegisterVariables(std::vector variables); diff --git a/scwx-qt/source/scwx/qt/settings/text_settings.cpp b/scwx-qt/source/scwx/qt/settings/text_settings.cpp index 6d4ceafa..980abf40 100644 --- a/scwx-qt/source/scwx/qt/settings/text_settings.cpp +++ b/scwx-qt/source/scwx/qt/settings/text_settings.cpp @@ -82,6 +82,7 @@ public: TextSettings* self_; std::unordered_map fontData_ {}; + std::vector fontSettings_ {}; SettingsVariable hoverTextWrap_ {"hover_text_wrap"}; SettingsVariable tooltipMethod_ {"tooltip_method"}; @@ -120,8 +121,15 @@ void TextSettings::Impl::InitializeFontVariables() font.fontPointSize_.SetMinimum(6.0); font.fontPointSize_.SetMaximum(72.0); - // TODO: Variable registration + // Variable registration + auto& settings = fontSettings_.emplace_back( + SettingsCategory {types::GetFontCategoryName(fontCategory)}); + + settings.RegisterVariables( + {&font.fontFamily_, &font.fontStyle_, &font.fontPointSize_}); } + + self_->RegisterSubcategoryArray("fonts", fontSettings_); } SettingsVariable& diff --git a/test/data b/test/data index 33caca18..1685e404 160000 --- a/test/data +++ b/test/data @@ -1 +1 @@ -Subproject commit 33caca188b1007c643db75afa560fdfe348c0ee5 +Subproject commit 1685e4048ef4a9f34bc11ecbb8db4905dd0a2e19 From 4e5aa7b5e1b71f2ed4fbff1e7a02e52f59dd95f0 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 27 Sep 2023 23:47:37 -0500 Subject: [PATCH 144/199] Connecting font selection to settings dialog, in-work --- .../scwx/qt/settings/settings_interface.cpp | 129 ++++++++------- .../scwx/qt/settings/settings_variable.cpp | 6 + .../scwx/qt/settings/settings_variable.hpp | 8 + .../source/scwx/qt/settings/text_settings.cpp | 10 +- scwx-qt/source/scwx/qt/ui/settings_dialog.cpp | 153 ++++++++++++++++-- scwx-qt/source/scwx/qt/ui/settings_dialog.ui | 34 +--- 6 files changed, 245 insertions(+), 95 deletions(-) diff --git a/scwx-qt/source/scwx/qt/settings/settings_interface.cpp b/scwx-qt/source/scwx/qt/settings/settings_interface.cpp index dd729da0..49fe7513 100644 --- a/scwx-qt/source/scwx/qt/settings/settings_interface.cpp +++ b/scwx-qt/source/scwx/qt/settings/settings_interface.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -33,6 +34,9 @@ public: ~Impl() {} + template + void SetWidgetText(U* widget, const T& currentValue); + void UpdateEditWidget(); void UpdateResetButton(); @@ -105,6 +109,11 @@ void SettingsInterface::SetEditWidget(QWidget* widget) p->editWidget_ = widget; + if (widget == nullptr) + { + return; + } + if (QLineEdit* lineEdit = dynamic_cast(widget)) { if constexpr (std::is_same_v) @@ -274,33 +283,36 @@ void SettingsInterface::SetResetButton(QAbstractButton* button) p->resetButton_ = button; - QObject::connect(p->resetButton_, - &QAbstractButton::clicked, - p->context_.get(), - [this]() - { - T defaultValue = p->variable_->GetDefault(); - - if (p->variable_->GetValue() == defaultValue) + if (p->resetButton_ != nullptr) + { + QObject::connect(p->resetButton_, + &QAbstractButton::clicked, + p->context_.get(), + [this]() { - // If the current value is default, reset the staged - // value - p->variable_->Reset(); - p->stagedValid_ = true; - p->UpdateEditWidget(); - p->UpdateResetButton(); - } - else - { - // Stage the default value - p->stagedValid_ = - p->variable_->StageValue(defaultValue); - p->UpdateEditWidget(); - p->UpdateResetButton(); - } - }); + T defaultValue = p->variable_->GetDefault(); - p->UpdateResetButton(); + if (p->variable_->GetValue() == defaultValue) + { + // If the current value is default, reset the + // staged value + p->variable_->Reset(); + p->stagedValid_ = true; + p->UpdateEditWidget(); + p->UpdateResetButton(); + } + else + { + // Stage the default value + p->stagedValid_ = + p->variable_->StageValue(defaultValue); + p->UpdateEditWidget(); + p->UpdateResetButton(); + } + }); + + p->UpdateResetButton(); + } } template @@ -317,6 +329,39 @@ void SettingsInterface::SetMapToValueFunction( p->mapToValue_ = function; } +template +template +void SettingsInterface::Impl::SetWidgetText(U* widget, const T& currentValue) +{ + if constexpr (std::is_integral_v) + { + widget->setText(QString::number(currentValue)); + } + else if constexpr (std::is_same_v) + { + if (mapFromValue_ != nullptr) + { + widget->setText(QString::fromStdString(mapFromValue_(currentValue))); + } + else + { + widget->setText(QString::fromStdString(currentValue)); + } + } + else if constexpr (std::is_same_v>) + { + if (mapFromValue_ != nullptr) + { + widget->setText(QString::fromStdString(mapFromValue_(currentValue))); + } + else + { + widget->setText(QString::fromStdString( + fmt::format("{}", fmt::join(currentValue, ", ")))); + } + } +} + template void SettingsInterface::Impl::UpdateEditWidget() { @@ -327,35 +372,11 @@ void SettingsInterface::Impl::UpdateEditWidget() if (QLineEdit* lineEdit = dynamic_cast(editWidget_)) { - if constexpr (std::is_integral_v) - { - lineEdit->setText(QString::number(currentValue)); - } - else if constexpr (std::is_same_v) - { - if (mapFromValue_ != nullptr) - { - lineEdit->setText( - QString::fromStdString(mapFromValue_(currentValue))); - } - else - { - lineEdit->setText(QString::fromStdString(currentValue)); - } - } - else if constexpr (std::is_same_v>) - { - if (mapFromValue_ != nullptr) - { - lineEdit->setText( - QString::fromStdString(mapFromValue_(currentValue))); - } - else - { - lineEdit->setText(QString::fromStdString( - fmt::format("{}", fmt::join(currentValue, ", ")))); - } - } + SetWidgetText(lineEdit, currentValue); + } + else if (QLabel* label = dynamic_cast(editWidget_)) + { + SetWidgetText(label, currentValue); } else if (QCheckBox* checkBox = dynamic_cast(editWidget_)) { diff --git a/scwx-qt/source/scwx/qt/settings/settings_variable.cpp b/scwx-qt/source/scwx/qt/settings/settings_variable.cpp index 270a2eed..1f7661c0 100644 --- a/scwx-qt/source/scwx/qt/settings/settings_variable.cpp +++ b/scwx-qt/source/scwx/qt/settings/settings_variable.cpp @@ -239,6 +239,12 @@ std::optional SettingsVariable::GetStaged() const return p->staged_; } +template +T SettingsVariable::GetStagedOrValue() const +{ + return p->staged_.value_or(GetValue()); +} + template T SettingsVariable::GetDefault() const { diff --git a/scwx-qt/source/scwx/qt/settings/settings_variable.hpp b/scwx-qt/source/scwx/qt/settings/settings_variable.hpp index c7999c0d..c05c543f 100644 --- a/scwx-qt/source/scwx/qt/settings/settings_variable.hpp +++ b/scwx-qt/source/scwx/qt/settings/settings_variable.hpp @@ -103,6 +103,14 @@ public: */ std::optional GetStaged() const; + /** + * Gets the staged value of the settings variable, if defined, otherwise the + * current value. + * + * @return Staged value or current value + */ + T GetStagedOrValue() const; + /** * Validate the value against the defined parameters of the settings * variable. diff --git a/scwx-qt/source/scwx/qt/settings/text_settings.cpp b/scwx-qt/source/scwx/qt/settings/text_settings.cpp index 980abf40..5bc9946b 100644 --- a/scwx-qt/source/scwx/qt/settings/text_settings.cpp +++ b/scwx-qt/source/scwx/qt/settings/text_settings.cpp @@ -79,6 +79,13 @@ public: void InitializeFontVariables(); + friend bool operator==(const FontData& lhs, const FontData& rhs) + { + return (lhs.fontFamily_ == rhs.fontFamily_ && + lhs.fontStyle_ == rhs.fontStyle_ && + lhs.fontPointSize_ == rhs.fontPointSize_); + } + TextSettings* self_; std::unordered_map fontData_ {}; @@ -168,7 +175,8 @@ TextSettings& TextSettings::Instance() bool operator==(const TextSettings& lhs, const TextSettings& rhs) { - return (lhs.p->hoverTextWrap_ == rhs.p->hoverTextWrap_ && + return (lhs.p->fontData_ == rhs.p->fontData_ && + lhs.p->hoverTextWrap_ == rhs.p->hoverTextWrap_ && lhs.p->tooltipMethod_ == rhs.p->tooltipMethod_); } diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp index c2449e9f..c175fe7b 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -119,7 +120,10 @@ public: } // Configure font dialog - fontDialog_->setOptions(QFontDialog::FontDialogOption::ScalableFonts); + fontDialog_->setOptions( + QFontDialog::FontDialogOption::DontUseNativeDialog | + QFontDialog::FontDialogOption::ScalableFonts); + fontDialog_->setWindowModality(Qt::WindowModality::WindowModal); } ~SettingsDialogImpl() = default; @@ -133,6 +137,10 @@ public: void ShowColorDialog(QLineEdit* lineEdit, QFrame* frame = nullptr); void UpdateRadarDialogLocation(const std::string& id); + QFont GetSelectedFont(); + void SelectFontCategory(types::FontCategory fontCategory); + void UpdateFontDisplayData(); + void ApplyChanges(); void DiscardChanges(); void ResetToDefault(); @@ -157,6 +165,8 @@ public: QStandardItemModel* fontCategoryModel_; + types::FontCategory selectedFontCategory_ {types::FontCategory::Unknown}; + settings::SettingsInterface defaultRadarSite_ {}; settings::SettingsInterface> fontSizes_ {}; settings::SettingsInterface gridWidth_ {}; @@ -251,18 +261,6 @@ void SettingsDialogImpl::ConnectSignals() } }); - QObject::connect(self_->ui->fontSelectButton, - &QAbstractButton::clicked, - self_, - [this]() { fontDialog_->show(); }); - - QObject::connect( - fontDialog_, - &QFontDialog::fontSelected, - self_, - [this](const QFont& font) - { logger_->debug("Selected font: {}", font.toString().toStdString()); }); - // Update the Radar Site dialog "map" location with the currently selected // radar site auto& defaultRadarSite = *defaultRadarSite_.GetSettingsVariable(); @@ -270,6 +268,66 @@ void SettingsDialogImpl::ConnectSignals() [this](const std::string& newValue) { UpdateRadarDialogLocation(newValue); }); + QObject::connect( + self_->ui->fontListView->selectionModel(), + &QItemSelectionModel::selectionChanged, + self_, + [this](const QItemSelection& selected, const QItemSelection& deselected) + { + if (selected.size() == 0 && deselected.size() == 0) + { + // Items which stay selected but change their index are not + // included in selected and deselected. Thus, this signal might + // be emitted with both selected and deselected empty, if only + // the indices of selected items change. + return; + } + + if (selected.size() > 0) + { + QModelIndex selectedIndex = selected[0].indexes()[0]; + QVariant variantData = + self_->ui->fontListView->model()->data(selectedIndex); + if (variantData.typeId() == QMetaType::QString) + { + types::FontCategory fontCategory = + types::GetFontCategory(variantData.toString().toStdString()); + SelectFontCategory(fontCategory); + UpdateFontDisplayData(); + } + } + }); + + QObject::connect(self_->ui->fontSelectButton, + &QAbstractButton::clicked, + self_, + [this]() + { + fontDialog_->setCurrentFont(GetSelectedFont()); + fontDialog_->show(); + }); + + QObject::connect(fontDialog_, + &QFontDialog::fontSelected, + self_, + [this](const QFont& font) + { + logger_->debug("Selected font: {}", + font.toString().toStdString()); + + fontFamilies_.at(selectedFontCategory_) + .GetSettingsVariable() + ->StageValue(font.family().toStdString()); + fontStyles_.at(selectedFontCategory_) + .GetSettingsVariable() + ->StageValue(font.styleName().toStdString()); + fontPointSizes_.at(selectedFontCategory_) + .GetSettingsVariable() + ->StageValue(font.pointSizeF()); + + UpdateFontDisplayData(); + }); + QObject::connect( self_->ui->buttonBox, &QDialogButtonBox::clicked, @@ -706,6 +764,8 @@ void SettingsDialogImpl::SetupTextTab() fontSize.SetSettingsVariable(textSettings.font_point_size(fontCategory)); } self_->ui->fontListView->setCurrentIndex(fontCategoryModel_->index(0, 0)); + SelectFontCategory(*types::FontCategoryIterator().begin()); + UpdateFontDisplayData(); hoverTextWrap_.SetSettingsVariable(textSettings.hover_text_wrap()); hoverTextWrap_.SetEditWidget(self_->ui->hoverTextWrapSpinBox); @@ -861,6 +921,73 @@ void SettingsDialogImpl::UpdateRadarDialogLocation(const std::string& id) } } +QFont SettingsDialogImpl::GetSelectedFont() +{ + std::string fontFamily = fontFamilies_.at(selectedFontCategory_) + .GetSettingsVariable() + ->GetStagedOrValue(); + std::string fontStyle = fontStyles_.at(selectedFontCategory_) + .GetSettingsVariable() + ->GetStagedOrValue(); + units::font_size::points fontSize { + fontPointSizes_.at(selectedFontCategory_) + .GetSettingsVariable() + ->GetStagedOrValue()}; + + QFont font(QString::fromStdString(fontFamily)); + font.setStyleName(QString::fromStdString(fontStyle)); + font.setPointSizeF(fontSize.value()); + + return font; +} + +void SettingsDialogImpl::SelectFontCategory(types::FontCategory fontCategory) +{ + if (selectedFontCategory_ != types::FontCategory::Unknown && + selectedFontCategory_ != fontCategory) + { + auto& fontFamily = fontFamilies_.at(selectedFontCategory_); + auto& fontStyle = fontStyles_.at(selectedFontCategory_); + auto& fontSize = fontPointSizes_.at(selectedFontCategory_); + + fontFamily.SetResetButton(nullptr); + fontStyle.SetResetButton(nullptr); + fontSize.SetResetButton(nullptr); + + fontFamily.SetEditWidget(nullptr); + fontStyle.SetEditWidget(nullptr); + fontSize.SetEditWidget(nullptr); + } + + if (selectedFontCategory_ != fontCategory) + { + auto& fontFamily = fontFamilies_.at(fontCategory); + auto& fontStyle = fontStyles_.at(fontCategory); + auto& fontSize = fontPointSizes_.at(fontCategory); + + fontFamily.SetResetButton(self_->ui->resetFontButton); + fontStyle.SetResetButton(self_->ui->resetFontButton); + fontSize.SetResetButton(self_->ui->resetFontButton); + + fontFamily.SetEditWidget(self_->ui->fontNameLabel); + fontStyle.SetEditWidget(self_->ui->fontStyleLabel); + fontSize.SetEditWidget(self_->ui->fontSizeLabel); + } + + selectedFontCategory_ = fontCategory; +} + +void SettingsDialogImpl::UpdateFontDisplayData() +{ + QFont font = GetSelectedFont(); + + self_->ui->fontNameLabel->setText(font.family()); + self_->ui->fontStyleLabel->setText(font.styleName()); + self_->ui->fontSizeLabel->setText(QString::number(font.pointSizeF())); + + self_->ui->fontPreviewLabel->setFont(font); +} + void SettingsDialogImpl::ApplyChanges() { logger_->info("Applying settings changes"); diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui index e921f38a..de695341 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui @@ -102,7 +102,7 @@ - 0 + 2 @@ -468,36 +468,16 @@ 0 - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Reset All Fonts - - - - - - - + Display Item: + + + @@ -538,10 +518,10 @@ - QFrame::StyledPanel + QFrame::Panel - QFrame::Raised + QFrame::Plain From d3a3c3db36cbcb15eb1483cf7d32d01dd18e9255 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 28 Sep 2023 23:11:19 -0500 Subject: [PATCH 145/199] Cleaning up font selection in settings dialog --- .../scwx/qt/settings/settings_interface.cpp | 62 ++++++++++++----- .../scwx/qt/settings/settings_interface.hpp | 13 ++++ .../qt/settings/settings_interface_base.hpp | 10 ++- scwx-qt/source/scwx/qt/ui/settings_dialog.cpp | 66 ++++++++----------- scwx-qt/source/scwx/qt/ui/settings_dialog.ui | 2 +- 5 files changed, 95 insertions(+), 58 deletions(-) diff --git a/scwx-qt/source/scwx/qt/settings/settings_interface.cpp b/scwx-qt/source/scwx/qt/settings/settings_interface.cpp index 49fe7513..b7133537 100644 --- a/scwx-qt/source/scwx/qt/settings/settings_interface.cpp +++ b/scwx-qt/source/scwx/qt/settings/settings_interface.cpp @@ -27,7 +27,7 @@ template class SettingsInterface::Impl { public: - explicit Impl() + explicit Impl(SettingsInterface* self) : self_ {self} { context_->moveToThread(QCoreApplication::instance()->thread()); } @@ -40,6 +40,8 @@ public: void UpdateEditWidget(); void UpdateResetButton(); + SettingsInterface* self_; + SettingsVariable* variable_ {nullptr}; bool stagedValid_ {true}; @@ -53,17 +55,27 @@ public: template SettingsInterface::SettingsInterface() : - SettingsInterfaceBase(), p(std::make_unique()) + SettingsInterfaceBase(), p(std::make_unique(this)) { } template SettingsInterface::~SettingsInterface() = default; template -SettingsInterface::SettingsInterface(SettingsInterface&&) noexcept = default; +SettingsInterface::SettingsInterface(SettingsInterface&& o) noexcept : + p {std::move(o.p)} +{ + p->self_ = this; +} + template SettingsInterface& -SettingsInterface::operator=(SettingsInterface&&) noexcept = default; +SettingsInterface::operator=(SettingsInterface&& o) noexcept +{ + p = std::move(o.p); + p->self_ = this; + return *this; +} template void SettingsInterface::SetSettingsVariable(SettingsVariable& variable) @@ -77,6 +89,27 @@ SettingsVariable* SettingsInterface::GetSettingsVariable() const return p->variable_; } +template +bool SettingsInterface::IsDefault() +{ + bool isDefault = false; + + const std::optional staged = p->variable_->GetStaged(); + const T defaultValue = p->variable_->GetDefault(); + const T value = p->variable_->GetValue(); + + if (staged.has_value()) + { + isDefault = (p->stagedValid_ && *staged == defaultValue); + } + else + { + isDefault = (value == defaultValue); + } + + return isDefault; +} + template bool SettingsInterface::Commit() { @@ -99,6 +132,14 @@ void SettingsInterface::StageDefault() p->UpdateResetButton(); } +template +void SettingsInterface::StageValue(const T& value) +{ + p->variable_->StageValue(value); + p->UpdateEditWidget(); + p->UpdateResetButton(); +} + template void SettingsInterface::SetEditWidget(QWidget* widget) { @@ -412,20 +453,9 @@ void SettingsInterface::Impl::UpdateEditWidget() template void SettingsInterface::Impl::UpdateResetButton() { - const std::optional staged = variable_->GetStaged(); - const T defaultValue = variable_->GetDefault(); - const T value = variable_->GetValue(); - if (resetButton_ != nullptr) { - if (staged.has_value()) - { - resetButton_->setVisible(!stagedValid_ || *staged != defaultValue); - } - else - { - resetButton_->setVisible(value != defaultValue); - } + resetButton_->setVisible(!self_->IsDefault()); } } diff --git a/scwx-qt/source/scwx/qt/settings/settings_interface.hpp b/scwx-qt/source/scwx/qt/settings/settings_interface.hpp index 4ba1ea0e..f5f5bb4a 100644 --- a/scwx-qt/source/scwx/qt/settings/settings_interface.hpp +++ b/scwx-qt/source/scwx/qt/settings/settings_interface.hpp @@ -45,6 +45,14 @@ public: */ SettingsVariable* GetSettingsVariable() const; + /** + * Gets whether the staged value (or current value, if none staged) is + * set to the default value. + * + * @return true if the settings variable is set to default, otherwise false. + */ + bool IsDefault() override; + /** * Sets the current value of the associated settings variable to the staged * value. @@ -64,6 +72,11 @@ public: */ void StageDefault() override; + /** + * Stages a value to the associated settings variable. + */ + void StageValue(const T& value); + /** * Sets the edit widget from the settings dialog. * diff --git a/scwx-qt/source/scwx/qt/settings/settings_interface_base.hpp b/scwx-qt/source/scwx/qt/settings/settings_interface_base.hpp index 97ae442d..d0dc2ff2 100644 --- a/scwx-qt/source/scwx/qt/settings/settings_interface_base.hpp +++ b/scwx-qt/source/scwx/qt/settings/settings_interface_base.hpp @@ -24,10 +24,18 @@ public: SettingsInterfaceBase(SettingsInterfaceBase&&) noexcept; SettingsInterfaceBase& operator=(SettingsInterfaceBase&&) noexcept; + /** + * Gets whether the staged value (or current value, if none staged) is + * set to the default value. + * + * @return true if the settings variable is set to default, otherwise false. + */ + virtual bool IsDefault() = 0; + /** * Sets the current value of the associated settings variable to the staged * value. - * + * * @return true if the staged value was committed, false if no staged value * is present. */ diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp index c175fe7b..32464ecf 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp @@ -312,18 +312,24 @@ void SettingsDialogImpl::ConnectSignals() self_, [this](const QFont& font) { - logger_->debug("Selected font: {}", - font.toString().toStdString()); - fontFamilies_.at(selectedFontCategory_) - .GetSettingsVariable() - ->StageValue(font.family().toStdString()); + .StageValue(font.family().toStdString()); fontStyles_.at(selectedFontCategory_) - .GetSettingsVariable() - ->StageValue(font.styleName().toStdString()); + .StageValue(font.styleName().toStdString()); fontPointSizes_.at(selectedFontCategory_) - .GetSettingsVariable() - ->StageValue(font.pointSizeF()); + .StageValue(font.pointSizeF()); + + UpdateFontDisplayData(); + }); + + QObject::connect(self_->ui->resetFontButton, + &QAbstractButton::clicked, + self_, + [this]() + { + fontFamilies_.at(selectedFontCategory_).StageDefault(); + fontStyles_.at(selectedFontCategory_).StageDefault(); + fontPointSizes_.at(selectedFontCategory_).StageDefault(); UpdateFontDisplayData(); }); @@ -943,37 +949,6 @@ QFont SettingsDialogImpl::GetSelectedFont() void SettingsDialogImpl::SelectFontCategory(types::FontCategory fontCategory) { - if (selectedFontCategory_ != types::FontCategory::Unknown && - selectedFontCategory_ != fontCategory) - { - auto& fontFamily = fontFamilies_.at(selectedFontCategory_); - auto& fontStyle = fontStyles_.at(selectedFontCategory_); - auto& fontSize = fontPointSizes_.at(selectedFontCategory_); - - fontFamily.SetResetButton(nullptr); - fontStyle.SetResetButton(nullptr); - fontSize.SetResetButton(nullptr); - - fontFamily.SetEditWidget(nullptr); - fontStyle.SetEditWidget(nullptr); - fontSize.SetEditWidget(nullptr); - } - - if (selectedFontCategory_ != fontCategory) - { - auto& fontFamily = fontFamilies_.at(fontCategory); - auto& fontStyle = fontStyles_.at(fontCategory); - auto& fontSize = fontPointSizes_.at(fontCategory); - - fontFamily.SetResetButton(self_->ui->resetFontButton); - fontStyle.SetResetButton(self_->ui->resetFontButton); - fontSize.SetResetButton(self_->ui->resetFontButton); - - fontFamily.SetEditWidget(self_->ui->fontNameLabel); - fontStyle.SetEditWidget(self_->ui->fontStyleLabel); - fontSize.SetEditWidget(self_->ui->fontSizeLabel); - } - selectedFontCategory_ = fontCategory; } @@ -986,6 +961,17 @@ void SettingsDialogImpl::UpdateFontDisplayData() self_->ui->fontSizeLabel->setText(QString::number(font.pointSizeF())); self_->ui->fontPreviewLabel->setFont(font); + + if (selectedFontCategory_ != types::FontCategory::Unknown) + { + auto& fontFamily = fontFamilies_.at(selectedFontCategory_); + auto& fontStyle = fontStyles_.at(selectedFontCategory_); + auto& fontSize = fontPointSizes_.at(selectedFontCategory_); + + self_->ui->resetFontButton->setVisible(!fontFamily.IsDefault() || + !fontStyle.IsDefault() || + !fontSize.IsDefault()); + } } void SettingsDialogImpl::ApplyChanges() diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui index de695341..8ed4a76f 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui @@ -446,7 +446,7 @@ QFrame::Plain - + From 2e9f5818cd6bb79cacb1321cf5ce3e5117573483 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 1 Oct 2023 22:07:36 -0500 Subject: [PATCH 146/199] Lock the ImGui font atlas when processing a frame --- scwx-qt/source/scwx/qt/manager/font_manager.cpp | 7 +++++++ scwx-qt/source/scwx/qt/manager/font_manager.hpp | 4 ++++ scwx-qt/source/scwx/qt/map/map_widget.cpp | 8 ++++++++ scwx-qt/source/scwx/qt/ui/imgui_debug_widget.cpp | 8 ++++++++ 4 files changed, 27 insertions(+) diff --git a/scwx-qt/source/scwx/qt/manager/font_manager.cpp b/scwx-qt/source/scwx/qt/manager/font_manager.cpp index 64b93d78..1cc752fe 100644 --- a/scwx-qt/source/scwx/qt/manager/font_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/font_manager.cpp @@ -45,12 +45,19 @@ public: const std::vector& styles); std::string fontCachePath_ {}; + + std::shared_mutex imguiFontAtlasMutex_ {}; }; FontManager::FontManager() : p(std::make_unique()) {} FontManager::~FontManager() {}; +std::shared_mutex& FontManager::imgui_font_atlas_mutex() +{ + return p->imguiFontAtlasMutex_; +} + std::shared_ptr FontManager::GetImGuiFont(const std::string& family, const std::vector& styles, diff --git a/scwx-qt/source/scwx/qt/manager/font_manager.hpp b/scwx-qt/source/scwx/qt/manager/font_manager.hpp index 0a4a812e..c647c31a 100644 --- a/scwx-qt/source/scwx/qt/manager/font_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/font_manager.hpp @@ -2,6 +2,8 @@ #include +#include + #include namespace scwx @@ -19,6 +21,8 @@ public: explicit FontManager(); ~FontManager(); + std::shared_mutex& imgui_font_atlas_mutex(); + std::shared_ptr GetImGuiFont(const std::string& family, const std::vector& styles, diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index 4f279ad3..89eabb3f 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -1028,6 +1029,10 @@ void MapWidget::paintGL() // Setup ImGui Frame ImGui::SetCurrentContext(p->imGuiContext_); + // Lock ImGui font atlas prior to new ImGui frame + std::shared_lock imguiFontAtlasLock { + manager::FontManager::Instance().imgui_font_atlas_mutex()}; + // Start ImGui Frame ImGui_ImplQt_NewFrame(this); ImGui_ImplOpenGL3_NewFrame(); @@ -1059,6 +1064,9 @@ void MapWidget::paintGL() ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + // Unlock ImGui font atlas after rendering + imguiFontAtlasLock.unlock(); + // Paint complete Q_EMIT WidgetPainted(); } diff --git a/scwx-qt/source/scwx/qt/ui/imgui_debug_widget.cpp b/scwx-qt/source/scwx/qt/ui/imgui_debug_widget.cpp index 18011b7f..04e692ac 100644 --- a/scwx-qt/source/scwx/qt/ui/imgui_debug_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/imgui_debug_widget.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -109,6 +110,10 @@ void ImGuiDebugWidget::paintGL() { ImGui::SetCurrentContext(p->currentContext_); + // Lock ImGui font atlas prior to new ImGui frame + std::shared_lock imguiFontAtlasLock { + manager::FontManager::Instance().imgui_font_atlas_mutex()}; + ImGui_ImplQt_NewFrame(this); ImGui_ImplOpenGL3_NewFrame(); @@ -131,6 +136,9 @@ void ImGuiDebugWidget::paintGL() ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + + // Unlock ImGui font atlas after rendering + imguiFontAtlasLock.unlock(); } } // namespace ui From aead8e726423eed8fd3843a0796411f99472afa2 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 2 Oct 2023 00:10:57 -0500 Subject: [PATCH 147/199] Start of dynamic ImGui font loading --- .../source/scwx/qt/manager/font_manager.cpp | 106 +++++++++++++++++- 1 file changed, 104 insertions(+), 2 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/font_manager.cpp b/scwx-qt/source/scwx/qt/manager/font_manager.cpp index 1cc752fe..994d5a53 100644 --- a/scwx-qt/source/scwx/qt/manager/font_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/font_manager.cpp @@ -2,10 +2,13 @@ #include #include +#include #include #include #include +#include +#include #include namespace scwx @@ -27,6 +30,18 @@ struct FontRecord std::string filename_ {}; }; +template +struct FontRecordHash; + +template +struct FontRecordKeyEqual; + +template<> +struct FontRecordHash> +{ + size_t operator()(const std::pair& x) const; +}; + class FontManager::Impl { public: @@ -41,12 +56,24 @@ public: void InitializeFontCache(); void InitializeFontconfig(); + const std::vector& GetRawFontData(const std::string& filename); + static FontRecord MatchFontFile(const std::string& family, const std::vector& styles); std::string fontCachePath_ {}; std::shared_mutex imguiFontAtlasMutex_ {}; + + boost::unordered_flat_map, + std::shared_ptr, + FontRecordHash>> + imguiFonts_ {}; + std::shared_mutex imguiFontsMutex_ {}; + + boost::unordered_flat_map> + rawFontData_ {}; + std::mutex rawFontDataMutex_ {}; }; FontManager::FontManager() : p(std::make_unique()) {} @@ -71,12 +98,69 @@ FontManager::GetImGuiFont(const std::string& family, FontRecord fontRecord = Impl::MatchFontFile(family, styles); - // TODO: - Q_UNUSED(fontRecord); + // Only allow whole pixels, and clamp to 6-72 pt + units::font_size::pixels pixels {size}; + int imFontSize = std::clamp(static_cast(pixels.value()), 8, 96); + + // Search for a loaded ImGui font + { + std::shared_lock imguiFontLock {p->imguiFontsMutex_}; + + // Search for the associated ImGui font + auto it = p->imguiFonts_.find(std::make_pair(fontRecord, imFontSize)); + if (it != p->imguiFonts_.end()) + { + return it->second; + } + + // No ImGui font was found, we need to create one + } + + // Get raw font data + const auto& rawFontData = p->GetRawFontData(fontRecord.filename_); + + // TODO: Create an ImGui font + // TODO: imguiFontLock could be acquired during a render loop, when the font + // atlas is already locked. Lock the font atlas first. Unless it's already + // locked. It might need to be reentrant? + // TODO: Search for font once more, to prevent loading the same font twice + Q_UNUSED(rawFontData); return nullptr; } +const std::vector& +FontManager::Impl::GetRawFontData(const std::string& filename) +{ + std::unique_lock rawFontDataLock {rawFontDataMutex_}; + + auto it = rawFontData_.find(filename); + if (it != rawFontData_.end()) + { + // Raw font data has already been loaded + return it->second; + } + + // Raw font data needs to be loaded + std::basic_ifstream ifs {filename, std::ios::binary}; + ifs.seekg(0, std::ios_base::end); + std::size_t dataSize = ifs.tellg(); + ifs.seekg(0, std::ios_base::beg); + + // Store the font data in a buffer + std::vector buffer {}; + buffer.reserve(dataSize); + std::copy(std::istreambuf_iterator(ifs), + std::istreambuf_iterator(), + std::back_inserter(buffer)); + + // Place the buffer in the cache + auto result = rawFontData_.emplace(filename, std::move(buffer)); + + // Return the cached buffer + return it->second; +} + void FontManager::LoadApplicationFont(const std::string& filename) { // If the font cache failed to create, don't attempt to cache any fonts @@ -227,6 +311,24 @@ FontManager& FontManager::Instance() return instance_; } +size_t FontRecordHash>::operator()( + const std::pair& x) const +{ + size_t seed = 0; + boost::hash_combine(seed, x.first.family_); + boost::hash_combine(seed, x.first.style_); + boost::hash_combine(seed, x.first.filename_); + boost::hash_combine(seed, x.second); + return seed; +} + +bool operator==(const FontRecord& lhs, const FontRecord& rhs) +{ + return lhs.family_ == rhs.family_ && // + lhs.style_ == rhs.style_ && // + lhs.filename_ == rhs.filename_; +} + } // namespace manager } // namespace qt } // namespace scwx From e4ab1e8c19a3f09114cf6b0f45a7ec8ea4f2a1b6 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 2 Oct 2023 23:57:19 -0500 Subject: [PATCH 148/199] Finish dynamic load GetImGuiFont function --- .../source/scwx/qt/manager/font_manager.cpp | 62 +++++++++++++------ .../source/scwx/qt/manager/font_manager.hpp | 3 +- scwx-qt/source/scwx/qt/types/imgui_font.cpp | 22 +++---- scwx-qt/source/scwx/qt/types/imgui_font.hpp | 7 ++- 4 files changed, 59 insertions(+), 35 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/font_manager.cpp b/scwx-qt/source/scwx/qt/manager/font_manager.cpp index 994d5a53..2b38168c 100644 --- a/scwx-qt/source/scwx/qt/manager/font_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/font_manager.cpp @@ -30,16 +30,15 @@ struct FontRecord std::string filename_ {}; }; +typedef std::pair> FontRecordPair; + template struct FontRecordHash; -template -struct FontRecordKeyEqual; - template<> -struct FontRecordHash> +struct FontRecordHash { - size_t operator()(const std::pair& x) const; + size_t operator()(const FontRecordPair& x) const; }; class FontManager::Impl @@ -65,9 +64,9 @@ public: std::shared_mutex imguiFontAtlasMutex_ {}; - boost::unordered_flat_map, + boost::unordered_flat_map, - FontRecordHash>> + FontRecordHash> imguiFonts_ {}; std::shared_mutex imguiFontsMutex_ {}; @@ -88,7 +87,8 @@ std::shared_mutex& FontManager::imgui_font_atlas_mutex() std::shared_ptr FontManager::GetImGuiFont(const std::string& family, const std::vector& styles, - units::font_size::points size) + units::font_size::points size, + bool loadIfNotFound) { const std::string styleString = fmt::format("{}", fmt::join(styles, " ")); const std::string fontString = @@ -100,14 +100,16 @@ FontManager::GetImGuiFont(const std::string& family, // Only allow whole pixels, and clamp to 6-72 pt units::font_size::pixels pixels {size}; - int imFontSize = std::clamp(static_cast(pixels.value()), 8, 96); + units::font_size::pixels imFontSize { + std::clamp(static_cast(pixels.value()), 8, 96)}; + auto imguiFontKey = std::make_pair(fontRecord, imFontSize); // Search for a loaded ImGui font { std::shared_lock imguiFontLock {p->imguiFontsMutex_}; // Search for the associated ImGui font - auto it = p->imguiFonts_.find(std::make_pair(fontRecord, imFontSize)); + auto it = p->imguiFonts_.find(imguiFontKey); if (it != p->imguiFonts_.end()) { return it->second; @@ -116,17 +118,38 @@ FontManager::GetImGuiFont(const std::string& family, // No ImGui font was found, we need to create one } + // No font was found, return an empty shared pointer if not loading + if (!loadIfNotFound) + { + return nullptr; + } + // Get raw font data const auto& rawFontData = p->GetRawFontData(fontRecord.filename_); - // TODO: Create an ImGui font - // TODO: imguiFontLock could be acquired during a render loop, when the font - // atlas is already locked. Lock the font atlas first. Unless it's already - // locked. It might need to be reentrant? - // TODO: Search for font once more, to prevent loading the same font twice - Q_UNUSED(rawFontData); + // The font atlas mutex might already be locked within an ImGui render frame. + // Lock the font atlas mutex before the fonts mutex to prevent deadlock. + std::unique_lock imguiFontAtlasLock {p->imguiFontAtlasMutex_}; + std::unique_lock imguiFontsLock {p->imguiFontsMutex_}; - return nullptr; + // Search for the associated ImGui font again, to prevent loading the same + // font twice + auto it = p->imguiFonts_.find(imguiFontKey); + if (it != p->imguiFonts_.end()) + { + return it->second; + } + + // Create an ImGui font + std::shared_ptr imguiFont = + std::make_shared( + fontRecord.filename_, rawFontData, imFontSize); + + // Store the ImGui font + p->imguiFonts_.insert_or_assign(imguiFontKey, imguiFont); + + // Return the ImGui font + return imguiFont; } const std::vector& @@ -311,14 +334,13 @@ FontManager& FontManager::Instance() return instance_; } -size_t FontRecordHash>::operator()( - const std::pair& x) const +size_t FontRecordHash::operator()(const FontRecordPair& x) const { size_t seed = 0; boost::hash_combine(seed, x.first.family_); boost::hash_combine(seed, x.first.style_); boost::hash_combine(seed, x.first.filename_); - boost::hash_combine(seed, x.second); + boost::hash_combine(seed, x.second.value()); return seed; } diff --git a/scwx-qt/source/scwx/qt/manager/font_manager.hpp b/scwx-qt/source/scwx/qt/manager/font_manager.hpp index c647c31a..171da79c 100644 --- a/scwx-qt/source/scwx/qt/manager/font_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/font_manager.hpp @@ -26,7 +26,8 @@ public: std::shared_ptr GetImGuiFont(const std::string& family, const std::vector& styles, - units::font_size::points size); + units::font_size::points size, + bool loadIfNotFound = false); void LoadApplicationFont(const std::string& filename); diff --git a/scwx-qt/source/scwx/qt/types/imgui_font.cpp b/scwx-qt/source/scwx/qt/types/imgui_font.cpp index b2b42acd..f7ba126c 100644 --- a/scwx-qt/source/scwx/qt/types/imgui_font.cpp +++ b/scwx-qt/source/scwx/qt/types/imgui_font.cpp @@ -23,9 +23,9 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_); class ImGuiFont::Impl { public: - explicit Impl(const std::string& fontName, - const std::string& fontData, - units::pixels size) : + explicit Impl(const std::string& fontName, + const std::vector& fontData, + units::font_size::pixels size) : fontName_ {fontName}, size_ {size} { CreateImGuiFont(fontData); @@ -33,23 +33,23 @@ public: ~Impl() {} - void CreateImGuiFont(const std::string& fontData); + void CreateImGuiFont(const std::vector& fontData); - const std::string fontName_; - const units::pixels size_; + const std::string fontName_; + const units::font_size::pixels size_; ImFont* imFont_ {nullptr}; }; -ImGuiFont::ImGuiFont(const std::string& fontName, - const std::string& fontData, - units::pixels size) : +ImGuiFont::ImGuiFont(const std::string& fontName, + const std::vector& fontData, + units::font_size::pixels size) : p(std::make_unique(fontName, fontData, size)) { } ImGuiFont::~ImGuiFont() = default; -void ImGuiFont::Impl::CreateImGuiFont(const std::string& fontData) +void ImGuiFont::Impl::CreateImGuiFont(const std::vector& fontData) { logger_->debug("Creating Font: {}", fontName_); @@ -66,7 +66,7 @@ void ImGuiFont::Impl::CreateImGuiFont(const std::string& fontData) fontConfig.Name[sizeof(fontConfig.Name) - 1] = 0; imFont_ = fontAtlas->AddFontFromMemoryTTF( - const_cast(static_cast(fontData.c_str())), + const_cast(static_cast(fontData.data())), static_cast(std::clamp( fontData.size(), 0, std::numeric_limits::max())), sizePixels, diff --git a/scwx-qt/source/scwx/qt/types/imgui_font.hpp b/scwx-qt/source/scwx/qt/types/imgui_font.hpp index 8c07cd95..9a69ac6e 100644 --- a/scwx-qt/source/scwx/qt/types/imgui_font.hpp +++ b/scwx-qt/source/scwx/qt/types/imgui_font.hpp @@ -2,6 +2,7 @@ #include #include +#include #include @@ -17,9 +18,9 @@ namespace types class ImGuiFont { public: - explicit ImGuiFont(const std::string& fontName, - const std::string& fontData, - units::pixels size); + explicit ImGuiFont(const std::string& fontName, + const std::vector& fontData, + units::font_size::pixels size); ~ImGuiFont(); ImGuiFont(const ImGuiFont&) = delete; From acc782b2bcc5ac7cc6b76640718d477b94b8836d Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 5 Oct 2023 21:18:58 -0500 Subject: [PATCH 149/199] Get ImGui font by font category --- .../source/scwx/qt/manager/font_manager.cpp | 40 ++++++++++++++++--- .../source/scwx/qt/manager/font_manager.hpp | 12 ++++-- .../scwx/qt/manager/placefile_manager.cpp | 2 +- scwx-qt/source/scwx/qt/map/map_widget.cpp | 16 ++++++++ 4 files changed, 60 insertions(+), 10 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/font_manager.cpp b/scwx-qt/source/scwx/qt/manager/font_manager.cpp index 2b38168c..295d0cd8 100644 --- a/scwx-qt/source/scwx/qt/manager/font_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/font_manager.cpp @@ -64,6 +64,8 @@ public: std::shared_mutex imguiFontAtlasMutex_ {}; + std::uint64_t imguiFontsBuildCount_ {}; + boost::unordered_flat_map, FontRecordHash> @@ -73,6 +75,12 @@ public: boost::unordered_flat_map> rawFontData_ {}; std::mutex rawFontDataMutex_ {}; + + std::shared_ptr defaultFont_ {}; + boost::unordered_flat_map> + fontCategoryMap_ {}; + std::mutex fontCategoryMutex_ {}; }; FontManager::FontManager() : p(std::make_unique()) {} @@ -84,11 +92,30 @@ std::shared_mutex& FontManager::imgui_font_atlas_mutex() return p->imguiFontAtlasMutex_; } +std::uint64_t FontManager::imgui_fonts_build_count() const +{ + return p->imguiFontsBuildCount_; +} + std::shared_ptr -FontManager::GetImGuiFont(const std::string& family, - const std::vector& styles, - units::font_size::points size, - bool loadIfNotFound) +FontManager::GetImGuiFont(types::FontCategory fontCategory) +{ + std::unique_lock lock {p->fontCategoryMutex_}; + + auto it = p->fontCategoryMap_.find(fontCategory); + if (it != p->fontCategoryMap_.cend()) + { + return it->second; + } + + return p->defaultFont_; +} + +std::shared_ptr +FontManager::LoadImGuiFont(const std::string& family, + const std::vector& styles, + units::font_size::points size, + bool loadIfNotFound) { const std::string styleString = fmt::format("{}", fmt::join(styles, " ")); const std::string fontString = @@ -148,6 +175,9 @@ FontManager::GetImGuiFont(const std::string& family, // Store the ImGui font p->imguiFonts_.insert_or_assign(imguiFontKey, imguiFont); + // Increment ImGui font build count + ++p->imguiFontsBuildCount_; + // Return the ImGui font return imguiFont; } @@ -181,7 +211,7 @@ FontManager::Impl::GetRawFontData(const std::string& filename) auto result = rawFontData_.emplace(filename, std::move(buffer)); // Return the cached buffer - return it->second; + return result.first->second; } void FontManager::LoadApplicationFont(const std::string& filename) diff --git a/scwx-qt/source/scwx/qt/manager/font_manager.hpp b/scwx-qt/source/scwx/qt/manager/font_manager.hpp index 171da79c..6cee1135 100644 --- a/scwx-qt/source/scwx/qt/manager/font_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/font_manager.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include @@ -22,12 +23,15 @@ public: ~FontManager(); std::shared_mutex& imgui_font_atlas_mutex(); + std::uint64_t imgui_fonts_build_count() const; std::shared_ptr - GetImGuiFont(const std::string& family, - const std::vector& styles, - units::font_size::points size, - bool loadIfNotFound = false); + GetImGuiFont(types::FontCategory fontCategory); + std::shared_ptr + LoadImGuiFont(const std::string& family, + const std::vector& styles, + units::font_size::points size, + bool loadIfNotFound = true); void LoadApplicationFont(const std::string& filename); diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index d721e5cb..52414031 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -707,7 +707,7 @@ void PlacefileManager::Impl::LoadFontResources( styles.push_back("italic"); } - FontManager::Instance().GetImGuiFont(font.second->face_, styles, size); + FontManager::Instance().LoadImGuiFont(font.second->face_, styles, size); } } diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index 89eabb3f..96aca7a2 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -155,6 +155,7 @@ public: ImGuiContext* imGuiContext_; std::string imGuiContextName_; bool imGuiRendererInitialized_; + std::uint64_t imGuiFontsBuildCount_ {}; std::shared_ptr placefileManager_ { manager::PlacefileManager::Instance()}; @@ -981,9 +982,15 @@ void MapWidget::initializeGL() makeCurrent(); p->context_->gl().initializeOpenGLFunctions(); + // Lock ImGui font atlas prior to new ImGui frame + std::shared_lock imguiFontAtlasLock { + manager::FontManager::Instance().imgui_font_atlas_mutex()}; + // Initialize ImGui OpenGL3 backend ImGui::SetCurrentContext(p->imGuiContext_); ImGui_ImplOpenGL3_Init(); + p->imGuiFontsBuildCount_ = + manager::FontManager::Instance().imgui_fonts_build_count(); p->imGuiRendererInitialized_ = true; p->map_.reset( @@ -1038,6 +1045,15 @@ void MapWidget::paintGL() ImGui_ImplOpenGL3_NewFrame(); ImGui::NewFrame(); + // Update ImGui Fonts if required + std::uint64_t currentImGuiFontsBuildCount = + manager::FontManager::Instance().imgui_fonts_build_count(); + if (p->imGuiFontsBuildCount_ != currentImGuiFontsBuildCount) + { + ImGui_ImplOpenGL3_DestroyFontsTexture(); + ImGui_ImplOpenGL3_CreateFontsTexture(); + } + // Update pixel ratio p->context_->set_pixel_ratio(pixelRatio()); From b66ca2cb094bcd3ed288cc88087025b6017b77ab Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 5 Oct 2023 21:55:43 -0500 Subject: [PATCH 150/199] Refactor settings to be managed by singletons --- scwx-qt/source/scwx/qt/main/main_window.cpp | 24 +++++---- .../scwx/qt/manager/settings_manager.cpp | 45 ++++++----------- .../scwx/qt/manager/settings_manager.hpp | 8 +-- .../scwx/qt/manager/timeline_manager.cpp | 4 +- scwx-qt/source/scwx/qt/map/alert_layer.cpp | 4 +- scwx-qt/source/scwx/qt/map/map_provider.cpp | 8 ++- scwx-qt/source/scwx/qt/map/map_widget.cpp | 8 +-- .../scwx/qt/settings/general_settings.cpp | 14 ++++-- .../scwx/qt/settings/general_settings.hpp | 7 +-- .../source/scwx/qt/settings/map_settings.cpp | 49 ++++++++++--------- .../source/scwx/qt/settings/map_settings.hpp | 7 +-- .../scwx/qt/settings/palette_settings.cpp | 16 ++++-- .../scwx/qt/settings/palette_settings.hpp | 7 +-- .../source/scwx/qt/settings/text_settings.cpp | 4 +- .../source/scwx/qt/ui/alert_dock_widget.cpp | 10 ++-- .../scwx/qt/ui/animation_dock_widget.cpp | 11 ++--- scwx-qt/source/scwx/qt/ui/settings_dialog.cpp | 10 ++-- scwx-qt/source/scwx/qt/util/font.cpp | 4 +- scwx-qt/source/scwx/qt/util/imgui.cpp | 6 +-- scwx-qt/source/scwx/qt/util/tooltip.cpp | 4 +- .../scwx/qt/manager/settings_manager.test.cpp | 21 +++++--- 21 files changed, 138 insertions(+), 133 deletions(-) diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp index 4fff5ca0..756ca182 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -7,13 +7,14 @@ #include #include #include -#include #include #include #include #include #include #include +#include +#include #include #include #include @@ -89,10 +90,8 @@ public: elevationButtonsChanged_ {false}, resizeElevationButtons_ {false} { - mapProvider_ = - map::GetMapProvider(manager::SettingsManager::general_settings() - .map_provider() - .GetValue()); + mapProvider_ = map::GetMapProvider( + settings::GeneralSettings::Instance().map_provider().GetValue()); const map::MapProviderInfo& mapProviderInfo = map::GetMapProviderInfo(mapProvider_); @@ -230,7 +229,7 @@ MainWindow::MainWindow(QWidget* parent) : ui->actionAlerts->setVisible(false); ui->menuDebug->menuAction()->setVisible( - manager::SettingsManager::general_settings().debug_enabled().GetValue()); + settings::GeneralSettings::Instance().debug_enabled().GetValue()); // Configure Resource Explorer Dock ui->resourceExplorerDock->setVisible(false); @@ -306,7 +305,7 @@ MainWindow::MainWindow(QWidget* parent) : // Update Dialog p->updateDialog_ = new ui::UpdateDialog(this); - auto& mapSettings = manager::SettingsManager::map_settings(); + auto& mapSettings = settings::MapSettings::Instance(); for (size_t i = 0; i < p->maps_.size(); i++) { p->SelectRadarProduct(p->maps_.at(i), @@ -582,7 +581,7 @@ void MainWindow::on_resourceTreeView_doubleClicked(const QModelIndex& index) void MainWindowImpl::AsyncSetup() { - auto& generalSettings = manager::SettingsManager::general_settings(); + auto& generalSettings = settings::GeneralSettings::Instance(); // Check for updates if (generalSettings.update_notifications_enabled().GetValue()) @@ -595,7 +594,7 @@ void MainWindowImpl::AsyncSetup() void MainWindowImpl::ConfigureMapLayout() { - auto& generalSettings = manager::SettingsManager::general_settings(); + auto& generalSettings = settings::GeneralSettings::Instance(); const int64_t gridWidth = generalSettings.grid_width().GetValue(); const int64_t gridHeight = generalSettings.grid_height().GetValue(); @@ -646,7 +645,7 @@ void MainWindowImpl::ConfigureMapLayout() void MainWindowImpl::ConfigureMapStyles() { const auto& mapProviderInfo = map::GetMapProviderInfo(mapProvider_); - auto& mapSettings = manager::SettingsManager::map_settings(); + auto& mapSettings = settings::MapSettings::Instance(); for (std::size_t i = 0; i < maps_.size(); i++) { @@ -897,8 +896,7 @@ void MainWindowImpl::ConnectOtherSignals() { if (maps_[i] == activeMap_) { - auto& mapSettings = - manager::SettingsManager::map_settings(); + auto& mapSettings = settings::MapSettings::Instance(); mapSettings.map_style(i).StageValue(text.toStdString()); break; } @@ -1075,7 +1073,7 @@ void MainWindowImpl::UpdateMapStyle(const std::string& styleName) { if (maps_[i] == activeMap_) { - auto& mapSettings = manager::SettingsManager::map_settings(); + auto& mapSettings = settings::MapSettings::Instance(); mapSettings.map_style(i).StageValue(styleName); break; } diff --git a/scwx-qt/source/scwx/qt/manager/settings_manager.cpp b/scwx-qt/source/scwx/qt/manager/settings_manager.cpp index 7c973ca4..1a2f6c04 100644 --- a/scwx-qt/source/scwx/qt/manager/settings_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/settings_manager.cpp @@ -1,5 +1,8 @@ #include #include +#include +#include +#include #include #include #include @@ -96,8 +99,8 @@ void Shutdown() { bool dataChanged = false; - dataChanged |= general_settings().Shutdown(); - dataChanged |= map_settings().Shutdown(); + dataChanged |= settings::GeneralSettings::Instance().Shutdown(); + dataChanged |= settings::MapSettings::Instance().Shutdown(); dataChanged |= settings::UiSettings::Instance().Shutdown(); if (dataChanged) @@ -106,31 +109,13 @@ void Shutdown() } } -settings::GeneralSettings& general_settings() -{ - static settings::GeneralSettings generalSettings_; - return generalSettings_; -} - -settings::MapSettings& map_settings() -{ - static settings::MapSettings mapSettings_; - return mapSettings_; -} - -settings::PaletteSettings& palette_settings() -{ - static settings::PaletteSettings paletteSettings_; - return paletteSettings_; -} - static boost::json::value ConvertSettingsToJson() { boost::json::object settingsJson; - general_settings().WriteJson(settingsJson); - map_settings().WriteJson(settingsJson); - palette_settings().WriteJson(settingsJson); + settings::GeneralSettings::Instance().WriteJson(settingsJson); + settings::MapSettings::Instance().WriteJson(settingsJson); + settings::PaletteSettings::Instance().WriteJson(settingsJson); settings::TextSettings::Instance().WriteJson(settingsJson); settings::UiSettings::Instance().WriteJson(settingsJson); @@ -141,9 +126,9 @@ static void GenerateDefaultSettings() { logger_->info("Generating default settings"); - general_settings().SetDefaults(); - map_settings().SetDefaults(); - palette_settings().SetDefaults(); + settings::GeneralSettings::Instance().SetDefaults(); + settings::MapSettings::Instance().SetDefaults(); + settings::PaletteSettings::Instance().SetDefaults(); settings::TextSettings::Instance().SetDefaults(); settings::UiSettings::Instance().SetDefaults(); } @@ -154,9 +139,9 @@ static bool LoadSettings(const boost::json::object& settingsJson) bool jsonDirty = false; - jsonDirty |= !general_settings().ReadJson(settingsJson); - jsonDirty |= !map_settings().ReadJson(settingsJson); - jsonDirty |= !palette_settings().ReadJson(settingsJson); + jsonDirty |= !settings::GeneralSettings::Instance().ReadJson(settingsJson); + jsonDirty |= !settings::MapSettings::Instance().ReadJson(settingsJson); + jsonDirty |= !settings::PaletteSettings::Instance().ReadJson(settingsJson); jsonDirty |= !settings::TextSettings::Instance().ReadJson(settingsJson); jsonDirty |= !settings::UiSettings::Instance().ReadJson(settingsJson); @@ -169,7 +154,7 @@ static void ValidateSettings() bool settingsChanged = false; - auto& generalSettings = general_settings(); + auto& generalSettings = settings::GeneralSettings::Instance(); // Validate map provider std::string mapProviderName = generalSettings.map_provider().GetValue(); diff --git a/scwx-qt/source/scwx/qt/manager/settings_manager.hpp b/scwx-qt/source/scwx/qt/manager/settings_manager.hpp index ed05ca1e..5cc17b19 100644 --- a/scwx-qt/source/scwx/qt/manager/settings_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/settings_manager.hpp @@ -1,8 +1,6 @@ #pragma once -#include -#include -#include +#include namespace scwx { @@ -18,10 +16,6 @@ void ReadSettings(const std::string& settingsPath); void SaveSettings(); void Shutdown(); -settings::GeneralSettings& general_settings(); -settings::MapSettings& map_settings(); -settings::PaletteSettings& palette_settings(); - } // namespace SettingsManager } // namespace manager } // namespace qt diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp index 8e79a552..f7fc3f48 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include #include @@ -39,7 +39,7 @@ class TimelineManager::Impl public: explicit Impl(TimelineManager* self) : self_ {self} { - auto& generalSettings = SettingsManager::general_settings(); + auto& generalSettings = settings::GeneralSettings::Instance(); loopDelay_ = std::chrono::milliseconds(generalSettings.loop_delay().GetValue()); diff --git a/scwx-qt/source/scwx/qt/map/alert_layer.cpp b/scwx-qt/source/scwx/qt/map/alert_layer.cpp index f769231c..044cfb00 100644 --- a/scwx-qt/source/scwx/qt/map/alert_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/alert_layer.cpp @@ -1,6 +1,6 @@ #include -#include #include +#include #include #include #include @@ -394,7 +394,7 @@ static void AddAlertLayer(std::shared_ptr map, const QString& beforeLayer) { settings::PaletteSettings& paletteSettings = - manager::SettingsManager::palette_settings(); + settings::PaletteSettings::Instance(); QString sourceId = GetSourceId(phenomenon, alertActive); QString idSuffix = GetSuffix(phenomenon, alertActive); diff --git a/scwx-qt/source/scwx/qt/map/map_provider.cpp b/scwx-qt/source/scwx/qt/map/map_provider.cpp index 2a431614..df4bba08 100644 --- a/scwx-qt/source/scwx/qt/map/map_provider.cpp +++ b/scwx-qt/source/scwx/qt/map/map_provider.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include @@ -128,12 +128,10 @@ std::string GetMapProviderApiKey(MapProvider mapProvider) switch (mapProvider) { case MapProvider::Mapbox: - return manager::SettingsManager::general_settings() - .mapbox_api_key() - .GetValue(); + return settings::GeneralSettings::Instance().mapbox_api_key().GetValue(); case MapProvider::MapTiler: - return manager::SettingsManager::general_settings() + return settings::GeneralSettings::Instance() .maptiler_api_key() .GetValue(); diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index 96aca7a2..adfcc467 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -13,6 +12,8 @@ #include #include #include +#include +#include #include #include #include @@ -81,8 +82,7 @@ public: prevBearing_ {0.0}, prevPitch_ {0.0} { - auto& generalSettings = - scwx::qt::manager::SettingsManager::general_settings(); + auto& generalSettings = settings::GeneralSettings::Instance(); SetRadarSite(generalSettings.default_radar_site().GetValue()); @@ -1227,7 +1227,7 @@ void MapWidgetImpl::InitializeNewRadarProductView( auto radarProductView = context_->radar_product_view(); std::string colorTableFile = - manager::SettingsManager::palette_settings() + settings::PaletteSettings::Instance() .palette(colorPalette) .GetValue(); if (!colorTableFile.empty()) diff --git a/scwx-qt/source/scwx/qt/settings/general_settings.cpp b/scwx-qt/source/scwx/qt/settings/general_settings.cpp index 8f2d3e74..bd94acef 100644 --- a/scwx-qt/source/scwx/qt/settings/general_settings.cpp +++ b/scwx-qt/source/scwx/qt/settings/general_settings.cpp @@ -16,10 +16,10 @@ namespace settings static const std::string logPrefix_ = "scwx::qt::settings::general_settings"; -class GeneralSettingsImpl +class GeneralSettings::Impl { public: - explicit GeneralSettingsImpl() + explicit Impl() { std::string defaultDefaultAlertActionValue = types::GetAlertActionName(types::AlertAction::Go); @@ -102,7 +102,7 @@ public: { return !value.empty(); }); } - ~GeneralSettingsImpl() {} + ~Impl() {} SettingsVariable debugEnabled_ {"debug_enabled"}; SettingsVariable defaultAlertAction_ {"default_alert_action"}; @@ -120,7 +120,7 @@ public: }; GeneralSettings::GeneralSettings() : - SettingsCategory("general"), p(std::make_unique()) + SettingsCategory("general"), p(std::make_unique()) { RegisterVariables({&p->debugEnabled_, &p->defaultAlertAction_, @@ -221,6 +221,12 @@ bool GeneralSettings::Shutdown() return dataChanged; } +GeneralSettings& GeneralSettings::Instance() +{ + static GeneralSettings generalSettings_; + return generalSettings_; +} + bool operator==(const GeneralSettings& lhs, const GeneralSettings& rhs) { return (lhs.p->debugEnabled_ == rhs.p->debugEnabled_ && diff --git a/scwx-qt/source/scwx/qt/settings/general_settings.hpp b/scwx-qt/source/scwx/qt/settings/general_settings.hpp index 375d887e..ba58676f 100644 --- a/scwx-qt/source/scwx/qt/settings/general_settings.hpp +++ b/scwx-qt/source/scwx/qt/settings/general_settings.hpp @@ -13,8 +13,6 @@ namespace qt namespace settings { -class GeneralSettingsImpl; - class GeneralSettings : public SettingsCategory { public: @@ -41,13 +39,16 @@ public: SettingsVariable& maptiler_api_key() const; SettingsVariable& update_notifications_enabled() const; + static GeneralSettings& Instance(); + friend bool operator==(const GeneralSettings& lhs, const GeneralSettings& rhs); bool Shutdown(); private: - std::unique_ptr p; + class Impl; + std::unique_ptr p; }; } // namespace settings diff --git a/scwx-qt/source/scwx/qt/settings/map_settings.cpp b/scwx-qt/source/scwx/qt/settings/map_settings.cpp index 8edd1c31..2d8e93b0 100644 --- a/scwx-qt/source/scwx/qt/settings/map_settings.cpp +++ b/scwx-qt/source/scwx/qt/settings/map_settings.cpp @@ -35,7 +35,7 @@ static const std::string kDefaultRadarProductGroupString_ = "L3"; static const std::array kDefaultRadarProduct_ { "N0B", "N0G", "N0C", "N0X"}; -class MapSettingsImpl +class MapSettings::Impl { public: struct MapData @@ -47,7 +47,7 @@ public: SettingsVariable radarProduct_ {kRadarProductName_}; }; - explicit MapSettingsImpl() + explicit Impl() { for (std::size_t i = 0; i < kCount_; i++) { @@ -101,7 +101,7 @@ public: } } - ~MapSettingsImpl() {} + ~Impl() {} void SetDefaults(std::size_t i) { @@ -111,12 +111,30 @@ public: map_[i].radarProduct_.SetValueToDefault(); } + friend void tag_invoke(boost::json::value_from_tag, + boost::json::value& jv, + const MapData& data) + { + jv = {{kMapStyleName_, data.mapStyle_.GetValue()}, + {kRadarSiteName_, data.radarSite_.GetValue()}, + {kRadarProductGroupName_, data.radarProductGroup_.GetValue()}, + {kRadarProductName_, data.radarProduct_.GetValue()}}; + } + + friend bool operator==(const MapData& lhs, const MapData& rhs) + { + return (lhs.mapStyle_ == rhs.mapStyle_ && // + lhs.radarSite_ == rhs.radarSite_ && + lhs.radarProductGroup_ == rhs.radarProductGroup_ && + lhs.radarProduct_ == rhs.radarProduct_); + } + std::array map_ {}; std::vector variables_ {}; }; MapSettings::MapSettings() : - SettingsCategory("maps"), p(std::make_unique()) + SettingsCategory("maps"), p(std::make_unique()) { RegisterVariables(p->variables_); SetDefaults(); @@ -161,7 +179,7 @@ bool MapSettings::Shutdown() // Commit settings that are managed separate from the settings dialog for (std::size_t i = 0; i < kCount_; ++i) { - MapSettingsImpl::MapData& mapRecordSettings = p->map_[i]; + Impl::MapData& mapRecordSettings = p->map_[i]; dataChanged |= mapRecordSettings.mapStyle_.Commit(); } @@ -184,7 +202,7 @@ bool MapSettings::ReadJson(const boost::json::object& json) if (i < mapArray.size() && mapArray.at(i).is_object()) { const boost::json::object& mapRecord = mapArray.at(i).as_object(); - MapSettingsImpl::MapData& mapRecordSettings = p->map_[i]; + Impl::MapData& mapRecordSettings = p->map_[i]; // Load JSON Elements validated &= mapRecordSettings.mapStyle_.ReadValue(mapRecord); @@ -234,14 +252,10 @@ void MapSettings::WriteJson(boost::json::object& json) const json.insert_or_assign(name(), object); } -void tag_invoke(boost::json::value_from_tag, - boost::json::value& jv, - const MapSettingsImpl::MapData& data) +MapSettings& MapSettings::Instance() { - jv = {{kMapStyleName_, data.mapStyle_.GetValue()}, - {kRadarSiteName_, data.radarSite_.GetValue()}, - {kRadarProductGroupName_, data.radarProductGroup_.GetValue()}, - {kRadarProductName_, data.radarProduct_.GetValue()}}; + static MapSettings mapSettings_; + return mapSettings_; } bool operator==(const MapSettings& lhs, const MapSettings& rhs) @@ -249,15 +263,6 @@ bool operator==(const MapSettings& lhs, const MapSettings& rhs) return (lhs.p->map_ == rhs.p->map_); } -bool operator==(const MapSettingsImpl::MapData& lhs, - const MapSettingsImpl::MapData& rhs) -{ - return (lhs.mapStyle_ == rhs.mapStyle_ && // - lhs.radarSite_ == rhs.radarSite_ && - lhs.radarProductGroup_ == rhs.radarProductGroup_ && - lhs.radarProduct_ == rhs.radarProduct_); -} - } // namespace settings } // namespace qt } // namespace scwx diff --git a/scwx-qt/source/scwx/qt/settings/map_settings.hpp b/scwx-qt/source/scwx/qt/settings/map_settings.hpp index fd0c74dd..c8726491 100644 --- a/scwx-qt/source/scwx/qt/settings/map_settings.hpp +++ b/scwx-qt/source/scwx/qt/settings/map_settings.hpp @@ -13,8 +13,6 @@ namespace qt namespace settings { -class MapSettingsImpl; - class MapSettings : public SettingsCategory { public: @@ -52,10 +50,13 @@ public: */ void WriteJson(boost::json::object& json) const override; + static MapSettings& Instance(); + friend bool operator==(const MapSettings& lhs, const MapSettings& rhs); private: - std::unique_ptr p; + class Impl; + std::unique_ptr p; }; } // namespace settings diff --git a/scwx-qt/source/scwx/qt/settings/palette_settings.cpp b/scwx-qt/source/scwx/qt/settings/palette_settings.cpp index 8edf2cf2..f041c078 100644 --- a/scwx-qt/source/scwx/qt/settings/palette_settings.cpp +++ b/scwx-qt/source/scwx/qt/settings/palette_settings.cpp @@ -72,10 +72,10 @@ static const std::map< static const std::string kDefaultKey_ {"???"}; static const awips::Phenomenon kDefaultPhenomenon_ {awips::Phenomenon::Marine}; -class PaletteSettingsImpl +class PaletteSettings::Impl { public: - explicit PaletteSettingsImpl() + explicit Impl() { for (const auto& name : kPaletteKeys_) { @@ -120,7 +120,7 @@ public: } } - ~PaletteSettingsImpl() {} + ~Impl() {} static bool ValidateColor(const std::string& value); @@ -132,14 +132,14 @@ public: std::vector variables_ {}; }; -bool PaletteSettingsImpl::ValidateColor(const std::string& value) +bool PaletteSettings::Impl::ValidateColor(const std::string& value) { static const std::regex re {"#[0-9A-Za-z]{8}"}; return std::regex_match(value, re); } PaletteSettings::PaletteSettings() : - SettingsCategory("palette"), p(std::make_unique()) + SettingsCategory("palette"), p(std::make_unique()) { RegisterVariables(p->variables_); SetDefaults(); @@ -200,6 +200,12 @@ const std::vector& PaletteSettings::alert_phenomena() return kAlertPhenomena_; } +PaletteSettings& PaletteSettings::Instance() +{ + static PaletteSettings paletteSettings_; + return paletteSettings_; +} + bool operator==(const PaletteSettings& lhs, const PaletteSettings& rhs) { return lhs.p->palette_ == rhs.p->palette_; diff --git a/scwx-qt/source/scwx/qt/settings/palette_settings.hpp b/scwx-qt/source/scwx/qt/settings/palette_settings.hpp index 948decf5..c0f7985a 100644 --- a/scwx-qt/source/scwx/qt/settings/palette_settings.hpp +++ b/scwx-qt/source/scwx/qt/settings/palette_settings.hpp @@ -14,8 +14,6 @@ namespace qt namespace settings { -class PaletteSettingsImpl; - class PaletteSettings : public SettingsCategory { public: @@ -34,11 +32,14 @@ public: static const std::vector& alert_phenomena(); + static PaletteSettings& Instance(); + friend bool operator==(const PaletteSettings& lhs, const PaletteSettings& rhs); private: - std::unique_ptr p; + class Impl; + std::unique_ptr p; }; } // namespace settings diff --git a/scwx-qt/source/scwx/qt/settings/text_settings.cpp b/scwx-qt/source/scwx/qt/settings/text_settings.cpp index 5bc9946b..fdf41acf 100644 --- a/scwx-qt/source/scwx/qt/settings/text_settings.cpp +++ b/scwx-qt/source/scwx/qt/settings/text_settings.cpp @@ -169,8 +169,8 @@ SettingsVariable& TextSettings::tooltip_method() const TextSettings& TextSettings::Instance() { - static TextSettings TextSettings_; - return TextSettings_; + static TextSettings textSettings_; + return textSettings_; } bool operator==(const TextSettings& lhs, const TextSettings& rhs) diff --git a/scwx-qt/source/scwx/qt/ui/alert_dock_widget.cpp b/scwx-qt/source/scwx/qt/ui/alert_dock_widget.cpp index 5efca78a..e827469c 100644 --- a/scwx-qt/source/scwx/qt/ui/alert_dock_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/alert_dock_widget.cpp @@ -1,10 +1,10 @@ #include "alert_dock_widget.hpp" #include "ui_alert_dock_widget.h" -#include #include #include #include +#include #include #include #include @@ -175,10 +175,10 @@ void AlertDockWidgetImpl::ConnectSignals() // If an item is selected if (selectedAlertKey_ != types::TextEventKey {}) { - types::AlertAction alertAction = types::GetAlertAction( - manager::SettingsManager::general_settings() - .default_alert_action() - .GetValue()); + types::AlertAction alertAction = + types::GetAlertAction(settings::GeneralSettings::Instance() + .default_alert_action() + .GetValue()); switch (alertAction) { diff --git a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp index 34b1001a..f19be01f 100644 --- a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp @@ -1,7 +1,7 @@ #include "animation_dock_widget.hpp" #include "ui_animation_dock_widget.h" -#include +#include #include #include @@ -101,7 +101,7 @@ AnimationDockWidget::AnimationDockWidget(QWidget* parent) : maxDateTimer->start(15000); // Set loop defaults - auto& generalSettings = manager::SettingsManager::general_settings(); + auto& generalSettings = settings::GeneralSettings::Instance(); ui->loopTimeSpinBox->setValue(generalSettings.loop_time().GetValue()); ui->loopSpeedSpinBox->setValue(generalSettings.loop_speed().GetValue()); ui->loopDelaySpinBox->setValue(generalSettings.loop_delay().GetValue() * @@ -175,7 +175,7 @@ void AnimationDockWidgetImpl::ConnectSignals() self_, [this](int i) { - manager::SettingsManager::general_settings().loop_time().StageValue(i); + settings::GeneralSettings::Instance().loop_time().StageValue(i); Q_EMIT self_->LoopTimeChanged(std::chrono::minutes(i)); }); QObject::connect( @@ -184,8 +184,7 @@ void AnimationDockWidgetImpl::ConnectSignals() self_, [this](double d) { - manager::SettingsManager::general_settings().loop_speed().StageValue( - d); + settings::GeneralSettings::Instance().loop_speed().StageValue(d); Q_EMIT self_->LoopSpeedChanged(d); }); QObject::connect( @@ -194,7 +193,7 @@ void AnimationDockWidgetImpl::ConnectSignals() self_, [this](double d) { - manager::SettingsManager::general_settings().loop_delay().StageValue( + settings::GeneralSettings::Instance().loop_delay().StageValue( static_cast(d * 1000.0)); Q_EMIT self_->LoopDelayChanged(std::chrono::milliseconds( static_cast(d * 1000.0))); diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp index 32464ecf..44a36bf0 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include #include #include @@ -104,7 +106,7 @@ public: &tooltipMethod_}} { // Configure default alert phenomena colors - auto& paletteSettings = manager::SettingsManager::palette_settings(); + auto& paletteSettings = settings::PaletteSettings::Instance(); int index = 0; for (auto& phenomenon : settings::PaletteSettings::alert_phenomena()) @@ -384,7 +386,7 @@ void SettingsDialogImpl::SetupGeneralTab() } settings::GeneralSettings& generalSettings = - manager::SettingsManager::general_settings(); + settings::GeneralSettings::Instance(); defaultRadarSite_.SetSettingsVariable(generalSettings.default_radar_site()); defaultRadarSite_.SetMapFromValueFunction( @@ -525,7 +527,7 @@ void SettingsDialogImpl::SetupGeneralTab() void SettingsDialogImpl::SetupPalettesColorTablesTab() { settings::PaletteSettings& paletteSettings = - manager::SettingsManager::palette_settings(); + settings::PaletteSettings::Instance(); // Palettes > Color Tables QGridLayout* colorTableLayout = @@ -617,7 +619,7 @@ void SettingsDialogImpl::SetupPalettesColorTablesTab() void SettingsDialogImpl::SetupPalettesAlertsTab() { settings::PaletteSettings& paletteSettings = - manager::SettingsManager::palette_settings(); + settings::PaletteSettings::Instance(); // Palettes > Alerts QGridLayout* alertsLayout = diff --git a/scwx-qt/source/scwx/qt/util/font.cpp b/scwx-qt/source/scwx/qt/util/font.cpp index 49fdee3a..74374163 100644 --- a/scwx-qt/source/scwx/qt/util/font.cpp +++ b/scwx-qt/source/scwx/qt/util/font.cpp @@ -5,8 +5,8 @@ #define _CRT_SECURE_NO_WARNINGS #include -#include #include +#include #include #include @@ -337,7 +337,7 @@ std::shared_ptr Font::Create(const std::string& resource) font->p->CreateImGuiFont( fontFile, fontData, - manager::SettingsManager::general_settings().font_sizes().GetValue()); + settings::GeneralSettings::Instance().font_sizes().GetValue()); font->p->atlas_ = ftgl::texture_atlas_new(512, 512, 1); ftgl::texture_font_t* textureFont = ftgl::texture_font_new_from_memory( diff --git a/scwx-qt/source/scwx/qt/util/imgui.cpp b/scwx-qt/source/scwx/qt/util/imgui.cpp index 9d1622ed..0afddf43 100644 --- a/scwx-qt/source/scwx/qt/util/imgui.cpp +++ b/scwx-qt/source/scwx/qt/util/imgui.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include @@ -48,7 +48,7 @@ void ImGui::Impl::Initialize() // Configure monospace font UpdateMonospaceFont(); - manager::SettingsManager::general_settings() + settings::GeneralSettings::Instance() .font_sizes() .RegisterValueChangedCallback([this](const std::vector&) { UpdateMonospaceFont(); }); @@ -61,7 +61,7 @@ void ImGui::Impl::UpdateMonospaceFont() // Get monospace font size std::size_t fontSize = 16; auto fontSizes = - manager::SettingsManager::general_settings().font_sizes().GetValue(); + settings::GeneralSettings::Instance().font_sizes().GetValue(); if (fontSizes.size() > 1) { fontSize = fontSizes[1]; diff --git a/scwx-qt/source/scwx/qt/util/tooltip.cpp b/scwx-qt/source/scwx/qt/util/tooltip.cpp index 5d7b4c6c..3e9432df 100644 --- a/scwx-qt/source/scwx/qt/util/tooltip.cpp +++ b/scwx-qt/source/scwx/qt/util/tooltip.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include @@ -100,7 +100,7 @@ void Show(const std::string& text, const QPointF& mouseGlobalPos) // Get monospace font size units::font_size::pixels fontSize {16}; auto fontSizes = - manager::SettingsManager::general_settings().font_sizes().GetValue(); + settings::GeneralSettings::Instance().font_sizes().GetValue(); if (fontSizes.size() > 1) { fontSize = units::font_size::pixels {fontSizes[1]}; diff --git a/test/source/scwx/qt/manager/settings_manager.test.cpp b/test/source/scwx/qt/manager/settings_manager.test.cpp index be576069..a4d69614 100644 --- a/test/source/scwx/qt/manager/settings_manager.test.cpp +++ b/test/source/scwx/qt/manager/settings_manager.test.cpp @@ -1,5 +1,10 @@ #include #include +#include +#include +#include +#include +#include #include #include @@ -39,10 +44,14 @@ void VerifyDefaults() settings::GeneralSettings defaultGeneralSettings {}; settings::MapSettings defaultMapSettings {}; settings::PaletteSettings defaultPaletteSettings {}; + settings::TextSettings defaultTextSettings {}; + settings::UiSettings defaultUiSettings {}; - EXPECT_EQ(defaultGeneralSettings, SettingsManager::general_settings()); - EXPECT_EQ(defaultMapSettings, SettingsManager::map_settings()); - EXPECT_EQ(defaultPaletteSettings, SettingsManager::palette_settings()); + EXPECT_EQ(defaultGeneralSettings, settings::GeneralSettings::Instance()); + EXPECT_EQ(defaultMapSettings, settings::MapSettings::Instance()); + EXPECT_EQ(defaultPaletteSettings, settings::PaletteSettings::Instance()); + EXPECT_EQ(defaultTextSettings, settings::TextSettings::Instance()); + EXPECT_EQ(defaultUiSettings, settings::UiSettings::Instance()); } void CompareFiles(const std::string& file1, const std::string& file2) @@ -86,11 +95,11 @@ TEST_F(SettingsManagerTest, SettingsKeax) SettingsManager::ReadSettings(filename); EXPECT_EQ( - SettingsManager::general_settings().default_radar_site().GetValue(), + settings::GeneralSettings::Instance().default_radar_site().GetValue(), "KEAX"); - for (size_t i = 0; i < SettingsManager::map_settings().count(); ++i) + for (size_t i = 0; i < settings::MapSettings::Instance().count(); ++i) { - EXPECT_EQ(SettingsManager::map_settings().radar_site(i).GetValue(), + EXPECT_EQ(settings::MapSettings::Instance().radar_site(i).GetValue(), "KEAX"); } } From 1f964c49f8ff74ad0f8fbe94da5a976293c612cf Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 6 Oct 2023 04:50:26 -0500 Subject: [PATCH 151/199] Check ImGui fonts before new frame --- scwx-qt/source/scwx/qt/map/map_widget.cpp | 27 ++++++++++++------- .../source/scwx/qt/ui/imgui_debug_widget.cpp | 27 ++++++++++++++++++- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index adfcc467..1e5397e6 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -124,6 +124,7 @@ public: std::shared_ptr layer, const std::string& before = {}); void ConnectSignals(); + void ImGuiCheckFonts(); void InitializeNewRadarProductView(const std::string& colorPalette); void RadarProductManagerConnect(); void RadarProductManagerDisconnect(); @@ -1043,17 +1044,9 @@ void MapWidget::paintGL() // Start ImGui Frame ImGui_ImplQt_NewFrame(this); ImGui_ImplOpenGL3_NewFrame(); + p->ImGuiCheckFonts(); ImGui::NewFrame(); - // Update ImGui Fonts if required - std::uint64_t currentImGuiFontsBuildCount = - manager::FontManager::Instance().imgui_fonts_build_count(); - if (p->imGuiFontsBuildCount_ != currentImGuiFontsBuildCount) - { - ImGui_ImplOpenGL3_DestroyFontsTexture(); - ImGui_ImplOpenGL3_CreateFontsTexture(); - } - // Update pixel ratio p->context_->set_pixel_ratio(pixelRatio()); @@ -1087,6 +1080,22 @@ void MapWidget::paintGL() Q_EMIT WidgetPainted(); } +void MapWidgetImpl::ImGuiCheckFonts() +{ + // Update ImGui Fonts if required + std::uint64_t currentImGuiFontsBuildCount = + manager::FontManager::Instance().imgui_fonts_build_count(); + + if (imGuiFontsBuildCount_ != currentImGuiFontsBuildCount || + !model::ImGuiContextModel::Instance().font_atlas()->IsBuilt()) + { + ImGui_ImplOpenGL3_DestroyFontsTexture(); + ImGui_ImplOpenGL3_CreateFontsTexture(); + } + + imGuiFontsBuildCount_ = currentImGuiFontsBuildCount; +} + void MapWidgetImpl::RunMousePicking() { const QMapLibreGL::CustomLayerRenderParameters params = diff --git a/scwx-qt/source/scwx/qt/ui/imgui_debug_widget.cpp b/scwx-qt/source/scwx/qt/ui/imgui_debug_widget.cpp index 04e692ac..fcbed5d6 100644 --- a/scwx-qt/source/scwx/qt/ui/imgui_debug_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/imgui_debug_widget.cpp @@ -51,6 +51,8 @@ public: model::ImGuiContextModel::Instance().DestroyContext(contextName_); } + void ImGuiCheckFonts(); + ImGuiDebugWidget* self_; ImGuiContext* context_; std::string contextName_; @@ -59,6 +61,7 @@ public: std::set renderedSet_ {}; bool imGuiRendererInitialized_ {false}; + std::uint64_t imGuiFontsBuildCount_ {}; }; ImGuiDebugWidget::ImGuiDebugWidget(QWidget* parent) : @@ -103,6 +106,8 @@ void ImGuiDebugWidget::initializeGL() // Initialize ImGui OpenGL3 backend ImGui::SetCurrentContext(p->context_); ImGui_ImplOpenGL3_Init(); + p->imGuiFontsBuildCount_ = + manager::FontManager::Instance().imgui_fonts_build_count(); p->imGuiRendererInitialized_ = true; } @@ -116,7 +121,7 @@ void ImGuiDebugWidget::paintGL() ImGui_ImplQt_NewFrame(this); ImGui_ImplOpenGL3_NewFrame(); - + p->ImGuiCheckFonts(); ImGui::NewFrame(); if (!p->renderedSet_.contains(p->currentContext_)) @@ -141,6 +146,26 @@ void ImGuiDebugWidget::paintGL() imguiFontAtlasLock.unlock(); } +void ImGuiDebugWidgetImpl::ImGuiCheckFonts() +{ + // Update ImGui Fonts if required + std::uint64_t currentImGuiFontsBuildCount = + manager::FontManager::Instance().imgui_fonts_build_count(); + + if ((context_ == currentContext_ && + imGuiFontsBuildCount_ != currentImGuiFontsBuildCount) || + !model::ImGuiContextModel::Instance().font_atlas()->IsBuilt()) + { + ImGui_ImplOpenGL3_DestroyFontsTexture(); + ImGui_ImplOpenGL3_CreateFontsTexture(); + } + + if (context_ == currentContext_) + { + imGuiFontsBuildCount_ = currentImGuiFontsBuildCount; + } +} + } // namespace ui } // namespace qt } // namespace scwx From 67881d31d58f5371e5b5fa07ac1dd7a944d46941 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 6 Oct 2023 05:05:59 -0500 Subject: [PATCH 152/199] Make SettingsManager an object instead of a namespace --- scwx-qt/source/scwx/qt/main/main.cpp | 4 +- .../scwx/qt/manager/settings_manager.cpp | 76 ++++++++++++------- .../scwx/qt/manager/settings_manager.hpp | 28 +++++-- scwx-qt/source/scwx/qt/ui/settings_dialog.cpp | 2 +- .../scwx/qt/manager/settings_manager.test.cpp | 8 +- 5 files changed, 76 insertions(+), 42 deletions(-) diff --git a/scwx-qt/source/scwx/qt/main/main.cpp b/scwx-qt/source/scwx/qt/main/main.cpp index b841b846..4c0bdac4 100644 --- a/scwx-qt/source/scwx/qt/main/main.cpp +++ b/scwx-qt/source/scwx/qt/main/main.cpp @@ -69,7 +69,7 @@ int main(int argc, char* argv[]) // Initialize application scwx::qt::config::RadarSite::Initialize(); - scwx::qt::manager::SettingsManager::Initialize(); + scwx::qt::manager::SettingsManager::Instance().Initialize(); scwx::qt::manager::ResourceManager::Initialize(); // Run Qt main loop @@ -89,7 +89,7 @@ int main(int argc, char* argv[]) // Shutdown application scwx::qt::manager::ResourceManager::Shutdown(); - scwx::qt::manager::SettingsManager::Shutdown(); + scwx::qt::manager::SettingsManager::Instance().Shutdown(); // Shutdown AWS SDK Aws::ShutdownAPI(awsSdkOptions); diff --git a/scwx-qt/source/scwx/qt/manager/settings_manager.cpp b/scwx-qt/source/scwx/qt/manager/settings_manager.cpp index 1a2f6c04..083453ac 100644 --- a/scwx-qt/source/scwx/qt/manager/settings_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/settings_manager.cpp @@ -21,21 +21,33 @@ namespace qt { namespace manager { -namespace SettingsManager -{ static const std::string logPrefix_ = "scwx::qt::manager::settings_manager"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); -static boost::json::value ConvertSettingsToJson(); -static void GenerateDefaultSettings(); -static bool LoadSettings(const boost::json::object& settingsJson); -static void ValidateSettings(); +class SettingsManager::Impl +{ +public: + explicit Impl(SettingsManager* self) : self_ {self} {} + ~Impl() = default; -static bool initialized_ {false}; -static std::string settingsPath_ {}; + void ValidateSettings(); -void Initialize() + static boost::json::value ConvertSettingsToJson(); + static void GenerateDefaultSettings(); + static bool LoadSettings(const boost::json::object& settingsJson); + + SettingsManager* self_; + + bool initialized_ {false}; + std::string settingsPath_ {}; +}; + +SettingsManager::SettingsManager() : p(std::make_unique(this)) {} + +SettingsManager::~SettingsManager() {}; + +void SettingsManager::Initialize() { std::string appDataPath { QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) @@ -50,14 +62,14 @@ void Initialize() } } - settingsPath_ = appDataPath + "/settings.json"; - initialized_ = true; + p->settingsPath_ = appDataPath + "/settings.json"; + p->initialized_ = true; - ReadSettings(settingsPath_); - ValidateSettings(); + ReadSettings(p->settingsPath_); + p->ValidateSettings(); } -void ReadSettings(const std::string& settingsPath) +void SettingsManager::ReadSettings(const std::string& settingsPath) { boost::json::value settingsJson = nullptr; @@ -68,34 +80,34 @@ void ReadSettings(const std::string& settingsPath) if (settingsJson == nullptr || !settingsJson.is_object()) { - GenerateDefaultSettings(); - settingsJson = ConvertSettingsToJson(); + Impl::GenerateDefaultSettings(); + settingsJson = Impl::ConvertSettingsToJson(); util::json::WriteJsonFile(settingsPath, settingsJson); } else { - bool jsonDirty = LoadSettings(settingsJson.as_object()); + bool jsonDirty = Impl::LoadSettings(settingsJson.as_object()); if (jsonDirty) { - settingsJson = ConvertSettingsToJson(); + settingsJson = Impl::ConvertSettingsToJson(); util::json::WriteJsonFile(settingsPath, settingsJson); } }; } -void SaveSettings() +void SettingsManager::SaveSettings() { - if (initialized_) + if (p->initialized_) { logger_->info("Saving settings"); - boost::json::value settingsJson = ConvertSettingsToJson(); - util::json::WriteJsonFile(settingsPath_, settingsJson); + boost::json::value settingsJson = Impl::ConvertSettingsToJson(); + util::json::WriteJsonFile(p->settingsPath_, settingsJson); } } -void Shutdown() +void SettingsManager::Shutdown() { bool dataChanged = false; @@ -109,7 +121,7 @@ void Shutdown() } } -static boost::json::value ConvertSettingsToJson() +boost::json::value SettingsManager::Impl::ConvertSettingsToJson() { boost::json::object settingsJson; @@ -122,7 +134,7 @@ static boost::json::value ConvertSettingsToJson() return settingsJson; } -static void GenerateDefaultSettings() +void SettingsManager::Impl::GenerateDefaultSettings() { logger_->info("Generating default settings"); @@ -133,7 +145,8 @@ static void GenerateDefaultSettings() settings::UiSettings::Instance().SetDefaults(); } -static bool LoadSettings(const boost::json::object& settingsJson) +bool SettingsManager::Impl::LoadSettings( + const boost::json::object& settingsJson) { logger_->info("Loading settings"); @@ -148,7 +161,7 @@ static bool LoadSettings(const boost::json::object& settingsJson) return jsonDirty; } -static void ValidateSettings() +void SettingsManager::Impl::ValidateSettings() { logger_->debug("Validating settings"); @@ -185,11 +198,16 @@ static void ValidateSettings() if (settingsChanged) { - SaveSettings(); + self_->SaveSettings(); } } -} // namespace SettingsManager +SettingsManager& SettingsManager::Instance() +{ + static SettingsManager instance_ {}; + return instance_; +} + } // namespace manager } // namespace qt } // namespace scwx diff --git a/scwx-qt/source/scwx/qt/manager/settings_manager.hpp b/scwx-qt/source/scwx/qt/manager/settings_manager.hpp index 5cc17b19..e2115e93 100644 --- a/scwx-qt/source/scwx/qt/manager/settings_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/settings_manager.hpp @@ -1,6 +1,9 @@ #pragma once #include +#include + +#include namespace scwx { @@ -8,15 +11,28 @@ namespace qt { namespace manager { -namespace SettingsManager + +class SettingsManager : public QObject { + Q_OBJECT + Q_DISABLE_COPY_MOVE(SettingsManager) -void Initialize(); -void ReadSettings(const std::string& settingsPath); -void SaveSettings(); -void Shutdown(); +public: + explicit SettingsManager(); + ~SettingsManager(); + + void Initialize(); + void ReadSettings(const std::string& settingsPath); + void SaveSettings(); + void Shutdown(); + + static SettingsManager& Instance(); + +private: + class Impl; + std::unique_ptr p; +}; -} // namespace SettingsManager } // namespace manager } // namespace qt } // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp index 44a36bf0..bfc17967 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp @@ -989,7 +989,7 @@ void SettingsDialogImpl::ApplyChanges() if (committed) { - manager::SettingsManager::SaveSettings(); + manager::SettingsManager::Instance().SaveSettings(); } } diff --git a/test/source/scwx/qt/manager/settings_manager.test.cpp b/test/source/scwx/qt/manager/settings_manager.test.cpp index a4d69614..54df04df 100644 --- a/test/source/scwx/qt/manager/settings_manager.test.cpp +++ b/test/source/scwx/qt/manager/settings_manager.test.cpp @@ -76,7 +76,7 @@ TEST_F(SettingsManagerTest, CreateJson) // Verify file doesn't exist prior to test start EXPECT_EQ(std::filesystem::exists(filename), false); - SettingsManager::ReadSettings(filename); + SettingsManager::Instance().ReadSettings(filename); EXPECT_EQ(std::filesystem::exists(filename), true); @@ -92,7 +92,7 @@ TEST_F(SettingsManagerTest, SettingsKeax) std::string filename(std::string(SCWX_TEST_DATA_DIR) + "/json/settings/settings-keax.json"); - SettingsManager::ReadSettings(filename); + SettingsManager::Instance().ReadSettings(filename); EXPECT_EQ( settings::GeneralSettings::Instance().default_radar_site().GetValue(), @@ -112,7 +112,7 @@ TEST_P(DefaultSettingsTest, DefaultSettings) std::filesystem::copy_file(sourceFile, filename); - SettingsManager::ReadSettings(filename); + SettingsManager::Instance().ReadSettings(filename); VerifyDefaults(); CompareFiles(filename, DEFAULT_SETTINGS_FILE); @@ -140,7 +140,7 @@ TEST_P(BadSettingsTest, BadSettings) std::filesystem::copy_file(sourceFile, filename); - SettingsManager::ReadSettings(filename); + SettingsManager::Instance().ReadSettings(filename); CompareFiles(filename, goodFile); From 3a4a32b97a869ed90478cf6905d6c05e53244d7c Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 6 Oct 2023 05:49:18 -0500 Subject: [PATCH 153/199] Load ImGui fonts on initialization and settings changes --- .../source/scwx/qt/manager/font_manager.cpp | 82 ++++++++++++++++++- .../source/scwx/qt/manager/font_manager.hpp | 2 + .../scwx/qt/manager/settings_manager.cpp | 2 + .../scwx/qt/manager/settings_manager.hpp | 3 + 4 files changed, 87 insertions(+), 2 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/font_manager.cpp b/scwx-qt/source/scwx/qt/manager/font_manager.cpp index 295d0cd8..db35ec68 100644 --- a/scwx-qt/source/scwx/qt/manager/font_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/font_manager.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include #include @@ -9,6 +11,7 @@ #include #include #include +#include #include namespace scwx @@ -44,22 +47,27 @@ struct FontRecordHash class FontManager::Impl { public: - explicit Impl() + explicit Impl(FontManager* self) : self_ {self} { InitializeFontCache(); InitializeFontconfig(); + ConnectSignals(); } ~Impl() { FinalizeFontconfig(); } + void ConnectSignals(); void FinalizeFontconfig(); void InitializeFontCache(); void InitializeFontconfig(); + void UpdateImGuiFont(types::FontCategory fontCategory); const std::vector& GetRawFontData(const std::string& filename); static FontRecord MatchFontFile(const std::string& family, const std::vector& styles); + FontManager* self_; + std::string fontCachePath_ {}; std::shared_mutex imguiFontAtlasMutex_ {}; @@ -81,12 +89,82 @@ public: std::shared_ptr> fontCategoryMap_ {}; std::mutex fontCategoryMutex_ {}; + + boost::unordered_flat_set dirtyFonts_ {}; + std::mutex dirtyFontsMutex_ {}; }; -FontManager::FontManager() : p(std::make_unique()) {} +FontManager::FontManager() : p(std::make_unique(this)) {} FontManager::~FontManager() {}; +void FontManager::Impl::ConnectSignals() +{ + auto& textSettings = settings::TextSettings::Instance(); + + for (auto fontCategory : types::FontCategoryIterator()) + { + textSettings.font_family(fontCategory) + .RegisterValueChangedCallback( + [this, fontCategory](const auto&) + { + std::unique_lock lock {dirtyFontsMutex_}; + dirtyFonts_.insert(fontCategory); + }); + textSettings.font_style(fontCategory) + .RegisterValueChangedCallback( + [this, fontCategory](const auto&) + { + std::unique_lock lock {dirtyFontsMutex_}; + dirtyFonts_.insert(fontCategory); + }); + textSettings.font_point_size(fontCategory) + .RegisterValueChangedCallback( + [this, fontCategory](const auto&) + { + std::unique_lock lock {dirtyFontsMutex_}; + dirtyFonts_.insert(fontCategory); + }); + } + + QObject::connect( + &SettingsManager::Instance(), + &SettingsManager::SettingsSaved, + self_, + [this]() + { + std::scoped_lock lock {dirtyFontsMutex_, fontCategoryMutex_}; + + for (auto fontCategory : dirtyFonts_) + { + UpdateImGuiFont(fontCategory); + } + + dirtyFonts_.clear(); + }); +} + +void FontManager::InitializeFonts() +{ + for (auto fontCategory : types::FontCategoryIterator()) + { + p->UpdateImGuiFont(fontCategory); + } +} + +void FontManager::Impl::UpdateImGuiFont(types::FontCategory fontCategory) +{ + auto& textSettings = settings::TextSettings::Instance(); + + auto family = textSettings.font_family(fontCategory).GetValue(); + auto styles = textSettings.font_style(fontCategory).GetValue(); + units::font_size::points size { + textSettings.font_point_size(fontCategory).GetValue()}; + + fontCategoryMap_.insert_or_assign( + fontCategory, self_->LoadImGuiFont(family, {styles}, size)); +} + std::shared_mutex& FontManager::imgui_font_atlas_mutex() { return p->imguiFontAtlasMutex_; diff --git a/scwx-qt/source/scwx/qt/manager/font_manager.hpp b/scwx-qt/source/scwx/qt/manager/font_manager.hpp index 6cee1135..184cea18 100644 --- a/scwx-qt/source/scwx/qt/manager/font_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/font_manager.hpp @@ -17,6 +17,7 @@ namespace manager class FontManager : public QObject { Q_OBJECT + Q_DISABLE_COPY_MOVE(FontManager) public: explicit FontManager(); @@ -34,6 +35,7 @@ public: bool loadIfNotFound = true); void LoadApplicationFont(const std::string& filename); + void InitializeFonts(); static FontManager& Instance(); diff --git a/scwx-qt/source/scwx/qt/manager/settings_manager.cpp b/scwx-qt/source/scwx/qt/manager/settings_manager.cpp index 083453ac..a4c0f4a3 100644 --- a/scwx-qt/source/scwx/qt/manager/settings_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/settings_manager.cpp @@ -104,6 +104,8 @@ void SettingsManager::SaveSettings() boost::json::value settingsJson = Impl::ConvertSettingsToJson(); util::json::WriteJsonFile(p->settingsPath_, settingsJson); + + Q_EMIT SettingsSaved(); } } diff --git a/scwx-qt/source/scwx/qt/manager/settings_manager.hpp b/scwx-qt/source/scwx/qt/manager/settings_manager.hpp index e2115e93..254ea4c8 100644 --- a/scwx-qt/source/scwx/qt/manager/settings_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/settings_manager.hpp @@ -28,6 +28,9 @@ public: static SettingsManager& Instance(); +signals: + void SettingsSaved(); + private: class Impl; std::unique_ptr p; From e37e64b3f281490b91ff02c618b5f1622cd4358b Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 6 Oct 2023 05:49:59 -0500 Subject: [PATCH 154/199] Initialize font manager fonts on startup --- scwx-qt/source/scwx/qt/manager/resource_manager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp index a622ea48..e01e21ff 100644 --- a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp @@ -119,6 +119,8 @@ static void LoadFonts() ImFontAtlas* fontAtlas = model::ImGuiContextModel::Instance().font_atlas(); fontAtlas->AddFontDefault(); + + fontManager.InitializeFonts(); } static void LoadTextures() From 11ea4676cf335b600bbe1961025e8d218f3b4289 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 8 Oct 2023 09:02:28 -0500 Subject: [PATCH 155/199] Use fonts defined by text settings for rendering --- .../source/scwx/qt/manager/font_manager.cpp | 16 +++++ .../source/scwx/qt/manager/font_manager.hpp | 3 + scwx-qt/source/scwx/qt/map/map_widget.cpp | 9 +++ scwx-qt/source/scwx/qt/util/imgui.cpp | 63 ++----------------- scwx-qt/source/scwx/qt/util/tooltip.cpp | 35 +++++------ 5 files changed, 47 insertions(+), 79 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/font_manager.cpp b/scwx-qt/source/scwx/qt/manager/font_manager.cpp index db35ec68..05d9e79e 100644 --- a/scwx-qt/source/scwx/qt/manager/font_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/font_manager.cpp @@ -189,6 +189,22 @@ FontManager::GetImGuiFont(types::FontCategory fontCategory) return p->defaultFont_; } +QFont FontManager::GetQFont(types::FontCategory fontCategory) +{ + auto& textSettings = settings::TextSettings::Instance(); + + auto family = textSettings.font_family(fontCategory).GetValue(); + auto styles = textSettings.font_style(fontCategory).GetValue(); + units::font_size::points size { + textSettings.font_point_size(fontCategory).GetValue()}; + + QFont font(QString::fromStdString(family)); + font.setStyleName(QString::fromStdString(styles)); + font.setPointSizeF(size.value()); + + return font; +} + std::shared_ptr FontManager::LoadImGuiFont(const std::string& family, const std::vector& styles, diff --git a/scwx-qt/source/scwx/qt/manager/font_manager.hpp b/scwx-qt/source/scwx/qt/manager/font_manager.hpp index 184cea18..7b824861 100644 --- a/scwx-qt/source/scwx/qt/manager/font_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/font_manager.hpp @@ -5,6 +5,7 @@ #include +#include #include namespace scwx @@ -37,6 +38,8 @@ public: void LoadApplicationFont(const std::string& filename); void InitializeFonts(); + static QFont GetQFont(types::FontCategory fontCategory); + static FontManager& Instance(); private: diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index 1e5397e6..a54c1a5e 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -1032,6 +1032,9 @@ void MapWidget::initializeGL() void MapWidget::paintGL() { + auto defaultFont = manager::FontManager::Instance().GetImGuiFont( + types::FontCategory::Default); + p->frameDraws_++; // Setup ImGui Frame @@ -1047,6 +1050,9 @@ void MapWidget::paintGL() p->ImGuiCheckFonts(); ImGui::NewFrame(); + // Set default font + ImGui::PushFont(defaultFont->font()); + // Update pixel ratio p->context_->set_pixel_ratio(pixelRatio()); @@ -1069,6 +1075,9 @@ void MapWidget::paintGL() p->lastItemPicked_ = false; } + // Pop default font + ImGui::PopFont(); + // Render ImGui Frame ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); diff --git a/scwx-qt/source/scwx/qt/util/imgui.cpp b/scwx-qt/source/scwx/qt/util/imgui.cpp index 0afddf43..46bc859e 100644 --- a/scwx-qt/source/scwx/qt/util/imgui.cpp +++ b/scwx-qt/source/scwx/qt/util/imgui.cpp @@ -1,10 +1,7 @@ #include -#include -#include +#include #include -#include - #include namespace scwx @@ -22,13 +19,6 @@ class ImGui::Impl public: explicit Impl() {} ~Impl() {} - - void Initialize(); - void UpdateMonospaceFont(); - - bool initialized_ {false}; - - ImFont* monospaceFont_ {nullptr}; }; ImGui::ImGui() : p(std::make_unique()) {} @@ -37,58 +27,13 @@ ImGui::~ImGui() = default; ImGui::ImGui(ImGui&&) noexcept = default; ImGui& ImGui::operator=(ImGui&&) noexcept = default; -void ImGui::Impl::Initialize() -{ - if (initialized_) - { - return; - } - - logger_->debug("Initialize"); - - // Configure monospace font - UpdateMonospaceFont(); - settings::GeneralSettings::Instance() - .font_sizes() - .RegisterValueChangedCallback([this](const std::vector&) - { UpdateMonospaceFont(); }); - - initialized_ = true; -} - -void ImGui::Impl::UpdateMonospaceFont() -{ - // Get monospace font size - std::size_t fontSize = 16; - auto fontSizes = - settings::GeneralSettings::Instance().font_sizes().GetValue(); - if (fontSizes.size() > 1) - { - fontSize = fontSizes[1]; - } - else if (fontSizes.size() > 0) - { - fontSize = fontSizes[0]; - } - - // Get monospace font pointer - auto monospace = - manager::ResourceManager::Font(types::Font::Inconsolata_Regular); - auto monospaceFont = monospace->ImGuiFont(fontSize); - - // Store monospace font pointer if not null - if (monospaceFont != nullptr) - { - monospaceFont_ = monospace->ImGuiFont(fontSize); - } -} - void ImGui::DrawTooltip(const std::string& hoverText) { - p->Initialize(); + auto tooltipFont = manager::FontManager::Instance().GetImGuiFont( + types::FontCategory::Tooltip); ::ImGui::BeginTooltip(); - ::ImGui::PushFont(p->monospaceFont_); + ::ImGui::PushFont(tooltipFont->font()); ::ImGui::TextUnformatted(hoverText.c_str()); ::ImGui::PopFont(); ::ImGui::EndTooltip(); diff --git a/scwx-qt/source/scwx/qt/util/tooltip.cpp b/scwx-qt/source/scwx/qt/util/tooltip.cpp index 3e9432df..509a0495 100644 --- a/scwx-qt/source/scwx/qt/util/tooltip.cpp +++ b/scwx-qt/source/scwx/qt/util/tooltip.cpp @@ -1,8 +1,6 @@ #include -#include +#include #include -#include -#include #include #include @@ -84,12 +82,22 @@ void Show(const std::string& text, const QPointF& mouseGlobalPos) } else if (tooltipMethod == types::TooltipMethod::QToolTip) { + QString fontFamily = QString::fromStdString( + textSettings.font_family(types::FontCategory::Tooltip).GetValue()); + QString fontStyle = QString::fromStdString( + textSettings.font_style(types::FontCategory::Tooltip).GetValue()); + double fontPointSize = + textSettings.font_point_size(types::FontCategory::Tooltip).GetValue(); + static std::size_t id = 0; QToolTip::showText( mouseGlobalPos.toPoint(), - QString("%3") + QString("%5") .arg(++id) - .arg("Inconsolata") + .arg(fontFamily) + .arg(fontStyle) + .arg(fontPointSize) .arg(QString::fromStdString(displayText).replace("\n", "
")), tooltipParent_.get(), {}, @@ -97,22 +105,9 @@ void Show(const std::string& text, const QPointF& mouseGlobalPos) } else if (tooltipMethod == types::TooltipMethod::QLabel) { - // Get monospace font size - units::font_size::pixels fontSize {16}; - auto fontSizes = - settings::GeneralSettings::Instance().font_sizes().GetValue(); - if (fontSizes.size() > 1) - { - fontSize = units::font_size::pixels {fontSizes[1]}; - } - else if (fontSizes.size() > 0) - { - fontSize = units::font_size::pixels {fontSizes[0]}; - } - // Configure the label - QFont font("Inconsolata"); - font.setPointSizeF(units::font_size::points(fontSize).value()); + QFont font = manager::FontManager::Instance().GetQFont( + types::FontCategory::Tooltip); tooltipLabel_->setFont(font); tooltipLabel_->setText(QString::fromStdString(displayText)); From 810b61f8f93f66d4e28234a6c7108d9f4addbce2 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 8 Oct 2023 22:05:17 -0500 Subject: [PATCH 156/199] Use fonts defined in placefiles --- .../source/scwx/qt/gl/draw/placefile_text.cpp | 34 ++++++++++++++ .../source/scwx/qt/gl/draw/placefile_text.hpp | 13 ++++++ .../scwx/qt/manager/placefile_manager.cpp | 44 ++++++++++++++++--- .../scwx/qt/manager/placefile_manager.hpp | 4 ++ .../source/scwx/qt/map/placefile_layer.cpp | 2 + 5 files changed, 92 insertions(+), 5 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp index 18f2efee..b318e19f 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include #include #include @@ -37,6 +39,7 @@ public: const std::string& text, const std::string& hoverText, boost::gil::rgba8_pixel_t color, + std::size_t fontNumber, float x, float y); @@ -62,6 +65,9 @@ public: std::mutex listMutex_ {}; std::vector> textList_ {}; std::vector> newList_ {}; + + std::vector> fonts_ {}; + std::vector> newFonts_ {}; }; PlacefileText::PlacefileText(const std::shared_ptr& context, @@ -155,6 +161,7 @@ void PlacefileText::Impl::RenderTextDrawItem( di->text_, di->hoverText_, di->color_, + std::clamp(di->fontNumber_, 1, 8), rotatedX + di->x_ + halfWidth_, rotatedY + di->y_ + halfHeight_); } @@ -165,6 +172,7 @@ void PlacefileText::Impl::RenderText( const std::string& text, const std::string& hoverText, boost::gil::rgba8_pixel_t color, + std::size_t fontNumber, float x, float y) { @@ -184,10 +192,12 @@ void PlacefileText::Impl::RenderText( ImGuiWindowFlags_NoBackground); // Render text + ImGui::PushFont(fonts_[fontNumber - 1]->font()); ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(color[0], color[1], color[2], color[3])); ImGui::TextUnformatted(text.c_str()); ImGui::PopStyleColor(); + ImGui::PopFont(); // Store hover text for mouse picking pass if (!hoverText.empty() && ImGui::IsItemHovered()) @@ -231,6 +241,28 @@ void PlacefileText::StartText() p->newList_.clear(); } +void PlacefileText::SetFonts( + const boost::unordered_flat_map>& fonts) +{ + auto defaultFont = manager::FontManager::Instance().GetImGuiFont( + types::FontCategory::Default); + + // Valid font numbers are from 1 to 8, place in 0-based font vector + for (std::size_t i = 1; i <= 8; ++i) + { + auto it = fonts.find(i); + if (it != fonts.cend()) + { + p->newFonts_.push_back(it->second); + } + else + { + p->newFonts_.push_back(defaultFont); + } + } +} + void PlacefileText::AddText( const std::shared_ptr& di) { @@ -246,9 +278,11 @@ void PlacefileText::FinishText() // Swap text lists p->textList_.swap(p->newList_); + p->fonts_.swap(p->newFonts_); // Clear the new list p->newList_.clear(); + p->newFonts_.clear(); } } // namespace draw diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp index 979a802a..a8d23bc3 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp @@ -2,8 +2,11 @@ #include #include +#include #include +#include + namespace scwx { namespace qt @@ -44,6 +47,16 @@ public: */ void StartText(); + /** + * Configures the fonts for drawing the placefile text. + * + * @param [in] fonts A map of ImGui fonts + */ + void + SetFonts(const boost::unordered_flat_map>& + fonts); + /** * Adds placefile text to the internal draw list. * diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index 52414031..67bc3f1c 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -52,7 +52,8 @@ public: void ReadPlacefileSettings(); void WritePlacefileSettings(); - static void + static boost::unordered_flat_map> LoadFontResources(const std::shared_ptr& placefile); static std::vector> LoadImageResources(const std::shared_ptr& placefile); @@ -66,7 +67,7 @@ public: std::shared_ptr radarSite_ {}; std::vector> placefileRecords_ {}; - std::unordered_map> + boost::unordered_flat_map> placefileRecordMap_ {}; std::shared_mutex placefileRecordLock_ {}; }; @@ -137,6 +138,10 @@ public: std::mutex refreshMutex_ {}; std::mutex timerMutex_ {}; + boost::unordered_flat_map> + fonts_ {}; + std::mutex fontsMutex_ {}; + std::vector> images_ {}; std::string lastRadarSite_ {}; @@ -211,6 +216,20 @@ PlacefileManager::placefile(const std::string& name) return nullptr; } +boost::unordered_flat_map> +PlacefileManager::placefile_fonts(const std::string& name) +{ + std::shared_lock lock(p->placefileRecordLock_); + + auto it = p->placefileRecordMap_.find(name); + if (it != p->placefileRecordMap_.cend()) + { + std::unique_lock fontsLock {it->second->fontsMutex_}; + return it->second->fonts_; + } + return {}; +} + void PlacefileManager::set_placefile_enabled(const std::string& name, bool enabled) { @@ -281,6 +300,7 @@ void PlacefileManager::set_placefile_url(const std::string& name, auto placefileRecord = it->second; placefileRecord->name_ = normalizedUrl; placefileRecord->placefile_ = nullptr; + placefileRecord->fonts_.clear(); placefileRecord->images_.clear(); p->placefileRecordMap_.erase(it); p->placefileRecordMap_.insert_or_assign(normalizedUrl, placefileRecord); @@ -590,7 +610,7 @@ void PlacefileManager::Impl::PlacefileRecord::Update() if (updatedPlacefile != nullptr) { // Load placefile resources - Impl::LoadFontResources(updatedPlacefile); + auto newFonts = Impl::LoadFontResources(updatedPlacefile); auto newImages = Impl::LoadImageResources(updatedPlacefile); // Check the name matches, in case the name updated @@ -601,6 +621,13 @@ void PlacefileManager::Impl::PlacefileRecord::Update() title_ = placefile_->title(); lastUpdateTime_ = std::chrono::system_clock::now(); + // Update font resources + { + std::unique_lock fontsLock {fontsMutex_}; + fonts_.swap(newFonts); + newFonts.clear(); + } + // Update image resources images_.swap(newImages); newImages.clear(); @@ -688,9 +715,12 @@ std::shared_ptr PlacefileManager::Instance() return placefileManager; } -void PlacefileManager::Impl::LoadFontResources( +boost::unordered_flat_map> +PlacefileManager::Impl::LoadFontResources( const std::shared_ptr& placefile) { + boost::unordered_flat_map> + imGuiFonts {}; auto fonts = placefile->fonts(); for (auto& font : fonts) @@ -707,8 +737,12 @@ void PlacefileManager::Impl::LoadFontResources( styles.push_back("italic"); } - FontManager::Instance().LoadImGuiFont(font.second->face_, styles, size); + auto imGuiFont = FontManager::Instance().LoadImGuiFont( + font.second->face_, styles, size); + imGuiFonts.emplace(font.first, std::move(imGuiFont)); } + + return imGuiFonts; } std::vector> diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp index 0567265f..ee9bf3a4 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp @@ -2,8 +2,10 @@ #include #include +#include #include +#include namespace scwx { @@ -24,6 +26,8 @@ public: bool placefile_thresholded(const std::string& name); std::string placefile_title(const std::string& name); std::shared_ptr placefile(const std::string& name); + boost::unordered_flat_map> + placefile_fonts(const std::string& name); void set_placefile_enabled(const std::string& name, bool enabled); void set_placefile_thresholded(const std::string& name, bool thresholded); diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp index f29d63e4..3f6e5961 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp @@ -197,6 +197,8 @@ void PlacefileLayer::ReloadData() p->placefileIcons_->SetIconFiles(placefile->icon_files(), placefile->name()); + p->placefileText_->SetFonts( + placefileManager->placefile_fonts(p->placefileName_)); for (auto& drawItem : placefile->GetDrawItems()) { From f4596a7964e1bd68b081a899729e4fbb1745642a Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 8 Oct 2023 22:13:07 -0500 Subject: [PATCH 157/199] Removing some unused ImGui font code --- .../scwx/qt/manager/resource_manager.cpp | 3 - scwx-qt/source/scwx/qt/ui/settings_dialog.cpp | 24 +-- scwx-qt/source/scwx/qt/ui/settings_dialog.ui | 203 ++++++++---------- scwx-qt/source/scwx/qt/util/font.cpp | 41 ---- 4 files changed, 100 insertions(+), 171 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp index e01e21ff..0302d1dc 100644 --- a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp @@ -117,9 +117,6 @@ static void LoadFonts() fonts_.emplace(fontName.first, font); } - ImFontAtlas* fontAtlas = model::ImGuiContextModel::Instance().font_atlas(); - fontAtlas->AddFontDefault(); - fontManager.InitializeFonts(); } diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp index bfc17967..9e7310a2 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp @@ -93,7 +93,6 @@ public: fontCategoryModel_ {new QStandardItemModel(self)}, settings_ {std::initializer_list { &defaultRadarSite_, - &fontSizes_, &gridWidth_, &gridHeight_, &mapProvider_, @@ -169,16 +168,15 @@ public: types::FontCategory selectedFontCategory_ {types::FontCategory::Unknown}; - settings::SettingsInterface defaultRadarSite_ {}; - settings::SettingsInterface> fontSizes_ {}; - settings::SettingsInterface gridWidth_ {}; - settings::SettingsInterface gridHeight_ {}; - settings::SettingsInterface mapProvider_ {}; - settings::SettingsInterface mapboxApiKey_ {}; - settings::SettingsInterface mapTilerApiKey_ {}; - settings::SettingsInterface defaultAlertAction_ {}; - settings::SettingsInterface updateNotificationsEnabled_ {}; - settings::SettingsInterface debugEnabled_ {}; + settings::SettingsInterface defaultRadarSite_ {}; + settings::SettingsInterface gridWidth_ {}; + settings::SettingsInterface gridHeight_ {}; + settings::SettingsInterface mapProvider_ {}; + settings::SettingsInterface mapboxApiKey_ {}; + settings::SettingsInterface mapTilerApiKey_ {}; + settings::SettingsInterface defaultAlertAction_ {}; + settings::SettingsInterface updateNotificationsEnabled_ {}; + settings::SettingsInterface debugEnabled_ {}; std::unordered_map> colorTables_ {}; @@ -424,10 +422,6 @@ void SettingsDialogImpl::SetupGeneralTab() defaultRadarSite_.SetResetButton(self_->ui->resetRadarSiteButton); UpdateRadarDialogLocation(generalSettings.default_radar_site().GetValue()); - fontSizes_.SetSettingsVariable(generalSettings.font_sizes()); - fontSizes_.SetEditWidget(self_->ui->fontSizesLineEdit); - fontSizes_.SetResetButton(self_->ui->resetFontSizesButton); - gridWidth_.SetSettingsVariable(generalSettings.grid_width()); gridWidth_.SetEditWidget(self_->ui->gridWidthSpinBox); gridWidth_.SetResetButton(self_->ui->resetGridWidthButton); diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui index 8ed4a76f..787c124e 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui @@ -102,7 +102,7 @@
- 2 + 0 @@ -127,27 +127,7 @@ 0 - - - - - - - Font Sizes - - - - - - - - - - Default Radar Site - - - - + ... @@ -158,45 +138,14 @@ - - - - - + + - Mapbox API Key + Grid Width - - - - MapTiler API Key - - - - - - - - - - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg - - - - - - - ... - - - - + ... @@ -207,16 +156,6 @@ - - - - QLineEdit::Password - - - - - - @@ -228,32 +167,14 @@ - - + + - Grid Height + MapTiler API Key - - - - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg - - - - - - - QLineEdit::Password - - - - + ... @@ -264,21 +185,97 @@ + + + + + + - + - Grid Width + Grid Height + + + + + + + Default Radar Site + + + + + + + QLineEdit::Password + + + + + + + QLineEdit::Password + + + + + + + Default Alert Action + + + Mapbox API Key + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + + + + ... + + + + Map Provider - + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + ... @@ -289,26 +286,8 @@ - - - - Default Alert Action - - - - - - - - - - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg - - + + diff --git a/scwx-qt/source/scwx/qt/util/font.cpp b/scwx-qt/source/scwx/qt/util/font.cpp index 74374163..f2397bd2 100644 --- a/scwx-qt/source/scwx/qt/util/font.cpp +++ b/scwx-qt/source/scwx/qt/util/font.cpp @@ -126,9 +126,6 @@ public: } } - void CreateImGuiFont(QFile& fontFile, - QByteArray& fontData, - const std::vector& fontSizes); void ParseNames(FT_Face face); const std::string resource_; @@ -266,39 +263,6 @@ GLuint Font::GenerateTexture(gl::OpenGLFunctions& gl) return p->atlas_->id; } -void FontImpl::CreateImGuiFont(QFile& fontFile, - QByteArray& fontData, - const std::vector& fontSizes) -{ - QFileInfo fileInfo(fontFile); - ImFontAtlas* fontAtlas = model::ImGuiContextModel::Instance().font_atlas(); - ImFontConfig fontConfig {}; - - // Do not transfer ownership of font data to ImGui, makes const_cast safe - fontConfig.FontDataOwnedByAtlas = false; - - for (int64_t fontSize : fontSizes) - { - const float sizePixels = static_cast(fontSize); - - // Assign name to font - strncpy(fontConfig.Name, - fmt::format("{}:{}", fileInfo.fileName().toStdString(), fontSize) - .c_str(), - sizeof(fontConfig.Name) - 1); - fontConfig.Name[sizeof(fontConfig.Name) - 1] = 0; - - // Add font to atlas - imGuiFonts_.emplace( - fontSize, - fontAtlas->AddFontFromMemoryTTF( - const_cast(static_cast(fontData.constData())), - fontData.size(), - sizePixels, - &fontConfig)); - } -} - ImFont* Font::ImGuiFont(std::size_t fontPixelSize) { auto it = p->imGuiFonts_.find(fontPixelSize); @@ -334,11 +298,6 @@ std::shared_ptr Font::Create(const std::string& resource) font = std::make_shared(resource); QByteArray fontData = fontFile.readAll(); - font->p->CreateImGuiFont( - fontFile, - fontData, - settings::GeneralSettings::Instance().font_sizes().GetValue()); - font->p->atlas_ = ftgl::texture_atlas_new(512, 512, 1); ftgl::texture_font_t* textureFont = ftgl::texture_font_new_from_memory( font->p->atlas_, BASE_POINT_SIZE, fontData.constData(), fontData.size()); From 0d7b9ae9a7778d6458a4d8cc64272aab96b56a37 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 8 Oct 2023 22:16:24 -0500 Subject: [PATCH 158/199] Disabling unused font initialization --- .../source/scwx/qt/manager/resource_manager.cpp | 17 +---------------- .../source/scwx/qt/manager/resource_manager.hpp | 4 +--- 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp index 0302d1dc..36665aab 100644 --- a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp @@ -2,7 +2,6 @@ #include #include #include -#include #include #include @@ -32,8 +31,7 @@ static const std::vector> fontNames_ { {types::Font::din1451alt_g, ":/res/fonts/din1451alt_g.ttf"}, {types::Font::Inconsolata_Regular, ":/res/fonts/Inconsolata-Regular.ttf"}}; -static std::unordered_map fontIds_ {}; -static std::unordered_map> fonts_ {}; +static std::unordered_map fontIds_ {}; void Initialize() { @@ -55,16 +53,6 @@ int FontId(types::Font font) return -1; } -std::shared_ptr Font(types::Font font) -{ - auto it = fonts_.find(font); - if (it != fonts_.cend()) - { - return it->second; - } - return nullptr; -} - std::shared_ptr LoadImageResource(const std::string& urlString) { @@ -112,9 +100,6 @@ static void LoadFonts() fontIds_.emplace(fontName.first, fontId); fontManager.LoadApplicationFont(fontName.second); - - auto font = util::Font::Create(fontName.second); - fonts_.emplace(fontName.first, font); } fontManager.InitializeFonts(); diff --git a/scwx-qt/source/scwx/qt/manager/resource_manager.hpp b/scwx-qt/source/scwx/qt/manager/resource_manager.hpp index 7b8003c9..12e95cab 100644 --- a/scwx-qt/source/scwx/qt/manager/resource_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/resource_manager.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include @@ -19,8 +18,7 @@ namespace ResourceManager void Initialize(); void Shutdown(); -int FontId(types::Font font); -std::shared_ptr Font(types::Font font); +int FontId(types::Font font); std::shared_ptr LoadImageResource(const std::string& urlString); From 7af2edd3ec079217cfe3be2b84c2c012b6f18f62 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 8 Oct 2023 22:25:17 -0500 Subject: [PATCH 159/199] Use a more friendly name for ImGui font registration --- .../source/scwx/qt/manager/font_manager.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/font_manager.cpp b/scwx-qt/source/scwx/qt/manager/font_manager.cpp index 05d9e79e..8472b69f 100644 --- a/scwx-qt/source/scwx/qt/manager/font_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/font_manager.cpp @@ -261,10 +261,24 @@ FontManager::LoadImGuiFont(const std::string& family, return it->second; } + // Define a name for the ImGui font + std::string fontName; + try + { + fontName = fmt::format( + "{}:{}", + std::filesystem::path(fontRecord.filename_).filename().string(), + imFontSize.value()); + } + catch (const std::exception& ex) + { + logger_->warn(ex.what()); + fontName = fmt::format("{}:{}", fontRecord.filename_, imFontSize.value()); + } + // Create an ImGui font std::shared_ptr imguiFont = - std::make_shared( - fontRecord.filename_, rawFontData, imFontSize); + std::make_shared(fontName, rawFontData, imFontSize); // Store the ImGui font p->imguiFonts_.insert_or_assign(imguiFontKey, imguiFont); From fe1acb32cf1cf625796d632266c2ec9c4df5298c Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 8 Oct 2023 23:33:27 -0500 Subject: [PATCH 160/199] Initialize Qt application fonts from cached versions on filesystem --- .../source/scwx/qt/manager/font_manager.cpp | 21 ++++++++++++++++++- .../source/scwx/qt/manager/font_manager.hpp | 4 +++- .../scwx/qt/manager/resource_manager.cpp | 19 +---------------- .../scwx/qt/manager/resource_manager.hpp | 2 -- scwx-qt/source/scwx/qt/ui/about_dialog.cpp | 4 ++-- scwx-qt/source/scwx/qt/ui/update_dialog.cpp | 4 ++-- 6 files changed, 28 insertions(+), 26 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/font_manager.cpp b/scwx-qt/source/scwx/qt/manager/font_manager.cpp index 8472b69f..84f37b03 100644 --- a/scwx-qt/source/scwx/qt/manager/font_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/font_manager.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -92,6 +93,8 @@ public: boost::unordered_flat_set dirtyFonts_ {}; std::mutex dirtyFontsMutex_ {}; + + boost::unordered_flat_map fontIds_ {}; }; FontManager::FontManager() : p(std::make_unique(this)) {} @@ -175,6 +178,16 @@ std::uint64_t FontManager::imgui_fonts_build_count() const return p->imguiFontsBuildCount_; } +int FontManager::GetFontId(types::Font font) const +{ + auto it = p->fontIds_.find(font); + if (it != p->fontIds_.cend()) + { + return it->second; + } + return -1; +} + std::shared_ptr FontManager::GetImGuiFont(types::FontCategory fontCategory) { @@ -322,7 +335,8 @@ FontManager::Impl::GetRawFontData(const std::string& filename) return result.first->second; } -void FontManager::LoadApplicationFont(const std::string& filename) +void FontManager::LoadApplicationFont(types::Font font, + const std::string& filename) { // If the font cache failed to create, don't attempt to cache any fonts if (p->fontCachePath_.empty()) @@ -358,6 +372,11 @@ void FontManager::LoadApplicationFont(const std::string& filename) return; } + // Load the file into the Qt Font Database + int fontId = + QFontDatabase::addApplicationFont(QString::fromStdString(cacheFilename)); + p->fontIds_.emplace(font, fontId); + // Load the file into fontconfig FcBool result = FcConfigAppFontAddFile( nullptr, reinterpret_cast(cacheFilename.c_str())); diff --git a/scwx-qt/source/scwx/qt/manager/font_manager.hpp b/scwx-qt/source/scwx/qt/manager/font_manager.hpp index 7b824861..4cc21083 100644 --- a/scwx-qt/source/scwx/qt/manager/font_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/font_manager.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -27,6 +28,7 @@ public: std::shared_mutex& imgui_font_atlas_mutex(); std::uint64_t imgui_fonts_build_count() const; + int GetFontId(types::Font font) const; std::shared_ptr GetImGuiFont(types::FontCategory fontCategory); std::shared_ptr @@ -35,7 +37,7 @@ public: units::font_size::points size, bool loadIfNotFound = true); - void LoadApplicationFont(const std::string& filename); + void LoadApplicationFont(types::Font font, const std::string& filename); void InitializeFonts(); static QFont GetQFont(types::FontCategory fontCategory); diff --git a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp index 36665aab..3048fc6c 100644 --- a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp @@ -8,7 +8,6 @@ #include #include -#include #include namespace scwx @@ -31,8 +30,6 @@ static const std::vector> fontNames_ { {types::Font::din1451alt_g, ":/res/fonts/din1451alt_g.ttf"}, {types::Font::Inconsolata_Regular, ":/res/fonts/Inconsolata-Regular.ttf"}}; -static std::unordered_map fontIds_ {}; - void Initialize() { config::CountyDatabase::Initialize(); @@ -43,16 +40,6 @@ void Initialize() void Shutdown() {} -int FontId(types::Font font) -{ - auto it = fontIds_.find(font); - if (it != fontIds_.cend()) - { - return it->second; - } - return -1; -} - std::shared_ptr LoadImageResource(const std::string& urlString) { @@ -95,11 +82,7 @@ static void LoadFonts() for (auto& fontName : fontNames_) { - int fontId = QFontDatabase::addApplicationFont( - QString::fromStdString(fontName.second)); - fontIds_.emplace(fontName.first, fontId); - - fontManager.LoadApplicationFont(fontName.second); + fontManager.LoadApplicationFont(fontName.first, fontName.second); } fontManager.InitializeFonts(); diff --git a/scwx-qt/source/scwx/qt/manager/resource_manager.hpp b/scwx-qt/source/scwx/qt/manager/resource_manager.hpp index 12e95cab..00658891 100644 --- a/scwx-qt/source/scwx/qt/manager/resource_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/resource_manager.hpp @@ -18,8 +18,6 @@ namespace ResourceManager void Initialize(); void Shutdown(); -int FontId(types::Font font); - std::shared_ptr LoadImageResource(const std::string& urlString); std::vector> diff --git a/scwx-qt/source/scwx/qt/ui/about_dialog.cpp b/scwx-qt/source/scwx/qt/ui/about_dialog.cpp index a42be06f..42ec4e32 100644 --- a/scwx-qt/source/scwx/qt/ui/about_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/about_dialog.cpp @@ -1,7 +1,7 @@ #include "about_dialog.hpp" #include "ui_about_dialog.h" #include -#include +#include #include @@ -27,7 +27,7 @@ AboutDialog::AboutDialog(QWidget* parent) : ui->setupUi(this); int titleFontId = - manager::ResourceManager::FontId(types::Font::din1451alt_g); + manager::FontManager::Instance().GetFontId(types::Font::din1451alt_g); QString titleFontFamily = QFontDatabase::applicationFontFamilies(titleFontId).at(0); QFont titleFont(titleFontFamily, 14); diff --git a/scwx-qt/source/scwx/qt/ui/update_dialog.cpp b/scwx-qt/source/scwx/qt/ui/update_dialog.cpp index 93b3ac0a..4029fa9a 100644 --- a/scwx-qt/source/scwx/qt/ui/update_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/update_dialog.cpp @@ -1,7 +1,7 @@ #include "update_dialog.hpp" #include "ui_update_dialog.h" #include -#include +#include #include #include @@ -30,7 +30,7 @@ UpdateDialog::UpdateDialog(QWidget* parent) : ui->setupUi(this); int titleFontId = - manager::ResourceManager::FontId(types::Font::din1451alt_g); + manager::FontManager::Instance().GetFontId(types::Font::din1451alt_g); QString titleFontFamily = QFontDatabase::applicationFontFamilies(titleFontId).at(0); QFont titleFont(titleFontFamily, 12); From 104227aa42123fbdd63ed21cce2fea12fbf6b0b7 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 8 Oct 2023 23:35:55 -0500 Subject: [PATCH 161/199] Represent raw font data as char instead of std::uint8_t --- scwx-qt/source/scwx/qt/manager/font_manager.cpp | 15 +++++++-------- scwx-qt/source/scwx/qt/types/imgui_font.cpp | 16 ++++++++-------- scwx-qt/source/scwx/qt/types/imgui_font.hpp | 6 +++--- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/font_manager.cpp b/scwx-qt/source/scwx/qt/manager/font_manager.cpp index 84f37b03..2747fd4d 100644 --- a/scwx-qt/source/scwx/qt/manager/font_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/font_manager.cpp @@ -62,7 +62,7 @@ public: void InitializeFontconfig(); void UpdateImGuiFont(types::FontCategory fontCategory); - const std::vector& GetRawFontData(const std::string& filename); + const std::vector& GetRawFontData(const std::string& filename); static FontRecord MatchFontFile(const std::string& family, const std::vector& styles); @@ -81,8 +81,7 @@ public: imguiFonts_ {}; std::shared_mutex imguiFontsMutex_ {}; - boost::unordered_flat_map> - rawFontData_ {}; + boost::unordered_flat_map> rawFontData_ {}; std::mutex rawFontDataMutex_ {}; std::shared_ptr defaultFont_ {}; @@ -303,7 +302,7 @@ FontManager::LoadImGuiFont(const std::string& family, return imguiFont; } -const std::vector& +const std::vector& FontManager::Impl::GetRawFontData(const std::string& filename) { std::unique_lock rawFontDataLock {rawFontDataMutex_}; @@ -316,16 +315,16 @@ FontManager::Impl::GetRawFontData(const std::string& filename) } // Raw font data needs to be loaded - std::basic_ifstream ifs {filename, std::ios::binary}; + std::basic_ifstream ifs {filename, std::ios::binary}; ifs.seekg(0, std::ios_base::end); std::size_t dataSize = ifs.tellg(); ifs.seekg(0, std::ios_base::beg); // Store the font data in a buffer - std::vector buffer {}; + std::vector buffer {}; buffer.reserve(dataSize); - std::copy(std::istreambuf_iterator(ifs), - std::istreambuf_iterator(), + std::copy(std::istreambuf_iterator(ifs), + std::istreambuf_iterator(), std::back_inserter(buffer)); // Place the buffer in the cache diff --git a/scwx-qt/source/scwx/qt/types/imgui_font.cpp b/scwx-qt/source/scwx/qt/types/imgui_font.cpp index f7ba126c..e6f22ad1 100644 --- a/scwx-qt/source/scwx/qt/types/imgui_font.cpp +++ b/scwx-qt/source/scwx/qt/types/imgui_font.cpp @@ -23,9 +23,9 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_); class ImGuiFont::Impl { public: - explicit Impl(const std::string& fontName, - const std::vector& fontData, - units::font_size::pixels size) : + explicit Impl(const std::string& fontName, + const std::vector& fontData, + units::font_size::pixels size) : fontName_ {fontName}, size_ {size} { CreateImGuiFont(fontData); @@ -33,7 +33,7 @@ public: ~Impl() {} - void CreateImGuiFont(const std::vector& fontData); + void CreateImGuiFont(const std::vector& fontData); const std::string fontName_; const units::font_size::pixels size_; @@ -41,15 +41,15 @@ public: ImFont* imFont_ {nullptr}; }; -ImGuiFont::ImGuiFont(const std::string& fontName, - const std::vector& fontData, - units::font_size::pixels size) : +ImGuiFont::ImGuiFont(const std::string& fontName, + const std::vector& fontData, + units::font_size::pixels size) : p(std::make_unique(fontName, fontData, size)) { } ImGuiFont::~ImGuiFont() = default; -void ImGuiFont::Impl::CreateImGuiFont(const std::vector& fontData) +void ImGuiFont::Impl::CreateImGuiFont(const std::vector& fontData) { logger_->debug("Creating Font: {}", fontName_); diff --git a/scwx-qt/source/scwx/qt/types/imgui_font.hpp b/scwx-qt/source/scwx/qt/types/imgui_font.hpp index 9a69ac6e..ace8ba09 100644 --- a/scwx-qt/source/scwx/qt/types/imgui_font.hpp +++ b/scwx-qt/source/scwx/qt/types/imgui_font.hpp @@ -18,9 +18,9 @@ namespace types class ImGuiFont { public: - explicit ImGuiFont(const std::string& fontName, - const std::vector& fontData, - units::font_size::pixels size); + explicit ImGuiFont(const std::string& fontName, + const std::vector& fontData, + units::font_size::pixels size); ~ImGuiFont(); ImGuiFont(const ImGuiFont&) = delete; From 4916dfe85a8e19a4a8877643c7709be144b5048c Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 9 Oct 2023 22:01:58 -0500 Subject: [PATCH 162/199] Add drop shadow to placefile text --- .../source/scwx/qt/gl/draw/placefile_text.cpp | 36 ++++++++++++++----- .../source/scwx/qt/settings/text_settings.cpp | 15 +++++++- .../source/scwx/qt/settings/text_settings.hpp | 1 + scwx-qt/source/scwx/qt/ui/settings_dialog.cpp | 9 ++++- scwx-qt/source/scwx/qt/ui/settings_dialog.ui | 25 ++++++++----- test/data | 2 +- 6 files changed, 68 insertions(+), 20 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp index b318e19f..44851fa3 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -39,7 +40,6 @@ public: const std::string& text, const std::string& hoverText, boost::gil::rgba8_pixel_t color, - std::size_t fontNumber, float x, float y); @@ -157,13 +157,36 @@ void PlacefileText::Impl::RenderTextDrawItem( screenCoordinates.y * mapBearingCos_; } + // Clamp font number to 0-8 + std::size_t fontNumber = std::clamp(di->fontNumber_, 0, 8); + + // Set the font for the drop shadow and text + ImGui::PushFont(fonts_[fontNumber]->font()); + + if (settings::TextSettings::Instance() + .placefile_text_drop_shadow_enabled() + .GetValue()) + { + // Draw a drop shadow 1 pixel to the lower right, in black, with the + // original transparency level + RenderText(params, + di->text_, + {}, + boost::gil::rgba8_pixel_t {0, 0, 0, di->color_[3]}, + rotatedX + di->x_ + halfWidth_ + 1.0f, + rotatedY + di->y_ + halfHeight_ - 1.0f); + } + + // Draw the text RenderText(params, di->text_, di->hoverText_, di->color_, - std::clamp(di->fontNumber_, 1, 8), rotatedX + di->x_ + halfWidth_, rotatedY + di->y_ + halfHeight_); + + // Reset the font + ImGui::PopFont(); } } @@ -172,7 +195,6 @@ void PlacefileText::Impl::RenderText( const std::string& text, const std::string& hoverText, boost::gil::rgba8_pixel_t color, - std::size_t fontNumber, float x, float y) { @@ -192,12 +214,10 @@ void PlacefileText::Impl::RenderText( ImGuiWindowFlags_NoBackground); // Render text - ImGui::PushFont(fonts_[fontNumber - 1]->font()); ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(color[0], color[1], color[2], color[3])); ImGui::TextUnformatted(text.c_str()); ImGui::PopStyleColor(); - ImGui::PopFont(); // Store hover text for mouse picking pass if (!hoverText.empty() && ImGui::IsItemHovered()) @@ -248,10 +268,10 @@ void PlacefileText::SetFonts( auto defaultFont = manager::FontManager::Instance().GetImGuiFont( types::FontCategory::Default); - // Valid font numbers are from 1 to 8, place in 0-based font vector - for (std::size_t i = 1; i <= 8; ++i) + // Valid font numbers are from 1 to 8, use 0 for the default font + for (std::size_t i = 0; i <= 8; ++i) { - auto it = fonts.find(i); + auto it = (i > 0) ? fonts.find(i) : fonts.cend(); if (it != fonts.cend()) { p->newFonts_.push_back(it->second); diff --git a/scwx-qt/source/scwx/qt/settings/text_settings.cpp b/scwx-qt/source/scwx/qt/settings/text_settings.cpp index fdf41acf..c6c221d4 100644 --- a/scwx-qt/source/scwx/qt/settings/text_settings.cpp +++ b/scwx-qt/source/scwx/qt/settings/text_settings.cpp @@ -49,6 +49,7 @@ public: hoverTextWrap_.SetDefault(80); hoverTextWrap_.SetMinimum(0); hoverTextWrap_.SetMaximum(999); + placefileTextDropShadowEnabled_.SetDefault(true); tooltipMethod_.SetDefault(defaultTooltipMethodValue); tooltipMethod_.SetValidator( @@ -93,12 +94,17 @@ public: SettingsVariable hoverTextWrap_ {"hover_text_wrap"}; SettingsVariable tooltipMethod_ {"tooltip_method"}; + + SettingsVariable placefileTextDropShadowEnabled_ { + "placefile_text_drop_shadow_enabled"}; }; TextSettings::TextSettings() : SettingsCategory("text"), p(std::make_unique(this)) { - RegisterVariables({&p->hoverTextWrap_, &p->tooltipMethod_}); + RegisterVariables({&p->hoverTextWrap_, + &p->placefileTextDropShadowEnabled_, + &p->tooltipMethod_}); SetDefaults(); } TextSettings::~TextSettings() = default; @@ -162,6 +168,11 @@ SettingsVariable& TextSettings::hover_text_wrap() const return p->hoverTextWrap_; } +SettingsVariable& TextSettings::placefile_text_drop_shadow_enabled() const +{ + return p->placefileTextDropShadowEnabled_; +} + SettingsVariable& TextSettings::tooltip_method() const { return p->tooltipMethod_; @@ -177,6 +188,8 @@ bool operator==(const TextSettings& lhs, const TextSettings& rhs) { return (lhs.p->fontData_ == rhs.p->fontData_ && lhs.p->hoverTextWrap_ == rhs.p->hoverTextWrap_ && + lhs.p->placefileTextDropShadowEnabled_ == + rhs.p->placefileTextDropShadowEnabled_ && lhs.p->tooltipMethod_ == rhs.p->tooltipMethod_); } diff --git a/scwx-qt/source/scwx/qt/settings/text_settings.hpp b/scwx-qt/source/scwx/qt/settings/text_settings.hpp index a0347b4f..ac300abf 100644 --- a/scwx-qt/source/scwx/qt/settings/text_settings.hpp +++ b/scwx-qt/source/scwx/qt/settings/text_settings.hpp @@ -34,6 +34,7 @@ public: font_point_size(types::FontCategory fontCategory) const; SettingsVariable& hover_text_wrap() const; + SettingsVariable& placefile_text_drop_shadow_enabled() const; SettingsVariable& tooltip_method() const; static TextSettings& Instance(); diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp index 9e7310a2..328f805a 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp @@ -102,7 +102,8 @@ public: &updateNotificationsEnabled_, &debugEnabled_, &hoverTextWrap_, - &tooltipMethod_}} + &tooltipMethod_, + &placefileTextDropShadowEnabled_}} { // Configure default alert phenomena colors auto& paletteSettings = settings::PaletteSettings::Instance(); @@ -198,6 +199,7 @@ public: settings::SettingsInterface hoverTextWrap_ {}; settings::SettingsInterface tooltipMethod_ {}; + settings::SettingsInterface placefileTextDropShadowEnabled_ {}; std::vector settings_; }; @@ -808,6 +810,11 @@ void SettingsDialogImpl::SetupTextTab() }); tooltipMethod_.SetEditWidget(self_->ui->tooltipMethodComboBox); tooltipMethod_.SetResetButton(self_->ui->resetTooltipMethodButton); + + placefileTextDropShadowEnabled_.SetSettingsVariable( + textSettings.placefile_text_drop_shadow_enabled()); + placefileTextDropShadowEnabled_.SetEditWidget( + self_->ui->placefileTextDropShadowCheckBox); } QImage SettingsDialogImpl::GenerateColorTableImage( diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui index 787c124e..8b0d2179 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui @@ -626,13 +626,6 @@ 0 - - - - Hover text character wrap (0 to disable) - - - @@ -640,6 +633,9 @@ + + + @@ -658,8 +654,12 @@ - - + + + + Hover text character wrap (0 to disable) + + @@ -672,6 +672,13 @@ + + + + Placefile Text Drop Shadow + + +
diff --git a/test/data b/test/data index 1685e404..58d61ba3 160000 --- a/test/data +++ b/test/data @@ -1 +1 @@ -Subproject commit 1685e4048ef4a9f34bc11ecbb8db4905dd0a2e19 +Subproject commit 58d61ba37385c699df1eca547668ec3c2a93871e From 3a754c01c5a0504b8f72b361d708b8568b3186b5 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 9 Oct 2023 22:40:53 -0500 Subject: [PATCH 163/199] Set fontconfig environment in Linux --- .../source/scwx/qt/manager/font_manager.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/scwx-qt/source/scwx/qt/manager/font_manager.cpp b/scwx-qt/source/scwx/qt/manager/font_manager.cpp index 2747fd4d..e7266a84 100644 --- a/scwx-qt/source/scwx/qt/manager/font_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/font_manager.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -50,6 +51,7 @@ class FontManager::Impl public: explicit Impl(FontManager* self) : self_ {self} { + InitializeEnvironment(); InitializeFontCache(); InitializeFontconfig(); ConnectSignals(); @@ -58,6 +60,7 @@ public: void ConnectSignals(); void FinalizeFontconfig(); + void InitializeEnvironment(); void InitializeFontCache(); void InitializeFontconfig(); void UpdateImGuiFont(types::FontCategory fontCategory); @@ -385,6 +388,21 @@ void FontManager::LoadApplicationFont(types::Font font, } } +void FontManager::Impl::InitializeEnvironment() +{ +#if defined(__linux__) + // Because of the way Fontconfig is built with Conan, FONTCONFIG_PATH must be + // defined on Linux to ensure fonts can be found + static const std::string kFontconfigPathKey {"FONTCONFIG_PATH"}; + + std::string fontconfigPath = scwx::util::GetEnvironment(kFontconfigPathKey); + if (fontconfigPath.empty()) + { + scwx::util::SetEnvironment(kFontconfigPathKey, "/etc/fonts"); + } +#endif +} + void FontManager::Impl::InitializeFontCache() { std::string cachePath { From c5a56680ea8c28d3278e8cf6c3f8aa772fcd2888 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 9 Oct 2023 23:10:10 -0500 Subject: [PATCH 164/199] Add anti-aliasing to settings --- scwx-qt/source/scwx/qt/map/map_widget.cpp | 9 ++++++--- .../source/scwx/qt/settings/general_settings.cpp | 13 +++++++++++-- .../source/scwx/qt/settings/general_settings.hpp | 1 + scwx-qt/source/scwx/qt/ui/settings_dialog.cpp | 6 ++++++ scwx-qt/source/scwx/qt/ui/settings_dialog.ui | 7 +++++++ test/data | 2 +- 6 files changed, 32 insertions(+), 6 deletions(-) diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index a54c1a5e..1df8f2ff 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -201,9 +201,12 @@ public slots: MapWidget::MapWidget(const QMapLibreGL::Settings& settings) : p(std::make_unique(this, settings)) { - QSurfaceFormat surfaceFormat = QSurfaceFormat::defaultFormat(); - surfaceFormat.setSamples(4); - setFormat(surfaceFormat); + if (settings::GeneralSettings::Instance().anti_aliasing_enabled().GetValue()) + { + QSurfaceFormat surfaceFormat = QSurfaceFormat::defaultFormat(); + surfaceFormat.setSamples(4); + setFormat(surfaceFormat); + } setFocusPolicy(Qt::StrongFocus); diff --git a/scwx-qt/source/scwx/qt/settings/general_settings.cpp b/scwx-qt/source/scwx/qt/settings/general_settings.cpp index bd94acef..997d4e06 100644 --- a/scwx-qt/source/scwx/qt/settings/general_settings.cpp +++ b/scwx-qt/source/scwx/qt/settings/general_settings.cpp @@ -29,6 +29,7 @@ public: boost::to_lower(defaultDefaultAlertActionValue); boost::to_lower(defaultMapProviderValue); + antiAliasingEnabled_.SetDefault(true); debugEnabled_.SetDefault(false); defaultAlertAction_.SetDefault(defaultDefaultAlertActionValue); defaultRadarSite_.SetDefault("KLSX"); @@ -104,6 +105,7 @@ public: ~Impl() {} + SettingsVariable antiAliasingEnabled_ {"anti_aliasing_enabled"}; SettingsVariable debugEnabled_ {"debug_enabled"}; SettingsVariable defaultAlertAction_ {"default_alert_action"}; SettingsVariable defaultRadarSite_ {"default_radar_site"}; @@ -122,7 +124,8 @@ public: GeneralSettings::GeneralSettings() : SettingsCategory("general"), p(std::make_unique()) { - RegisterVariables({&p->debugEnabled_, + RegisterVariables({&p->antiAliasingEnabled_, + &p->debugEnabled_, &p->defaultAlertAction_, &p->defaultRadarSite_, &p->fontSizes_, @@ -143,6 +146,11 @@ GeneralSettings::GeneralSettings(GeneralSettings&&) noexcept = default; GeneralSettings& GeneralSettings::operator=(GeneralSettings&&) noexcept = default; +SettingsVariable& GeneralSettings::anti_aliasing_enabled() const +{ + return p->antiAliasingEnabled_; +} + SettingsVariable& GeneralSettings::debug_enabled() const { return p->debugEnabled_; @@ -229,7 +237,8 @@ GeneralSettings& GeneralSettings::Instance() bool operator==(const GeneralSettings& lhs, const GeneralSettings& rhs) { - return (lhs.p->debugEnabled_ == rhs.p->debugEnabled_ && + return (lhs.p->antiAliasingEnabled_ == rhs.p->antiAliasingEnabled_ && + lhs.p->debugEnabled_ == rhs.p->debugEnabled_ && lhs.p->defaultAlertAction_ == rhs.p->defaultAlertAction_ && lhs.p->defaultRadarSite_ == rhs.p->defaultRadarSite_ && lhs.p->fontSizes_ == rhs.p->fontSizes_ && diff --git a/scwx-qt/source/scwx/qt/settings/general_settings.hpp b/scwx-qt/source/scwx/qt/settings/general_settings.hpp index ba58676f..57045ac6 100644 --- a/scwx-qt/source/scwx/qt/settings/general_settings.hpp +++ b/scwx-qt/source/scwx/qt/settings/general_settings.hpp @@ -25,6 +25,7 @@ public: GeneralSettings(GeneralSettings&&) noexcept; GeneralSettings& operator=(GeneralSettings&&) noexcept; + SettingsVariable& anti_aliasing_enabled() const; SettingsVariable& debug_enabled() const; SettingsVariable& default_alert_action() const; SettingsVariable& default_radar_site() const; diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp index 328f805a..d1ef35ff 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp @@ -99,6 +99,7 @@ public: &mapboxApiKey_, &mapTilerApiKey_, &defaultAlertAction_, + &antiAliasingEnabled_, &updateNotificationsEnabled_, &debugEnabled_, &hoverTextWrap_, @@ -176,6 +177,7 @@ public: settings::SettingsInterface mapboxApiKey_ {}; settings::SettingsInterface mapTilerApiKey_ {}; settings::SettingsInterface defaultAlertAction_ {}; + settings::SettingsInterface antiAliasingEnabled_ {}; settings::SettingsInterface updateNotificationsEnabled_ {}; settings::SettingsInterface debugEnabled_ {}; @@ -511,6 +513,10 @@ void SettingsDialogImpl::SetupGeneralTab() defaultAlertAction_.SetEditWidget(self_->ui->defaultAlertActionComboBox); defaultAlertAction_.SetResetButton(self_->ui->resetDefaultAlertActionButton); + antiAliasingEnabled_.SetSettingsVariable( + generalSettings.anti_aliasing_enabled()); + antiAliasingEnabled_.SetEditWidget(self_->ui->antiAliasingEnabledCheckBox); + updateNotificationsEnabled_.SetSettingsVariable( generalSettings.update_notifications_enabled()); updateNotificationsEnabled_.SetEditWidget( diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui index 8b0d2179..248a75fa 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui @@ -292,6 +292,13 @@ + + + + Anti-Aliasing Enabled + + + diff --git a/test/data b/test/data index 58d61ba3..fc428fa1 160000 --- a/test/data +++ b/test/data @@ -1 +1 @@ -Subproject commit 58d61ba37385c699df1eca547668ec3c2a93871e +Subproject commit fc428fa1460b3d5ce04646727e379e2f4f90f5ec From 8326b2f2bfd9afff46fbc36f24e5adcaf81062e0 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 9 Oct 2023 23:36:21 -0500 Subject: [PATCH 165/199] Fix backslash interpretation in IconFile and Image statements --- scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp | 4 +++- scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp | 6 ++++-- scwx-qt/source/scwx/qt/manager/placefile_manager.cpp | 8 +++++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp index 84ffb4b2..36c792db 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp @@ -6,6 +6,7 @@ #include +#include #include #include @@ -44,7 +45,8 @@ struct PlacefileIconInfo { // Resolve using base URL auto baseUrl = QUrl::fromUserInput(QString::fromStdString(baseUrlString)); - auto relativeUrl = QUrl(QString::fromStdString(iconFile->filename_)); + auto relativeUrl = QUrl(QDir::fromNativeSeparators( + QString::fromStdString(iconFile->filename_))); resolvedUrl_ = baseUrl.resolved(relativeUrl).toString().toStdString(); } diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp index 3a5809c9..1b46bd99 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -39,8 +40,9 @@ struct PlacefileImageInfo { // Resolve using base URL auto baseUrl = QUrl::fromUserInput(QString::fromStdString(baseUrlString)); - auto relativeUrl = QUrl(QString::fromStdString(imageFile)); - resolvedUrl_ = baseUrl.resolved(relativeUrl).toString().toStdString(); + auto relativeUrl = + QUrl(QDir::fromNativeSeparators(QString::fromStdString(imageFile))); + resolvedUrl_ = baseUrl.resolved(relativeUrl).toString().toStdString(); } void UpdateTextureInfo(); diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index 67bc3f1c..a4ebd0fa 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -765,8 +765,9 @@ PlacefileManager::Impl::LoadImageResources( [&baseUrl](auto& iconFile) { // Resolve target URL relative to base URL - QUrl fileUrl = - QUrl(QString::fromStdString(iconFile->filename_)); + QString filePath = + QString::fromStdString(iconFile->filename_); + QUrl fileUrl = QUrl(QDir::fromNativeSeparators(filePath)); QUrl resolvedUrl = baseUrl.resolved(fileUrl); return resolvedUrl.toString().toStdString(); @@ -783,7 +784,8 @@ PlacefileManager::Impl::LoadImageResources( std::static_pointer_cast(di) ->imageFile_; - QUrl fileUrl = QUrl(QString::fromStdString(imageFile)); + QString filePath = QString::fromStdString(imageFile); + QUrl fileUrl = QUrl(QDir::fromNativeSeparators(filePath)); QUrl resolvedUrl = baseUrl.resolved(fileUrl); std::string urlString = resolvedUrl.toString().toStdString(); From 73adeda60463912da41091cf181c0a0ff652855d Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 10 Oct 2023 19:18:20 -0500 Subject: [PATCH 166/199] Fix QFont styling --- .../source/scwx/qt/manager/font_manager.cpp | 48 +++++++++++++------ .../source/scwx/qt/manager/font_manager.hpp | 5 +- scwx-qt/source/scwx/qt/ui/settings_dialog.cpp | 6 ++- 3 files changed, 40 insertions(+), 19 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/font_manager.cpp b/scwx-qt/source/scwx/qt/manager/font_manager.cpp index e7266a84..20f5f9a6 100644 --- a/scwx-qt/source/scwx/qt/manager/font_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/font_manager.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -64,6 +65,7 @@ public: void InitializeFontCache(); void InitializeFontconfig(); void UpdateImGuiFont(types::FontCategory fontCategory); + void UpdateQFont(types::FontCategory fontCategory); const std::vector& GetRawFontData(const std::string& filename); @@ -90,7 +92,9 @@ public: std::shared_ptr defaultFont_ {}; boost::unordered_flat_map> - fontCategoryMap_ {}; + fontCategoryImguiFontMap_ {}; + boost::unordered_flat_map + fontCategoryQFontMap_ {}; std::mutex fontCategoryMutex_ {}; boost::unordered_flat_set dirtyFonts_ {}; @@ -143,6 +147,7 @@ void FontManager::Impl::ConnectSignals() for (auto fontCategory : dirtyFonts_) { UpdateImGuiFont(fontCategory); + UpdateQFont(fontCategory); } dirtyFonts_.clear(); @@ -154,6 +159,7 @@ void FontManager::InitializeFonts() for (auto fontCategory : types::FontCategoryIterator()) { p->UpdateImGuiFont(fontCategory); + p->UpdateQFont(fontCategory); } } @@ -166,10 +172,27 @@ void FontManager::Impl::UpdateImGuiFont(types::FontCategory fontCategory) units::font_size::points size { textSettings.font_point_size(fontCategory).GetValue()}; - fontCategoryMap_.insert_or_assign( + fontCategoryImguiFontMap_.insert_or_assign( fontCategory, self_->LoadImGuiFont(family, {styles}, size)); } +void FontManager::Impl::UpdateQFont(types::FontCategory fontCategory) +{ + auto& textSettings = settings::TextSettings::Instance(); + + auto family = textSettings.font_family(fontCategory).GetValue(); + auto styles = textSettings.font_style(fontCategory).GetValue(); + units::font_size::points size { + textSettings.font_point_size(fontCategory).GetValue()}; + + QFont font = QFontDatabase::font(QString::fromStdString(family), + QString::fromStdString(styles), + static_cast(size.value())); + font.setPointSizeF(size.value()); + + fontCategoryQFontMap_.insert_or_assign(fontCategory, font); +} + std::shared_mutex& FontManager::imgui_font_atlas_mutex() { return p->imguiFontAtlasMutex_; @@ -195,8 +218,8 @@ FontManager::GetImGuiFont(types::FontCategory fontCategory) { std::unique_lock lock {p->fontCategoryMutex_}; - auto it = p->fontCategoryMap_.find(fontCategory); - if (it != p->fontCategoryMap_.cend()) + auto it = p->fontCategoryImguiFontMap_.find(fontCategory); + if (it != p->fontCategoryImguiFontMap_.cend()) { return it->second; } @@ -206,18 +229,15 @@ FontManager::GetImGuiFont(types::FontCategory fontCategory) QFont FontManager::GetQFont(types::FontCategory fontCategory) { - auto& textSettings = settings::TextSettings::Instance(); + std::unique_lock lock {p->fontCategoryMutex_}; - auto family = textSettings.font_family(fontCategory).GetValue(); - auto styles = textSettings.font_style(fontCategory).GetValue(); - units::font_size::points size { - textSettings.font_point_size(fontCategory).GetValue()}; + auto it = p->fontCategoryQFontMap_.find(fontCategory); + if (it != p->fontCategoryQFontMap_.cend()) + { + return it->second; + } - QFont font(QString::fromStdString(family)); - font.setStyleName(QString::fromStdString(styles)); - font.setPointSizeF(size.value()); - - return font; + return QGuiApplication::font(); } std::shared_ptr diff --git a/scwx-qt/source/scwx/qt/manager/font_manager.hpp b/scwx-qt/source/scwx/qt/manager/font_manager.hpp index 4cc21083..e52d0d16 100644 --- a/scwx-qt/source/scwx/qt/manager/font_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/font_manager.hpp @@ -30,7 +30,8 @@ public: int GetFontId(types::Font font) const; std::shared_ptr - GetImGuiFont(types::FontCategory fontCategory); + GetImGuiFont(types::FontCategory fontCategory); + QFont GetQFont(types::FontCategory fontCategory); std::shared_ptr LoadImGuiFont(const std::string& family, const std::vector& styles, @@ -40,8 +41,6 @@ public: void LoadApplicationFont(types::Font font, const std::string& filename); void InitializeFonts(); - static QFont GetQFont(types::FontCategory fontCategory); - static FontManager& Instance(); private: diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp index d1ef35ff..ae32d450 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -949,8 +950,9 @@ QFont SettingsDialogImpl::GetSelectedFont() .GetSettingsVariable() ->GetStagedOrValue()}; - QFont font(QString::fromStdString(fontFamily)); - font.setStyleName(QString::fromStdString(fontStyle)); + QFont font = QFontDatabase::font(QString::fromStdString(fontFamily), + QString::fromStdString(fontStyle), + static_cast(fontSize.value())); font.setPointSizeF(fontSize.value()); return font; From 4a92f11d0031a223aeefdf214a4fb43819f9f5e0 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 10 Oct 2023 21:48:20 -0500 Subject: [PATCH 167/199] Resize tooltip label every frame to account for text changes --- scwx-qt/source/scwx/qt/util/tooltip.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/scwx-qt/source/scwx/qt/util/tooltip.cpp b/scwx-qt/source/scwx/qt/util/tooltip.cpp index 509a0495..42fcaa4c 100644 --- a/scwx-qt/source/scwx/qt/util/tooltip.cpp +++ b/scwx-qt/source/scwx/qt/util/tooltip.cpp @@ -110,6 +110,7 @@ void Show(const std::string& text, const QPointF& mouseGlobalPos) types::FontCategory::Tooltip); tooltipLabel_->setFont(font); tooltipLabel_->setText(QString::fromStdString(displayText)); + tooltipLabel_->resize(tooltipLabel_->sizeHint()); // Get the screen the label will be displayed on QScreen* screen = QGuiApplication::screenAt(mouseGlobalPos.toPoint()); From f2addd29e6045cd29e63113fa75585d11552fbf0 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 10 Oct 2023 22:33:23 -0500 Subject: [PATCH 168/199] Add dedicated Placefile Manager dialog separate from settings --- .../font-awesome-6/layer-group-solid.svg | 1 + scwx-qt/scwx-qt.cmake | 3 + scwx-qt/scwx-qt.qrc | 1 + scwx-qt/source/scwx/qt/main/main_window.cpp | 11 +++ scwx-qt/source/scwx/qt/main/main_window.hpp | 1 + scwx-qt/source/scwx/qt/main/main_window.ui | 26 ++++++ .../source/scwx/qt/ui/placefile_dialog.cpp | 45 ++++++++++ .../source/scwx/qt/ui/placefile_dialog.hpp | 35 ++++++++ scwx-qt/source/scwx/qt/ui/placefile_dialog.ui | 88 +++++++++++++++++++ scwx-qt/source/scwx/qt/ui/settings_dialog.cpp | 18 +--- scwx-qt/source/scwx/qt/ui/settings_dialog.ui | 27 +----- 11 files changed, 215 insertions(+), 41 deletions(-) create mode 100644 scwx-qt/res/icons/font-awesome-6/layer-group-solid.svg create mode 100644 scwx-qt/source/scwx/qt/ui/placefile_dialog.cpp create mode 100644 scwx-qt/source/scwx/qt/ui/placefile_dialog.hpp create mode 100644 scwx-qt/source/scwx/qt/ui/placefile_dialog.ui diff --git a/scwx-qt/res/icons/font-awesome-6/layer-group-solid.svg b/scwx-qt/res/icons/font-awesome-6/layer-group-solid.svg new file mode 100644 index 00000000..a6366610 --- /dev/null +++ b/scwx-qt/res/icons/font-awesome-6/layer-group-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 4b8cadda..60e9a87f 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -186,6 +186,7 @@ set(HDR_UI source/scwx/qt/ui/about_dialog.hpp source/scwx/qt/ui/level2_settings_widget.hpp source/scwx/qt/ui/level3_products_widget.hpp source/scwx/qt/ui/open_url_dialog.hpp + source/scwx/qt/ui/placefile_dialog.hpp source/scwx/qt/ui/placefile_settings_widget.hpp source/scwx/qt/ui/radar_site_dialog.hpp source/scwx/qt/ui/settings_dialog.hpp @@ -203,6 +204,7 @@ set(SRC_UI source/scwx/qt/ui/about_dialog.cpp source/scwx/qt/ui/level2_settings_widget.cpp source/scwx/qt/ui/level3_products_widget.cpp source/scwx/qt/ui/open_url_dialog.cpp + source/scwx/qt/ui/placefile_dialog.cpp source/scwx/qt/ui/placefile_settings_widget.cpp source/scwx/qt/ui/radar_site_dialog.cpp source/scwx/qt/ui/settings_dialog.cpp @@ -214,6 +216,7 @@ set(UI_UI source/scwx/qt/ui/about_dialog.ui source/scwx/qt/ui/collapsible_group.ui source/scwx/qt/ui/imgui_debug_dialog.ui source/scwx/qt/ui/open_url_dialog.ui + source/scwx/qt/ui/placefile_dialog.ui source/scwx/qt/ui/placefile_settings_widget.ui source/scwx/qt/ui/radar_site_dialog.ui source/scwx/qt/ui/settings_dialog.ui diff --git a/scwx-qt/scwx-qt.qrc b/scwx-qt/scwx-qt.qrc index 47e49c4c..4f914f25 100644 --- a/scwx-qt/scwx-qt.qrc +++ b/scwx-qt/scwx-qt.qrc @@ -30,6 +30,7 @@ res/icons/font-awesome-6/forward-step-solid.svg res/icons/font-awesome-6/gears-solid.svg res/icons/font-awesome-6/github.svg + res/icons/font-awesome-6/layer-group-solid.svg res/icons/font-awesome-6/palette-solid.svg res/icons/font-awesome-6/pause-solid.svg res/icons/font-awesome-6/play-solid.svg diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp index 756ca182..b3aa618f 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -77,6 +78,7 @@ public: animationDockWidget_ {nullptr}, aboutDialog_ {nullptr}, imGuiDebugDialog_ {nullptr}, + placefileDialog_ {nullptr}, radarSiteDialog_ {nullptr}, settingsDialog_ {nullptr}, updateDialog_ {nullptr}, @@ -165,6 +167,7 @@ public: ui::AnimationDockWidget* animationDockWidget_; ui::AboutDialog* aboutDialog_; ui::ImGuiDebugDialog* imGuiDebugDialog_; + ui::PlacefileDialog* placefileDialog_; ui::RadarSiteDialog* radarSiteDialog_; ui::SettingsDialog* settingsDialog_; ui::UpdateDialog* updateDialog_; @@ -243,6 +246,9 @@ MainWindow::MainWindow(QWidget* parent) : // Radar Site Dialog p->radarSiteDialog_ = new ui::RadarSiteDialog(this); + // Placefile Manager Dialog + p->placefileDialog_ = new ui::PlacefileDialog(this); + // Settings Dialog p->settingsDialog_ = new ui::SettingsDialog(this); @@ -443,6 +449,11 @@ void MainWindow::on_actionExit_triggered() close(); } +void MainWindow::on_actionPlacefileManager_triggered() +{ + p->placefileDialog_->show(); +} + void MainWindow::on_actionImGuiDebug_triggered() { p->imGuiDebugDialog_->show(); diff --git a/scwx-qt/source/scwx/qt/main/main_window.hpp b/scwx-qt/source/scwx/qt/main/main_window.hpp index 6ed96aba..2657abea 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.hpp +++ b/scwx-qt/source/scwx/qt/main/main_window.hpp @@ -36,6 +36,7 @@ private slots: void on_actionOpenTextEvent_triggered(); void on_actionSettings_triggered(); void on_actionExit_triggered(); + void on_actionPlacefileManager_triggered(); void on_actionImGuiDebug_triggered(); void on_actionDumpRadarProductRecords_triggered(); void on_actionUserManual_triggered(); diff --git a/scwx-qt/source/scwx/qt/main/main_window.ui b/scwx-qt/source/scwx/qt/main/main_window.ui index 369c8d28..d2065105 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.ui +++ b/scwx-qt/source/scwx/qt/main/main_window.ui @@ -87,8 +87,16 @@ + + + &Tools + + + + + @@ -415,6 +423,24 @@ &Check for Updates + + + + :/res/icons/font-awesome-6/earth-americas-solid.svg:/res/icons/font-awesome-6/earth-americas-solid.svg + + + &Placefile Manager + + + + + + :/res/icons/font-awesome-6/layer-group-solid.svg:/res/icons/font-awesome-6/layer-group-solid.svg + + + &Layer Manager + + diff --git a/scwx-qt/source/scwx/qt/ui/placefile_dialog.cpp b/scwx-qt/source/scwx/qt/ui/placefile_dialog.cpp new file mode 100644 index 00000000..6adb96ba --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/placefile_dialog.cpp @@ -0,0 +1,45 @@ +#include "placefile_dialog.hpp" +#include "ui_placefile_dialog.h" + +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace ui +{ + +static const std::string logPrefix_ = "scwx::qt::ui::placefile_dialog"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +class PlacefileDialogImpl +{ +public: + explicit PlacefileDialogImpl() {} + ~PlacefileDialogImpl() = default; + + PlacefileSettingsWidget* placefileSettingsWidget_ {nullptr}; +}; + +PlacefileDialog::PlacefileDialog(QWidget* parent) : + QDialog(parent), + p {std::make_unique()}, + ui(new Ui::PlacefileDialog) +{ + ui->setupUi(this); + + p->placefileSettingsWidget_ = new PlacefileSettingsWidget(this); + p->placefileSettingsWidget_->layout()->setContentsMargins(0, 0, 0, 0); + ui->contentsFrame->layout()->addWidget(p->placefileSettingsWidget_); +} + +PlacefileDialog::~PlacefileDialog() +{ + delete ui; +} + +} // namespace ui +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/placefile_dialog.hpp b/scwx-qt/source/scwx/qt/ui/placefile_dialog.hpp new file mode 100644 index 00000000..eb93cc44 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/placefile_dialog.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include + +namespace Ui +{ +class PlacefileDialog; +} + +namespace scwx +{ +namespace qt +{ +namespace ui +{ + +class PlacefileDialogImpl; + +class PlacefileDialog : public QDialog +{ + Q_OBJECT + +public: + explicit PlacefileDialog(QWidget* parent = nullptr); + ~PlacefileDialog(); + +private: + friend class PlacefileDialogImpl; + std::unique_ptr p; + Ui::PlacefileDialog* ui; +}; + +} // namespace ui +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/placefile_dialog.ui b/scwx-qt/source/scwx/qt/ui/placefile_dialog.ui new file mode 100644 index 00000000..8ff045a6 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/placefile_dialog.ui @@ -0,0 +1,88 @@ + + + PlacefileDialog + + + + 0 + 0 + 700 + 600 + + + + Placefile Manager + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + + buttonBox + accepted() + PlacefileDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + PlacefileDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp index ae32d450..ee216db0 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -135,7 +134,6 @@ public: void SetupGeneralTab(); void SetupPalettesColorTablesTab(); void SetupPalettesAlertsTab(); - void SetupPlacefilesTab(); void SetupTextTab(); void ShowColorDialog(QLineEdit* lineEdit, QFrame* frame = nullptr); @@ -162,10 +160,9 @@ public: RadarSiteLabel(std::shared_ptr& radarSite); static void SetBackgroundColor(const std::string& value, QFrame* frame); - SettingsDialog* self_; - PlacefileSettingsWidget* placefileSettingsWidget_ {nullptr}; - RadarSiteDialog* radarSiteDialog_; - QFontDialog* fontDialog_; + SettingsDialog* self_; + RadarSiteDialog* radarSiteDialog_; + QFontDialog* fontDialog_; QStandardItemModel* fontCategoryModel_; @@ -226,9 +223,6 @@ SettingsDialog::SettingsDialog(QWidget* parent) : // Text p->SetupTextTab(); - // Placefiles - p->SetupPlacefilesTab(); - p->ConnectSignals(); } @@ -735,12 +729,6 @@ void SettingsDialogImpl::SetupPalettesAlertsTab() } } -void SettingsDialogImpl::SetupPlacefilesTab() -{ - placefileSettingsWidget_ = new PlacefileSettingsWidget(self_); - self_->ui->placefiles->layout()->addWidget(placefileSettingsWidget_); -} - void SettingsDialogImpl::SetupTextTab() { settings::TextSettings& textSettings = settings::TextSettings::Instance(); diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui index 248a75fa..8a065168 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui @@ -82,15 +82,6 @@ :/res/icons/font-awesome-6/font-solid.svg:/res/icons/font-awesome-6/font-solid.svg - - - Placefiles - - - - :/res/icons/font-awesome-6/earth-americas-solid.svg:/res/icons/font-awesome-6/earth-americas-solid.svg - - @@ -350,7 +341,7 @@ 0 0 - 481 + 512 382 @@ -704,22 +695,6 @@ - - - - 0 - - - 0 - - - 0 - - - 0 - - - From ad55ec7f51529a67b6612a352d87d1d2a3202cbe Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 11 Oct 2023 05:31:37 -0500 Subject: [PATCH 169/199] Layer manager dialog stub --- .../icons/font-awesome-6/angle-down-solid.svg | 1 + .../icons/font-awesome-6/angle-up-solid.svg | 1 + .../font-awesome-6/angles-down-solid.svg | 1 + .../icons/font-awesome-6/angles-up-solid.svg | 1 + scwx-qt/scwx-qt.cmake | 3 + scwx-qt/scwx-qt.qrc | 4 + scwx-qt/source/scwx/qt/main/main_window.cpp | 11 ++ scwx-qt/source/scwx/qt/main/main_window.hpp | 1 + scwx-qt/source/scwx/qt/ui/layer_dialog.cpp | 38 ++++ scwx-qt/source/scwx/qt/ui/layer_dialog.hpp | 35 ++++ scwx-qt/source/scwx/qt/ui/layer_dialog.ui | 187 ++++++++++++++++++ 11 files changed, 283 insertions(+) create mode 100644 scwx-qt/res/icons/font-awesome-6/angle-down-solid.svg create mode 100644 scwx-qt/res/icons/font-awesome-6/angle-up-solid.svg create mode 100644 scwx-qt/res/icons/font-awesome-6/angles-down-solid.svg create mode 100644 scwx-qt/res/icons/font-awesome-6/angles-up-solid.svg create mode 100644 scwx-qt/source/scwx/qt/ui/layer_dialog.cpp create mode 100644 scwx-qt/source/scwx/qt/ui/layer_dialog.hpp create mode 100644 scwx-qt/source/scwx/qt/ui/layer_dialog.ui diff --git a/scwx-qt/res/icons/font-awesome-6/angle-down-solid.svg b/scwx-qt/res/icons/font-awesome-6/angle-down-solid.svg new file mode 100644 index 00000000..c877d491 --- /dev/null +++ b/scwx-qt/res/icons/font-awesome-6/angle-down-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/scwx-qt/res/icons/font-awesome-6/angle-up-solid.svg b/scwx-qt/res/icons/font-awesome-6/angle-up-solid.svg new file mode 100644 index 00000000..da43225c --- /dev/null +++ b/scwx-qt/res/icons/font-awesome-6/angle-up-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/scwx-qt/res/icons/font-awesome-6/angles-down-solid.svg b/scwx-qt/res/icons/font-awesome-6/angles-down-solid.svg new file mode 100644 index 00000000..68671b02 --- /dev/null +++ b/scwx-qt/res/icons/font-awesome-6/angles-down-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/scwx-qt/res/icons/font-awesome-6/angles-up-solid.svg b/scwx-qt/res/icons/font-awesome-6/angles-up-solid.svg new file mode 100644 index 00000000..74692e85 --- /dev/null +++ b/scwx-qt/res/icons/font-awesome-6/angles-up-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 60e9a87f..0e0f2091 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -181,6 +181,7 @@ set(HDR_UI source/scwx/qt/ui/about_dialog.hpp source/scwx/qt/ui/flow_layout.hpp source/scwx/qt/ui/imgui_debug_dialog.hpp source/scwx/qt/ui/imgui_debug_widget.hpp + source/scwx/qt/ui/layer_dialog.hpp source/scwx/qt/ui/left_elided_item_delegate.hpp source/scwx/qt/ui/level2_products_widget.hpp source/scwx/qt/ui/level2_settings_widget.hpp @@ -199,6 +200,7 @@ set(SRC_UI source/scwx/qt/ui/about_dialog.cpp source/scwx/qt/ui/flow_layout.cpp source/scwx/qt/ui/imgui_debug_dialog.cpp source/scwx/qt/ui/imgui_debug_widget.cpp + source/scwx/qt/ui/layer_dialog.cpp source/scwx/qt/ui/left_elided_item_delegate.cpp source/scwx/qt/ui/level2_products_widget.cpp source/scwx/qt/ui/level2_settings_widget.cpp @@ -215,6 +217,7 @@ set(UI_UI source/scwx/qt/ui/about_dialog.ui source/scwx/qt/ui/animation_dock_widget.ui source/scwx/qt/ui/collapsible_group.ui source/scwx/qt/ui/imgui_debug_dialog.ui + source/scwx/qt/ui/layer_dialog.ui source/scwx/qt/ui/open_url_dialog.ui source/scwx/qt/ui/placefile_dialog.ui source/scwx/qt/ui/placefile_settings_widget.ui diff --git a/scwx-qt/scwx-qt.qrc b/scwx-qt/scwx-qt.qrc index 4f914f25..10d8b0a2 100644 --- a/scwx-qt/scwx-qt.qrc +++ b/scwx-qt/scwx-qt.qrc @@ -20,8 +20,12 @@ res/fonts/Inconsolata-Regular.ttf res/icons/scwx-256.ico res/icons/scwx-256.png + res/icons/font-awesome-6/angle-down-solid.svg res/icons/font-awesome-6/angle-left-solid.svg res/icons/font-awesome-6/angle-right-solid.svg + res/icons/font-awesome-6/angle-up-solid.svg + res/icons/font-awesome-6/angles-down-solid.svg + res/icons/font-awesome-6/angles-up-solid.svg res/icons/font-awesome-6/backward-step-solid.svg res/icons/font-awesome-6/book-solid.svg res/icons/font-awesome-6/discord.svg diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp index b3aa618f..267da846 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -78,6 +79,7 @@ public: animationDockWidget_ {nullptr}, aboutDialog_ {nullptr}, imGuiDebugDialog_ {nullptr}, + layerDialog_ {nullptr}, placefileDialog_ {nullptr}, radarSiteDialog_ {nullptr}, settingsDialog_ {nullptr}, @@ -167,6 +169,7 @@ public: ui::AnimationDockWidget* animationDockWidget_; ui::AboutDialog* aboutDialog_; ui::ImGuiDebugDialog* imGuiDebugDialog_; + ui::LayerDialog* layerDialog_; ui::PlacefileDialog* placefileDialog_; ui::RadarSiteDialog* radarSiteDialog_; ui::SettingsDialog* settingsDialog_; @@ -249,6 +252,9 @@ MainWindow::MainWindow(QWidget* parent) : // Placefile Manager Dialog p->placefileDialog_ = new ui::PlacefileDialog(this); + // Layer Dialog + p->layerDialog_ = new ui::LayerDialog(this); + // Settings Dialog p->settingsDialog_ = new ui::SettingsDialog(this); @@ -454,6 +460,11 @@ void MainWindow::on_actionPlacefileManager_triggered() p->placefileDialog_->show(); } +void MainWindow::on_actionLayerManager_triggered() +{ + p->layerDialog_->show(); +} + void MainWindow::on_actionImGuiDebug_triggered() { p->imGuiDebugDialog_->show(); diff --git a/scwx-qt/source/scwx/qt/main/main_window.hpp b/scwx-qt/source/scwx/qt/main/main_window.hpp index 2657abea..19fddd79 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.hpp +++ b/scwx-qt/source/scwx/qt/main/main_window.hpp @@ -37,6 +37,7 @@ private slots: void on_actionSettings_triggered(); void on_actionExit_triggered(); void on_actionPlacefileManager_triggered(); + void on_actionLayerManager_triggered(); void on_actionImGuiDebug_triggered(); void on_actionDumpRadarProductRecords_triggered(); void on_actionUserManual_triggered(); diff --git a/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp b/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp new file mode 100644 index 00000000..b5d535dc --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp @@ -0,0 +1,38 @@ +#include "layer_dialog.hpp" +#include "ui_layer_dialog.h" + +#include + +namespace scwx +{ +namespace qt +{ +namespace ui +{ + +static const std::string logPrefix_ = "scwx::qt::ui::layer_dialog"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +class LayerDialogImpl +{ +public: + explicit LayerDialogImpl() {} + ~LayerDialogImpl() = default; +}; + +LayerDialog::LayerDialog(QWidget* parent) : + QDialog(parent), + p {std::make_unique()}, + ui(new Ui::LayerDialog) +{ + ui->setupUi(this); +} + +LayerDialog::~LayerDialog() +{ + delete ui; +} + +} // namespace ui +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/layer_dialog.hpp b/scwx-qt/source/scwx/qt/ui/layer_dialog.hpp new file mode 100644 index 00000000..738befe4 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/layer_dialog.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include + +namespace Ui +{ +class LayerDialog; +} + +namespace scwx +{ +namespace qt +{ +namespace ui +{ + +class LayerDialogImpl; + +class LayerDialog : public QDialog +{ + Q_OBJECT + +public: + explicit LayerDialog(QWidget* parent = nullptr); + ~LayerDialog(); + +private: + friend class LayerDialogImpl; + std::unique_ptr p; + Ui::LayerDialog* ui; +}; + +} // namespace ui +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/layer_dialog.ui b/scwx-qt/source/scwx/qt/ui/layer_dialog.ui new file mode 100644 index 00000000..8d2c4ed4 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/layer_dialog.ui @@ -0,0 +1,187 @@ + + + LayerDialog + + + + 0 + 0 + 400 + 300 + + + + Layer Manager + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + ... + + + + :/res/icons/font-awesome-6/angles-up-solid.svg:/res/icons/font-awesome-6/angles-up-solid.svg + + + + + + + ... + + + + :/res/icons/font-awesome-6/angle-up-solid.svg:/res/icons/font-awesome-6/angle-up-solid.svg + + + + + + + ... + + + + :/res/icons/font-awesome-6/angle-down-solid.svg:/res/icons/font-awesome-6/angle-down-solid.svg + + + + + + + ... + + + + :/res/icons/font-awesome-6/angles-down-solid.svg:/res/icons/font-awesome-6/angles-down-solid.svg + + + + + + + Qt::Vertical + + + + 20 + 60 + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + + + + buttonBox + accepted() + LayerDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + LayerDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + From f0822205a43b47177a00259d6f103424daa55a53 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 12 Oct 2023 07:26:25 -0500 Subject: [PATCH 170/199] Initial layer model for layer manager --- scwx-qt/scwx-qt.cmake | 2 + scwx-qt/source/scwx/qt/model/layer_model.cpp | 393 +++++++++++++++++++ scwx-qt/source/scwx/qt/model/layer_model.hpp | 72 ++++ scwx-qt/source/scwx/qt/ui/layer_dialog.cpp | 30 +- scwx-qt/source/scwx/qt/ui/layer_dialog.hpp | 1 + scwx-qt/source/scwx/qt/ui/layer_dialog.ui | 9 +- 6 files changed, 504 insertions(+), 3 deletions(-) create mode 100644 scwx-qt/source/scwx/qt/model/layer_model.cpp create mode 100644 scwx-qt/source/scwx/qt/model/layer_model.hpp diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 0e0f2091..bd03dde8 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -120,6 +120,7 @@ set(SRC_MAP source/scwx/qt/map/alert_layer.cpp set(HDR_MODEL source/scwx/qt/model/alert_model.hpp source/scwx/qt/model/alert_proxy_model.hpp source/scwx/qt/model/imgui_context_model.hpp + source/scwx/qt/model/layer_model.hpp source/scwx/qt/model/placefile_model.hpp source/scwx/qt/model/radar_product_model.hpp source/scwx/qt/model/radar_site_model.hpp @@ -128,6 +129,7 @@ set(HDR_MODEL source/scwx/qt/model/alert_model.hpp set(SRC_MODEL source/scwx/qt/model/alert_model.cpp source/scwx/qt/model/alert_proxy_model.cpp source/scwx/qt/model/imgui_context_model.cpp + source/scwx/qt/model/layer_model.cpp source/scwx/qt/model/placefile_model.cpp source/scwx/qt/model/radar_product_model.cpp source/scwx/qt/model/radar_site_model.cpp diff --git a/scwx-qt/source/scwx/qt/model/layer_model.cpp b/scwx-qt/source/scwx/qt/model/layer_model.cpp new file mode 100644 index 00000000..36847512 --- /dev/null +++ b/scwx-qt/source/scwx/qt/model/layer_model.cpp @@ -0,0 +1,393 @@ +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace model +{ + +static const std::string logPrefix_ = "scwx::qt::model::layer_model"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +static constexpr int kFirstColumn = static_cast(LayerModel::Column::Order); +static constexpr int kLastColumn = + static_cast(LayerModel::Column::Description); +static constexpr int kNumColumns = kLastColumn - kFirstColumn + 1; + +static const std::unordered_map + layerTypeNames_ {{LayerModel::LayerType::Map, "Map"}, + {LayerModel::LayerType::Radar, "Radar"}, + {LayerModel::LayerType::Alert, "Alert"}, + {LayerModel::LayerType::Placefile, "Placefile"}}; + +class LayerModelImpl +{ +public: + explicit LayerModelImpl() {} + ~LayerModelImpl() = default; + + std::shared_ptr placefileManager_ { + manager::PlacefileManager::Instance()}; + + std::vector>> + layers_ {}; +}; + +LayerModel::LayerModel(QObject* parent) : + QAbstractTableModel(parent), p(std::make_unique()) +{ + connect(p->placefileManager_.get(), + &manager::PlacefileManager::PlacefileEnabled, + this, + &LayerModel::HandlePlacefileUpdate); + + connect(p->placefileManager_.get(), + &manager::PlacefileManager::PlacefileRemoved, + this, + &LayerModel::HandlePlacefileRemoved); + + connect(p->placefileManager_.get(), + &manager::PlacefileManager::PlacefileRenamed, + this, + &LayerModel::HandlePlacefileRenamed); + + connect(p->placefileManager_.get(), + &manager::PlacefileManager::PlacefileUpdated, + this, + &LayerModel::HandlePlacefileUpdate); +} +LayerModel::~LayerModel() = default; + +int LayerModel::rowCount(const QModelIndex& parent) const +{ + return parent.isValid() ? 0 : static_cast(p->layers_.size()); +} + +int LayerModel::columnCount(const QModelIndex& parent) const +{ + return parent.isValid() ? 0 : kNumColumns; +} + +Qt::ItemFlags LayerModel::flags(const QModelIndex& index) const +{ + Qt::ItemFlags flags = QAbstractTableModel::flags(index); + + switch (index.column()) + { + case static_cast(Column::EnabledMap1): + case static_cast(Column::EnabledMap2): + case static_cast(Column::EnabledMap3): + case static_cast(Column::EnabledMap4): + flags |= Qt::ItemFlag::ItemIsUserCheckable | Qt::ItemFlag::ItemIsEditable; + break; + + default: + break; + } + + return flags; +} + +QVariant LayerModel::data(const QModelIndex& index, int role) const +{ + static const QString enabledString = QObject::tr("Enabled"); + static const QString disabledString = QObject::tr("Disabled"); + + if (!index.isValid() || index.row() < 0 || + static_cast(index.row()) >= p->layers_.size()) + { + return QVariant(); + } + + const auto& layer = p->layers_.at(index.row()); + bool enabled = true; // TODO + + switch (index.column()) + { + case static_cast(Column::Order): + if (role == Qt::ItemDataRole::DisplayRole) + { + return index.row(); + } + break; + + case static_cast(Column::EnabledMap1): + case static_cast(Column::EnabledMap2): + case static_cast(Column::EnabledMap3): + case static_cast(Column::EnabledMap4): + // TODO + if (role == Qt::ItemDataRole::ToolTipRole) + { + return enabled ? enabledString : disabledString; + } + else if (role == Qt::ItemDataRole::CheckStateRole) + { + return static_cast(enabled ? Qt::CheckState::Checked : + Qt::CheckState::Unchecked); + } + break; + + case static_cast(Column::Type): + if (role == Qt::ItemDataRole::DisplayRole || + role == Qt::ItemDataRole::ToolTipRole) + { + return QString::fromStdString(layerTypeNames_.at(layer.first)); + } + break; + + case static_cast(Column::Description): + if (role == Qt::ItemDataRole::DisplayRole || + role == Qt::ItemDataRole::ToolTipRole) + { + if (layer.first == LayerType::Placefile) + { + std::string placefileName = std::get(layer.second); + std::string description = placefileName; + std::string title = + p->placefileManager_->placefile_title(placefileName); + if (!title.empty()) + { + description = title + '\n' + description; + } + + return QString::fromStdString(description); + } + else + { + if (std::holds_alternative(layer.second)) + { + return QString::fromStdString( + std::get(layer.second)); + } + } + } + break; + + default: + break; + } + + return QVariant(); +} + +QVariant +LayerModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role == Qt::ItemDataRole::DisplayRole) + { + if (orientation == Qt::Horizontal) + { + switch (section) + { + case static_cast(Column::EnabledMap1): + return tr("1"); + + case static_cast(Column::EnabledMap2): + return tr("2"); + + case static_cast(Column::EnabledMap3): + return tr("3"); + + case static_cast(Column::EnabledMap4): + return tr("4"); + + case static_cast(Column::Type): + return tr("Type"); + + case static_cast(Column::Description): + return tr("Description"); + + default: + break; + } + } + } + else if (role == Qt::ItemDataRole::ToolTipRole) + { + switch (section) + { + case static_cast(Column::Order): + return tr("Order"); + + case static_cast(Column::EnabledMap1): + return tr("Enabled on Map 1"); + + case static_cast(Column::EnabledMap2): + return tr("Enabled on Map 2"); + + case static_cast(Column::EnabledMap3): + return tr("Enabled on Map 3"); + + case static_cast(Column::EnabledMap4): + return tr("Enabled on Map 4"); + + default: + break; + } + } + else if (role == Qt::ItemDataRole::SizeHintRole) + { + switch (section) + { + case static_cast(Column::EnabledMap1): + case static_cast(Column::EnabledMap2): + case static_cast(Column::EnabledMap3): + case static_cast(Column::EnabledMap4): + { + static const QCheckBox checkBox {}; + QStyleOptionButton option {}; + option.initFrom(&checkBox); + + // Width values from QCheckBox + return QApplication::style()->sizeFromContents( + QStyle::ContentsType::CT_CheckBox, + &option, + {option.iconSize.width() + 4, 0}); + } + + default: + break; + } + } + + return QVariant(); +} + +bool LayerModel::setData(const QModelIndex& index, + const QVariant& value, + int role) +{ + if (!index.isValid() || index.row() < 0 || + static_cast(index.row()) >= p->layers_.size()) + { + return false; + } + + const auto& layer = p->layers_.at(index.row()); + bool result = false; + + switch (index.column()) + { + case static_cast(Column::EnabledMap1): + case static_cast(Column::EnabledMap2): + case static_cast(Column::EnabledMap3): + case static_cast(Column::EnabledMap4): + if (role == Qt::ItemDataRole::CheckStateRole) + { + // TODO + Q_UNUSED(layer); + Q_UNUSED(value); + } + break; + + default: + break; + } + + if (result) + { + Q_EMIT dataChanged(index, index); + } + + return result; +} + +void LayerModel::HandlePlacefileRemoved(const std::string& name) +{ + auto it = std::find_if(p->layers_.begin(), + p->layers_.end(), + [&name](const auto& layer) + { + return layer.first == LayerType::Placefile && + std::get(layer.second) == name; + }); + + if (it != p->layers_.end()) + { + // Placefile exists, delete row + const int row = std::distance(p->layers_.begin(), it); + + beginRemoveRows(QModelIndex(), row, row); + p->layers_.erase(it); + endRemoveRows(); + } +} + +void LayerModel::HandlePlacefileRenamed(const std::string& oldName, + const std::string& newName) +{ + auto it = + std::find_if(p->layers_.begin(), + p->layers_.end(), + [&oldName](const auto& layer) + { + return layer.first == LayerType::Placefile && + std::get(layer.second) == oldName; + }); + + if (it != p->layers_.end()) + { + // Placefile exists, mark row as updated + const int row = std::distance(p->layers_.begin(), it); + QModelIndex topLeft = createIndex(row, kFirstColumn); + QModelIndex bottomRight = createIndex(row, kLastColumn); + + // Rename placefile + it->second = newName; + + Q_EMIT dataChanged(topLeft, bottomRight); + } + else + { + // Placefile is new, append row + const int newIndex = static_cast(p->layers_.size()); + beginInsertRows(QModelIndex(), newIndex, newIndex); + p->layers_.push_back({LayerType::Placefile, newName}); + endInsertRows(); + } +} + +void LayerModel::HandlePlacefileUpdate(const std::string& name) +{ + auto it = std::find_if(p->layers_.begin(), + p->layers_.end(), + [&name](const auto& layer) + { + return layer.first == LayerType::Placefile && + std::get(layer.second) == name; + }); + + if (it != p->layers_.end()) + { + // Placefile exists, mark row as updated + const int row = std::distance(p->layers_.begin(), it); + QModelIndex topLeft = createIndex(row, kFirstColumn); + QModelIndex bottomRight = createIndex(row, kLastColumn); + + Q_EMIT dataChanged(topLeft, bottomRight); + } + else + { + // Placefile is new, append row + const int newIndex = static_cast(p->layers_.size()); + beginInsertRows(QModelIndex(), newIndex, newIndex); + p->layers_.push_back({LayerType::Placefile, name}); + endInsertRows(); + } +} + +} // namespace model +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/model/layer_model.hpp b/scwx-qt/source/scwx/qt/model/layer_model.hpp new file mode 100644 index 00000000..bb7c4828 --- /dev/null +++ b/scwx-qt/source/scwx/qt/model/layer_model.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include + +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace model +{ + +class LayerModelImpl; + +class LayerModel : public QAbstractTableModel +{ +public: + enum class Column : int + { + Order = 0, + EnabledMap1 = 1, + EnabledMap2 = 2, + EnabledMap3 = 3, + EnabledMap4 = 4, + Type = 5, + Description = 6 + }; + + enum class LayerType + { + Map, + Radar, + Alert, + Placefile + }; + + explicit LayerModel(QObject* parent = nullptr); + ~LayerModel(); + + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + + Qt::ItemFlags flags(const QModelIndex& index) const override; + + QVariant data(const QModelIndex& index, + int role = Qt::DisplayRole) const override; + QVariant headerData(int section, + Qt::Orientation orientation, + int role = Qt::DisplayRole) const override; + + bool setData(const QModelIndex& index, + const QVariant& value, + int role = Qt::EditRole) override; + +public slots: + void HandlePlacefileRemoved(const std::string& name); + void HandlePlacefileRenamed(const std::string& oldName, + const std::string& newName); + void HandlePlacefileUpdate(const std::string& name); + +private: + friend class LayerModelImpl; + std::unique_ptr p; +}; + +} // namespace model +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp b/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp index b5d535dc..3df3b06d 100644 --- a/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp @@ -1,6 +1,7 @@ #include "layer_dialog.hpp" #include "ui_layer_dialog.h" +#include #include namespace scwx @@ -16,16 +17,41 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_); class LayerDialogImpl { public: - explicit LayerDialogImpl() {} + explicit LayerDialogImpl(LayerDialog* self) : + layerModel_ {new model::LayerModel(self)} + { + } ~LayerDialogImpl() = default; + + model::LayerModel* layerModel_; }; LayerDialog::LayerDialog(QWidget* parent) : QDialog(parent), - p {std::make_unique()}, + p {std::make_unique(this)}, ui(new Ui::LayerDialog) { ui->setupUi(this); + + ui->layerTreeView->setModel(p->layerModel_); + + auto layerViewHeader = ui->layerTreeView->header(); + + layerViewHeader->setMinimumSectionSize(10); + + // Enabled columns have a fixed size (checkbox) + layerViewHeader->setSectionResizeMode( + static_cast(model::LayerModel::Column::EnabledMap1), + QHeaderView::ResizeMode::ResizeToContents); + layerViewHeader->setSectionResizeMode( + static_cast(model::LayerModel::Column::EnabledMap2), + QHeaderView::ResizeMode::ResizeToContents); + layerViewHeader->setSectionResizeMode( + static_cast(model::LayerModel::Column::EnabledMap3), + QHeaderView::ResizeMode::ResizeToContents); + layerViewHeader->setSectionResizeMode( + static_cast(model::LayerModel::Column::EnabledMap4), + QHeaderView::ResizeMode::ResizeToContents); } LayerDialog::~LayerDialog() diff --git a/scwx-qt/source/scwx/qt/ui/layer_dialog.hpp b/scwx-qt/source/scwx/qt/ui/layer_dialog.hpp index 738befe4..a8101d0d 100644 --- a/scwx-qt/source/scwx/qt/ui/layer_dialog.hpp +++ b/scwx-qt/source/scwx/qt/ui/layer_dialog.hpp @@ -19,6 +19,7 @@ class LayerDialogImpl; class LayerDialog : public QDialog { Q_OBJECT + Q_DISABLE_COPY_MOVE(LayerDialog) public: explicit LayerDialog(QWidget* parent = nullptr); diff --git a/scwx-qt/source/scwx/qt/ui/layer_dialog.ui b/scwx-qt/source/scwx/qt/ui/layer_dialog.ui index 8d2c4ed4..18b937c8 100644 --- a/scwx-qt/source/scwx/qt/ui/layer_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/layer_dialog.ui @@ -36,7 +36,14 @@ 0 - + + + true + + + QAbstractItemView::ContiguousSelection + + From d82a1cc17122aa1215e77fdb44d7e39cb1a44377 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 12 Oct 2023 21:43:53 -0500 Subject: [PATCH 171/199] Additional layer model work, including alert, radar and map layers --- scwx-qt/source/scwx/qt/model/layer_model.cpp | 132 ++++++++++++------- scwx-qt/source/scwx/qt/model/layer_model.hpp | 20 +-- scwx-qt/source/scwx/qt/ui/layer_dialog.cpp | 23 ++-- scwx-qt/source/scwx/qt/ui/layer_dialog.ui | 66 ++++++++-- 4 files changed, 167 insertions(+), 74 deletions(-) diff --git a/scwx-qt/source/scwx/qt/model/layer_model.cpp b/scwx-qt/source/scwx/qt/model/layer_model.cpp index 36847512..3a95f15c 100644 --- a/scwx-qt/source/scwx/qt/model/layer_model.cpp +++ b/scwx-qt/source/scwx/qt/model/layer_model.cpp @@ -10,6 +10,7 @@ #include #include #include +#include namespace scwx { @@ -32,21 +33,37 @@ static const std::unordered_map {LayerModel::LayerType::Alert, "Alert"}, {LayerModel::LayerType::Placefile, "Placefile"}}; -class LayerModelImpl +typedef std::variant + LayerDescription; +typedef boost::container::devector< + std::pair> + LayerVector; + +class LayerModel::Impl { public: - explicit LayerModelImpl() {} - ~LayerModelImpl() = default; + explicit Impl() + { + layers_.emplace_back(LayerType::Alert, awips::Phenomenon::Tornado); + layers_.emplace_back(LayerType::Alert, awips::Phenomenon::SnowSquall); + layers_.emplace_back(LayerType::Alert, + awips::Phenomenon::SevereThunderstorm); + layers_.emplace_back(LayerType::Alert, awips::Phenomenon::FlashFlood); + layers_.emplace_back(LayerType::Alert, awips::Phenomenon::Marine); + layers_.emplace_back(LayerType::Map, "Map Overlay"); + layers_.emplace_back(LayerType::Radar, std::monostate {}); + layers_.emplace_back(LayerType::Map, "Map Underlay"); + } + ~Impl() = default; std::shared_ptr placefileManager_ { manager::PlacefileManager::Instance()}; - std::vector>> - layers_ {}; + LayerVector layers_ {}; }; LayerModel::LayerModel(QObject* parent) : - QAbstractTableModel(parent), p(std::make_unique()) + QAbstractTableModel(parent), p(std::make_unique()) { connect(p->placefileManager_.get(), &manager::PlacefileManager::PlacefileEnabled, @@ -86,10 +103,10 @@ Qt::ItemFlags LayerModel::flags(const QModelIndex& index) const switch (index.column()) { - case static_cast(Column::EnabledMap1): - case static_cast(Column::EnabledMap2): - case static_cast(Column::EnabledMap3): - case static_cast(Column::EnabledMap4): + case static_cast(Column::DisplayMap1): + case static_cast(Column::DisplayMap2): + case static_cast(Column::DisplayMap3): + case static_cast(Column::DisplayMap4): flags |= Qt::ItemFlag::ItemIsUserCheckable | Qt::ItemFlag::ItemIsEditable; break; @@ -105,6 +122,9 @@ QVariant LayerModel::data(const QModelIndex& index, int role) const static const QString enabledString = QObject::tr("Enabled"); static const QString disabledString = QObject::tr("Disabled"); + static const QString displayedString = QObject::tr("Displayed"); + static const QString hiddenString = QObject::tr("Hidden"); + if (!index.isValid() || index.row() < 0 || static_cast(index.row()) >= p->layers_.size()) { @@ -119,18 +139,18 @@ QVariant LayerModel::data(const QModelIndex& index, int role) const case static_cast(Column::Order): if (role == Qt::ItemDataRole::DisplayRole) { - return index.row(); + return index.row() + 1; } break; - case static_cast(Column::EnabledMap1): - case static_cast(Column::EnabledMap2): - case static_cast(Column::EnabledMap3): - case static_cast(Column::EnabledMap4): + case static_cast(Column::DisplayMap1): + case static_cast(Column::DisplayMap2): + case static_cast(Column::DisplayMap3): + case static_cast(Column::DisplayMap4): // TODO if (role == Qt::ItemDataRole::ToolTipRole) { - return enabled ? enabledString : disabledString; + return enabled ? displayedString : hiddenString; } else if (role == Qt::ItemDataRole::CheckStateRole) { @@ -147,6 +167,24 @@ QVariant LayerModel::data(const QModelIndex& index, int role) const } break; + case static_cast(Column::Enabled): + if (role == Qt::ItemDataRole::DisplayRole || + role == Qt::ItemDataRole::ToolTipRole) + { + if (layer.first == LayerType::Placefile) + { + return p->placefileManager_->placefile_enabled( + std::get(layer.second)) ? + enabledString : + disabledString; + } + else + { + return enabledString; + } + } + break; + case static_cast(Column::Description): if (role == Qt::ItemDataRole::DisplayRole || role == Qt::ItemDataRole::ToolTipRole) @@ -171,6 +209,11 @@ QVariant LayerModel::data(const QModelIndex& index, int role) const return QString::fromStdString( std::get(layer.second)); } + else if (std::holds_alternative(layer.second)) + { + return QString::fromStdString(awips::GetPhenomenonText( + std::get(layer.second))); + } } } break; @@ -191,21 +234,24 @@ LayerModel::headerData(int section, Qt::Orientation orientation, int role) const { switch (section) { - case static_cast(Column::EnabledMap1): + case static_cast(Column::DisplayMap1): return tr("1"); - case static_cast(Column::EnabledMap2): + case static_cast(Column::DisplayMap2): return tr("2"); - case static_cast(Column::EnabledMap3): + case static_cast(Column::DisplayMap3): return tr("3"); - case static_cast(Column::EnabledMap4): + case static_cast(Column::DisplayMap4): return tr("4"); case static_cast(Column::Type): return tr("Type"); + case static_cast(Column::Enabled): + return tr("Enabled"); + case static_cast(Column::Description): return tr("Description"); @@ -221,17 +267,17 @@ LayerModel::headerData(int section, Qt::Orientation orientation, int role) const case static_cast(Column::Order): return tr("Order"); - case static_cast(Column::EnabledMap1): - return tr("Enabled on Map 1"); + case static_cast(Column::DisplayMap1): + return tr("Display on Map 1"); - case static_cast(Column::EnabledMap2): - return tr("Enabled on Map 2"); + case static_cast(Column::DisplayMap2): + return tr("Display on Map 2"); - case static_cast(Column::EnabledMap3): - return tr("Enabled on Map 3"); + case static_cast(Column::DisplayMap3): + return tr("Display on Map 3"); - case static_cast(Column::EnabledMap4): - return tr("Enabled on Map 4"); + case static_cast(Column::DisplayMap4): + return tr("Display on Map 4"); default: break; @@ -241,10 +287,10 @@ LayerModel::headerData(int section, Qt::Orientation orientation, int role) const { switch (section) { - case static_cast(Column::EnabledMap1): - case static_cast(Column::EnabledMap2): - case static_cast(Column::EnabledMap3): - case static_cast(Column::EnabledMap4): + case static_cast(Column::DisplayMap1): + case static_cast(Column::DisplayMap2): + case static_cast(Column::DisplayMap3): + case static_cast(Column::DisplayMap4): { static const QCheckBox checkBox {}; QStyleOptionButton option {}; @@ -280,10 +326,10 @@ bool LayerModel::setData(const QModelIndex& index, switch (index.column()) { - case static_cast(Column::EnabledMap1): - case static_cast(Column::EnabledMap2): - case static_cast(Column::EnabledMap3): - case static_cast(Column::EnabledMap4): + case static_cast(Column::DisplayMap1): + case static_cast(Column::DisplayMap2): + case static_cast(Column::DisplayMap3): + case static_cast(Column::DisplayMap4): if (role == Qt::ItemDataRole::CheckStateRole) { // TODO @@ -351,10 +397,9 @@ void LayerModel::HandlePlacefileRenamed(const std::string& oldName, } else { - // Placefile is new, append row - const int newIndex = static_cast(p->layers_.size()); - beginInsertRows(QModelIndex(), newIndex, newIndex); - p->layers_.push_back({LayerType::Placefile, newName}); + // Placefile is new, prepend row + beginInsertRows(QModelIndex(), 0, 0); + p->layers_.push_front({LayerType::Placefile, newName}); endInsertRows(); } } @@ -380,10 +425,9 @@ void LayerModel::HandlePlacefileUpdate(const std::string& name) } else { - // Placefile is new, append row - const int newIndex = static_cast(p->layers_.size()); - beginInsertRows(QModelIndex(), newIndex, newIndex); - p->layers_.push_back({LayerType::Placefile, name}); + // Placefile is new, prepend row + beginInsertRows(QModelIndex(), 0, 0); + p->layers_.push_front({LayerType::Placefile, name}); endInsertRows(); } } diff --git a/scwx-qt/source/scwx/qt/model/layer_model.hpp b/scwx-qt/source/scwx/qt/model/layer_model.hpp index bb7c4828..0a0a46bd 100644 --- a/scwx-qt/source/scwx/qt/model/layer_model.hpp +++ b/scwx-qt/source/scwx/qt/model/layer_model.hpp @@ -2,6 +2,7 @@ #include #include +#include #include @@ -14,21 +15,22 @@ namespace qt namespace model { -class LayerModelImpl; - class LayerModel : public QAbstractTableModel { public: enum class Column : int { Order = 0, - EnabledMap1 = 1, - EnabledMap2 = 2, - EnabledMap3 = 3, - EnabledMap4 = 4, + DisplayMap1 = 1, + DisplayMap2 = 2, + DisplayMap3 = 3, + DisplayMap4 = 4, Type = 5, - Description = 6 + Enabled = 6, + Description = 7 }; + typedef scwx::util::Iterator + ColumnIterator; enum class LayerType { @@ -63,8 +65,8 @@ public slots: void HandlePlacefileUpdate(const std::string& name); private: - friend class LayerModelImpl; - std::unique_ptr p; + class Impl; + std::unique_ptr p; }; } // namespace model diff --git a/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp b/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp index 3df3b06d..88d3515e 100644 --- a/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp @@ -39,19 +39,16 @@ LayerDialog::LayerDialog(QWidget* parent) : layerViewHeader->setMinimumSectionSize(10); - // Enabled columns have a fixed size (checkbox) - layerViewHeader->setSectionResizeMode( - static_cast(model::LayerModel::Column::EnabledMap1), - QHeaderView::ResizeMode::ResizeToContents); - layerViewHeader->setSectionResizeMode( - static_cast(model::LayerModel::Column::EnabledMap2), - QHeaderView::ResizeMode::ResizeToContents); - layerViewHeader->setSectionResizeMode( - static_cast(model::LayerModel::Column::EnabledMap3), - QHeaderView::ResizeMode::ResizeToContents); - layerViewHeader->setSectionResizeMode( - static_cast(model::LayerModel::Column::EnabledMap4), - QHeaderView::ResizeMode::ResizeToContents); + // Give small columns a fixed size + for (auto column : model::LayerModel::ColumnIterator()) + { + if (column != model::LayerModel::Column::Description) + { + layerViewHeader->setSectionResizeMode( + static_cast(column), + QHeaderView::ResizeMode::ResizeToContents); + } + } } LayerDialog::~LayerDialog() diff --git a/scwx-qt/source/scwx/qt/ui/layer_dialog.ui b/scwx-qt/source/scwx/qt/ui/layer_dialog.ui index 18b937c8..83c63f9e 100644 --- a/scwx-qt/source/scwx/qt/ui/layer_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/layer_dialog.ui @@ -6,8 +6,8 @@ 0 0 - 400 - 300 + 700 + 600 @@ -43,6 +43,9 @@ QAbstractItemView::ContiguousSelection + + 0 + @@ -131,7 +134,7 @@ 20 - 60 + 209 @@ -143,13 +146,60 @@ - - - Qt::Horizontal + + + QFrame::StyledPanel - - QDialogButtonBox::Close + + QFrame::Raised + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Filter + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + From 14d69120146d1ac4dd7b96e2cebe72c53c761485 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 13 Oct 2023 13:04:48 -0500 Subject: [PATCH 172/199] Additional layer model row population, properties --- scwx-qt/source/scwx/qt/model/layer_model.cpp | 225 ++++++++++++------- scwx-qt/source/scwx/qt/model/layer_model.hpp | 10 +- scwx-qt/source/scwx/qt/types/map_types.cpp | 23 ++ scwx-qt/source/scwx/qt/types/map_types.hpp | 19 ++ 4 files changed, 187 insertions(+), 90 deletions(-) diff --git a/scwx-qt/source/scwx/qt/model/layer_model.cpp b/scwx-qt/source/scwx/qt/model/layer_model.cpp index 3a95f15c..6c3ac23d 100644 --- a/scwx-qt/source/scwx/qt/model/layer_model.cpp +++ b/scwx-qt/source/scwx/qt/model/layer_model.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -27,35 +28,53 @@ static constexpr int kLastColumn = static_cast(LayerModel::Column::Description); static constexpr int kNumColumns = kLastColumn - kFirstColumn + 1; -static const std::unordered_map - layerTypeNames_ {{LayerModel::LayerType::Map, "Map"}, - {LayerModel::LayerType::Radar, "Radar"}, - {LayerModel::LayerType::Alert, "Alert"}, - {LayerModel::LayerType::Placefile, "Placefile"}}; +static constexpr std::size_t kMapCount_ = 4u; -typedef std::variant - LayerDescription; -typedef boost::container::devector< - std::pair> - LayerVector; +typedef std:: + variant + LayerDescription; class LayerModel::Impl { public: - explicit Impl() + struct LayerInfo { - layers_.emplace_back(LayerType::Alert, awips::Phenomenon::Tornado); - layers_.emplace_back(LayerType::Alert, awips::Phenomenon::SnowSquall); - layers_.emplace_back(LayerType::Alert, - awips::Phenomenon::SevereThunderstorm); - layers_.emplace_back(LayerType::Alert, awips::Phenomenon::FlashFlood); - layers_.emplace_back(LayerType::Alert, awips::Phenomenon::Marine); - layers_.emplace_back(LayerType::Map, "Map Overlay"); - layers_.emplace_back(LayerType::Radar, std::monostate {}); - layers_.emplace_back(LayerType::Map, "Map Underlay"); + types::LayerType type_; + LayerDescription description_; + bool movable_; + std::array displayed_ {true, true, true, true}; + }; + + typedef boost::container::devector LayerVector; + + explicit Impl(LayerModel* self) : self_ {self} + { + layers_.emplace_back( + types::LayerType::Information, types::Layer::MapOverlay, false); + layers_.emplace_back( + types::LayerType::Information, types::Layer::ColorTable, false); + layers_.emplace_back( + types::LayerType::Alert, awips::Phenomenon::Tornado, true); + layers_.emplace_back( + types::LayerType::Alert, awips::Phenomenon::SnowSquall, true); + layers_.emplace_back( + types::LayerType::Alert, awips::Phenomenon::SevereThunderstorm, true); + layers_.emplace_back( + types::LayerType::Alert, awips::Phenomenon::FlashFlood, true); + layers_.emplace_back( + types::LayerType::Alert, awips::Phenomenon::Marine, true); + layers_.emplace_back( + types::LayerType::Map, types::Layer::MapSymbology, false); + layers_.emplace_back(types::LayerType::Radar, std::monostate {}, true); + layers_.emplace_back( + types::LayerType::Map, types::Layer::MapUnderlay, false); } ~Impl() = default; + void AddPlacefile(const std::string& name); + + LayerModel* self_; + std::shared_ptr placefileManager_ { manager::PlacefileManager::Instance()}; @@ -63,7 +82,7 @@ public: }; LayerModel::LayerModel(QObject* parent) : - QAbstractTableModel(parent), p(std::make_unique()) + QAbstractTableModel(parent), p(std::make_unique(this)) { connect(p->placefileManager_.get(), &manager::PlacefileManager::PlacefileEnabled, @@ -101,13 +120,25 @@ Qt::ItemFlags LayerModel::flags(const QModelIndex& index) const { Qt::ItemFlags flags = QAbstractTableModel::flags(index); + if (!index.isValid() || index.row() < 0 || + static_cast(index.row()) >= p->layers_.size()) + { + return flags; + } + + const auto& layer = p->layers_.at(index.row()); + switch (index.column()) { case static_cast(Column::DisplayMap1): case static_cast(Column::DisplayMap2): case static_cast(Column::DisplayMap3): case static_cast(Column::DisplayMap4): - flags |= Qt::ItemFlag::ItemIsUserCheckable | Qt::ItemFlag::ItemIsEditable; + if (layer.type_ != types::LayerType::Map) + { + flags |= + Qt::ItemFlag::ItemIsUserCheckable | Qt::ItemFlag::ItemIsEditable; + } break; default: @@ -131,8 +162,7 @@ QVariant LayerModel::data(const QModelIndex& index, int role) const return QVariant(); } - const auto& layer = p->layers_.at(index.row()); - bool enabled = true; // TODO + const auto& layer = p->layers_.at(index.row()); switch (index.column()) { @@ -147,15 +177,21 @@ QVariant LayerModel::data(const QModelIndex& index, int role) const case static_cast(Column::DisplayMap2): case static_cast(Column::DisplayMap3): case static_cast(Column::DisplayMap4): - // TODO - if (role == Qt::ItemDataRole::ToolTipRole) + if (layer.type_ != types::LayerType::Map) { - return enabled ? displayedString : hiddenString; - } - else if (role == Qt::ItemDataRole::CheckStateRole) - { - return static_cast(enabled ? Qt::CheckState::Checked : - Qt::CheckState::Unchecked); + bool displayed = + layer.displayed_[index.column() - + static_cast(Column::DisplayMap1)]; + + if (role == Qt::ItemDataRole::ToolTipRole) + { + return displayed ? displayedString : hiddenString; + } + else if (role == Qt::ItemDataRole::CheckStateRole) + { + return static_cast(displayed ? Qt::CheckState::Checked : + Qt::CheckState::Unchecked); + } } break; @@ -163,7 +199,7 @@ QVariant LayerModel::data(const QModelIndex& index, int role) const if (role == Qt::ItemDataRole::DisplayRole || role == Qt::ItemDataRole::ToolTipRole) { - return QString::fromStdString(layerTypeNames_.at(layer.first)); + return QString::fromStdString(types::GetLayerTypeName(layer.type_)); } break; @@ -171,17 +207,13 @@ QVariant LayerModel::data(const QModelIndex& index, int role) const if (role == Qt::ItemDataRole::DisplayRole || role == Qt::ItemDataRole::ToolTipRole) { - if (layer.first == LayerType::Placefile) + if (layer.type_ == types::LayerType::Placefile) { return p->placefileManager_->placefile_enabled( - std::get(layer.second)) ? + std::get(layer.description_)) ? enabledString : disabledString; } - else - { - return enabledString; - } } break; @@ -189,10 +221,11 @@ QVariant LayerModel::data(const QModelIndex& index, int role) const if (role == Qt::ItemDataRole::DisplayRole || role == Qt::ItemDataRole::ToolTipRole) { - if (layer.first == LayerType::Placefile) + if (layer.type_ == types::LayerType::Placefile) { - std::string placefileName = std::get(layer.second); - std::string description = placefileName; + std::string placefileName = + std::get(layer.description_); + std::string description = placefileName; std::string title = p->placefileManager_->placefile_title(placefileName); if (!title.empty()) @@ -204,15 +237,21 @@ QVariant LayerModel::data(const QModelIndex& index, int role) const } else { - if (std::holds_alternative(layer.second)) + if (std::holds_alternative(layer.description_)) { return QString::fromStdString( - std::get(layer.second)); + std::get(layer.description_)); } - else if (std::holds_alternative(layer.second)) + else if (std::holds_alternative(layer.description_)) + { + return QString::fromStdString(types::GetLayerName( + std::get(layer.description_))); + } + else if (std::holds_alternative( + layer.description_)) { return QString::fromStdString(awips::GetPhenomenonText( - std::get(layer.second))); + std::get(layer.description_))); } } } @@ -321,8 +360,8 @@ bool LayerModel::setData(const QModelIndex& index, return false; } - const auto& layer = p->layers_.at(index.row()); - bool result = false; + auto& layer = p->layers_.at(index.row()); + bool result = false; switch (index.column()) { @@ -332,9 +371,10 @@ bool LayerModel::setData(const QModelIndex& index, case static_cast(Column::DisplayMap4): if (role == Qt::ItemDataRole::CheckStateRole) { - // TODO - Q_UNUSED(layer); - Q_UNUSED(value); + layer.displayed_[index.column() - + static_cast(Column::DisplayMap1)] = + value.toBool(); + result = true; } break; @@ -352,13 +392,14 @@ bool LayerModel::setData(const QModelIndex& index, void LayerModel::HandlePlacefileRemoved(const std::string& name) { - auto it = std::find_if(p->layers_.begin(), - p->layers_.end(), - [&name](const auto& layer) - { - return layer.first == LayerType::Placefile && - std::get(layer.second) == name; - }); + auto it = + std::find_if(p->layers_.begin(), + p->layers_.end(), + [&name](const auto& layer) + { + return layer.type_ == types::LayerType::Placefile && + std::get(layer.description_) == name; + }); if (it != p->layers_.end()) { @@ -374,14 +415,14 @@ void LayerModel::HandlePlacefileRemoved(const std::string& name) void LayerModel::HandlePlacefileRenamed(const std::string& oldName, const std::string& newName) { - auto it = - std::find_if(p->layers_.begin(), - p->layers_.end(), - [&oldName](const auto& layer) - { - return layer.first == LayerType::Placefile && - std::get(layer.second) == oldName; - }); + auto it = std::find_if( + p->layers_.begin(), + p->layers_.end(), + [&oldName](const auto& layer) + { + return layer.type_ == types::LayerType::Placefile && + std::get(layer.description_) == oldName; + }); if (it != p->layers_.end()) { @@ -391,28 +432,27 @@ void LayerModel::HandlePlacefileRenamed(const std::string& oldName, QModelIndex bottomRight = createIndex(row, kLastColumn); // Rename placefile - it->second = newName; + it->description_ = newName; Q_EMIT dataChanged(topLeft, bottomRight); } else { - // Placefile is new, prepend row - beginInsertRows(QModelIndex(), 0, 0); - p->layers_.push_front({LayerType::Placefile, newName}); - endInsertRows(); + // Placefile doesn't exist, add row + p->AddPlacefile(newName); } } void LayerModel::HandlePlacefileUpdate(const std::string& name) { - auto it = std::find_if(p->layers_.begin(), - p->layers_.end(), - [&name](const auto& layer) - { - return layer.first == LayerType::Placefile && - std::get(layer.second) == name; - }); + auto it = + std::find_if(p->layers_.begin(), + p->layers_.end(), + [&name](const auto& layer) + { + return layer.type_ == types::LayerType::Placefile && + std::get(layer.description_) == name; + }); if (it != p->layers_.end()) { @@ -425,13 +465,34 @@ void LayerModel::HandlePlacefileUpdate(const std::string& name) } else { - // Placefile is new, prepend row - beginInsertRows(QModelIndex(), 0, 0); - p->layers_.push_front({LayerType::Placefile, name}); - endInsertRows(); + // Placefile doesn't exist, add row + p->AddPlacefile(name); } } +void LayerModel::Impl::AddPlacefile(const std::string& name) +{ + // Insert after color table + auto insertPosition = std::find_if( + layers_.begin(), + layers_.end(), + [](const Impl::LayerInfo& layerInfo) + { + return std::holds_alternative(layerInfo.description_) && + std::get(layerInfo.description_) == + types::Layer::ColorTable; + }); + if (insertPosition != layers_.end()) + { + ++insertPosition; + } + + // Placefile is new, add row + self_->beginInsertRows(QModelIndex(), 0, 0); + layers_.insert(insertPosition, {types::LayerType::Placefile, name}); + self_->endInsertRows(); +} + } // namespace model } // namespace qt } // namespace scwx diff --git a/scwx-qt/source/scwx/qt/model/layer_model.hpp b/scwx-qt/source/scwx/qt/model/layer_model.hpp index 0a0a46bd..91e3a9f0 100644 --- a/scwx-qt/source/scwx/qt/model/layer_model.hpp +++ b/scwx-qt/source/scwx/qt/model/layer_model.hpp @@ -17,6 +17,8 @@ namespace model class LayerModel : public QAbstractTableModel { + Q_DISABLE_COPY_MOVE(LayerModel) + public: enum class Column : int { @@ -32,14 +34,6 @@ public: typedef scwx::util::Iterator ColumnIterator; - enum class LayerType - { - Map, - Radar, - Alert, - Placefile - }; - explicit LayerModel(QObject* parent = nullptr); ~LayerModel(); diff --git a/scwx-qt/source/scwx/qt/types/map_types.cpp b/scwx-qt/source/scwx/qt/types/map_types.cpp index d3ea6bc0..57612242 100644 --- a/scwx-qt/source/scwx/qt/types/map_types.cpp +++ b/scwx-qt/source/scwx/qt/types/map_types.cpp @@ -9,9 +9,32 @@ namespace qt namespace types { +static const std::unordered_map layerTypeName_ { + {types::LayerType::Map, "Map"}, + {types::LayerType::Radar, "Radar"}, + {types::LayerType::Alert, "Alert"}, + {types::LayerType::Placefile, "Placefile"}, + {types::LayerType::Information, "Information"}}; + +static const std::unordered_map layerName_ { + {types::Layer::MapOverlay, "Map Overlay"}, + {types::Layer::ColorTable, "Color Table"}, + {types::Layer::MapSymbology, "Map Symbology"}, + {types::Layer::MapUnderlay, "Map Underlay"}}; + static const std::unordered_map mapTimeName_ { {MapTime::Live, "Live"}, {MapTime::Archive, "Archive"}}; +std::string GetLayerTypeName(LayerType layerType) +{ + return layerTypeName_.at(layerType); +} + +std::string GetLayerName(Layer layer) +{ + return layerName_.at(layer); +} + std::string GetMapTimeName(MapTime mapTime) { return mapTimeName_.at(mapTime); diff --git a/scwx-qt/source/scwx/qt/types/map_types.hpp b/scwx-qt/source/scwx/qt/types/map_types.hpp index 5853a188..d9dd5548 100644 --- a/scwx-qt/source/scwx/qt/types/map_types.hpp +++ b/scwx-qt/source/scwx/qt/types/map_types.hpp @@ -9,6 +9,23 @@ namespace qt namespace types { +enum class LayerType +{ + Map, + Radar, + Alert, + Placefile, + Information +}; + +enum class Layer +{ + MapOverlay, + ColorTable, + MapSymbology, + MapUnderlay +}; + enum class AnimationState { Play, @@ -29,6 +46,8 @@ enum class NoUpdateReason InvalidData }; +std::string GetLayerTypeName(LayerType layerType); +std::string GetLayerName(Layer layer); std::string GetMapTimeName(MapTime mapTime); } // namespace types From ec2663aee6d26e347d34b357e7c4b540b512c932 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 15 Oct 2023 09:09:47 -0500 Subject: [PATCH 173/199] Drag and drop layers in the layer manager --- scwx-qt/source/scwx/qt/model/layer_model.cpp | 133 ++++++++++++++++++- scwx-qt/source/scwx/qt/model/layer_model.hpp | 15 ++- scwx-qt/source/scwx/qt/ui/layer_dialog.ui | 9 ++ 3 files changed, 153 insertions(+), 4 deletions(-) diff --git a/scwx-qt/source/scwx/qt/model/layer_model.cpp b/scwx-qt/source/scwx/qt/model/layer_model.cpp index 6c3ac23d..e9bbc0dd 100644 --- a/scwx-qt/source/scwx/qt/model/layer_model.cpp +++ b/scwx-qt/source/scwx/qt/model/layer_model.cpp @@ -4,14 +4,16 @@ #include #include +#include #include #include #include #include +#include +#include #include #include -#include namespace scwx { @@ -30,6 +32,8 @@ static constexpr int kNumColumns = kLastColumn - kFirstColumn + 1; static constexpr std::size_t kMapCount_ = 4u; +static const QString kMimeFormat {"application/x.scwx-layer-model"}; + typedef std:: variant LayerDescription; @@ -45,7 +49,7 @@ public: std::array displayed_ {true, true, true, true}; }; - typedef boost::container::devector LayerVector; + typedef std::vector LayerVector; explicit Impl(LayerModel* self) : self_ {self} { @@ -145,9 +149,21 @@ Qt::ItemFlags LayerModel::flags(const QModelIndex& index) const break; } + if (layer.movable_) + { + flags |= Qt::ItemFlag::ItemIsDragEnabled; + } + + flags |= Qt::ItemFlag::ItemIsDropEnabled; + return flags; } +Qt::DropActions LayerModel::supportedDropActions() const +{ + return Qt::DropAction::MoveAction; +} + QVariant LayerModel::data(const QModelIndex& index, int role) const { static const QString enabledString = QObject::tr("Enabled"); @@ -390,6 +406,117 @@ bool LayerModel::setData(const QModelIndex& index, return result; } +QStringList LayerModel::mimeTypes() const +{ + return {kMimeFormat}; +} + +QMimeData* LayerModel::mimeData(const QModelIndexList& indexes) const +{ + // Get parent QMimeData + QMimeData* mimeData = QAbstractTableModel::mimeData(indexes); + + // Generate LayerModel data + QByteArray data {}; + QDataStream stream(&data, QIODevice::WriteOnly); + std::set rows {}; + + for (auto& index : indexes) + { + if (!rows.contains(index.row())) + { + rows.insert(index.row()); + stream << index.row(); + } + } + + // Set LayerModel data in QMimeData + mimeData->setData(kMimeFormat, data); + + return mimeData; +} + +bool LayerModel::dropMimeData(const QMimeData* data, + Qt::DropAction /* action */, + int /* row */, + int /* column */, + const QModelIndex& parent) +{ + QByteArray mimeData = data->data(kMimeFormat); + QDataStream stream(&mimeData, QIODevice::ReadOnly); + std::vector sourceRows {}; + + // Read source rows from QMimeData + while (!stream.atEnd()) + { + int sourceRow; + stream >> sourceRow; + sourceRows.push_back(sourceRow); + } + + // Ensure rows are in numerical order + std::sort(sourceRows.begin(), sourceRows.end()); + + if (sourceRows.back() >= p->layers_.size()) + { + logger_->error("Cannot perform drop action, invalid source rows"); + return false; + } + + // Nothing to insert + if (sourceRows.empty()) + { + return false; + } + + // Create a copy of the layers to insert (don't insert in-place) + std::vector newLayers {}; + for (auto& sourceRow : sourceRows) + { + newLayers.push_back(p->layers_.at(sourceRow)); + } + + // Insert the copied layers + auto insertPosition = p->layers_.begin() + parent.row(); + beginInsertRows(QModelIndex(), + parent.row(), + parent.row() + static_cast(sourceRows.size()) - 1); + p->layers_.insert(insertPosition, newLayers.begin(), newLayers.end()); + endInsertRows(); + + return true; +} + +bool LayerModel::removeRows(int row, int count, const QModelIndex& parent) +{ + // Validate count + if (count <= 0) + { + return false; + } + + // Remove rows + auto erasePosition = p->layers_.begin() + row; + for (int i = 0; i < count; ++i) + { + if (erasePosition->movable_) + { + // Remove the current row if movable + beginRemoveRows(parent, row, row); + erasePosition = p->layers_.erase(erasePosition); + endRemoveRows(); + } + else + { + // Don't remove immovable rows + ++erasePosition; + ++row; + } + } + + return true; +} + void LayerModel::HandlePlacefileRemoved(const std::string& name) { auto it = @@ -489,7 +616,7 @@ void LayerModel::Impl::AddPlacefile(const std::string& name) // Placefile is new, add row self_->beginInsertRows(QModelIndex(), 0, 0); - layers_.insert(insertPosition, {types::LayerType::Placefile, name}); + layers_.insert(insertPosition, {types::LayerType::Placefile, name, true}); self_->endInsertRows(); } diff --git a/scwx-qt/source/scwx/qt/model/layer_model.hpp b/scwx-qt/source/scwx/qt/model/layer_model.hpp index 91e3a9f0..ef0aebde 100644 --- a/scwx-qt/source/scwx/qt/model/layer_model.hpp +++ b/scwx-qt/source/scwx/qt/model/layer_model.hpp @@ -40,7 +40,8 @@ public: int rowCount(const QModelIndex& parent = QModelIndex()) const override; int columnCount(const QModelIndex& parent = QModelIndex()) const override; - Qt::ItemFlags flags(const QModelIndex& index) const override; + Qt::ItemFlags flags(const QModelIndex& index) const override; + Qt::DropActions supportedDropActions() const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; @@ -52,6 +53,18 @@ public: const QVariant& value, int role = Qt::EditRole) override; + QStringList mimeTypes() const override; + QMimeData* mimeData(const QModelIndexList& indexes) const override; + + bool dropMimeData(const QMimeData* data, + Qt::DropAction action, + int row, + int column, + const QModelIndex& parent) override; + bool removeRows(int row, + int count, + const QModelIndex& parent = QModelIndex()) override; + public slots: void HandlePlacefileRemoved(const std::string& name); void HandlePlacefileRenamed(const std::string& oldName, diff --git a/scwx-qt/source/scwx/qt/ui/layer_dialog.ui b/scwx-qt/source/scwx/qt/ui/layer_dialog.ui index 83c63f9e..a47c1583 100644 --- a/scwx-qt/source/scwx/qt/ui/layer_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/layer_dialog.ui @@ -37,6 +37,15 @@ + + true + + + QAbstractItemView::InternalMove + + + Qt::MoveAction + true From b45ec9dfa571914148474c056d7d8bd99f91baed Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 15 Oct 2023 22:23:39 -0500 Subject: [PATCH 174/199] Enable/disable layer move buttons according to selection --- scwx-qt/source/scwx/qt/model/layer_model.cpp | 12 ++++ scwx-qt/source/scwx/qt/model/layer_model.hpp | 2 + scwx-qt/source/scwx/qt/ui/layer_dialog.cpp | 64 +++++++++++++++++++- 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/scwx-qt/source/scwx/qt/model/layer_model.cpp b/scwx-qt/source/scwx/qt/model/layer_model.cpp index e9bbc0dd..8ced1199 100644 --- a/scwx-qt/source/scwx/qt/model/layer_model.cpp +++ b/scwx-qt/source/scwx/qt/model/layer_model.cpp @@ -164,6 +164,18 @@ Qt::DropActions LayerModel::supportedDropActions() const return Qt::DropAction::MoveAction; } +bool LayerModel::IsMovable(int row) const +{ + bool movable = false; + + if (0 <= row && static_cast(row) < p->layers_.size()) + { + movable = p->layers_.at(row).movable_; + } + + return movable; +} + QVariant LayerModel::data(const QModelIndex& index, int role) const { static const QString enabledString = QObject::tr("Enabled"); diff --git a/scwx-qt/source/scwx/qt/model/layer_model.hpp b/scwx-qt/source/scwx/qt/model/layer_model.hpp index ef0aebde..b63a6f9c 100644 --- a/scwx-qt/source/scwx/qt/model/layer_model.hpp +++ b/scwx-qt/source/scwx/qt/model/layer_model.hpp @@ -43,6 +43,8 @@ public: Qt::ItemFlags flags(const QModelIndex& index) const override; Qt::DropActions supportedDropActions() const override; + bool IsMovable(int row) const; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; QVariant headerData(int section, diff --git a/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp b/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp index 88d3515e..71783e36 100644 --- a/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp @@ -18,11 +18,14 @@ class LayerDialogImpl { public: explicit LayerDialogImpl(LayerDialog* self) : - layerModel_ {new model::LayerModel(self)} + self_ {self}, layerModel_ {new model::LayerModel(self)} { } ~LayerDialogImpl() = default; + void ConnectSignals(); + + LayerDialog* self_; model::LayerModel* layerModel_; }; @@ -49,6 +52,14 @@ LayerDialog::LayerDialog(QWidget* parent) : QHeaderView::ResizeMode::ResizeToContents); } } + + // Disable move buttons + ui->moveTopButton->setEnabled(false); + ui->moveUpButton->setEnabled(false); + ui->moveDownButton->setEnabled(false); + ui->moveBottomButton->setEnabled(false); + + p->ConnectSignals(); } LayerDialog::~LayerDialog() @@ -56,6 +67,57 @@ LayerDialog::~LayerDialog() delete ui; } +void LayerDialogImpl::ConnectSignals() +{ + QObject::connect( + self_->ui->layerTreeView->selectionModel(), + &QItemSelectionModel::selectionChanged, + self_, + [this](const QItemSelection& /* selected */, + const QItemSelection& /* deselected */) + { + QModelIndexList selectedRows = + self_->ui->layerTreeView->selectionModel()->selectedRows(); + + bool itemsSelected = selectedRows.size() > 0; + bool itemsMovableUp = itemsSelected; + bool itemsMovableDown = itemsSelected; + int rowCount = layerModel_->rowCount(); + + for (auto& rowIndex : selectedRows) + { + int row = rowIndex.row(); + if (!layerModel_->IsMovable(row)) + { + // If an item in the selection is not movable, disable all moves + itemsMovableUp = false; + itemsMovableDown = false; + break; + } + else + { + // If the first row is selected, items cannot be moved up + if (row == 0) + { + itemsMovableUp = false; + } + + // If the last row is selected, items cannot be moved down + if (row == rowCount - 1) + { + itemsMovableDown = false; + } + } + } + + // Enable move buttons according to selection + self_->ui->moveTopButton->setEnabled(itemsMovableUp); + self_->ui->moveUpButton->setEnabled(itemsMovableUp); + self_->ui->moveDownButton->setEnabled(itemsMovableDown); + self_->ui->moveBottomButton->setEnabled(itemsMovableDown); + }); +} + } // namespace ui } // namespace qt } // namespace scwx From 5d06f6bc210a4032bc7dcacd6c6dd8ea102c139d Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 16 Oct 2023 22:29:35 -0500 Subject: [PATCH 175/199] Implement layer move functionality using buttons --- scwx-qt/source/scwx/qt/model/layer_model.cpp | 56 +++++++ scwx-qt/source/scwx/qt/model/layer_model.hpp | 5 + scwx-qt/source/scwx/qt/ui/layer_dialog.cpp | 168 ++++++++++++++----- 3 files changed, 190 insertions(+), 39 deletions(-) diff --git a/scwx-qt/source/scwx/qt/model/layer_model.cpp b/scwx-qt/source/scwx/qt/model/layer_model.cpp index 8ced1199..6966baf0 100644 --- a/scwx-qt/source/scwx/qt/model/layer_model.cpp +++ b/scwx-qt/source/scwx/qt/model/layer_model.cpp @@ -529,6 +529,62 @@ bool LayerModel::removeRows(int row, int count, const QModelIndex& parent) return true; } +bool LayerModel::moveRows(const QModelIndex& sourceParent, + int sourceRow, + int count, + const QModelIndex& destinationParent, + int destinationChild) +{ + bool moved = false; + + if (sourceParent != destinationParent || // Only accept internal moves + count < 1 || // Minimum selection size of 1 + sourceRow < 0 || // Valid source row (start) + sourceRow + count > p->layers_.size() || // Valid source row (end) + destinationChild < 0 || // Valid destination row + destinationChild > p->layers_.size()) + { + return false; + } + + if (destinationChild < sourceRow) + { + // Move up + auto first = p->layers_.begin() + destinationChild; + auto middle = p->layers_.begin() + sourceRow; + auto last = middle + count; + + beginMoveRows(sourceParent, + sourceRow, + sourceRow + count - 1, + destinationParent, + destinationChild); + std::rotate(first, middle, last); + endMoveRows(); + + moved = true; + } + else if (sourceRow + count < destinationChild) + { + // Move down + auto first = p->layers_.begin() + sourceRow; + auto middle = first + count; + auto last = p->layers_.begin() + destinationChild; + + beginMoveRows(sourceParent, + sourceRow, + sourceRow + count - 1, + destinationParent, + destinationChild); + std::rotate(first, middle, last); + endMoveRows(); + + moved = true; + } + + return moved; +} + void LayerModel::HandlePlacefileRemoved(const std::string& name) { auto it = diff --git a/scwx-qt/source/scwx/qt/model/layer_model.hpp b/scwx-qt/source/scwx/qt/model/layer_model.hpp index b63a6f9c..55b51b2b 100644 --- a/scwx-qt/source/scwx/qt/model/layer_model.hpp +++ b/scwx-qt/source/scwx/qt/model/layer_model.hpp @@ -66,6 +66,11 @@ public: bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()) override; + bool moveRows(const QModelIndex& sourceParent, + int sourceRow, + int count, + const QModelIndex& destinationParent, + int destinationChild) override; public slots: void HandlePlacefileRemoved(const std::string& name); diff --git a/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp b/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp index 71783e36..00f49a80 100644 --- a/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp @@ -24,6 +24,9 @@ public: ~LayerDialogImpl() = default; void ConnectSignals(); + void UpdateMoveButtonsEnabled(); + + std::vector GetSelectedRows(); LayerDialog* self_; model::LayerModel* layerModel_; @@ -69,53 +72,140 @@ LayerDialog::~LayerDialog() void LayerDialogImpl::ConnectSignals() { + QObject::connect(self_->ui->layerTreeView->selectionModel(), + &QItemSelectionModel::selectionChanged, + self_, + [this](const QItemSelection& /* selected */, + const QItemSelection& /* deselected */) + { UpdateMoveButtonsEnabled(); }); + + QObject::connect(layerModel_, + &QAbstractItemModel::rowsMoved, + self_, + [this]() + { + UpdateMoveButtonsEnabled(); + + auto selectedRows = GetSelectedRows(); + if (!selectedRows.empty()) + { + self_->ui->layerTreeView->scrollTo( + layerModel_->index(selectedRows.front(), 0)); + } + }); + QObject::connect( - self_->ui->layerTreeView->selectionModel(), - &QItemSelectionModel::selectionChanged, + self_->ui->moveTopButton, + &QAbstractButton::clicked, self_, - [this](const QItemSelection& /* selected */, - const QItemSelection& /* deselected */) + [this]() { - QModelIndexList selectedRows = - self_->ui->layerTreeView->selectionModel()->selectedRows(); + auto selectedRows = GetSelectedRows(); + int sourceRow = selectedRows.front(); + int count = static_cast(selectedRows.size()); + int destinationChild = 0; - bool itemsSelected = selectedRows.size() > 0; - bool itemsMovableUp = itemsSelected; - bool itemsMovableDown = itemsSelected; - int rowCount = layerModel_->rowCount(); + layerModel_->moveRows( + QModelIndex(), sourceRow, count, QModelIndex(), destinationChild); + }); + QObject::connect( + self_->ui->moveUpButton, + &QAbstractButton::clicked, + self_, + [this]() + { + auto selectedRows = GetSelectedRows(); + int sourceRow = selectedRows.front(); + int count = static_cast(selectedRows.size()); + int destinationChild = sourceRow - 1; - for (auto& rowIndex : selectedRows) + layerModel_->moveRows( + QModelIndex(), sourceRow, count, QModelIndex(), destinationChild); + }); + QObject::connect( + self_->ui->moveDownButton, + &QAbstractButton::clicked, + self_, + [this]() + { + auto selectedRows = GetSelectedRows(); + int sourceRow = selectedRows.front(); + int count = static_cast(selectedRows.size()); + int destinationChild = selectedRows.back() + 2; + + layerModel_->moveRows( + QModelIndex(), sourceRow, count, QModelIndex(), destinationChild); + }); + QObject::connect( + self_->ui->moveBottomButton, + &QAbstractButton::clicked, + self_, + [this]() + { + auto selectedRows = GetSelectedRows(); + int sourceRow = selectedRows.front(); + int count = static_cast(selectedRows.size()); + int destinationChild = layerModel_->rowCount(); + + layerModel_->moveRows( + QModelIndex(), sourceRow, count, QModelIndex(), destinationChild); + }); +} + +std::vector LayerDialogImpl::GetSelectedRows() +{ + QModelIndexList selectedRows = + self_->ui->layerTreeView->selectionModel()->selectedRows(); + std::vector rows {}; + for (auto& selectedRow : selectedRows) + { + rows.push_back(selectedRow.row()); + } + std::sort(rows.begin(), rows.end()); + return rows; +} + +void LayerDialogImpl::UpdateMoveButtonsEnabled() +{ + QModelIndexList selectedRows = + self_->ui->layerTreeView->selectionModel()->selectedRows(); + + bool itemsSelected = selectedRows.size() > 0; + bool itemsMovableUp = itemsSelected; + bool itemsMovableDown = itemsSelected; + int rowCount = layerModel_->rowCount(); + + for (auto& rowIndex : selectedRows) + { + int row = rowIndex.row(); + if (!layerModel_->IsMovable(row)) + { + // If an item in the selection is not movable, disable all moves + itemsMovableUp = false; + itemsMovableDown = false; + break; + } + else + { + // If the first row is selected, items cannot be moved up + if (row == 0) { - int row = rowIndex.row(); - if (!layerModel_->IsMovable(row)) - { - // If an item in the selection is not movable, disable all moves - itemsMovableUp = false; - itemsMovableDown = false; - break; - } - else - { - // If the first row is selected, items cannot be moved up - if (row == 0) - { - itemsMovableUp = false; - } - - // If the last row is selected, items cannot be moved down - if (row == rowCount - 1) - { - itemsMovableDown = false; - } - } + itemsMovableUp = false; } - // Enable move buttons according to selection - self_->ui->moveTopButton->setEnabled(itemsMovableUp); - self_->ui->moveUpButton->setEnabled(itemsMovableUp); - self_->ui->moveDownButton->setEnabled(itemsMovableDown); - self_->ui->moveBottomButton->setEnabled(itemsMovableDown); - }); + // If the last row is selected, items cannot be moved down + if (row == rowCount - 1) + { + itemsMovableDown = false; + } + } + } + + // Enable move buttons according to selection + self_->ui->moveTopButton->setEnabled(itemsMovableUp); + self_->ui->moveUpButton->setEnabled(itemsMovableUp); + self_->ui->moveDownButton->setEnabled(itemsMovableDown); + self_->ui->moveBottomButton->setEnabled(itemsMovableDown); } } // namespace ui From b3d75b10caa8f6b65aefa2c1e5462928700a6fe8 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 16 Oct 2023 22:30:00 -0500 Subject: [PATCH 176/199] Removing unused delegate from placefile settings widget --- scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp b/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp index 51679e0f..ddc5dda6 100644 --- a/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include @@ -27,8 +26,7 @@ public: self_ {self}, openUrlDialog_ {new OpenUrlDialog(QObject::tr("Add Placefile"), self_)}, placefileModel_ {new model::PlacefileModel(self_)}, - placefileProxyModel_ {new QSortFilterProxyModel(self_)}, - leftElidedItemDelegate_ {new LeftElidedItemDelegate(self_)} + placefileProxyModel_ {new QSortFilterProxyModel(self_)} { placefileProxyModel_->setSourceModel(placefileModel_); placefileProxyModel_->setSortRole(types::ItemDataRole::SortRole); @@ -46,9 +44,8 @@ public: std::shared_ptr placefileManager_ { manager::PlacefileManager::Instance()}; - model::PlacefileModel* placefileModel_; - QSortFilterProxyModel* placefileProxyModel_; - LeftElidedItemDelegate* leftElidedItemDelegate_; + model::PlacefileModel* placefileModel_; + QSortFilterProxyModel* placefileProxyModel_; }; PlacefileSettingsWidget::PlacefileSettingsWidget(QWidget* parent) : From ddad68253a995d2dd1955c3ad94c1c48499bf81c Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 18 Oct 2023 21:22:07 -0500 Subject: [PATCH 177/199] Enable layer filtering, and update moving to handle the proxy model --- scwx-qt/source/scwx/qt/ui/layer_dialog.cpp | 161 ++++++++++++++++----- scwx-qt/source/scwx/qt/ui/layer_dialog.ui | 4 +- 2 files changed, 130 insertions(+), 35 deletions(-) diff --git a/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp b/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp index 00f49a80..18a34655 100644 --- a/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp @@ -4,6 +4,8 @@ #include #include +#include + namespace scwx { namespace qt @@ -18,18 +20,26 @@ class LayerDialogImpl { public: explicit LayerDialogImpl(LayerDialog* self) : - self_ {self}, layerModel_ {new model::LayerModel(self)} + self_ {self}, + layerModel_ {new model::LayerModel(self)}, + layerProxyModel_ {new QSortFilterProxyModel(self_)} { + layerProxyModel_->setSourceModel(layerModel_); + layerProxyModel_->setFilterCaseSensitivity( + Qt::CaseSensitivity::CaseInsensitive); + layerProxyModel_->setFilterKeyColumn(-1); } ~LayerDialogImpl() = default; void ConnectSignals(); void UpdateMoveButtonsEnabled(); - std::vector GetSelectedRows(); + std::vector GetSelectedRows(); + std::vector> GetContiguousRows(); - LayerDialog* self_; - model::LayerModel* layerModel_; + LayerDialog* self_; + model::LayerModel* layerModel_; + QSortFilterProxyModel* layerProxyModel_; }; LayerDialog::LayerDialog(QWidget* parent) : @@ -39,7 +49,7 @@ LayerDialog::LayerDialog(QWidget* parent) : { ui->setupUi(this); - ui->layerTreeView->setModel(p->layerModel_); + ui->layerTreeView->setModel(p->layerProxyModel_); auto layerViewHeader = ui->layerTreeView->header(); @@ -72,6 +82,11 @@ LayerDialog::~LayerDialog() void LayerDialogImpl::ConnectSignals() { + QObject::connect(self_->ui->layerFilter, + &QLineEdit::textChanged, + layerProxyModel_, + &QSortFilterProxyModel::setFilterWildcard); + QObject::connect(self_->ui->layerTreeView->selectionModel(), &QItemSelectionModel::selectionChanged, self_, @@ -94,61 +109,111 @@ void LayerDialogImpl::ConnectSignals() } }); - QObject::connect( + QObject::connect( // self_->ui->moveTopButton, &QAbstractButton::clicked, self_, [this]() { - auto selectedRows = GetSelectedRows(); - int sourceRow = selectedRows.front(); - int count = static_cast(selectedRows.size()); + auto contiguousRows = GetContiguousRows(); int destinationChild = 0; - layerModel_->moveRows( - QModelIndex(), sourceRow, count, QModelIndex(), destinationChild); + for (auto& selectedRows : contiguousRows) + { + int sourceRow = selectedRows.front(); + int count = static_cast(selectedRows.size()); + + layerModel_->moveRows(QModelIndex(), + sourceRow, + count, + QModelIndex(), + destinationChild); + + // Next set of rows should follow rows just added + destinationChild += count; + } }); - QObject::connect( + QObject::connect( // self_->ui->moveUpButton, &QAbstractButton::clicked, self_, [this]() { - auto selectedRows = GetSelectedRows(); - int sourceRow = selectedRows.front(); - int count = static_cast(selectedRows.size()); - int destinationChild = sourceRow - 1; + auto contiguousRows = GetContiguousRows(); + int destinationChild = -1; - layerModel_->moveRows( - QModelIndex(), sourceRow, count, QModelIndex(), destinationChild); + for (auto& selectedRows : contiguousRows) + { + int sourceRow = selectedRows.front(); + int count = static_cast(selectedRows.size()); + if (destinationChild == -1) + { + destinationChild = sourceRow - 1; + } + + layerModel_->moveRows(QModelIndex(), + sourceRow, + count, + QModelIndex(), + destinationChild); + + // Next set of rows should follow rows just added + destinationChild += count; + } }); - QObject::connect( + QObject::connect( // self_->ui->moveDownButton, &QAbstractButton::clicked, self_, [this]() { - auto selectedRows = GetSelectedRows(); - int sourceRow = selectedRows.front(); - int count = static_cast(selectedRows.size()); - int destinationChild = selectedRows.back() + 2; + auto contiguousRows = GetContiguousRows(); + int destinationChild = 0; + int offset = 0; + if (!contiguousRows.empty()) + { + destinationChild = contiguousRows.back().back() + 2; + } - layerModel_->moveRows( - QModelIndex(), sourceRow, count, QModelIndex(), destinationChild); + for (auto& selectedRows : contiguousRows) + { + int sourceRow = selectedRows.front() - offset; + int count = static_cast(selectedRows.size()); + + layerModel_->moveRows(QModelIndex(), + sourceRow, + count, + QModelIndex(), + destinationChild); + + // Next set of rows should be offset + offset += count; + } }); - QObject::connect( + QObject::connect( // self_->ui->moveBottomButton, &QAbstractButton::clicked, self_, [this]() { - auto selectedRows = GetSelectedRows(); - int sourceRow = selectedRows.front(); - int count = static_cast(selectedRows.size()); + auto contiguousRows = GetContiguousRows(); int destinationChild = layerModel_->rowCount(); + int offset = 0; - layerModel_->moveRows( - QModelIndex(), sourceRow, count, QModelIndex(), destinationChild); + for (auto& selectedRows : contiguousRows) + { + int sourceRow = selectedRows.front() - offset; + int count = static_cast(selectedRows.size()); + + layerModel_->moveRows(QModelIndex(), + sourceRow, + count, + QModelIndex(), + destinationChild); + + // Next set of rows should be offset + offset += count; + } }); } @@ -159,12 +224,42 @@ std::vector LayerDialogImpl::GetSelectedRows() std::vector rows {}; for (auto& selectedRow : selectedRows) { - rows.push_back(selectedRow.row()); + rows.push_back(layerProxyModel_->mapToSource(selectedRow).row()); } std::sort(rows.begin(), rows.end()); return rows; } +std::vector> LayerDialogImpl::GetContiguousRows() +{ + std::vector> contiguousRows {}; + std::vector currentContiguousRows {}; + auto rows = GetSelectedRows(); + + for (auto& row : rows) + { + // Next row is not contiguous with current row set + if (!currentContiguousRows.empty() && + currentContiguousRows.back() + 1 < row) + { + // Add current row set to contiguous rows, and reset current set + contiguousRows.emplace_back(std::move(currentContiguousRows)); + currentContiguousRows.clear(); + } + + // Add row to current row set + currentContiguousRows.push_back(row); + } + + if (!currentContiguousRows.empty()) + { + // Add remaining rows to contiguous rows + contiguousRows.emplace_back(currentContiguousRows); + } + + return contiguousRows; +} + void LayerDialogImpl::UpdateMoveButtonsEnabled() { QModelIndexList selectedRows = @@ -177,7 +272,7 @@ void LayerDialogImpl::UpdateMoveButtonsEnabled() for (auto& rowIndex : selectedRows) { - int row = rowIndex.row(); + int row = layerProxyModel_->mapToSource(rowIndex).row(); if (!layerModel_->IsMovable(row)) { // If an item in the selection is not movable, disable all moves diff --git a/scwx-qt/source/scwx/qt/ui/layer_dialog.ui b/scwx-qt/source/scwx/qt/ui/layer_dialog.ui index a47c1583..20979d63 100644 --- a/scwx-qt/source/scwx/qt/ui/layer_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/layer_dialog.ui @@ -50,7 +50,7 @@ true - QAbstractItemView::ContiguousSelection + QAbstractItemView::ExtendedSelection 0 @@ -176,7 +176,7 @@ 0 - + Filter From a20e053d1b53d5a40a4d4c534e827e2517cebdab Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 22 Oct 2023 23:46:32 -0500 Subject: [PATCH 178/199] Fixing placefile manager destructor timing --- scwx-qt/source/scwx/qt/manager/placefile_manager.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index a4ebd0fa..452bba25 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -94,6 +94,10 @@ public: std::unique_lock refreshLock(refreshMutex_); std::unique_lock timerLock(timerMutex_); refreshTimer_.cancel(); + timerLock.unlock(); + refreshLock.unlock(); + + threadPool_.join(); } bool refresh_enabled() const; From 1217a13fb04683044fa87701f03f224bd633bb99 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 22 Oct 2023 23:49:41 -0500 Subject: [PATCH 179/199] Save layer settings to file --- scwx-qt/source/scwx/qt/model/layer_model.cpp | 75 +++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/scwx-qt/source/scwx/qt/model/layer_model.cpp b/scwx-qt/source/scwx/qt/model/layer_model.cpp index 6966baf0..a074dc86 100644 --- a/scwx-qt/source/scwx/qt/model/layer_model.cpp +++ b/scwx-qt/source/scwx/qt/model/layer_model.cpp @@ -2,8 +2,10 @@ #include #include #include +#include #include +#include #include #include @@ -14,6 +16,8 @@ #include #include #include +#include +#include namespace scwx { @@ -34,6 +38,11 @@ static constexpr std::size_t kMapCount_ = 4u; static const QString kMimeFormat {"application/x.scwx-layer-model"}; +static const std::string kTypeName_ {"type"}; +static const std::string kDescriptionName_ {"description"}; +static const std::string kMovableName_ {"movable"}; +static const std::string kDisplayedName_ {"displayed"}; + typedef std:: variant LayerDescription; @@ -76,9 +85,40 @@ public: ~Impl() = default; void AddPlacefile(const std::string& name); + void InitializeLayerSettings(); + void WriteLayerSettings(); + + friend void tag_invoke(boost::json::value_from_tag, + boost::json::value& jv, + const LayerInfo& record) + { + std::string description {}; + + if (std::holds_alternative(record.description_)) + { + description = awips::GetPhenomenonCode( + std::get(record.description_)); + } + else if (std::holds_alternative(record.description_)) + { + description = + types::GetLayerName(std::get(record.description_)); + } + else if (std::holds_alternative(record.description_)) + { + description = std::get(record.description_); + } + + jv = {{kTypeName_, types::GetLayerTypeName(record.type_)}, + {kDescriptionName_, description}, + {kMovableName_, record.movable_}, + {kDisplayedName_, boost::json::value_from(record.displayed_)}}; + } LayerModel* self_; + std::string layerSettingsPath_ {}; + std::shared_ptr placefileManager_ { manager::PlacefileManager::Instance()}; @@ -107,8 +147,41 @@ LayerModel::LayerModel(QObject* parent) : &manager::PlacefileManager::PlacefileUpdated, this, &LayerModel::HandlePlacefileUpdate); + + p->InitializeLayerSettings(); +} + +LayerModel::~LayerModel() +{ + // Write layer settings on shutdown + p->WriteLayerSettings(); +}; + +void LayerModel::Impl::InitializeLayerSettings() +{ + std::string appDataPath { + QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + .toStdString()}; + + if (!std::filesystem::exists(appDataPath)) + { + if (!std::filesystem::create_directories(appDataPath)) + { + logger_->error("Unable to create application data directory: \"{}\"", + appDataPath); + } + } + + layerSettingsPath_ = appDataPath + "/layers.json"; +} + +void LayerModel::Impl::WriteLayerSettings() +{ + logger_->info("Saving layer settings"); + + auto layerJson = boost::json::value_from(layers_); + util::json::WriteJsonFile(layerSettingsPath_, layerJson); } -LayerModel::~LayerModel() = default; int LayerModel::rowCount(const QModelIndex& parent) const { From 10fe904011eef945e14a0b47a8f4739aa578e523 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 25 Oct 2023 22:30:47 -0500 Subject: [PATCH 180/199] Add map types string conversions --- scwx-qt/source/scwx/qt/types/map_types.cpp | 62 ++++++++++++++++++---- scwx-qt/source/scwx/qt/types/map_types.hpp | 10 +++- 2 files changed, 59 insertions(+), 13 deletions(-) diff --git a/scwx-qt/source/scwx/qt/types/map_types.cpp b/scwx-qt/source/scwx/qt/types/map_types.cpp index 57612242..21202914 100644 --- a/scwx-qt/source/scwx/qt/types/map_types.cpp +++ b/scwx-qt/source/scwx/qt/types/map_types.cpp @@ -2,6 +2,8 @@ #include +#include + namespace scwx { namespace qt @@ -9,27 +11,65 @@ namespace qt namespace types { -static const std::unordered_map layerTypeName_ { - {types::LayerType::Map, "Map"}, - {types::LayerType::Radar, "Radar"}, - {types::LayerType::Alert, "Alert"}, - {types::LayerType::Placefile, "Placefile"}, - {types::LayerType::Information, "Information"}}; +static const std::unordered_map layerTypeName_ { + {LayerType::Map, "Map"}, + {LayerType::Radar, "Radar"}, + {LayerType::Alert, "Alert"}, + {LayerType::Placefile, "Placefile"}, + {LayerType::Information, "Information"}, + {LayerType::Unknown, "?"}}; -static const std::unordered_map layerName_ { - {types::Layer::MapOverlay, "Map Overlay"}, - {types::Layer::ColorTable, "Color Table"}, - {types::Layer::MapSymbology, "Map Symbology"}, - {types::Layer::MapUnderlay, "Map Underlay"}}; +static const std::unordered_map layerName_ { + {Layer::MapOverlay, "Map Overlay"}, + {Layer::ColorTable, "Color Table"}, + {Layer::MapSymbology, "Map Symbology"}, + {Layer::MapUnderlay, "Map Underlay"}, + {Layer::Unknown, "?"}}; static const std::unordered_map mapTimeName_ { {MapTime::Live, "Live"}, {MapTime::Archive, "Archive"}}; +LayerType GetLayerType(const std::string& name) +{ + auto result = + std::find_if(layerTypeName_.cbegin(), + layerTypeName_.cend(), + [&](const std::pair& pair) -> bool + { return boost::iequals(pair.second, name); }); + + if (result != layerTypeName_.cend()) + { + return result->first; + } + else + { + return LayerType::Unknown; + } +} + std::string GetLayerTypeName(LayerType layerType) { return layerTypeName_.at(layerType); } +Layer GetLayer(const std::string& name) +{ + auto result = + std::find_if(layerName_.cbegin(), + layerName_.cend(), + [&](const std::pair& pair) -> bool + { return boost::iequals(pair.second, name); }); + + if (result != layerName_.cend()) + { + return result->first; + } + else + { + return Layer::Unknown; + } +} + std::string GetLayerName(Layer layer) { return layerName_.at(layer); diff --git a/scwx-qt/source/scwx/qt/types/map_types.hpp b/scwx-qt/source/scwx/qt/types/map_types.hpp index d9dd5548..ca2ecf04 100644 --- a/scwx-qt/source/scwx/qt/types/map_types.hpp +++ b/scwx-qt/source/scwx/qt/types/map_types.hpp @@ -15,7 +15,8 @@ enum class LayerType Radar, Alert, Placefile, - Information + Information, + Unknown }; enum class Layer @@ -23,7 +24,8 @@ enum class Layer MapOverlay, ColorTable, MapSymbology, - MapUnderlay + MapUnderlay, + Unknown }; enum class AnimationState @@ -46,8 +48,12 @@ enum class NoUpdateReason InvalidData }; +LayerType GetLayerType(const std::string& name); std::string GetLayerTypeName(LayerType layerType); + +Layer GetLayer(const std::string& name); std::string GetLayerName(Layer layer); + std::string GetMapTimeName(MapTime mapTime); } // namespace types From 2fe3facbd265542ab7322db0d320c71529b8853a Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 27 Oct 2023 06:01:44 -0500 Subject: [PATCH 181/199] Read layer settings from JSON --- scwx-qt/source/scwx/qt/model/layer_model.cpp | 218 ++++++++++++++----- 1 file changed, 158 insertions(+), 60 deletions(-) diff --git a/scwx-qt/source/scwx/qt/model/layer_model.cpp b/scwx-qt/source/scwx/qt/model/layer_model.cpp index a074dc86..5514c655 100644 --- a/scwx-qt/source/scwx/qt/model/layer_model.cpp +++ b/scwx-qt/source/scwx/qt/model/layer_model.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include namespace scwx @@ -47,73 +48,41 @@ typedef std:: variant LayerDescription; +struct LayerInfo +{ + types::LayerType type_; + LayerDescription description_; + bool movable_; + std::array displayed_ {true, true, true, true}; +}; + +typedef boost::container::stable_vector LayerVector; + +static const std::vector kDefaultLayers_ { + {types::LayerType::Information, types::Layer::MapOverlay, false}, + {types::LayerType::Information, types::Layer::ColorTable, false}, + {types::LayerType::Alert, awips::Phenomenon::Tornado, true}, + {types::LayerType::Alert, awips::Phenomenon::SnowSquall, true}, + {types::LayerType::Alert, awips::Phenomenon::SevereThunderstorm, true}, + {types::LayerType::Alert, awips::Phenomenon::FlashFlood, true}, + {types::LayerType::Alert, awips::Phenomenon::Marine, true}, + {types::LayerType::Map, types::Layer::MapSymbology, false}, + {types::LayerType::Radar, std::monostate {}, true}, + {types::LayerType::Map, types::Layer::MapUnderlay, false}, +}; + class LayerModel::Impl { public: - struct LayerInfo - { - types::LayerType type_; - LayerDescription description_; - bool movable_; - std::array displayed_ {true, true, true, true}; - }; - - typedef std::vector LayerVector; - - explicit Impl(LayerModel* self) : self_ {self} - { - layers_.emplace_back( - types::LayerType::Information, types::Layer::MapOverlay, false); - layers_.emplace_back( - types::LayerType::Information, types::Layer::ColorTable, false); - layers_.emplace_back( - types::LayerType::Alert, awips::Phenomenon::Tornado, true); - layers_.emplace_back( - types::LayerType::Alert, awips::Phenomenon::SnowSquall, true); - layers_.emplace_back( - types::LayerType::Alert, awips::Phenomenon::SevereThunderstorm, true); - layers_.emplace_back( - types::LayerType::Alert, awips::Phenomenon::FlashFlood, true); - layers_.emplace_back( - types::LayerType::Alert, awips::Phenomenon::Marine, true); - layers_.emplace_back( - types::LayerType::Map, types::Layer::MapSymbology, false); - layers_.emplace_back(types::LayerType::Radar, std::monostate {}, true); - layers_.emplace_back( - types::LayerType::Map, types::Layer::MapUnderlay, false); - } + explicit Impl(LayerModel* self) : self_ {self} {} ~Impl() = default; void AddPlacefile(const std::string& name); void InitializeLayerSettings(); + void ReadLayerSettings(); void WriteLayerSettings(); - friend void tag_invoke(boost::json::value_from_tag, - boost::json::value& jv, - const LayerInfo& record) - { - std::string description {}; - - if (std::holds_alternative(record.description_)) - { - description = awips::GetPhenomenonCode( - std::get(record.description_)); - } - else if (std::holds_alternative(record.description_)) - { - description = - types::GetLayerName(std::get(record.description_)); - } - else if (std::holds_alternative(record.description_)) - { - description = std::get(record.description_); - } - - jv = {{kTypeName_, types::GetLayerTypeName(record.type_)}, - {kDescriptionName_, description}, - {kMovableName_, record.movable_}, - {kDisplayedName_, boost::json::value_from(record.displayed_)}}; - } + static void ValidateLayerSettings(LayerVector& layers); LayerModel* self_; @@ -149,6 +118,12 @@ LayerModel::LayerModel(QObject* parent) : &LayerModel::HandlePlacefileUpdate); p->InitializeLayerSettings(); + p->ReadLayerSettings(); + + if (p->layers_.empty()) + { + p->layers_.assign(kDefaultLayers_.cbegin(), kDefaultLayers_.cend()); + } } LayerModel::~LayerModel() @@ -175,6 +150,52 @@ void LayerModel::Impl::InitializeLayerSettings() layerSettingsPath_ = appDataPath + "/layers.json"; } +void LayerModel::Impl::ReadLayerSettings() +{ + logger_->info("Reading layer settings"); + + boost::json::value layerJson = nullptr; + LayerVector newLayers {}; + + // Determine if layer settings exists + if (std::filesystem::exists(layerSettingsPath_)) + { + layerJson = util::json::ReadJsonFile(layerSettingsPath_); + } + + // If layer settings was successfully read + if (layerJson != nullptr && layerJson.is_array()) + { + // For each layer entry + auto& layerArray = layerJson.as_array(); + for (auto& layerEntry : layerArray) + { + try + { + // Convert layer entry to a LayerInfo record, and add to new layers + newLayers.emplace_back( + boost::json::value_to(layerEntry)); + } + catch (const std::exception& ex) + { + logger_->warn("Invalid layer entry: {}", ex.what()); + } + } + + // Validate and correct read layers + ValidateLayerSettings(newLayers); + + // Assign read layers + layers_.swap(newLayers); + } +} + +void LayerModel::Impl::ValidateLayerSettings(LayerVector& layers) +{ + // TODO + Q_UNUSED(layers); +} + void LayerModel::Impl::WriteLayerSettings() { logger_->info("Saving layer settings"); @@ -555,7 +576,7 @@ bool LayerModel::dropMimeData(const QMimeData* data, } // Create a copy of the layers to insert (don't insert in-place) - std::vector newLayers {}; + std::vector newLayers {}; for (auto& sourceRow : sourceRows) { newLayers.push_back(p->layers_.at(sourceRow)); @@ -744,7 +765,7 @@ void LayerModel::Impl::AddPlacefile(const std::string& name) auto insertPosition = std::find_if( layers_.begin(), layers_.end(), - [](const Impl::LayerInfo& layerInfo) + [](const LayerInfo& layerInfo) { return std::holds_alternative(layerInfo.description_) && std::get(layerInfo.description_) == @@ -761,6 +782,83 @@ void LayerModel::Impl::AddPlacefile(const std::string& name) self_->endInsertRows(); } +void tag_invoke(boost::json::value_from_tag, + boost::json::value& jv, + const LayerInfo& record) +{ + std::string description {}; + + if (std::holds_alternative(record.description_)) + { + description = awips::GetPhenomenonCode( + std::get(record.description_)); + } + else if (std::holds_alternative(record.description_)) + { + description = + types::GetLayerName(std::get(record.description_)); + } + else if (std::holds_alternative(record.description_)) + { + description = std::get(record.description_); + } + + jv = {{kTypeName_, types::GetLayerTypeName(record.type_)}, + {kDescriptionName_, description}, + {kMovableName_, record.movable_}, + {kDisplayedName_, boost::json::value_from(record.displayed_)}}; +} + +template +std::array tag_invoke(boost::json::value_to_tag>, + const boost::json::value& jv) +{ + std::array array {}; + boost::json::array jsonArray = jv.as_array(); + + for (std::size_t i = 0; i < n && i < jsonArray.size(); ++i) + { + array[i] = jsonArray[i]; + } + + return array; +} + +LayerInfo tag_invoke(boost::json::value_to_tag, + const boost::json::value& jv) +{ + const types::LayerType layerType = types::GetLayerType( + boost::json::value_to(jv.at(kTypeName_))); + const std::string descriptionName = + boost::json::value_to(jv.at(kDescriptionName_)); + + LayerDescription description {}; + + if (layerType == types::LayerType::Map || + layerType == types::LayerType::Information) + { + description = types::GetLayer(descriptionName); + } + else if (layerType == types::LayerType::Radar) + { + description = std::monostate {}; + } + else if (layerType == types::LayerType::Alert) + { + description = awips::GetPhenomenon(descriptionName); + } + else + { + description = descriptionName; + } + + return LayerInfo { + layerType, + description, + jv.at(kMovableName_).as_bool(), + boost::json::value_to>(jv.at(kDisplayedName_))}; +} + } // namespace model } // namespace qt } // namespace scwx From 7cd1cd0681356190334268b22b9be0752e3984d0 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 27 Oct 2023 06:07:46 -0500 Subject: [PATCH 182/199] Split layer types from map types --- scwx-qt/scwx-qt.cmake | 2 + scwx-qt/source/scwx/qt/model/layer_model.cpp | 2 +- scwx-qt/source/scwx/qt/types/layer_types.cpp | 77 ++++++++++++++++++++ scwx-qt/source/scwx/qt/types/layer_types.hpp | 39 ++++++++++ scwx-qt/source/scwx/qt/types/map_types.cpp | 63 ---------------- scwx-qt/source/scwx/qt/types/map_types.hpp | 25 ------- 6 files changed, 119 insertions(+), 89 deletions(-) create mode 100644 scwx-qt/source/scwx/qt/types/layer_types.cpp create mode 100644 scwx-qt/source/scwx/qt/types/layer_types.hpp diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index bd03dde8..50767c34 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -163,6 +163,7 @@ set(HDR_TYPES source/scwx/qt/types/alert_types.hpp source/scwx/qt/types/font_types.hpp source/scwx/qt/types/github_types.hpp source/scwx/qt/types/imgui_font.hpp + source/scwx/qt/types/layer_types.hpp source/scwx/qt/types/map_types.hpp source/scwx/qt/types/qt_types.hpp source/scwx/qt/types/radar_product_record.hpp @@ -171,6 +172,7 @@ set(HDR_TYPES source/scwx/qt/types/alert_types.hpp set(SRC_TYPES source/scwx/qt/types/alert_types.cpp source/scwx/qt/types/github_types.cpp source/scwx/qt/types/imgui_font.cpp + source/scwx/qt/types/layer_types.cpp source/scwx/qt/types/map_types.cpp source/scwx/qt/types/radar_product_record.cpp source/scwx/qt/types/text_event_key.cpp diff --git a/scwx-qt/source/scwx/qt/model/layer_model.cpp b/scwx-qt/source/scwx/qt/model/layer_model.cpp index 5514c655..9de658a6 100644 --- a/scwx-qt/source/scwx/qt/model/layer_model.cpp +++ b/scwx-qt/source/scwx/qt/model/layer_model.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include #include diff --git a/scwx-qt/source/scwx/qt/types/layer_types.cpp b/scwx-qt/source/scwx/qt/types/layer_types.cpp new file mode 100644 index 00000000..24cbd989 --- /dev/null +++ b/scwx-qt/source/scwx/qt/types/layer_types.cpp @@ -0,0 +1,77 @@ +#include + +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace types +{ + +static const std::unordered_map layerTypeName_ { + {LayerType::Map, "Map"}, + {LayerType::Radar, "Radar"}, + {LayerType::Alert, "Alert"}, + {LayerType::Placefile, "Placefile"}, + {LayerType::Information, "Information"}, + {LayerType::Unknown, "?"}}; + +static const std::unordered_map layerName_ { + {Layer::MapOverlay, "Map Overlay"}, + {Layer::ColorTable, "Color Table"}, + {Layer::MapSymbology, "Map Symbology"}, + {Layer::MapUnderlay, "Map Underlay"}, + {Layer::Unknown, "?"}}; + +LayerType GetLayerType(const std::string& name) +{ + auto result = + std::find_if(layerTypeName_.cbegin(), + layerTypeName_.cend(), + [&](const std::pair& pair) -> bool + { return boost::iequals(pair.second, name); }); + + if (result != layerTypeName_.cend()) + { + return result->first; + } + else + { + return LayerType::Unknown; + } +} + +std::string GetLayerTypeName(LayerType layerType) +{ + return layerTypeName_.at(layerType); +} + +Layer GetLayer(const std::string& name) +{ + auto result = + std::find_if(layerName_.cbegin(), + layerName_.cend(), + [&](const std::pair& pair) -> bool + { return boost::iequals(pair.second, name); }); + + if (result != layerName_.cend()) + { + return result->first; + } + else + { + return Layer::Unknown; + } +} + +std::string GetLayerName(Layer layer) +{ + return layerName_.at(layer); +} + +} // namespace types +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/types/layer_types.hpp b/scwx-qt/source/scwx/qt/types/layer_types.hpp new file mode 100644 index 00000000..1f771879 --- /dev/null +++ b/scwx-qt/source/scwx/qt/types/layer_types.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include + +namespace scwx +{ +namespace qt +{ +namespace types +{ + +enum class LayerType +{ + Map, + Radar, + Alert, + Placefile, + Information, + Unknown +}; + +enum class Layer +{ + MapOverlay, + ColorTable, + MapSymbology, + MapUnderlay, + Unknown +}; + +LayerType GetLayerType(const std::string& name); +std::string GetLayerTypeName(LayerType layerType); + +Layer GetLayer(const std::string& name); +std::string GetLayerName(Layer layer); + +} // namespace types +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/types/map_types.cpp b/scwx-qt/source/scwx/qt/types/map_types.cpp index 21202914..d3ea6bc0 100644 --- a/scwx-qt/source/scwx/qt/types/map_types.cpp +++ b/scwx-qt/source/scwx/qt/types/map_types.cpp @@ -2,8 +2,6 @@ #include -#include - namespace scwx { namespace qt @@ -11,70 +9,9 @@ namespace qt namespace types { -static const std::unordered_map layerTypeName_ { - {LayerType::Map, "Map"}, - {LayerType::Radar, "Radar"}, - {LayerType::Alert, "Alert"}, - {LayerType::Placefile, "Placefile"}, - {LayerType::Information, "Information"}, - {LayerType::Unknown, "?"}}; - -static const std::unordered_map layerName_ { - {Layer::MapOverlay, "Map Overlay"}, - {Layer::ColorTable, "Color Table"}, - {Layer::MapSymbology, "Map Symbology"}, - {Layer::MapUnderlay, "Map Underlay"}, - {Layer::Unknown, "?"}}; - static const std::unordered_map mapTimeName_ { {MapTime::Live, "Live"}, {MapTime::Archive, "Archive"}}; -LayerType GetLayerType(const std::string& name) -{ - auto result = - std::find_if(layerTypeName_.cbegin(), - layerTypeName_.cend(), - [&](const std::pair& pair) -> bool - { return boost::iequals(pair.second, name); }); - - if (result != layerTypeName_.cend()) - { - return result->first; - } - else - { - return LayerType::Unknown; - } -} - -std::string GetLayerTypeName(LayerType layerType) -{ - return layerTypeName_.at(layerType); -} - -Layer GetLayer(const std::string& name) -{ - auto result = - std::find_if(layerName_.cbegin(), - layerName_.cend(), - [&](const std::pair& pair) -> bool - { return boost::iequals(pair.second, name); }); - - if (result != layerName_.cend()) - { - return result->first; - } - else - { - return Layer::Unknown; - } -} - -std::string GetLayerName(Layer layer) -{ - return layerName_.at(layer); -} - std::string GetMapTimeName(MapTime mapTime) { return mapTimeName_.at(mapTime); diff --git a/scwx-qt/source/scwx/qt/types/map_types.hpp b/scwx-qt/source/scwx/qt/types/map_types.hpp index ca2ecf04..5853a188 100644 --- a/scwx-qt/source/scwx/qt/types/map_types.hpp +++ b/scwx-qt/source/scwx/qt/types/map_types.hpp @@ -9,25 +9,6 @@ namespace qt namespace types { -enum class LayerType -{ - Map, - Radar, - Alert, - Placefile, - Information, - Unknown -}; - -enum class Layer -{ - MapOverlay, - ColorTable, - MapSymbology, - MapUnderlay, - Unknown -}; - enum class AnimationState { Play, @@ -48,12 +29,6 @@ enum class NoUpdateReason InvalidData }; -LayerType GetLayerType(const std::string& name); -std::string GetLayerTypeName(LayerType layerType); - -Layer GetLayer(const std::string& name); -std::string GetLayerName(Layer layer); - std::string GetMapTimeName(MapTime mapTime); } // namespace types From 3c79f484938bf8bacf2f7fae05dc7a27943ae86b Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 27 Oct 2023 07:11:46 -0500 Subject: [PATCH 183/199] Validate immovable layers --- scwx-qt/source/scwx/qt/model/layer_model.cpp | 59 +++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/scwx-qt/source/scwx/qt/model/layer_model.cpp b/scwx-qt/source/scwx/qt/model/layer_model.cpp index 9de658a6..c6ce636f 100644 --- a/scwx-qt/source/scwx/qt/model/layer_model.cpp +++ b/scwx-qt/source/scwx/qt/model/layer_model.cpp @@ -71,6 +71,13 @@ static const std::vector kDefaultLayers_ { {types::LayerType::Map, types::Layer::MapUnderlay, false}, }; +static const std::vector kImmovableLayers_ { + {types::LayerType::Information, types::Layer::MapOverlay, false}, + {types::LayerType::Information, types::Layer::ColorTable, false}, + {types::LayerType::Map, types::Layer::MapSymbology, false}, + {types::LayerType::Map, types::Layer::MapUnderlay, false}, +}; + class LayerModel::Impl { public: @@ -192,8 +199,56 @@ void LayerModel::Impl::ReadLayerSettings() void LayerModel::Impl::ValidateLayerSettings(LayerVector& layers) { - // TODO - Q_UNUSED(layers); + // Validate immovable layers + std::vector immovableIterators {}; + for (auto& immovableLayer : kImmovableLayers_) + { + // Set the default displayed state for a layer that is not found + std::array displayed {true, true, true, true}; + + // Find the immovable layer + auto it = std::find_if(layers.begin(), + layers.end(), + [&immovableLayer](const LayerInfo& layer) + { + return layer.type_ == immovableLayer.type_ && + layer.description_ == + immovableLayer.description_; + }); + + // If the immovable layer is out of order + if (!immovableIterators.empty() && immovableIterators.back() > it) + { + // Save the displayed state of the immovable layer + displayed = it->displayed_; + + // Remove the layer from the list, to re-add it later + layers.erase(it); + + // Treat the layer as not found + it = layers.end(); + } + + // If the immovable layer is not found + if (it == layers.end()) + { + // If this is the first immovable layer, insert at the beginning, + // otherwise, insert after the previous immovable layer + LayerVector::iterator insertPosition = + immovableIterators.empty() ? layers.begin() : + immovableIterators.back() + 1; + it = layers.insert(insertPosition, immovableLayer); + + // Restore the displayed state of the immovable layer + it->displayed_ = displayed; + } + + // Add the immovable iterator to the list + immovableIterators.push_back(it); + + // Ensure the layer is marked immovable + it->movable_ = false; + } } void LayerModel::Impl::WriteLayerSettings() From 8719d2d02aa66901ff1aa9980141d939feb9df66 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 27 Oct 2023 07:35:18 -0500 Subject: [PATCH 184/199] Validate alert layers and movable status --- scwx-qt/source/scwx/qt/model/layer_model.cpp | 57 ++++++++++++++++++-- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/scwx-qt/source/scwx/qt/model/layer_model.cpp b/scwx-qt/source/scwx/qt/model/layer_model.cpp index c6ce636f..da57d462 100644 --- a/scwx-qt/source/scwx/qt/model/layer_model.cpp +++ b/scwx-qt/source/scwx/qt/model/layer_model.cpp @@ -52,7 +52,7 @@ struct LayerInfo { types::LayerType type_; LayerDescription description_; - bool movable_; + bool movable_ {true}; std::array displayed_ {true, true, true, true}; }; @@ -78,6 +78,13 @@ static const std::vector kImmovableLayers_ { {types::LayerType::Map, types::Layer::MapUnderlay, false}, }; +static const std::array kAlertPhenomena_ { + awips::Phenomenon::Tornado, + awips::Phenomenon::SnowSquall, + awips::Phenomenon::SevereThunderstorm, + awips::Phenomenon::FlashFlood, + awips::Phenomenon::Marine}; + class LayerModel::Impl { public: @@ -201,6 +208,8 @@ void LayerModel::Impl::ValidateLayerSettings(LayerVector& layers) { // Validate immovable layers std::vector immovableIterators {}; + LayerVector::iterator mapSymbologyIterator {}; + LayerVector::iterator mapUnderlayIterator {}; for (auto& immovableLayer : kImmovableLayers_) { // Set the default displayed state for a layer that is not found @@ -243,11 +252,53 @@ void LayerModel::Impl::ValidateLayerSettings(LayerVector& layers) it->displayed_ = displayed; } + // Store positional iterators + if (it->type_ == types::LayerType::Map) + { + switch (std::get(it->description_)) + { + case types::Layer::MapSymbology: + mapSymbologyIterator = it; + break; + + case types::Layer::MapUnderlay: + mapUnderlayIterator = it; + break; + + default: + break; + } + } + // Add the immovable iterator to the list immovableIterators.push_back(it); + } - // Ensure the layer is marked immovable - it->movable_ = false; + // Validate alert layers + for (auto& phenomenon : kAlertPhenomena_) + { + // Find the alert layer + auto it = std::find_if(layers.begin(), + layers.end(), + [&phenomenon](const LayerInfo& layer) + { + return layer.type_ == types::LayerType::Alert && + std::get( + layer.description_) == phenomenon; + }); + + if (it == layers.end()) + { + layers.insert(mapSymbologyIterator, + {types::LayerType::Alert, phenomenon}); + } + } + + // Ensure layers are appropriately marked movable + for (auto& layer : layers) + { + layer.movable_ = (layer.type_ != types::LayerType::Information && + layer.type_ != types::LayerType::Map); } } From f222b4f6665aace43915ef2718dc2b2f664f2c5f Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 27 Oct 2023 08:40:24 -0500 Subject: [PATCH 185/199] Finish layer validation, including strong typing for different layer categories --- scwx-qt/source/scwx/qt/model/layer_model.cpp | 182 +++++++++++++++---- scwx-qt/source/scwx/qt/types/layer_types.cpp | 81 +++++++-- scwx-qt/source/scwx/qt/types/layer_types.hpp | 29 ++- 3 files changed, 243 insertions(+), 49 deletions(-) diff --git a/scwx-qt/source/scwx/qt/model/layer_model.cpp b/scwx-qt/source/scwx/qt/model/layer_model.cpp index da57d462..5645d330 100644 --- a/scwx-qt/source/scwx/qt/model/layer_model.cpp +++ b/scwx-qt/source/scwx/qt/model/layer_model.cpp @@ -44,9 +44,13 @@ static const std::string kDescriptionName_ {"description"}; static const std::string kMovableName_ {"movable"}; static const std::string kDisplayedName_ {"displayed"}; -typedef std:: - variant - LayerDescription; +typedef std::variant + LayerDescription; struct LayerInfo { @@ -59,23 +63,24 @@ struct LayerInfo typedef boost::container::stable_vector LayerVector; static const std::vector kDefaultLayers_ { - {types::LayerType::Information, types::Layer::MapOverlay, false}, - {types::LayerType::Information, types::Layer::ColorTable, false}, + {types::LayerType::Information, types::InformationLayer::MapOverlay, false}, + {types::LayerType::Information, types::InformationLayer::ColorTable, false}, + {types::LayerType::Data, types::DataLayer::RadarRange, true}, {types::LayerType::Alert, awips::Phenomenon::Tornado, true}, {types::LayerType::Alert, awips::Phenomenon::SnowSquall, true}, {types::LayerType::Alert, awips::Phenomenon::SevereThunderstorm, true}, {types::LayerType::Alert, awips::Phenomenon::FlashFlood, true}, {types::LayerType::Alert, awips::Phenomenon::Marine, true}, - {types::LayerType::Map, types::Layer::MapSymbology, false}, + {types::LayerType::Map, types::MapLayer::MapSymbology, false}, {types::LayerType::Radar, std::monostate {}, true}, - {types::LayerType::Map, types::Layer::MapUnderlay, false}, + {types::LayerType::Map, types::MapLayer::MapUnderlay, false}, }; static const std::vector kImmovableLayers_ { - {types::LayerType::Information, types::Layer::MapOverlay, false}, - {types::LayerType::Information, types::Layer::ColorTable, false}, - {types::LayerType::Map, types::Layer::MapSymbology, false}, - {types::LayerType::Map, types::Layer::MapUnderlay, false}, + {types::LayerType::Information, types::InformationLayer::MapOverlay, false}, + {types::LayerType::Information, types::InformationLayer::ColorTable, false}, + {types::LayerType::Map, types::MapLayer::MapSymbology, false}, + {types::LayerType::Map, types::MapLayer::MapUnderlay, false}, }; static const std::array kAlertPhenomena_ { @@ -206,8 +211,40 @@ void LayerModel::Impl::ReadLayerSettings() void LayerModel::Impl::ValidateLayerSettings(LayerVector& layers) { + // Validate layer properties + for (auto it = layers.begin(); it != layers.end();) + { + // If the layer is invalid, remove it + if (it->type_ == types::LayerType::Unknown || + (std::holds_alternative(it->description_) && + std::get(it->description_) == + types::DataLayer::Unknown) || + (std::holds_alternative(it->description_) && + std::get(it->description_) == + types::InformationLayer::Unknown) || + (std::holds_alternative(it->description_) && + std::get(it->description_) == + types::MapLayer::Unknown) || + (std::holds_alternative(it->description_) && + std::get(it->description_) == + awips::Phenomenon::Unknown)) + { + // Erase the current layer and continue + it = layers.erase(it); + continue; + } + + // Ensure layers are appropriately marked movable + it->movable_ = (it->type_ != types::LayerType::Information && + it->type_ != types::LayerType::Map); + + // Continue to the next layer + ++it; + } + // Validate immovable layers std::vector immovableIterators {}; + LayerVector::iterator colorTableIterator {}; LayerVector::iterator mapSymbologyIterator {}; LayerVector::iterator mapUnderlayIterator {}; for (auto& immovableLayer : kImmovableLayers_) @@ -253,15 +290,27 @@ void LayerModel::Impl::ValidateLayerSettings(LayerVector& layers) } // Store positional iterators - if (it->type_ == types::LayerType::Map) + if (it->type_ == types::LayerType::Information) { - switch (std::get(it->description_)) + switch (std::get(it->description_)) { - case types::Layer::MapSymbology: + case types::InformationLayer::ColorTable: + colorTableIterator = it; + break; + + default: + break; + } + } + else if (it->type_ == types::LayerType::Map) + { + switch (std::get(it->description_)) + { + case types::MapLayer::MapSymbology: mapSymbologyIterator = it; break; - case types::Layer::MapUnderlay: + case types::MapLayer::MapUnderlay: mapUnderlayIterator = it; break; @@ -274,7 +323,36 @@ void LayerModel::Impl::ValidateLayerSettings(LayerVector& layers) immovableIterators.push_back(it); } + // Validate data layers + std::vector dataIterators {}; + for (const auto& dataLayer : types::DataLayerIterator()) + { + // Find the data layer + auto it = std::find_if(layers.begin(), + layers.end(), + [&dataLayer](const LayerInfo& layer) + { + return layer.type_ == types::LayerType::Data && + std::get( + layer.description_) == dataLayer; + }); + + if (it == layers.end()) + { + // If this is the first data layer, insert after the color table layer, + // otherwise, insert after the previous data layer + LayerVector::iterator insertPosition = dataIterators.empty() ? + colorTableIterator + 1 : + dataIterators.back() + 1; + it = + layers.insert(insertPosition, {types::LayerType::Data, dataLayer}); + } + + dataIterators.push_back(it); + } + // Validate alert layers + std::vector alertIterators {}; for (auto& phenomenon : kAlertPhenomena_) { // Find the alert layer @@ -289,16 +367,24 @@ void LayerModel::Impl::ValidateLayerSettings(LayerVector& layers) if (it == layers.end()) { - layers.insert(mapSymbologyIterator, - {types::LayerType::Alert, phenomenon}); + // Insert before the map symbology layer + it = layers.insert(mapSymbologyIterator, + {types::LayerType::Alert, phenomenon}); } + + alertIterators.push_back(it); } - // Ensure layers are appropriately marked movable - for (auto& layer : layers) + // Validate the radar layer + auto it = std::find_if(layers.begin(), + layers.end(), + [](const LayerInfo& layer) + { return layer.type_ == types::LayerType::Radar; }); + if (it == layers.end()) { - layer.movable_ = (layer.type_ != types::LayerType::Information && - layer.type_ != types::LayerType::Map); + // Insert before the map underlay layer + it = layers.insert(mapUnderlayIterator, + {types::LayerType::Radar, std::monostate {}}); } } @@ -470,10 +556,23 @@ QVariant LayerModel::data(const QModelIndex& index, int role) const return QString::fromStdString( std::get(layer.description_)); } - else if (std::holds_alternative(layer.description_)) + else if (std::holds_alternative( + layer.description_)) { - return QString::fromStdString(types::GetLayerName( - std::get(layer.description_))); + return QString::fromStdString(types::GetDataLayerName( + std::get(layer.description_))); + } + else if (std::holds_alternative( + layer.description_)) + { + return QString::fromStdString(types::GetInformationLayerName( + std::get(layer.description_))); + } + else if (std::holds_alternative( + layer.description_)) + { + return QString::fromStdString(types::GetMapLayerName( + std::get(layer.description_))); } else if (std::holds_alternative( layer.description_)) @@ -873,9 +972,10 @@ void LayerModel::Impl::AddPlacefile(const std::string& name) layers_.end(), [](const LayerInfo& layerInfo) { - return std::holds_alternative(layerInfo.description_) && - std::get(layerInfo.description_) == - types::Layer::ColorTable; + return std::holds_alternative( + layerInfo.description_) && + std::get(layerInfo.description_) == + types::InformationLayer::ColorTable; }); if (insertPosition != layers_.end()) { @@ -899,10 +999,21 @@ void tag_invoke(boost::json::value_from_tag, description = awips::GetPhenomenonCode( std::get(record.description_)); } - else if (std::holds_alternative(record.description_)) + else if (std::holds_alternative(record.description_)) + { + description = types::GetDataLayerName( + std::get(record.description_)); + } + else if (std::holds_alternative( + record.description_)) + { + description = types::GetInformationLayerName( + std::get(record.description_)); + } + else if (std::holds_alternative(record.description_)) { description = - types::GetLayerName(std::get(record.description_)); + types::GetMapLayerName(std::get(record.description_)); } else if (std::holds_alternative(record.description_)) { @@ -940,10 +1051,17 @@ LayerInfo tag_invoke(boost::json::value_to_tag, LayerDescription description {}; - if (layerType == types::LayerType::Map || - layerType == types::LayerType::Information) + if (layerType == types::LayerType::Map) { - description = types::GetLayer(descriptionName); + description = types::GetMapLayer(descriptionName); + } + else if (layerType == types::LayerType::Information) + { + description = types::GetInformationLayer(descriptionName); + } + else if (layerType == types::LayerType::Data) + { + description = types::GetDataLayer(descriptionName); } else if (layerType == types::LayerType::Radar) { diff --git a/scwx-qt/source/scwx/qt/types/layer_types.cpp b/scwx-qt/source/scwx/qt/types/layer_types.cpp index 24cbd989..df9e6386 100644 --- a/scwx-qt/source/scwx/qt/types/layer_types.cpp +++ b/scwx-qt/source/scwx/qt/types/layer_types.cpp @@ -17,14 +17,21 @@ static const std::unordered_map layerTypeName_ { {LayerType::Alert, "Alert"}, {LayerType::Placefile, "Placefile"}, {LayerType::Information, "Information"}, + {LayerType::Data, "Data"}, {LayerType::Unknown, "?"}}; -static const std::unordered_map layerName_ { - {Layer::MapOverlay, "Map Overlay"}, - {Layer::ColorTable, "Color Table"}, - {Layer::MapSymbology, "Map Symbology"}, - {Layer::MapUnderlay, "Map Underlay"}, - {Layer::Unknown, "?"}}; +static const std::unordered_map dataLayerName_ { + {DataLayer::RadarRange, "Radar Range"}, {DataLayer::Unknown, "?"}}; + +static const std::unordered_map + informationLayerName_ {{InformationLayer::MapOverlay, "Map Overlay"}, + {InformationLayer::ColorTable, "Color Table"}, + {InformationLayer::Unknown, "?"}}; + +static const std::unordered_map mapLayerName_ { + {MapLayer::MapSymbology, "Map Symbology"}, + {MapLayer::MapUnderlay, "Map Underlay"}, + {MapLayer::Unknown, "?"}}; LayerType GetLayerType(const std::string& name) { @@ -49,27 +56,73 @@ std::string GetLayerTypeName(LayerType layerType) return layerTypeName_.at(layerType); } -Layer GetLayer(const std::string& name) +DataLayer GetDataLayer(const std::string& name) { auto result = - std::find_if(layerName_.cbegin(), - layerName_.cend(), - [&](const std::pair& pair) -> bool + std::find_if(dataLayerName_.cbegin(), + dataLayerName_.cend(), + [&](const std::pair& pair) -> bool { return boost::iequals(pair.second, name); }); - if (result != layerName_.cend()) + if (result != dataLayerName_.cend()) { return result->first; } else { - return Layer::Unknown; + return DataLayer::Unknown; } } -std::string GetLayerName(Layer layer) +std::string GetDataLayerName(DataLayer layer) { - return layerName_.at(layer); + return dataLayerName_.at(layer); +} + +InformationLayer GetInformationLayer(const std::string& name) +{ + auto result = std::find_if( + informationLayerName_.cbegin(), + informationLayerName_.cend(), + [&](const std::pair& pair) -> bool + { return boost::iequals(pair.second, name); }); + + if (result != informationLayerName_.cend()) + { + return result->first; + } + else + { + return InformationLayer::Unknown; + } +} + +std::string GetInformationLayerName(InformationLayer layer) +{ + return informationLayerName_.at(layer); +} + +MapLayer GetMapLayer(const std::string& name) +{ + auto result = + std::find_if(mapLayerName_.cbegin(), + mapLayerName_.cend(), + [&](const std::pair& pair) -> bool + { return boost::iequals(pair.second, name); }); + + if (result != mapLayerName_.cend()) + { + return result->first; + } + else + { + return MapLayer::Unknown; + } +} + +std::string GetMapLayerName(MapLayer layer) +{ + return mapLayerName_.at(layer); } } // namespace types diff --git a/scwx-qt/source/scwx/qt/types/layer_types.hpp b/scwx-qt/source/scwx/qt/types/layer_types.hpp index 1f771879..f418986d 100644 --- a/scwx-qt/source/scwx/qt/types/layer_types.hpp +++ b/scwx-qt/source/scwx/qt/types/layer_types.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include namespace scwx @@ -16,13 +18,28 @@ enum class LayerType Alert, Placefile, Information, + Data, Unknown }; -enum class Layer +enum class DataLayer +{ + RadarRange, + Unknown +}; +typedef scwx::util:: + Iterator + DataLayerIterator; + +enum class InformationLayer { MapOverlay, ColorTable, + Unknown +}; + +enum class MapLayer +{ MapSymbology, MapUnderlay, Unknown @@ -31,8 +48,14 @@ enum class Layer LayerType GetLayerType(const std::string& name); std::string GetLayerTypeName(LayerType layerType); -Layer GetLayer(const std::string& name); -std::string GetLayerName(Layer layer); +DataLayer GetDataLayer(const std::string& name); +std::string GetDataLayerName(DataLayer layer); + +InformationLayer GetInformationLayer(const std::string& name); +std::string GetInformationLayerName(InformationLayer layer); + +MapLayer GetMapLayer(const std::string& name); +std::string GetMapLayerName(MapLayer layer); } // namespace types } // namespace qt From dd7bfc7a6fce3e576e118941c797f4bf00f6cd85 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 28 Oct 2023 05:39:54 -0500 Subject: [PATCH 186/199] Add reset button to layer manager --- scwx-qt/source/scwx/qt/model/layer_model.cpp | 34 ++++++++++++++++++++ scwx-qt/source/scwx/qt/model/layer_model.hpp | 2 ++ scwx-qt/source/scwx/qt/ui/layer_dialog.cpp | 7 ++++ scwx-qt/source/scwx/qt/ui/layer_dialog.ui | 8 ++++- 4 files changed, 50 insertions(+), 1 deletion(-) diff --git a/scwx-qt/source/scwx/qt/model/layer_model.cpp b/scwx-qt/source/scwx/qt/model/layer_model.cpp index 5645d330..c2d1fcce 100644 --- a/scwx-qt/source/scwx/qt/model/layer_model.cpp +++ b/scwx-qt/source/scwx/qt/model/layer_model.cpp @@ -396,6 +396,40 @@ void LayerModel::Impl::WriteLayerSettings() util::json::WriteJsonFile(layerSettingsPath_, layerJson); } +void LayerModel::ResetLayers() +{ + // Initialize a new layer vector from the default + LayerVector newLayers {}; + newLayers.assign(kDefaultLayers_.cbegin(), kDefaultLayers_.cend()); + + auto colorTableIterator = std::find_if( + newLayers.begin(), + newLayers.end(), + [](const LayerInfo& layerInfo) + { + return std::holds_alternative( + layerInfo.description_) && + std::get(layerInfo.description_) == + types::InformationLayer::ColorTable; + }); + + // Add all existing placefile layers + for (auto it = p->layers_.rbegin(); it != p->layers_.rend(); ++it) + { + if (it->type_ == types::LayerType::Placefile) + { + newLayers.insert( + colorTableIterator + 1, + {it->type_, it->description_, it->movable_, it->displayed_}); + } + } + + // Swap the model + beginResetModel(); + p->layers_.swap(newLayers); + endResetModel(); +} + int LayerModel::rowCount(const QModelIndex& parent) const { return parent.isValid() ? 0 : static_cast(p->layers_.size()); diff --git a/scwx-qt/source/scwx/qt/model/layer_model.hpp b/scwx-qt/source/scwx/qt/model/layer_model.hpp index 55b51b2b..0058aa43 100644 --- a/scwx-qt/source/scwx/qt/model/layer_model.hpp +++ b/scwx-qt/source/scwx/qt/model/layer_model.hpp @@ -37,6 +37,8 @@ public: explicit LayerModel(QObject* parent = nullptr); ~LayerModel(); + void ResetLayers(); + int rowCount(const QModelIndex& parent = QModelIndex()) const override; int columnCount(const QModelIndex& parent = QModelIndex()) const override; diff --git a/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp b/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp index 18a34655..1f754ae9 100644 --- a/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp @@ -4,6 +4,7 @@ #include #include +#include #include namespace scwx @@ -82,6 +83,12 @@ LayerDialog::~LayerDialog() void LayerDialogImpl::ConnectSignals() { + QObject::connect( + self_->ui->buttonBox->button(QDialogButtonBox::StandardButton::Reset), + &QAbstractButton::clicked, + self_, + [this]() { layerModel_->ResetLayers(); }); + QObject::connect(self_->ui->layerFilter, &QLineEdit::textChanged, layerProxyModel_, diff --git a/scwx-qt/source/scwx/qt/ui/layer_dialog.ui b/scwx-qt/source/scwx/qt/ui/layer_dialog.ui index 20979d63..f9b2a076 100644 --- a/scwx-qt/source/scwx/qt/ui/layer_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/layer_dialog.ui @@ -200,11 +200,17 @@ + + + 0 + 0 + + Qt::Horizontal - QDialogButtonBox::Close + QDialogButtonBox::Close|QDialogButtonBox::Reset From b6aef07af45d80eb71f4958d4df1b8b8e2d8ae28 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 28 Oct 2023 05:54:38 -0500 Subject: [PATCH 187/199] Create LayerModel singleton --- scwx-qt/source/scwx/qt/model/layer_model.cpp | 18 ++++++++++++++++++ scwx-qt/source/scwx/qt/model/layer_model.hpp | 2 ++ scwx-qt/source/scwx/qt/ui/layer_dialog.cpp | 12 ++++++------ 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/scwx-qt/source/scwx/qt/model/layer_model.cpp b/scwx-qt/source/scwx/qt/model/layer_model.cpp index c2d1fcce..32234753 100644 --- a/scwx-qt/source/scwx/qt/model/layer_model.cpp +++ b/scwx-qt/source/scwx/qt/model/layer_model.cpp @@ -1117,6 +1117,24 @@ LayerInfo tag_invoke(boost::json::value_to_tag, boost::json::value_to>(jv.at(kDisplayedName_))}; } +std::shared_ptr LayerModel::Instance() +{ + static std::weak_ptr layerModelReference_ {}; + static std::mutex instanceMutex_ {}; + + std::unique_lock lock(instanceMutex_); + + std::shared_ptr layerModel = layerModelReference_.lock(); + + if (layerModel == nullptr) + { + layerModel = std::make_shared(); + layerModelReference_ = layerModel; + } + + return layerModel; +} + } // namespace model } // namespace qt } // namespace scwx diff --git a/scwx-qt/source/scwx/qt/model/layer_model.hpp b/scwx-qt/source/scwx/qt/model/layer_model.hpp index 0058aa43..6b1fe43d 100644 --- a/scwx-qt/source/scwx/qt/model/layer_model.hpp +++ b/scwx-qt/source/scwx/qt/model/layer_model.hpp @@ -74,6 +74,8 @@ public: const QModelIndex& destinationParent, int destinationChild) override; + static std::shared_ptr Instance(); + public slots: void HandlePlacefileRemoved(const std::string& name); void HandlePlacefileRenamed(const std::string& oldName, diff --git a/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp b/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp index 1f754ae9..828a6b48 100644 --- a/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp @@ -22,10 +22,10 @@ class LayerDialogImpl public: explicit LayerDialogImpl(LayerDialog* self) : self_ {self}, - layerModel_ {new model::LayerModel(self)}, + layerModel_ {model::LayerModel::Instance()}, layerProxyModel_ {new QSortFilterProxyModel(self_)} { - layerProxyModel_->setSourceModel(layerModel_); + layerProxyModel_->setSourceModel(layerModel_.get()); layerProxyModel_->setFilterCaseSensitivity( Qt::CaseSensitivity::CaseInsensitive); layerProxyModel_->setFilterKeyColumn(-1); @@ -38,9 +38,9 @@ public: std::vector GetSelectedRows(); std::vector> GetContiguousRows(); - LayerDialog* self_; - model::LayerModel* layerModel_; - QSortFilterProxyModel* layerProxyModel_; + LayerDialog* self_; + std::shared_ptr layerModel_; + QSortFilterProxyModel* layerProxyModel_; }; LayerDialog::LayerDialog(QWidget* parent) : @@ -101,7 +101,7 @@ void LayerDialogImpl::ConnectSignals() const QItemSelection& /* deselected */) { UpdateMoveButtonsEnabled(); }); - QObject::connect(layerModel_, + QObject::connect(layerModel_.get(), &QAbstractItemModel::rowsMoved, self_, [this]() From ba75326c0cbe431a673b9914678bcef7b20deb9e Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 28 Oct 2023 06:21:01 -0500 Subject: [PATCH 188/199] Synchronize placefile layers with placefile manager after initialization --- .../scwx/qt/manager/placefile_manager.cpp | 1 + .../scwx/qt/manager/placefile_manager.hpp | 1 + scwx-qt/source/scwx/qt/model/layer_model.cpp | 42 +++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index 452bba25..c069d16c 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -162,6 +162,7 @@ PlacefileManager::PlacefileManager() : p(std::make_unique(this)) // Read placefile settings on startup main::Application::WaitForInitialization(); p->ReadPlacefileSettings(); + Q_EMIT PlacefilesInitialized(); }); } diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp index ee9bf3a4..7010ca94 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp @@ -51,6 +51,7 @@ public: static std::shared_ptr Instance(); signals: + void PlacefilesInitialized(); void PlacefileEnabled(const std::string& name, bool enabled); void PlacefileRemoved(const std::string& name); void PlacefileRenamed(const std::string& oldName, diff --git a/scwx-qt/source/scwx/qt/model/layer_model.cpp b/scwx-qt/source/scwx/qt/model/layer_model.cpp index 32234753..7bf835e0 100644 --- a/scwx-qt/source/scwx/qt/model/layer_model.cpp +++ b/scwx-qt/source/scwx/qt/model/layer_model.cpp @@ -99,6 +99,7 @@ public: void AddPlacefile(const std::string& name); void InitializeLayerSettings(); void ReadLayerSettings(); + void SynchronizePlacefileLayers(); void WriteLayerSettings(); static void ValidateLayerSettings(LayerVector& layers); @@ -107,6 +108,9 @@ public: std::string layerSettingsPath_ {}; + bool placefilesInitialized_ {false}; + std::vector initialPlacefiles_ {}; + std::shared_ptr placefileManager_ { manager::PlacefileManager::Instance()}; @@ -116,6 +120,11 @@ public: LayerModel::LayerModel(QObject* parent) : QAbstractTableModel(parent), p(std::make_unique(this)) { + connect(p->placefileManager_.get(), + &manager::PlacefileManager::PlacefilesInitialized, + this, + [this]() { p->SynchronizePlacefileLayers(); }); + connect(p->placefileManager_.get(), &manager::PlacefileManager::PlacefileEnabled, this, @@ -430,6 +439,34 @@ void LayerModel::ResetLayers() endResetModel(); } +void LayerModel::Impl::SynchronizePlacefileLayers() +{ + placefilesInitialized_ = true; + + int row = 0; + for (auto it = layers_.begin(); it != layers_.end();) + { + if (it->type_ == types::LayerType::Placefile && + std::find(initialPlacefiles_.begin(), + initialPlacefiles_.end(), + std::get(it->description_)) == + initialPlacefiles_.end()) + { + // If the placefile layer was not loaded by the placefile manager, + // erase it + self_->beginRemoveRows(QModelIndex(), row, row); + it = layers_.erase(it); + self_->endRemoveRows(); + continue; + } + + ++it; + ++row; + } + + initialPlacefiles_.clear(); +} + int LayerModel::rowCount(const QModelIndex& parent) const { return parent.isValid() ? 0 : static_cast(p->layers_.size()); @@ -973,6 +1010,11 @@ void LayerModel::HandlePlacefileRenamed(const std::string& oldName, void LayerModel::HandlePlacefileUpdate(const std::string& name) { + if (!p->placefilesInitialized_) + { + p->initialPlacefiles_.push_back(name); + } + auto it = std::find_if(p->layers_.begin(), p->layers_.end(), From f7851488d6cb01cd6f73832c38b231ffca130c20 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 28 Oct 2023 07:08:59 -0500 Subject: [PATCH 189/199] Fix layer model sign comparisons --- scwx-qt/source/scwx/qt/model/layer_model.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/scwx-qt/source/scwx/qt/model/layer_model.cpp b/scwx-qt/source/scwx/qt/model/layer_model.cpp index 7bf835e0..6582ed6c 100644 --- a/scwx-qt/source/scwx/qt/model/layer_model.cpp +++ b/scwx-qt/source/scwx/qt/model/layer_model.cpp @@ -839,7 +839,7 @@ bool LayerModel::dropMimeData(const QMimeData* data, // Ensure rows are in numerical order std::sort(sourceRows.begin(), sourceRows.end()); - if (sourceRows.back() >= p->layers_.size()) + if (sourceRows.back() >= static_cast(p->layers_.size())) { logger_->error("Cannot perform drop action, invalid source rows"); return false; @@ -907,12 +907,13 @@ bool LayerModel::moveRows(const QModelIndex& sourceParent, { bool moved = false; - if (sourceParent != destinationParent || // Only accept internal moves - count < 1 || // Minimum selection size of 1 - sourceRow < 0 || // Valid source row (start) - sourceRow + count > p->layers_.size() || // Valid source row (end) - destinationChild < 0 || // Valid destination row - destinationChild > p->layers_.size()) + if (sourceParent != destinationParent || // Only accept internal moves + count < 1 || // Minimum selection size of 1 + sourceRow < 0 || // Valid source row (start) + sourceRow + count > + static_cast(p->layers_.size()) || // Valid source row (end) + destinationChild < 0 || // Valid destination row + destinationChild > static_cast(p->layers_.size())) { return false; } From a5cee797d99545f47b79356f42cb8d05eb305c5e Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 1 Nov 2023 00:05:32 -0500 Subject: [PATCH 190/199] Refactor layer types from layer model --- scwx-qt/source/scwx/qt/model/layer_model.cpp | 161 ++++--------------- scwx-qt/source/scwx/qt/model/layer_model.hpp | 5 +- scwx-qt/source/scwx/qt/types/layer_types.cpp | 83 ++++++++++ scwx-qt/source/scwx/qt/types/layer_types.hpp | 31 ++++ 4 files changed, 147 insertions(+), 133 deletions(-) diff --git a/scwx-qt/source/scwx/qt/model/layer_model.cpp b/scwx-qt/source/scwx/qt/model/layer_model.cpp index 6582ed6c..6cace5a9 100644 --- a/scwx-qt/source/scwx/qt/model/layer_model.cpp +++ b/scwx-qt/source/scwx/qt/model/layer_model.cpp @@ -1,13 +1,11 @@ #include #include -#include #include #include #include #include #include -#include #include #include @@ -17,7 +15,6 @@ #include #include #include -#include #include namespace scwx @@ -39,30 +36,7 @@ static constexpr std::size_t kMapCount_ = 4u; static const QString kMimeFormat {"application/x.scwx-layer-model"}; -static const std::string kTypeName_ {"type"}; -static const std::string kDescriptionName_ {"description"}; -static const std::string kMovableName_ {"movable"}; -static const std::string kDisplayedName_ {"displayed"}; - -typedef std::variant - LayerDescription; - -struct LayerInfo -{ - types::LayerType type_; - LayerDescription description_; - bool movable_ {true}; - std::array displayed_ {true, true, true, true}; -}; - -typedef boost::container::stable_vector LayerVector; - -static const std::vector kDefaultLayers_ { +static const std::vector kDefaultLayers_ { {types::LayerType::Information, types::InformationLayer::MapOverlay, false}, {types::LayerType::Information, types::InformationLayer::ColorTable, false}, {types::LayerType::Data, types::DataLayer::RadarRange, true}, @@ -76,7 +50,7 @@ static const std::vector kDefaultLayers_ { {types::LayerType::Map, types::MapLayer::MapUnderlay, false}, }; -static const std::vector kImmovableLayers_ { +static const std::vector kImmovableLayers_ { {types::LayerType::Information, types::InformationLayer::MapOverlay, false}, {types::LayerType::Information, types::InformationLayer::ColorTable, false}, {types::LayerType::Map, types::MapLayer::MapSymbology, false}, @@ -102,7 +76,7 @@ public: void SynchronizePlacefileLayers(); void WriteLayerSettings(); - static void ValidateLayerSettings(LayerVector& layers); + static void ValidateLayerSettings(types::LayerVector& layers); LayerModel* self_; @@ -114,7 +88,7 @@ public: std::shared_ptr placefileManager_ { manager::PlacefileManager::Instance()}; - LayerVector layers_ {}; + types::LayerVector layers_ {}; }; LayerModel::LayerModel(QObject* parent) : @@ -183,7 +157,7 @@ void LayerModel::Impl::ReadLayerSettings() logger_->info("Reading layer settings"); boost::json::value layerJson = nullptr; - LayerVector newLayers {}; + types::LayerVector newLayers {}; // Determine if layer settings exists if (std::filesystem::exists(layerSettingsPath_)) @@ -202,7 +176,7 @@ void LayerModel::Impl::ReadLayerSettings() { // Convert layer entry to a LayerInfo record, and add to new layers newLayers.emplace_back( - boost::json::value_to(layerEntry)); + boost::json::value_to(layerEntry)); } catch (const std::exception& ex) { @@ -218,7 +192,7 @@ void LayerModel::Impl::ReadLayerSettings() } } -void LayerModel::Impl::ValidateLayerSettings(LayerVector& layers) +void LayerModel::Impl::ValidateLayerSettings(types::LayerVector& layers) { // Validate layer properties for (auto it = layers.begin(); it != layers.end();) @@ -252,10 +226,10 @@ void LayerModel::Impl::ValidateLayerSettings(LayerVector& layers) } // Validate immovable layers - std::vector immovableIterators {}; - LayerVector::iterator colorTableIterator {}; - LayerVector::iterator mapSymbologyIterator {}; - LayerVector::iterator mapUnderlayIterator {}; + std::vector immovableIterators {}; + types::LayerVector::iterator colorTableIterator {}; + types::LayerVector::iterator mapSymbologyIterator {}; + types::LayerVector::iterator mapUnderlayIterator {}; for (auto& immovableLayer : kImmovableLayers_) { // Set the default displayed state for a layer that is not found @@ -264,7 +238,7 @@ void LayerModel::Impl::ValidateLayerSettings(LayerVector& layers) // Find the immovable layer auto it = std::find_if(layers.begin(), layers.end(), - [&immovableLayer](const LayerInfo& layer) + [&immovableLayer](const types::LayerInfo& layer) { return layer.type_ == immovableLayer.type_ && layer.description_ == @@ -289,7 +263,7 @@ void LayerModel::Impl::ValidateLayerSettings(LayerVector& layers) { // If this is the first immovable layer, insert at the beginning, // otherwise, insert after the previous immovable layer - LayerVector::iterator insertPosition = + types::LayerVector::iterator insertPosition = immovableIterators.empty() ? layers.begin() : immovableIterators.back() + 1; it = layers.insert(insertPosition, immovableLayer); @@ -333,13 +307,13 @@ void LayerModel::Impl::ValidateLayerSettings(LayerVector& layers) } // Validate data layers - std::vector dataIterators {}; + std::vector dataIterators {}; for (const auto& dataLayer : types::DataLayerIterator()) { // Find the data layer auto it = std::find_if(layers.begin(), layers.end(), - [&dataLayer](const LayerInfo& layer) + [&dataLayer](const types::LayerInfo& layer) { return layer.type_ == types::LayerType::Data && std::get( @@ -350,9 +324,9 @@ void LayerModel::Impl::ValidateLayerSettings(LayerVector& layers) { // If this is the first data layer, insert after the color table layer, // otherwise, insert after the previous data layer - LayerVector::iterator insertPosition = dataIterators.empty() ? - colorTableIterator + 1 : - dataIterators.back() + 1; + types::LayerVector::iterator insertPosition = + dataIterators.empty() ? colorTableIterator + 1 : + dataIterators.back() + 1; it = layers.insert(insertPosition, {types::LayerType::Data, dataLayer}); } @@ -361,13 +335,13 @@ void LayerModel::Impl::ValidateLayerSettings(LayerVector& layers) } // Validate alert layers - std::vector alertIterators {}; + std::vector alertIterators {}; for (auto& phenomenon : kAlertPhenomena_) { // Find the alert layer auto it = std::find_if(layers.begin(), layers.end(), - [&phenomenon](const LayerInfo& layer) + [&phenomenon](const types::LayerInfo& layer) { return layer.type_ == types::LayerType::Alert && std::get( @@ -387,7 +361,7 @@ void LayerModel::Impl::ValidateLayerSettings(LayerVector& layers) // Validate the radar layer auto it = std::find_if(layers.begin(), layers.end(), - [](const LayerInfo& layer) + [](const types::LayerInfo& layer) { return layer.type_ == types::LayerType::Radar; }); if (it == layers.end()) { @@ -405,16 +379,21 @@ void LayerModel::Impl::WriteLayerSettings() util::json::WriteJsonFile(layerSettingsPath_, layerJson); } +types::LayerVector LayerModel::GetLayers() const +{ + return p->layers_; +} + void LayerModel::ResetLayers() { // Initialize a new layer vector from the default - LayerVector newLayers {}; + types::LayerVector newLayers {}; newLayers.assign(kDefaultLayers_.cbegin(), kDefaultLayers_.cend()); auto colorTableIterator = std::find_if( newLayers.begin(), newLayers.end(), - [](const LayerInfo& layerInfo) + [](const types::LayerInfo& layerInfo) { return std::holds_alternative( layerInfo.description_) && @@ -852,7 +831,7 @@ bool LayerModel::dropMimeData(const QMimeData* data, } // Create a copy of the layers to insert (don't insert in-place) - std::vector newLayers {}; + std::vector newLayers {}; for (auto& sourceRow : sourceRows) { newLayers.push_back(p->layers_.at(sourceRow)); @@ -1047,7 +1026,7 @@ void LayerModel::Impl::AddPlacefile(const std::string& name) auto insertPosition = std::find_if( layers_.begin(), layers_.end(), - [](const LayerInfo& layerInfo) + [](const types::LayerInfo& layerInfo) { return std::holds_alternative( layerInfo.description_) && @@ -1065,44 +1044,6 @@ void LayerModel::Impl::AddPlacefile(const std::string& name) self_->endInsertRows(); } -void tag_invoke(boost::json::value_from_tag, - boost::json::value& jv, - const LayerInfo& record) -{ - std::string description {}; - - if (std::holds_alternative(record.description_)) - { - description = awips::GetPhenomenonCode( - std::get(record.description_)); - } - else if (std::holds_alternative(record.description_)) - { - description = types::GetDataLayerName( - std::get(record.description_)); - } - else if (std::holds_alternative( - record.description_)) - { - description = types::GetInformationLayerName( - std::get(record.description_)); - } - else if (std::holds_alternative(record.description_)) - { - description = - types::GetMapLayerName(std::get(record.description_)); - } - else if (std::holds_alternative(record.description_)) - { - description = std::get(record.description_); - } - - jv = {{kTypeName_, types::GetLayerTypeName(record.type_)}, - {kDescriptionName_, description}, - {kMovableName_, record.movable_}, - {kDisplayedName_, boost::json::value_from(record.displayed_)}}; -} - template std::array tag_invoke(boost::json::value_to_tag>, const boost::json::value& jv) @@ -1118,48 +1059,6 @@ std::array tag_invoke(boost::json::value_to_tag>, return array; } -LayerInfo tag_invoke(boost::json::value_to_tag, - const boost::json::value& jv) -{ - const types::LayerType layerType = types::GetLayerType( - boost::json::value_to(jv.at(kTypeName_))); - const std::string descriptionName = - boost::json::value_to(jv.at(kDescriptionName_)); - - LayerDescription description {}; - - if (layerType == types::LayerType::Map) - { - description = types::GetMapLayer(descriptionName); - } - else if (layerType == types::LayerType::Information) - { - description = types::GetInformationLayer(descriptionName); - } - else if (layerType == types::LayerType::Data) - { - description = types::GetDataLayer(descriptionName); - } - else if (layerType == types::LayerType::Radar) - { - description = std::monostate {}; - } - else if (layerType == types::LayerType::Alert) - { - description = awips::GetPhenomenon(descriptionName); - } - else - { - description = descriptionName; - } - - return LayerInfo { - layerType, - description, - jv.at(kMovableName_).as_bool(), - boost::json::value_to>(jv.at(kDisplayedName_))}; -} - std::shared_ptr LayerModel::Instance() { static std::weak_ptr layerModelReference_ {}; diff --git a/scwx-qt/source/scwx/qt/model/layer_model.hpp b/scwx-qt/source/scwx/qt/model/layer_model.hpp index 6b1fe43d..893264bc 100644 --- a/scwx-qt/source/scwx/qt/model/layer_model.hpp +++ b/scwx-qt/source/scwx/qt/model/layer_model.hpp @@ -1,7 +1,6 @@ #pragma once -#include -#include +#include #include #include @@ -37,6 +36,8 @@ public: explicit LayerModel(QObject* parent = nullptr); ~LayerModel(); + types::LayerVector GetLayers() const; + void ResetLayers(); int rowCount(const QModelIndex& parent = QModelIndex()) const override; diff --git a/scwx-qt/source/scwx/qt/types/layer_types.cpp b/scwx-qt/source/scwx/qt/types/layer_types.cpp index df9e6386..c6d042d8 100644 --- a/scwx-qt/source/scwx/qt/types/layer_types.cpp +++ b/scwx-qt/source/scwx/qt/types/layer_types.cpp @@ -3,6 +3,7 @@ #include #include +#include namespace scwx { @@ -33,6 +34,11 @@ static const std::unordered_map mapLayerName_ { {MapLayer::MapUnderlay, "Map Underlay"}, {MapLayer::Unknown, "?"}}; +static const std::string kTypeName_ {"type"}; +static const std::string kDescriptionName_ {"description"}; +static const std::string kMovableName_ {"movable"}; +static const std::string kDisplayedName_ {"displayed"}; + LayerType GetLayerType(const std::string& name) { auto result = @@ -125,6 +131,83 @@ std::string GetMapLayerName(MapLayer layer) return mapLayerName_.at(layer); } +void tag_invoke(boost::json::value_from_tag, + boost::json::value& jv, + const LayerInfo& record) +{ + std::string description {}; + + if (std::holds_alternative(record.description_)) + { + description = awips::GetPhenomenonCode( + std::get(record.description_)); + } + else if (std::holds_alternative(record.description_)) + { + description = GetDataLayerName(std::get(record.description_)); + } + else if (std::holds_alternative(record.description_)) + { + description = GetInformationLayerName( + std::get(record.description_)); + } + else if (std::holds_alternative(record.description_)) + { + description = GetMapLayerName(std::get(record.description_)); + } + else if (std::holds_alternative(record.description_)) + { + description = std::get(record.description_); + } + + jv = {{kTypeName_, GetLayerTypeName(record.type_)}, + {kDescriptionName_, description}, + {kMovableName_, record.movable_}, + {kDisplayedName_, boost::json::value_from(record.displayed_)}}; +} + +LayerInfo tag_invoke(boost::json::value_to_tag, + const boost::json::value& jv) +{ + const LayerType layerType = + GetLayerType(boost::json::value_to(jv.at(kTypeName_))); + const std::string descriptionName = + boost::json::value_to(jv.at(kDescriptionName_)); + + LayerDescription description {}; + + if (layerType == LayerType::Map) + { + description = GetMapLayer(descriptionName); + } + else if (layerType == LayerType::Information) + { + description = GetInformationLayer(descriptionName); + } + else if (layerType == LayerType::Data) + { + description = GetDataLayer(descriptionName); + } + else if (layerType == LayerType::Radar) + { + description = std::monostate {}; + } + else if (layerType == LayerType::Alert) + { + description = awips::GetPhenomenon(descriptionName); + } + else + { + description = descriptionName; + } + + return LayerInfo { + layerType, + description, + jv.at(kMovableName_).as_bool(), + boost::json::value_to>(jv.at(kDisplayedName_))}; +} + } // namespace types } // namespace qt } // namespace scwx diff --git a/scwx-qt/source/scwx/qt/types/layer_types.hpp b/scwx-qt/source/scwx/qt/types/layer_types.hpp index f418986d..d7c79a0a 100644 --- a/scwx-qt/source/scwx/qt/types/layer_types.hpp +++ b/scwx-qt/source/scwx/qt/types/layer_types.hpp @@ -1,8 +1,15 @@ #pragma once +#include #include +#include #include +#include + +#include +#include +#include namespace scwx { @@ -45,6 +52,24 @@ enum class MapLayer Unknown }; +typedef std::variant + LayerDescription; + +struct LayerInfo +{ + LayerType type_; + LayerDescription description_; + bool movable_ {true}; + std::array displayed_ {true, true, true, true}; +}; + +typedef boost::container::stable_vector LayerVector; + LayerType GetLayerType(const std::string& name); std::string GetLayerTypeName(LayerType layerType); @@ -57,6 +82,12 @@ std::string GetInformationLayerName(InformationLayer layer); MapLayer GetMapLayer(const std::string& name); std::string GetMapLayerName(MapLayer layer); +void tag_invoke(boost::json::value_from_tag, + boost::json::value& jv, + const LayerInfo& record); +LayerInfo tag_invoke(boost::json::value_to_tag, + const boost::json::value& jv); + } // namespace types } // namespace qt } // namespace scwx From 3392a9a4027ac55c0879a3726ae3e8eec85dec96 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 1 Nov 2023 23:17:23 -0500 Subject: [PATCH 191/199] Common layer naming --- scwx-qt/source/scwx/qt/map/alert_layer.cpp | 42 ++++++++++--------- scwx-qt/source/scwx/qt/map/alert_layer.hpp | 12 +++--- .../source/scwx/qt/map/radar_range_layer.cpp | 18 ++++---- scwx-qt/source/scwx/qt/model/layer_model.cpp | 31 +------------- scwx-qt/source/scwx/qt/types/layer_types.cpp | 41 ++++++++++++++++++ scwx-qt/source/scwx/qt/types/layer_types.hpp | 4 ++ 6 files changed, 85 insertions(+), 63 deletions(-) diff --git a/scwx-qt/source/scwx/qt/map/alert_layer.cpp b/scwx-qt/source/scwx/qt/map/alert_layer.cpp index 044cfb00..82a7bb3c 100644 --- a/scwx-qt/source/scwx/qt/map/alert_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/alert_layer.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -132,33 +133,31 @@ public: }; AlertLayer::AlertLayer(std::shared_ptr context) : - DrawLayer(context), p(std::make_unique(context)) + p(std::make_unique(context)) { } AlertLayer::~AlertLayer() = default; -void AlertLayer::Initialize() +void AlertLayer::AddLayers(awips::Phenomenon phenomenon, + const std::string& before) { - logger_->debug("Initialize()"); + logger_->debug("AddLayers(): {}", awips::GetPhenomenonCode(phenomenon)); - DrawLayer::Initialize(); -} + auto map = p->context_->map().lock(); + if (map == nullptr) + { + return; + } -void AlertLayer::Render(const QMapLibreGL::CustomLayerRenderParameters& params) -{ - gl::OpenGLFunctions& gl = context()->gl(); + const QString beforeLayer {QString::fromStdString(before)}; - DrawLayer::Render(params); - - SCWX_GL_CHECK_ERROR(); -} - -void AlertLayer::Deinitialize() -{ - logger_->debug("Deinitialize()"); - - DrawLayer::Deinitialize(); + // Add/update GeoJSON sources and create layers + for (bool alertActive : {false, true}) + { + p->UpdateSource(phenomenon, alertActive); + AddAlertLayer(map, phenomenon, alertActive, beforeLayer); + } } void AlertLayer::AddLayers(const std::string& before) @@ -396,13 +395,16 @@ static void AddAlertLayer(std::shared_ptr map, settings::PaletteSettings& paletteSettings = settings::PaletteSettings::Instance(); + QString layerPrefix = QString::fromStdString( + types::GetLayerName(types::LayerType::Alert, phenomenon)); + QString sourceId = GetSourceId(phenomenon, alertActive); QString idSuffix = GetSuffix(phenomenon, alertActive); auto outlineColor = util::color::ToRgba8PixelT( paletteSettings.alert_color(phenomenon, alertActive).GetValue()); - QString bgLayerId = QString("alertPolygonLayerBg-%1").arg(idSuffix); - QString fgLayerId = QString("alertPolygonLayerFg-%1").arg(idSuffix); + QString bgLayerId = QString("%1::bg-%2").arg(layerPrefix).arg(idSuffix); + QString fgLayerId = QString("%1::fg-%2").arg(layerPrefix).arg(idSuffix); if (map->layerExists(bgLayerId)) { diff --git a/scwx-qt/source/scwx/qt/map/alert_layer.hpp b/scwx-qt/source/scwx/qt/map/alert_layer.hpp index 8b081c64..e728118a 100644 --- a/scwx-qt/source/scwx/qt/map/alert_layer.hpp +++ b/scwx-qt/source/scwx/qt/map/alert_layer.hpp @@ -1,6 +1,9 @@ #pragma once -#include +#include +#include + +#include namespace scwx { @@ -11,16 +14,13 @@ namespace map class AlertLayerImpl; -class AlertLayer : public DrawLayer +class AlertLayer { public: explicit AlertLayer(std::shared_ptr context); ~AlertLayer(); - void Initialize() override final; - void Render(const QMapLibreGL::CustomLayerRenderParameters&) override final; - void Deinitialize() override final; - + void AddLayers(awips::Phenomenon phenomenon, const std::string& before = {}); void AddLayers(const std::string& before = {}); private: diff --git a/scwx-qt/source/scwx/qt/map/radar_range_layer.cpp b/scwx-qt/source/scwx/qt/map/radar_range_layer.cpp index ea4fa55b..a4738404 100644 --- a/scwx-qt/source/scwx/qt/map/radar_range_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/radar_range_layer.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -22,11 +23,14 @@ void RadarRangeLayer::Add(std::shared_ptr map, QMapLibreGL::Coordinate center, const QString& before) { + static const QString layerId = QString::fromStdString(types::GetLayerName( + types::LayerType::Data, types::DataLayer::RadarRange)); + logger_->debug("Add()"); - if (map->layerExists("rangeCircleLayer")) + if (map->layerExists(layerId)) { - map->removeLayer("rangeCircleLayer"); + map->removeLayer(layerId); } if (map->sourceExists("rangeCircleSource")) { @@ -39,12 +43,10 @@ void RadarRangeLayer::Add(std::shared_ptr map, map->addSource( "rangeCircleSource", {{"type", "geojson"}, {"data", QVariant::fromValue(*rangeCircle)}}); - map->addLayer({{"id", "rangeCircleLayer"}, - {"type", "line"}, - {"source", "rangeCircleSource"}}, - before); - map->setPaintProperty( - "rangeCircleLayer", "line-color", "rgba(128, 128, 128, 128)"); + map->addLayer( + {{"id", layerId}, {"type", "line"}, {"source", "rangeCircleSource"}}, + before); + map->setPaintProperty(layerId, "line-color", "rgba(128, 128, 128, 128)"); } void RadarRangeLayer::Update(std::shared_ptr map, diff --git a/scwx-qt/source/scwx/qt/model/layer_model.cpp b/scwx-qt/source/scwx/qt/model/layer_model.cpp index 6cace5a9..f52475e1 100644 --- a/scwx-qt/source/scwx/qt/model/layer_model.cpp +++ b/scwx-qt/source/scwx/qt/model/layer_model.cpp @@ -601,35 +601,8 @@ QVariant LayerModel::data(const QModelIndex& index, int role) const } else { - if (std::holds_alternative(layer.description_)) - { - return QString::fromStdString( - std::get(layer.description_)); - } - else if (std::holds_alternative( - layer.description_)) - { - return QString::fromStdString(types::GetDataLayerName( - std::get(layer.description_))); - } - else if (std::holds_alternative( - layer.description_)) - { - return QString::fromStdString(types::GetInformationLayerName( - std::get(layer.description_))); - } - else if (std::holds_alternative( - layer.description_)) - { - return QString::fromStdString(types::GetMapLayerName( - std::get(layer.description_))); - } - else if (std::holds_alternative( - layer.description_)) - { - return QString::fromStdString(awips::GetPhenomenonText( - std::get(layer.description_))); - } + return QString::fromStdString( + types::GetLayerDescriptionName(layer.description_)); } } break; diff --git a/scwx-qt/source/scwx/qt/types/layer_types.cpp b/scwx-qt/source/scwx/qt/types/layer_types.cpp index c6d042d8..0f7e1db8 100644 --- a/scwx-qt/source/scwx/qt/types/layer_types.cpp +++ b/scwx-qt/source/scwx/qt/types/layer_types.cpp @@ -4,6 +4,7 @@ #include #include +#include namespace scwx { @@ -131,6 +132,46 @@ std::string GetMapLayerName(MapLayer layer) return mapLayerName_.at(layer); } +std::string GetLayerDescriptionName(LayerDescription description) +{ + if (std::holds_alternative(description)) + { + return std::get(description); + } + else if (std::holds_alternative(description)) + { + return GetDataLayerName(std::get(description)); + } + else if (std::holds_alternative(description)) + { + return GetInformationLayerName(std::get(description)); + } + else if (std::holds_alternative(description)) + { + return GetMapLayerName(std::get(description)); + } + else if (std::holds_alternative(description)) + { + return awips::GetPhenomenonText(std::get(description)); + } + else if (std::holds_alternative(description)) + { + return ""; + } + else + { + return "?"; + } +} + +std::string GetLayerName(types::LayerType type, + types::LayerDescription description) +{ + return fmt::format("scwx.{}.{}", + types::GetLayerTypeName(type), + types::GetLayerDescriptionName(description)); +} + void tag_invoke(boost::json::value_from_tag, boost::json::value& jv, const LayerInfo& record) diff --git a/scwx-qt/source/scwx/qt/types/layer_types.hpp b/scwx-qt/source/scwx/qt/types/layer_types.hpp index d7c79a0a..6a00d03b 100644 --- a/scwx-qt/source/scwx/qt/types/layer_types.hpp +++ b/scwx-qt/source/scwx/qt/types/layer_types.hpp @@ -82,6 +82,10 @@ std::string GetInformationLayerName(InformationLayer layer); MapLayer GetMapLayer(const std::string& name); std::string GetMapLayerName(MapLayer layer); +std::string GetLayerDescriptionName(LayerDescription description); + +std::string GetLayerName(LayerType type, LayerDescription description); + void tag_invoke(boost::json::value_from_tag, boost::json::value& jv, const LayerInfo& record); From cc0c82bbd2b1c189ee08d45d3d2b8cf7e27cbd52 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 3 Nov 2023 05:09:07 -0500 Subject: [PATCH 192/199] Add layers in order defined by the layer manager --- scwx-qt/source/scwx/qt/main/main_window.cpp | 2 +- scwx-qt/source/scwx/qt/map/map_widget.cpp | 233 +++++++++++++++----- scwx-qt/source/scwx/qt/map/map_widget.hpp | 4 +- 3 files changed, 176 insertions(+), 63 deletions(-) diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp index 267da846..10b80f1d 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -650,7 +650,7 @@ void MainWindowImpl::ConfigureMapLayout() { if (maps_.at(mapIndex) == nullptr) { - maps_[mapIndex] = new map::MapWidget(settings_); + maps_[mapIndex] = new map::MapWidget(mapIndex, settings_); } hs->addWidget(maps_[mapIndex]); diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index 1df8f2ff..2502567d 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -56,7 +57,9 @@ class MapWidgetImpl : public QObject public: explicit MapWidgetImpl(MapWidget* widget, + std::size_t id, const QMapLibreGL::Settings& settings) : + id_ {id}, uuid_ {boost::uuids::random_generator()()}, context_ {std::make_shared()}, widget_ {widget}, @@ -120,9 +123,15 @@ public: threadPool_.join(); } + void AddLayer(types::LayerType type, + types::LayerDescription description, + const std::string& before = {}); void AddLayer(const std::string& id, std::shared_ptr layer, const std::string& before = {}); + void AddLayers(); + void AddPlacefileLayer(const std::string& placefileName, + const std::string& before = "colorTable"); void ConnectSignals(); void ImGuiCheckFonts(); void InitializeNewRadarProductView(const std::string& colorPalette); @@ -133,9 +142,12 @@ public: void RemovePlacefileLayer(const std::string& placefileName); void RunMousePicking(); void SetRadarSite(const std::string& radarSite); + void UpdateLoadedStyle(); void UpdatePlacefileLayers(); bool UpdateStoredMapParameters(); + std::string FindMapSymbologyLayer(); + common::Level2Product GetLevel2ProductOrDefault(const std::string& productName) const; @@ -143,6 +155,7 @@ public: boost::asio::thread_pool threadPool_ {1u}; + std::size_t id_; boost::uuids::uuid uuid_; std::shared_ptr context_; @@ -153,6 +166,9 @@ public: std::shared_ptr map_; std::list layerList_; + QStringList styleLayers_; + types::LayerVector customLayers_; + ImGuiContext* imGuiContext_; std::string imGuiContextName_; bool imGuiRendererInitialized_; @@ -198,8 +214,8 @@ public slots: void Update(); }; -MapWidget::MapWidget(const QMapLibreGL::Settings& settings) : - p(std::make_unique(this, settings)) +MapWidget::MapWidget(std::size_t id, const QMapLibreGL::Settings& settings) : + p(std::make_unique(this, id, settings)) { if (settings::GeneralSettings::Instance().anti_aliasing_enabled().GetValue()) { @@ -590,7 +606,7 @@ void MapWidget::SelectRadarSite(std::shared_ptr radarSite, false); } - AddLayers(); + p->AddLayers(); // TODO: Disable refresh from old site @@ -738,66 +754,154 @@ void MapWidget::changeStyle() Q_EMIT MapStyleChanged(p->currentStyle_->name_); } -void MapWidget::AddLayers() +std::string MapWidgetImpl::FindMapSymbologyLayer() { - logger_->debug("AddLayers()"); + std::string before = "ferry"; + + for (const QString& qlayer : styleLayers_) + { + const std::string layer = qlayer.toStdString(); + + // Draw below layers defined in map style + auto it = std::find_if( + currentStyle_->drawBelow_.cbegin(), + currentStyle_->drawBelow_.cend(), + [&layer](const std::string& styleLayer) -> bool + { + std::regex re {styleLayer, std::regex_constants::icase}; + return std::regex_match(layer, re); + }); + + if (it != currentStyle_->drawBelow_.cend()) + { + before = layer; + break; + } + } + + return before; +} + +void MapWidgetImpl::AddLayers() +{ + logger_->debug("Add Layers"); // Clear custom layers - for (const std::string& id : p->layerList_) + for (const std::string& id : layerList_) { - p->map_->removeLayer(id.c_str()); + map_->removeLayer(id.c_str()); } - p->layerList_.clear(); - p->placefileLayers_.clear(); + layerList_.clear(); + placefileLayers_.clear(); - auto radarProductView = p->context_->radar_product_view(); + // Update custom layer list from model + customLayers_ = model::LayerModel::Instance()->GetLayers(); - if (radarProductView != nullptr) + // Start by drawing layers before any style-defined layers + std::string before = styleLayers_.front().toStdString(); + + // Loop through each custom layer in reverse order + for (auto it = customLayers_.crbegin(); it != customLayers_.crend(); ++it) { - p->radarProductLayer_ = std::make_shared(p->context_); - p->colorTableLayer_ = std::make_shared(p->context_); - - std::shared_ptr radarSite = - p->radarProductManager_->radar_site(); - - const auto& mapStyle = *p->currentStyle_; - - std::string before = "ferry"; - - for (const QString& qlayer : p->map_->layerIds()) + if (it->type_ == types::LayerType::Map) { - const std::string layer = qlayer.toStdString(); - - // Draw below layers defined in map style - auto it = std::find_if( - mapStyle.drawBelow_.cbegin(), - mapStyle.drawBelow_.cend(), - [&layer](const std::string& styleLayer) -> bool - { - std::regex re {styleLayer, std::regex_constants::icase}; - return std::regex_match(layer, re); - }); - - if (it != mapStyle.drawBelow_.cend()) + // Style-defined map layers + switch (std::get(it->description_)) { - before = layer; + // Subsequent layers are drawn underneath the map symbology layer + case types::MapLayer::MapUnderlay: + before = FindMapSymbologyLayer(); + break; + + // Subsequent layers are drawn after all style-defined layers + case types::MapLayer::MapSymbology: + before = ""; + break; + + default: break; } } - - p->AddLayer("radar", p->radarProductLayer_, before); - RadarRangeLayer::Add(p->map_, - radarProductView->range(), - {radarSite->latitude(), radarSite->longitude()}); - p->AddLayer("colorTable", p->colorTableLayer_); + else if (it->displayed_[id_]) + { + // If the layer is displayed for the current map, add it + AddLayer(it->type_, it->description_, before); + } } +} - p->alertLayer_->AddLayers("colorTable"); +void MapWidgetImpl::AddLayer(types::LayerType type, + types::LayerDescription description, + const std::string& before) +{ + std::string layerName = types::GetLayerName(type, description); - p->UpdatePlacefileLayers(); + auto radarProductView = context_->radar_product_view(); - p->overlayLayer_ = std::make_shared(p->context_); - p->AddLayer("overlay", p->overlayLayer_); + if (type == types::LayerType::Radar) + { + // If there is a radar product view, create the radar product layer + if (radarProductView != nullptr) + { + radarProductLayer_ = std::make_shared(context_); + AddLayer(layerName, radarProductLayer_, before); + } + } + else if (type == types::LayerType::Alert) + { + // Add the alert layer for the phenomenon + alertLayer_->AddLayers(std::get(description), before); + } + else if (type == types::LayerType::Placefile) + { + // Add the placefile layer + AddPlacefileLayer(std::get(description), before); + } + else if (type == types::LayerType::Information) + { + switch (std::get(description)) + { + // Create the map overlay layer + case types::InformationLayer::MapOverlay: + overlayLayer_ = std::make_shared(context_); + AddLayer(layerName, overlayLayer_, before); + break; + + // If there is a radar product view, create the color table layer + case types::InformationLayer::ColorTable: + if (radarProductView != nullptr) + { + colorTableLayer_ = std::make_shared(context_); + AddLayer(layerName, colorTableLayer_, before); + } + break; + + default: + break; + } + } + else if (type == types::LayerType::Data) + { + switch (std::get(description)) + { + // If there is a radar product view, create the radar range layer + case types::DataLayer::RadarRange: + if (radarProductView != nullptr) + { + std::shared_ptr radarSite = + radarProductManager_->radar_site(); + RadarRangeLayer::Add( + map_, + radarProductView->range(), + {radarSite->latitude(), radarSite->longitude()}, + QString::fromStdString(before)); + } + break; + + default: + break; + } + } } void MapWidgetImpl::RemovePlacefileLayer(const std::string& placefileName) @@ -841,21 +945,26 @@ void MapWidgetImpl::UpdatePlacefileLayers() // If the layer doesn't exist, create it if (it == placefileLayers_.end()) { - std::shared_ptr placefileLayer = - std::make_shared(context_, placefileName); - placefileLayers_.push_back(placefileLayer); - AddLayer( - GetPlacefileLayerName(placefileName), placefileLayer, "colorTable"); - - // When the layer updates, trigger a map widget update - connect(placefileLayer.get(), - &PlacefileLayer::DataReloaded, - widget_, - [this]() { widget_->update(); }); + AddPlacefileLayer(placefileName); } } } +void MapWidgetImpl::AddPlacefileLayer(const std::string& placefileName, + const std::string& before) +{ + std::shared_ptr placefileLayer = + std::make_shared(context_, placefileName); + placefileLayers_.push_back(placefileLayer); + AddLayer(GetPlacefileLayerName(placefileName), placefileLayer, before); + + // When the layer updates, trigger a map widget update + connect(placefileLayer.get(), + &PlacefileLayer::DataReloaded, + widget_, + [this]() { widget_->update(); }); +} + std::string MapWidgetImpl::GetPlacefileLayerName(const std::string& placefileName) { @@ -1147,7 +1256,8 @@ void MapWidget::mapChanged(QMapLibreGL::Map::MapChange mapChange) switch (mapChange) { case QMapLibreGL::Map::MapChangeDidFinishLoadingStyle: - AddLayers(); + p->UpdateLoadedStyle(); + p->AddLayers(); break; default: @@ -1155,6 +1265,11 @@ void MapWidget::mapChanged(QMapLibreGL::Map::MapChange mapChange) } } +void MapWidgetImpl::UpdateLoadedStyle() +{ + styleLayers_ = map_->layerIds(); +} + void MapWidgetImpl::RadarProductManagerConnect() { if (radarProductManager_ != nullptr) @@ -1265,7 +1380,7 @@ void MapWidgetImpl::InitializeNewRadarProductView( if (map_ != nullptr) { - widget_->AddLayers(); + AddLayers(); } } diff --git a/scwx-qt/source/scwx/qt/map/map_widget.hpp b/scwx-qt/source/scwx/qt/map/map_widget.hpp index d18bec6e..aadf1a8e 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.hpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.hpp @@ -32,7 +32,7 @@ class MapWidget : public QOpenGLWidget Q_OBJECT public: - explicit MapWidget(const QMapLibreGL::Settings&); + explicit MapWidget(std::size_t id, const QMapLibreGL::Settings&); ~MapWidget(); common::Level3ProductCategoryMap GetAvailableLevel3Categories(); @@ -130,8 +130,6 @@ private: void initializeGL() override final; void paintGL() override final; - void AddLayers(); - std::unique_ptr p; friend class MapWidgetImpl; From 44ada2cc8d3d06652e82cd8ee17167d0befa198e Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 3 Nov 2023 05:41:59 -0500 Subject: [PATCH 193/199] Add layer debug --- scwx-qt/source/scwx/qt/main/main_window.cpp | 5 +++++ scwx-qt/source/scwx/qt/main/main_window.hpp | 1 + scwx-qt/source/scwx/qt/main/main_window.ui | 6 ++++++ scwx-qt/source/scwx/qt/map/map_widget.cpp | 5 +++++ scwx-qt/source/scwx/qt/map/map_widget.hpp | 2 ++ 5 files changed, 19 insertions(+) diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp index 10b80f1d..5c2a94fc 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -470,6 +470,11 @@ void MainWindow::on_actionImGuiDebug_triggered() p->imGuiDebugDialog_->show(); } +void MainWindow::on_actionDumpLayerList_triggered() +{ + p->activeMap_->DumpLayerList(); +} + void MainWindow::on_actionDumpRadarProductRecords_triggered() { manager::RadarProductManager::DumpRecords(); diff --git a/scwx-qt/source/scwx/qt/main/main_window.hpp b/scwx-qt/source/scwx/qt/main/main_window.hpp index 19fddd79..f4532f44 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.hpp +++ b/scwx-qt/source/scwx/qt/main/main_window.hpp @@ -39,6 +39,7 @@ private slots: void on_actionPlacefileManager_triggered(); void on_actionLayerManager_triggered(); void on_actionImGuiDebug_triggered(); + void on_actionDumpLayerList_triggered(); void on_actionDumpRadarProductRecords_triggered(); void on_actionUserManual_triggered(); void on_actionDiscord_triggered(); diff --git a/scwx-qt/source/scwx/qt/main/main_window.ui b/scwx-qt/source/scwx/qt/main/main_window.ui index d2065105..628d6ddc 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.ui +++ b/scwx-qt/source/scwx/qt/main/main_window.ui @@ -85,6 +85,7 @@ + @@ -441,6 +442,11 @@ &Layer Manager + + + Dump &Layer List + + diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index 2502567d..403a15c4 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -754,6 +754,11 @@ void MapWidget::changeStyle() Q_EMIT MapStyleChanged(p->currentStyle_->name_); } +void MapWidget::DumpLayerList() const +{ + logger_->debug("Layers: {}", p->map_->layerIds().join(", ").toStdString()); +} + std::string MapWidgetImpl::FindMapSymbologyLayer() { std::string before = "ferry"; diff --git a/scwx-qt/source/scwx/qt/map/map_widget.hpp b/scwx-qt/source/scwx/qt/map/map_widget.hpp index aadf1a8e..c130c3e6 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.hpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.hpp @@ -35,6 +35,8 @@ public: explicit MapWidget(std::size_t id, const QMapLibreGL::Settings&); ~MapWidget(); + void DumpLayerList() const; + common::Level3ProductCategoryMap GetAvailableLevel3Categories(); float GetElevation() const; std::vector GetElevationCuts() const; From 7e2fd7c1099ef102b5cc61d76cfb7149a31f18c8 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 3 Nov 2023 05:42:32 -0500 Subject: [PATCH 194/199] Layer naming updates --- scwx-qt/source/scwx/qt/map/map_widget.cpp | 2 +- scwx-qt/source/scwx/qt/types/layer_types.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index 403a15c4..35e4bd2e 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -973,7 +973,7 @@ void MapWidgetImpl::AddPlacefileLayer(const std::string& placefileName, std::string MapWidgetImpl::GetPlacefileLayerName(const std::string& placefileName) { - return fmt::format("placefile-{}", placefileName); + return types::GetLayerName(types::LayerType::Placefile, placefileName); } void MapWidgetImpl::AddLayer(const std::string& id, diff --git a/scwx-qt/source/scwx/qt/types/layer_types.cpp b/scwx-qt/source/scwx/qt/types/layer_types.cpp index 0f7e1db8..c25beb12 100644 --- a/scwx-qt/source/scwx/qt/types/layer_types.cpp +++ b/scwx-qt/source/scwx/qt/types/layer_types.cpp @@ -152,7 +152,7 @@ std::string GetLayerDescriptionName(LayerDescription description) } else if (std::holds_alternative(description)) { - return awips::GetPhenomenonText(std::get(description)); + return awips::GetPhenomenonCode(std::get(description)); } else if (std::holds_alternative(description)) { From c81d9d9ba61f8b5ca85ef0f862ae768b43360df7 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 3 Nov 2023 05:43:06 -0500 Subject: [PATCH 195/199] Only add placefile layers if the placefile is enabled --- scwx-qt/source/scwx/qt/map/map_widget.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index 35e4bd2e..ff5a53b4 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -859,8 +859,12 @@ void MapWidgetImpl::AddLayer(types::LayerType type, } else if (type == types::LayerType::Placefile) { - // Add the placefile layer - AddPlacefileLayer(std::get(description), before); + // If the placefile is enabled, add the placefile layer + std::string placefileName = std::get(description); + if (placefileManager_->placefile_enabled(placefileName)) + { + AddPlacefileLayer(placefileName, before); + } } else if (type == types::LayerType::Information) { From 2b6f70697c858567e5d52497e4e32590c752a041 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 5 Nov 2023 06:03:54 -0600 Subject: [PATCH 196/199] Update displayed layers when the layer model changes --- scwx-qt/source/scwx/qt/map/alert_layer.cpp | 54 +++--- scwx-qt/source/scwx/qt/map/alert_layer.hpp | 6 +- scwx-qt/source/scwx/qt/map/map_widget.cpp | 179 ++++++++----------- scwx-qt/source/scwx/qt/model/layer_model.cpp | 78 ++++---- scwx-qt/source/scwx/qt/model/layer_model.hpp | 6 - 5 files changed, 142 insertions(+), 181 deletions(-) diff --git a/scwx-qt/source/scwx/qt/map/alert_layer.cpp b/scwx-qt/source/scwx/qt/map/alert_layer.cpp index 82a7bb3c..d1676d7a 100644 --- a/scwx-qt/source/scwx/qt/map/alert_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/alert_layer.cpp @@ -23,10 +23,11 @@ namespace map static const std::string logPrefix_ = "scwx::qt::map::alert_layer"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); -static void AddAlertLayer(std::shared_ptr map, - awips::Phenomenon phenomenon, - bool alertActive, - const QString& beforeLayer); +static std::vector +AddAlertLayer(std::shared_ptr map, + awips::Phenomenon phenomenon, + bool alertActive, + const QString& beforeLayer); static QMapLibreGL::Feature CreateFeature(const awips::CodedLocation& codedLocation); static QMapLibreGL::Coordinate @@ -139,15 +140,17 @@ AlertLayer::AlertLayer(std::shared_ptr context) : AlertLayer::~AlertLayer() = default; -void AlertLayer::AddLayers(awips::Phenomenon phenomenon, - const std::string& before) +std::vector AlertLayer::AddLayers(awips::Phenomenon phenomenon, + const std::string& before) { logger_->debug("AddLayers(): {}", awips::GetPhenomenonCode(phenomenon)); + std::vector layers {}; + auto map = p->context_->map().lock(); if (map == nullptr) { - return; + return layers; } const QString beforeLayer {QString::fromStdString(before)}; @@ -156,31 +159,11 @@ void AlertLayer::AddLayers(awips::Phenomenon phenomenon, for (bool alertActive : {false, true}) { p->UpdateSource(phenomenon, alertActive); - AddAlertLayer(map, phenomenon, alertActive, beforeLayer); - } -} - -void AlertLayer::AddLayers(const std::string& before) -{ - logger_->debug("AddLayers()"); - - auto map = p->context_->map().lock(); - if (map == nullptr) - { - return; + auto newLayers = AddAlertLayer(map, phenomenon, alertActive, beforeLayer); + layers.insert(layers.end(), newLayers.cbegin(), newLayers.cend()); } - const QString beforeLayer {QString::fromStdString(before)}; - - // Add/update GeoJSON sources and create layers - for (auto& phenomenon : kAlertPhenomena_) - { - for (bool alertActive : {false, true}) - { - p->UpdateSource(phenomenon, alertActive); - AddAlertLayer(map, phenomenon, alertActive, beforeLayer); - } - } + return layers; } std::list* @@ -387,10 +370,11 @@ std::shared_ptr AlertLayerHandler::Instance() return alertLayerHandler; } -static void AddAlertLayer(std::shared_ptr map, - awips::Phenomenon phenomenon, - bool alertActive, - const QString& beforeLayer) +static std::vector +AddAlertLayer(std::shared_ptr map, + awips::Phenomenon phenomenon, + bool alertActive, + const QString& beforeLayer) { settings::PaletteSettings& paletteSettings = settings::PaletteSettings::Instance(); @@ -438,6 +422,8 @@ static void AddAlertLayer(std::shared_ptr map, .arg(outlineColor[3])); map->setPaintProperty(fgLayerId, "line-opacity", QString("%1").arg(opacity)); map->setPaintProperty(fgLayerId, "line-width", "3"); + + return {bgLayerId.toStdString(), fgLayerId.toStdString()}; } static QMapLibreGL::Feature diff --git a/scwx-qt/source/scwx/qt/map/alert_layer.hpp b/scwx-qt/source/scwx/qt/map/alert_layer.hpp index e728118a..6ce681a9 100644 --- a/scwx-qt/source/scwx/qt/map/alert_layer.hpp +++ b/scwx-qt/source/scwx/qt/map/alert_layer.hpp @@ -4,6 +4,8 @@ #include #include +#include +#include namespace scwx { @@ -20,8 +22,8 @@ public: explicit AlertLayer(std::shared_ptr context); ~AlertLayer(); - void AddLayers(awips::Phenomenon phenomenon, const std::string& before = {}); - void AddLayers(const std::string& before = {}); + std::vector AddLayers(awips::Phenomenon phenomenon, + const std::string& before = {}); private: std::unique_ptr p; diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index ff5a53b4..52c177bd 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -131,7 +131,7 @@ public: const std::string& before = {}); void AddLayers(); void AddPlacefileLayer(const std::string& placefileName, - const std::string& before = "colorTable"); + const std::string& before); void ConnectSignals(); void ImGuiCheckFonts(); void InitializeNewRadarProductView(const std::string& colorPalette); @@ -139,11 +139,9 @@ public: void RadarProductManagerDisconnect(); void RadarProductViewConnect(); void RadarProductViewDisconnect(); - void RemovePlacefileLayer(const std::string& placefileName); void RunMousePicking(); void SetRadarSite(const std::string& radarSite); void UpdateLoadedStyle(); - void UpdatePlacefileLayers(); bool UpdateStoredMapParameters(); std::string FindMapSymbologyLayer(); @@ -174,6 +172,9 @@ public: bool imGuiRendererInitialized_; std::uint64_t imGuiFontsBuildCount_ {}; + std::shared_ptr layerModel_ { + model::LayerModel::Instance()}; + std::shared_ptr placefileManager_ { manager::PlacefileManager::Instance()}; std::shared_ptr radarProductManager_; @@ -186,7 +187,6 @@ public: std::shared_ptr placefileLayer_; std::shared_ptr colorTableLayer_; - std::set enabledPlacefiles_ {}; std::list> placefileLayers_ {}; bool autoRefreshEnabled_; @@ -237,62 +237,59 @@ MapWidget::~MapWidget() void MapWidgetImpl::ConnectSignals() { - connect(placefileManager_.get(), - &manager::PlacefileManager::PlacefileEnabled, - widget_, - [this](const std::string& name, bool enabled) - { - if (enabled && !enabledPlacefiles_.contains(name)) - { - // Placefile enabled, add layer - enabledPlacefiles_.emplace(name); - UpdatePlacefileLayers(); - } - else if (!enabled && enabledPlacefiles_.contains(name)) - { - // Placefile disabled, remove layer - enabledPlacefiles_.erase(name); - RemovePlacefileLayer(name); - } - widget_->update(); - }); - connect(placefileManager_.get(), - &manager::PlacefileManager::PlacefileRemoved, - widget_, - [this](const std::string& name) - { - if (enabledPlacefiles_.contains(name)) - { - // Placefile removed, remove layer - enabledPlacefiles_.erase(name); - RemovePlacefileLayer(name); - } - widget_->update(); - }); - connect(placefileManager_.get(), - &manager::PlacefileManager::PlacefileRenamed, - widget_, - [this](const std::string& oldName, const std::string& newName) - { - if (enabledPlacefiles_.contains(oldName)) - { - // Remove old placefile layer - enabledPlacefiles_.erase(oldName); - RemovePlacefileLayer(oldName); - } - if (!enabledPlacefiles_.contains(newName) && - placefileManager_->placefile_enabled(newName)) - { - // Add new placefile layer - enabledPlacefiles_.emplace(newName); - UpdatePlacefileLayers(); - } - widget_->update(); - }); connect(placefileManager_.get(), &manager::PlacefileManager::PlacefileUpdated, widget_, [this]() { widget_->update(); }); + + // When the layer model changes, update the layers + connect(layerModel_.get(), + &QAbstractItemModel::dataChanged, + widget_, + [this](const QModelIndex& topLeft, + const QModelIndex& bottomRight, + const QList& /* roles */) + { + static const int enabledColumn = + static_cast(model::LayerModel::Column::Enabled); + const int displayColumn = + static_cast(model::LayerModel::Column::DisplayMap1) + + static_cast(id_); + + // Update layers if the displayed or enabled state of the layer + // has changed + if ((topLeft.column() <= displayColumn && + displayColumn <= bottomRight.column()) || + (topLeft.column() <= enabledColumn && + enabledColumn <= bottomRight.column())) + { + AddLayers(); + } + }); + connect(layerModel_.get(), + &QAbstractItemModel::modelReset, + widget_, + [this]() { AddLayers(); }); + connect(layerModel_.get(), + &QAbstractItemModel::rowsInserted, + widget_, + [this](const QModelIndex& /* parent */, // + int /* first */, + int /* last */) { AddLayers(); }); + connect(layerModel_.get(), + &QAbstractItemModel::rowsMoved, + widget_, + [this](const QModelIndex& /* sourceParent */, + int /* sourceStart */, + int /* sourceEnd */, + const QModelIndex& /* destinationParent */, + int /* destinationRow */) { AddLayers(); }); + connect(layerModel_.get(), + &QAbstractItemModel::rowsRemoved, + widget_, + [this](const QModelIndex& /* parent */, // + int /* first */, + int /* last */) { AddLayers(); }); } common::Level3ProductCategoryMap MapWidget::GetAvailableLevel3Categories() @@ -756,7 +753,7 @@ void MapWidget::changeStyle() void MapWidget::DumpLayerList() const { - logger_->debug("Layers: {}", p->map_->layerIds().join(", ").toStdString()); + logger_->info("Layers: {}", p->map_->layerIds().join(", ").toStdString()); } std::string MapWidgetImpl::FindMapSymbologyLayer() @@ -789,6 +786,12 @@ std::string MapWidgetImpl::FindMapSymbologyLayer() void MapWidgetImpl::AddLayers() { + if (styleLayers_.isEmpty()) + { + // Skip if the map has not yet been initialized + return; + } + logger_->debug("Add Layers"); // Clear custom layers @@ -855,7 +858,9 @@ void MapWidgetImpl::AddLayer(types::LayerType type, else if (type == types::LayerType::Alert) { // Add the alert layer for the phenomenon - alertLayer_->AddLayers(std::get(description), before); + auto newLayers = alertLayer_->AddLayers( + std::get(description), before); + layerList_.insert(layerList_.end(), newLayers.cbegin(), newLayers.cend()); } else if (type == types::LayerType::Placefile) { @@ -904,6 +909,7 @@ void MapWidgetImpl::AddLayer(types::LayerType type, radarProductView->range(), {radarSite->latitude(), radarSite->longitude()}, QString::fromStdString(before)); + layerList_.push_back(types::GetLayerName(type, description)); } break; @@ -913,52 +919,6 @@ void MapWidgetImpl::AddLayer(types::LayerType type, } } -void MapWidgetImpl::RemovePlacefileLayer(const std::string& placefileName) -{ - std::string layerName = GetPlacefileLayerName(placefileName); - - // Remove layer from map - map_->removeLayer(layerName.c_str()); - - // Remove layer from internal layer list - auto layerIt = std::find(layerList_.begin(), layerList_.end(), layerName); - if (layerIt != layerList_.end()) - { - layerList_.erase(layerIt); - } - - // Determine if a layer exists for the placefile - auto placefileIt = - std::find_if(placefileLayers_.begin(), - placefileLayers_.end(), - [&placefileName](auto& layer) - { return placefileName == layer->placefile_name(); }); - if (placefileIt != placefileLayers_.end()) - { - placefileLayers_.erase(placefileIt); - } -} - -void MapWidgetImpl::UpdatePlacefileLayers() -{ - // Loop through enabled placefiles - for (auto& placefileName : enabledPlacefiles_) - { - // Determine if a layer exists for the placefile - auto it = std::find_if(placefileLayers_.begin(), - placefileLayers_.end(), - [&placefileName](auto& layer) { - return placefileName == layer->placefile_name(); - }); - - // If the layer doesn't exist, create it - if (it == placefileLayers_.end()) - { - AddPlacefileLayer(placefileName); - } - } -} - void MapWidgetImpl::AddPlacefileLayer(const std::string& placefileName, const std::string& before) { @@ -988,9 +948,16 @@ void MapWidgetImpl::AddLayer(const std::string& id, std::unique_ptr pHost = std::make_unique(layer); - map_->addCustomLayer(id.c_str(), std::move(pHost), before.c_str()); + try + { + map_->addCustomLayer(id.c_str(), std::move(pHost), before.c_str()); - layerList_.push_back(id); + layerList_.push_back(id); + } + catch (const std::exception&) + { + // When dragging and dropping, a temporary duplicate layer exists + } } void MapWidget::enterEvent(QEnterEvent* /* ev */) diff --git a/scwx-qt/source/scwx/qt/model/layer_model.cpp b/scwx-qt/source/scwx/qt/model/layer_model.cpp index f52475e1..a153f4f2 100644 --- a/scwx-qt/source/scwx/qt/model/layer_model.cpp +++ b/scwx-qt/source/scwx/qt/model/layer_model.cpp @@ -71,6 +71,10 @@ public: ~Impl() = default; void AddPlacefile(const std::string& name); + void HandlePlacefileRemoved(const std::string& name); + void HandlePlacefileRenamed(const std::string& oldName, + const std::string& newName); + void HandlePlacefileUpdate(const std::string& name, Column column); void InitializeLayerSettings(); void ReadLayerSettings(); void SynchronizePlacefileLayers(); @@ -102,22 +106,26 @@ LayerModel::LayerModel(QObject* parent) : connect(p->placefileManager_.get(), &manager::PlacefileManager::PlacefileEnabled, this, - &LayerModel::HandlePlacefileUpdate); + [this](const std::string& name, bool /* enabled */) + { p->HandlePlacefileUpdate(name, Column::Enabled); }); connect(p->placefileManager_.get(), &manager::PlacefileManager::PlacefileRemoved, this, - &LayerModel::HandlePlacefileRemoved); + [this](const std::string& name) + { p->HandlePlacefileRemoved(name); }); connect(p->placefileManager_.get(), &manager::PlacefileManager::PlacefileRenamed, this, - &LayerModel::HandlePlacefileRenamed); + [this](const std::string& oldName, const std::string& newName) + { p->HandlePlacefileRenamed(oldName, newName); }); connect(p->placefileManager_.get(), &manager::PlacefileManager::PlacefileUpdated, this, - &LayerModel::HandlePlacefileUpdate); + [this](const std::string& name) + { p->HandlePlacefileUpdate(name, Column::Description); }); p->InitializeLayerSettings(); p->ReadLayerSettings(); @@ -908,88 +916,92 @@ bool LayerModel::moveRows(const QModelIndex& sourceParent, return moved; } -void LayerModel::HandlePlacefileRemoved(const std::string& name) +void LayerModel::Impl::HandlePlacefileRemoved(const std::string& name) { auto it = - std::find_if(p->layers_.begin(), - p->layers_.end(), + std::find_if(layers_.begin(), + layers_.end(), [&name](const auto& layer) { return layer.type_ == types::LayerType::Placefile && std::get(layer.description_) == name; }); - if (it != p->layers_.end()) + if (it != layers_.end()) { // Placefile exists, delete row - const int row = std::distance(p->layers_.begin(), it); + const int row = std::distance(layers_.begin(), it); - beginRemoveRows(QModelIndex(), row, row); - p->layers_.erase(it); - endRemoveRows(); + self_->beginRemoveRows(QModelIndex(), row, row); + layers_.erase(it); + self_->endRemoveRows(); } } -void LayerModel::HandlePlacefileRenamed(const std::string& oldName, - const std::string& newName) +void LayerModel::Impl::HandlePlacefileRenamed(const std::string& oldName, + const std::string& newName) { auto it = std::find_if( - p->layers_.begin(), - p->layers_.end(), + layers_.begin(), + layers_.end(), [&oldName](const auto& layer) { return layer.type_ == types::LayerType::Placefile && std::get(layer.description_) == oldName; }); - if (it != p->layers_.end()) + if (it != layers_.end()) { // Placefile exists, mark row as updated - const int row = std::distance(p->layers_.begin(), it); - QModelIndex topLeft = createIndex(row, kFirstColumn); - QModelIndex bottomRight = createIndex(row, kLastColumn); + const int row = std::distance(layers_.begin(), it); + QModelIndex topLeft = + self_->createIndex(row, static_cast(Column::Description)); + QModelIndex bottomRight = + self_->createIndex(row, static_cast(Column::Description)); // Rename placefile it->description_ = newName; - Q_EMIT dataChanged(topLeft, bottomRight); + Q_EMIT self_->dataChanged(topLeft, bottomRight); } else { // Placefile doesn't exist, add row - p->AddPlacefile(newName); + AddPlacefile(newName); } } -void LayerModel::HandlePlacefileUpdate(const std::string& name) +void LayerModel::Impl::HandlePlacefileUpdate(const std::string& name, + Column column) { - if (!p->placefilesInitialized_) + if (!placefilesInitialized_) { - p->initialPlacefiles_.push_back(name); + initialPlacefiles_.push_back(name); } auto it = - std::find_if(p->layers_.begin(), - p->layers_.end(), + std::find_if(layers_.begin(), + layers_.end(), [&name](const auto& layer) { return layer.type_ == types::LayerType::Placefile && std::get(layer.description_) == name; }); - if (it != p->layers_.end()) + if (it != layers_.end()) { // Placefile exists, mark row as updated - const int row = std::distance(p->layers_.begin(), it); - QModelIndex topLeft = createIndex(row, kFirstColumn); - QModelIndex bottomRight = createIndex(row, kLastColumn); + const int row = std::distance(layers_.begin(), it); + QModelIndex topLeft = self_->createIndex(row, static_cast(column)); + QModelIndex bottomRight = + self_->createIndex(row, static_cast(column)); - Q_EMIT dataChanged(topLeft, bottomRight); + Q_EMIT self_->dataChanged(topLeft, bottomRight); } else { // Placefile doesn't exist, add row - p->AddPlacefile(name); + AddPlacefile(name); } } diff --git a/scwx-qt/source/scwx/qt/model/layer_model.hpp b/scwx-qt/source/scwx/qt/model/layer_model.hpp index 893264bc..426615c7 100644 --- a/scwx-qt/source/scwx/qt/model/layer_model.hpp +++ b/scwx-qt/source/scwx/qt/model/layer_model.hpp @@ -77,12 +77,6 @@ public: static std::shared_ptr Instance(); -public slots: - void HandlePlacefileRemoved(const std::string& name); - void HandlePlacefileRenamed(const std::string& oldName, - const std::string& newName); - void HandlePlacefileUpdate(const std::string& name); - private: class Impl; std::unique_ptr p; From 92f24528b7cb56d07c075ba4817e64f292c4e915 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 5 Nov 2023 06:23:17 -0600 Subject: [PATCH 197/199] Update maplibre-native to latest --- external/mapbox-gl-native | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/mapbox-gl-native b/external/mapbox-gl-native index fbb06ff5..3e85454f 160000 --- a/external/mapbox-gl-native +++ b/external/mapbox-gl-native @@ -1 +1 @@ -Subproject commit fbb06ff53e74d3a81b434b84fff1a5dfe4b2d3c7 +Subproject commit 3e85454fe5e571e7b235131912bb867ef9d75c3c From e43dcb9a6b629692c8bffe782eb7c533555762dd Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 5 Nov 2023 06:23:57 -0600 Subject: [PATCH 198/199] PlacefileManager::GetActivePlacefiles is no longer used --- .../scwx/qt/manager/placefile_manager.cpp | 18 ------------------ .../scwx/qt/manager/placefile_manager.hpp | 7 ------- 2 files changed, 25 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index c069d16c..60b4047f 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -436,24 +436,6 @@ void PlacefileManager::SetRadarSite( } } -std::vector> -PlacefileManager::GetActivePlacefiles() -{ - std::vector> placefiles; - - std::shared_lock lock {p->placefileRecordLock_}; - - for (const auto& record : p->placefileRecords_) - { - if (record->enabled_ && record->placefile_ != nullptr) - { - placefiles.push_back(record->placefile_); - } - } - - return placefiles; -} - void PlacefileManager::AddUrl(const std::string& urlString, const std::string& title, bool enabled, diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp index 7010ca94..235b6e65 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp @@ -35,13 +35,6 @@ public: void SetRadarSite(std::shared_ptr radarSite); - /** - * @brief Gets a list of active placefiles - * - * @return Vector of placefile pointers - */ - std::vector> GetActivePlacefiles(); - void AddUrl(const std::string& urlString, const std::string& title = {}, bool enabled = false, From 66ef65fe2f45ade1a804e24d28df01cbc2b8a75e Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 5 Nov 2023 06:32:47 -0600 Subject: [PATCH 199/199] Force placefile refresh button --- .../scwx/qt/manager/placefile_manager.cpp | 11 ++++++++ .../scwx/qt/manager/placefile_manager.hpp | 2 ++ .../scwx/qt/ui/placefile_settings_widget.cpp | 27 +++++++++++++++++++ .../scwx/qt/ui/placefile_settings_widget.ui | 12 ++++++++- 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index 60b4047f..4b1b0550 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -508,6 +508,17 @@ void PlacefileManager::RemoveUrl(const std::string& urlString) Q_EMIT PlacefileRemoved(urlString); } +void PlacefileManager::Refresh(const std::string& name) +{ + std::shared_lock lock {p->placefileRecordLock_}; + + auto it = p->placefileRecordMap_.find(name); + if (it != p->placefileRecordMap_.cend()) + { + it->second->UpdateAsync(); + } +} + void PlacefileManager::Impl::PlacefileRecord::Update() { logger_->debug("Update: {}", name_); diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp index 235b6e65..17b39b2f 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp @@ -41,6 +41,8 @@ public: bool thresholded = false); void RemoveUrl(const std::string& urlString); + void Refresh(const std::string& name); + static std::shared_ptr Instance(); signals: diff --git a/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp b/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp index ddc5dda6..a23a3f0c 100644 --- a/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.cpp @@ -56,6 +56,7 @@ PlacefileSettingsWidget::PlacefileSettingsWidget(QWidget* parent) : ui->setupUi(this); ui->removeButton->setEnabled(false); + ui->refreshButton->setEnabled(false); ui->placefileView->setModel(p->placefileProxyModel_); @@ -114,6 +115,31 @@ void PlacefileSettingsWidgetImpl::ConnectSignals() } }); + QObject::connect(self_->ui->refreshButton, + &QPushButton::clicked, + self_, + [this]() + { + auto selectionModel = + self_->ui->placefileView->selectionModel(); + + // Get selected URL string + QModelIndex selected = + selectionModel + ->selectedRows(static_cast( + model::PlacefileModel::Column::Placefile)) + .first(); + QVariant data = self_->ui->placefileView->model()->data( + selected, types::ItemDataRole::SortRole); + std::string urlString = data.toString().toStdString(); + + // Refresh placefile + if (!urlString.empty()) + { + placefileManager_->Refresh(urlString); + } + }); + QObject::connect( openUrlDialog_, &OpenUrlDialog::accepted, @@ -143,6 +169,7 @@ void PlacefileSettingsWidgetImpl::ConnectSignals() bool itemSelected = selected.size() > 0; self_->ui->removeButton->setEnabled(itemSelected); + self_->ui->refreshButton->setEnabled(itemSelected); }); } diff --git a/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.ui b/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.ui index e5f3595b..b95ab784 100644 --- a/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.ui +++ b/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.ui @@ -84,7 +84,17 @@ false - &Remove + R&emove + + + + + + + false + + + &Refresh