diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 53701c11..af189e39 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -48,9 +48,10 @@ find_package(Qt${QT_VERSION_MAJOR} include(qt6-linguist.cmake) +set(SRC_EXE_MAIN source/scwx/qt/main/main.cpp) + set(HDR_MAIN source/scwx/qt/main/main_window.hpp) -set(SRC_MAIN source/scwx/qt/main/main.cpp - source/scwx/qt/main/main_window.cpp) +set(SRC_MAIN source/scwx/qt/main/main_window.cpp) set(UI_MAIN source/scwx/qt/main/main_window.ui) set(HDR_GL source/scwx/qt/gl/gl.hpp source/scwx/qt/gl/shader_program.hpp @@ -58,9 +59,11 @@ set(HDR_GL source/scwx/qt/gl/gl.hpp set(SRC_GL source/scwx/qt/gl/shader_program.cpp source/scwx/qt/gl/text_shader.cpp) set(HDR_MANAGER source/scwx/qt/manager/radar_manager.hpp - source/scwx/qt/manager/resource_manager.hpp) + source/scwx/qt/manager/resource_manager.hpp + source/scwx/qt/manager/settings_manager.hpp) set(SRC_MANAGER source/scwx/qt/manager/radar_manager.cpp - source/scwx/qt/manager/resource_manager.cpp) + source/scwx/qt/manager/resource_manager.cpp + source/scwx/qt/manager/settings_manager.cpp) set(HDR_MAP source/scwx/qt/map/map_widget.hpp source/scwx/qt/map/overlay_layer.hpp source/scwx/qt/map/radar_layer.hpp @@ -71,10 +74,14 @@ set(SRC_MAP source/scwx/qt/map/map_widget.cpp source/scwx/qt/map/radar_layer.cpp source/scwx/qt/map/radar_range_layer.cpp source/scwx/qt/map/triangle_layer.cpp) +set(HDR_SETTINGS source/scwx/qt/settings/general_settings.hpp) +set(SRC_SETTINGS source/scwx/qt/settings/general_settings.cpp) set(HDR_UTIL source/scwx/qt/util/font.hpp - source/scwx/qt/util/font_buffer.hpp) + source/scwx/qt/util/font_buffer.hpp + source/scwx/qt/util/json.hpp) set(SRC_UTIL source/scwx/qt/util/font.cpp - source/scwx/qt/util/font_buffer.cpp) + source/scwx/qt/util/font_buffer.cpp + source/scwx/qt/util/json.cpp) set(HDR_VIEW source/scwx/qt/view/radar_view.hpp) set(SRC_VIEW source/scwx/qt/view/radar_view.cpp) @@ -98,6 +105,8 @@ set(PROJECT_SOURCES ${HDR_MAIN} ${UI_MAIN} ${HDR_MAP} ${SRC_MAP} + ${HDR_SETTINGS} + ${SRC_SETTINGS} ${HDR_UTIL} ${SRC_UTIL} ${HDR_VIEW} @@ -105,44 +114,55 @@ set(PROJECT_SOURCES ${HDR_MAIN} ${SHADER_FILES} ${RESOURCE_FILES} ${TS_FILES}) +set(EXECUTABLE_SOURCES ${SRC_EXE_MAIN}) -source_group("Header Files\\main" FILES ${HDR_MAIN}) -source_group("Source Files\\main" FILES ${SRC_MAIN}) -source_group("Header Files\\gl" FILES ${HDR_GL}) -source_group("Source Files\\gl" FILES ${SRC_GL}) -source_group("Header Files\\manager" FILES ${HDR_MANAGER}) -source_group("Source Files\\manager" FILES ${SRC_MANAGER}) -source_group("UI Files\\main" FILES ${UI_MAIN}) -source_group("Header Files\\map" FILES ${HDR_MAP}) -source_group("Source Files\\map" FILES ${SRC_MAP}) -source_group("Header Files\\util" FILES ${HDR_UTIL}) -source_group("Source Files\\util" FILES ${SRC_UTIL}) -source_group("Header Files\\view" FILES ${HDR_VIEW}) -source_group("Source Files\\view" FILES ${SRC_VIEW}) -source_group("OpenGL Shaders" FILES ${SHADER_FILES}) -source_group("Resources" FILES ${RESOURCE_FILES}) -source_group("I18N Files" FILES ${TS_FILES}) +source_group("Header Files\\main" FILES ${HDR_MAIN}) +source_group("Source Files\\main" FILES ${SRC_MAIN}) +source_group("Header Files\\gl" FILES ${HDR_GL}) +source_group("Source Files\\gl" FILES ${SRC_GL}) +source_group("Header Files\\manager" FILES ${HDR_MANAGER}) +source_group("Source Files\\manager" FILES ${SRC_MANAGER}) +source_group("UI Files\\main" FILES ${UI_MAIN}) +source_group("Header Files\\map" FILES ${HDR_MAP}) +source_group("Source Files\\map" FILES ${SRC_MAP}) +source_group("Header Files\\settings" FILES ${HDR_SETTINGS}) +source_group("Source Files\\settings" FILES ${SRC_SETTINGS}) +source_group("Header Files\\util" FILES ${HDR_UTIL}) +source_group("Source Files\\util" FILES ${SRC_UTIL}) +source_group("Header Files\\view" FILES ${HDR_VIEW}) +source_group("Source Files\\view" FILES ${SRC_VIEW}) +source_group("OpenGL Shaders" FILES ${SHADER_FILES}) +source_group("Resources" FILES ${RESOURCE_FILES}) +source_group("I18N Files" FILES ${TS_FILES}) -qt_add_executable(scwx-qt - ${PROJECT_SOURCES} -) +add_library(scwx-qt OBJECT ${PROJECT_SOURCES}) +set_property(TARGET scwx-qt PROPERTY AUTOMOC ON) + +qt_add_executable(supercell-wx ${EXECUTABLE_SOURCES}) qt6_create_translation_scwx(QM_FILES ${scwx-qt_SOURCE_DIR} ${TS_FILES}) if (WIN32) - target_compile_definitions(scwx-qt PRIVATE WIN32_LEAN_AND_MEAN) + target_compile_definitions(scwx-qt PUBLIC WIN32_LEAN_AND_MEAN) + target_compile_definitions(supercell-wx PUBLIC WIN32_LEAN_AND_MEAN) endif() -target_include_directories(scwx-qt PRIVATE ${scwx-qt_SOURCE_DIR}/source - ${FTGL_INCLUDE_DIR} - ${MBGL_INCLUDE_DIR}) +target_include_directories(scwx-qt PUBLIC ${scwx-qt_SOURCE_DIR}/source + ${FTGL_INCLUDE_DIR} + ${MBGL_INCLUDE_DIR}) -target_link_libraries(scwx-qt PRIVATE Qt${QT_VERSION_MAJOR}::Widgets - Qt${QT_VERSION_MAJOR}::OpenGLWidgets - Boost::timer - qmapboxgl - opengl32 - freetype-gl - GeographicLib::GeographicLib - glm::glm - wxdata) +target_include_directories(supercell-wx PUBLIC ${scwx-qt_SOURCE_DIR}/source) + +target_link_libraries(scwx-qt PUBLIC Qt${QT_VERSION_MAJOR}::Widgets + Qt${QT_VERSION_MAJOR}::OpenGLWidgets + Boost::json + Boost::timer + qmapboxgl + opengl32 + freetype-gl + GeographicLib::GeographicLib + glm::glm + wxdata) + +target_link_libraries(supercell-wx PRIVATE scwx-qt + wxdata) diff --git a/scwx-qt/source/scwx/qt/main/main.cpp b/scwx-qt/source/scwx/qt/main/main.cpp index f4038493..c70dbff0 100644 --- a/scwx-qt/source/scwx/qt/main/main.cpp +++ b/scwx-qt/source/scwx/qt/main/main.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -10,6 +11,9 @@ int main(int argc, char* argv[]) boost::log::core::get()->set_filter(boost::log::trivial::severity >= boost::log::trivial::debug); + QCoreApplication::setApplicationName("Supercell Wx"); + + scwx::qt::manager::SettingsManager::Initialize(); scwx::qt::manager::ResourceManager::PreLoad(); QApplication a(argc, argv); diff --git a/scwx-qt/source/scwx/qt/manager/settings_manager.cpp b/scwx-qt/source/scwx/qt/manager/settings_manager.cpp new file mode 100644 index 00000000..da4470ff --- /dev/null +++ b/scwx-qt/source/scwx/qt/manager/settings_manager.cpp @@ -0,0 +1,116 @@ +#include +#include + +#include +#include + +#include +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace manager +{ +namespace SettingsManager +{ + +static const std::string logPrefix_ = "[scwx::qt::manager::settings_manager] "; + +static std::shared_ptr generalSettings_ = nullptr; + +static boost::json::value ConvertSettingsToJson(); +static void GenerateDefaultSettings(); +static bool LoadSettings(const boost::json::object& settingsJson); + +bool Initialize() +{ + std::string appDataPath { + QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + .toStdString()}; + + if (!std::filesystem::is_directory(appDataPath)) + { + if (!std::filesystem::create_directories(appDataPath)) + { + BOOST_LOG_TRIVIAL(error) + << logPrefix_ << "Unable to create application data directory: \"" + << appDataPath << "\""; + return false; + } + } + + std::string settingsPath {appDataPath + "/settings.json"}; + + ReadSettings(settingsPath); + + return true; +} + +void ReadSettings(const std::string& settingsPath) +{ + boost::json::value settingsJson = nullptr; + + if (std::filesystem::exists(settingsPath)) + { + settingsJson = util::json::ReadJsonFile(settingsPath); + } + + if (settingsJson == nullptr || !settingsJson.is_object()) + { + GenerateDefaultSettings(); + settingsJson = ConvertSettingsToJson(); + util::json::WriteJsonFile(settingsPath, settingsJson); + } + else + { + bool jsonDirty = LoadSettings(settingsJson.as_object()); + + if (jsonDirty) + { + settingsJson = ConvertSettingsToJson(); + util::json::WriteJsonFile(settingsPath, settingsJson); + } + }; +} + +std::shared_ptr general_settings() +{ + return generalSettings_; +} + +static boost::json::value ConvertSettingsToJson() +{ + boost::json::object settingsJson; + + settingsJson["general"] = generalSettings_->ToJson(); + + return settingsJson; +} + +static void GenerateDefaultSettings() +{ + BOOST_LOG_TRIVIAL(info) << logPrefix_ << "Generating default settings"; + + generalSettings_ = settings::GeneralSettings::Create(); +} + +static bool LoadSettings(const boost::json::object& settingsJson) +{ + BOOST_LOG_TRIVIAL(info) << logPrefix_ << "Loading settings"; + + bool jsonDirty = false; + + generalSettings_ = settings::GeneralSettings::Load( + settingsJson.if_contains("general"), jsonDirty); + + return jsonDirty; +} + +} // namespace SettingsManager +} // 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 new file mode 100644 index 00000000..657ff9c7 --- /dev/null +++ b/scwx-qt/source/scwx/qt/manager/settings_manager.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include + +namespace scwx +{ +namespace qt +{ +namespace manager +{ +namespace SettingsManager +{ + +bool Initialize(); +void ReadSettings(const std::string& settingsPath); + +std::shared_ptr general_settings(); + +} // namespace SettingsManager +} // namespace manager +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/settings/general_settings.cpp b/scwx-qt/source/scwx/qt/settings/general_settings.cpp new file mode 100644 index 00000000..1cacee2b --- /dev/null +++ b/scwx-qt/source/scwx/qt/settings/general_settings.cpp @@ -0,0 +1,98 @@ +#include +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace settings +{ + +static const std::string logPrefix_ = "[scwx::qt::settings::general_settings] "; + +static const std::string& DEFAULT_DEFAULT_RADAR_SITE = "KLSX"; + +class GeneralSettingsImpl +{ +public: + explicit GeneralSettingsImpl() {} + + ~GeneralSettingsImpl() {} + + void SetDefaults() { defaultRadarSite_ = DEFAULT_DEFAULT_RADAR_SITE; } + + std::string defaultRadarSite_; +}; + +GeneralSettings::GeneralSettings() : p(std::make_unique()) +{ +} +GeneralSettings::~GeneralSettings() = default; + +GeneralSettings::GeneralSettings(GeneralSettings&&) noexcept = default; +GeneralSettings& +GeneralSettings::operator=(GeneralSettings&&) noexcept = default; + +const std::string& GeneralSettings::default_radar_site() +{ + return p->defaultRadarSite_; +} + +boost::json::value GeneralSettings::ToJson() +{ + boost::json::object json; + + json["default_radar_site"] = p->defaultRadarSite_; + + return json; +} + +std::shared_ptr GeneralSettings::Create() +{ + std::shared_ptr generalSettings = + std::make_shared(); + + generalSettings->p->SetDefaults(); + + return generalSettings; +} + +std::shared_ptr +GeneralSettings::Load(const boost::json::value* json, bool& jsonDirty) +{ + std::shared_ptr generalSettings = + std::make_shared(); + + if (json != nullptr && json->is_object()) + { + jsonDirty |= + !util::json::FromJsonString(json->as_object(), + "default_radar_site", + generalSettings->p->defaultRadarSite_, + DEFAULT_DEFAULT_RADAR_SITE); + } + else + { + if (json == nullptr) + { + BOOST_LOG_TRIVIAL(warning) + << logPrefix_ << "Key is not present, resetting to defaults"; + } + else if (!json->is_object()) + { + BOOST_LOG_TRIVIAL(warning) + << logPrefix_ << "Invalid json, resetting to defaults"; + } + + generalSettings->p->SetDefaults(); + jsonDirty = true; + } + + return generalSettings; +} + +} // namespace settings +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/settings/general_settings.hpp b/scwx-qt/source/scwx/qt/settings/general_settings.hpp new file mode 100644 index 00000000..9ef2e9d5 --- /dev/null +++ b/scwx-qt/source/scwx/qt/settings/general_settings.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace settings +{ + +class GeneralSettingsImpl; + +class GeneralSettings +{ +public: + explicit GeneralSettings(); + ~GeneralSettings(); + + GeneralSettings(const GeneralSettings&) = delete; + GeneralSettings& operator=(const GeneralSettings&) = delete; + + GeneralSettings(GeneralSettings&&) noexcept; + GeneralSettings& operator=(GeneralSettings&&) noexcept; + + const std::string& default_radar_site(); + + boost::json::value ToJson(); + + static std::shared_ptr Create(); + static std::shared_ptr Load(const boost::json::value* json, + bool& jsonDirty); + +private: + std::unique_ptr p; +}; + +} // namespace settings +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/util/json.cpp b/scwx-qt/source/scwx/qt/util/json.cpp new file mode 100644 index 00000000..a0d1cb3d --- /dev/null +++ b/scwx-qt/source/scwx/qt/util/json.cpp @@ -0,0 +1,193 @@ +#include + +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace util +{ +namespace json +{ + +static const std::string logPrefix_ = "[scwx::qt::util::json] "; + +/* Adapted from: + * https://www.boost.org/doc/libs/1_77_0/libs/json/doc/html/json/examples.html#json.examples.pretty + * + * Copyright (c) 2019, 2020 Vinnie Falco + * Copyright (c) 2020 Krystian Stasiowski + * Distributed under the Boost Software License, Version 1.0. (See + * http://www.boost.org/LICENSE_1_0.txt) + */ +static void PrettyPrintJson(std::ostream& os, + boost::json::value const& jv, + std::string* indent = nullptr); + +bool FromJsonString(const boost::json::object& json, + const std::string& key, + std::string& value, + const std::string& defaultValue) +{ + const boost::json::value* jv = json.if_contains(key); + bool found = false; + + if (jv != nullptr) + { + if (jv->is_string()) + { + value = boost::json::value_to(*jv); + found = true; + } + else + { + BOOST_LOG_TRIVIAL(warning) + << logPrefix_ << key + << " is not a string, setting to default: " << defaultValue; + value = defaultValue; + } + } + else + { + BOOST_LOG_TRIVIAL(debug) + << logPrefix_ << key + << " is not present, setting to default: " << defaultValue; + value = defaultValue; + } + + return found; +} + +boost::json::value ReadJsonFile(const std::string& path) +{ + std::ifstream ifs {path}; + std::string line; + + boost::json::stream_parser p; + boost::json::error_code ec; + + while (std::getline(ifs, line)) + { + p.write(line, ec); + if (ec) + { + BOOST_LOG_TRIVIAL(warning) << logPrefix_ << ec.message(); + return nullptr; + } + } + + p.finish(ec); + if (ec) + { + BOOST_LOG_TRIVIAL(warning) << logPrefix_ << ec.message(); + return nullptr; + } + + return p.release(); +} + +void WriteJsonFile(const std::string& path, + const boost::json::value& json, + bool prettyPrint) +{ + std::ofstream ofs {path}; + + if (prettyPrint) + { + PrettyPrintJson(ofs, json); + } + else + { + ofs << json; + } + ofs.close(); +} + +static void PrettyPrintJson(std::ostream& os, + boost::json::value const& jv, + std::string* indent) +{ + std::string indent_; + if (!indent) + indent = &indent_; + switch (jv.kind()) + { + case boost::json::kind::object: + { + os << "{\n"; + indent->append(4, ' '); + auto const& obj = jv.get_object(); + if (!obj.empty()) + { + auto it = obj.begin(); + for (;;) + { + os << *indent << boost::json::serialize(it->key()) << " : "; + PrettyPrintJson(os, it->value(), indent); + if (++it == obj.end()) + break; + os << ",\n"; + } + } + os << "\n"; + indent->resize(indent->size() - 4); + os << *indent << "}"; + break; + } + + case boost::json::kind::array: + { + os << "[\n"; + indent->append(4, ' '); + auto const& arr = jv.get_array(); + if (!arr.empty()) + { + auto it = arr.begin(); + for (;;) + { + os << *indent; + PrettyPrintJson(os, *it, indent); + if (++it == arr.end()) + break; + os << ",\n"; + } + } + os << "\n"; + indent->resize(indent->size() - 4); + os << *indent << "]"; + break; + } + + case boost::json::kind::string: + { + os << boost::json::serialize(jv.get_string()); + break; + } + + case boost::json::kind::uint64: os << jv.get_uint64(); break; + + case boost::json::kind::int64: os << jv.get_int64(); break; + + case boost::json::kind::double_: os << jv.get_double(); break; + + case boost::json::kind::bool_: + if (jv.get_bool()) + os << "true"; + else + os << "false"; + break; + + case boost::json::kind::null: os << "null"; break; + } + + if (indent->empty()) + os << "\n"; +} + +} // namespace json +} // namespace util +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/util/json.hpp b/scwx-qt/source/scwx/qt/util/json.hpp new file mode 100644 index 00000000..c72d62d6 --- /dev/null +++ b/scwx-qt/source/scwx/qt/util/json.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include + +namespace scwx +{ +namespace qt +{ +namespace util +{ +namespace json +{ + +bool FromJsonString(const boost::json::object& json, + const std::string& key, + std::string& value, + const std::string& defaultValue); + +boost::json::value ReadJsonFile(const std::string& path); +void WriteJsonFile(const std::string& path, + const boost::json::value& json, + bool prettyPrint = true); + +} // namespace json +} // namespace util +} // namespace qt +} // namespace scwx