From f6576e01f30bf6cc324894440193388801c3fcf0 Mon Sep 17 00:00:00 2001 From: Seraphima Zykova Date: Tue, 18 Jan 2022 16:13:32 +0300 Subject: [PATCH] [FancyZones] Split zones-settings: layout templates (#15588) --- .../FancyZonesLib/FancyZonesData.cpp | 6 + .../fancyzones/FancyZonesLib/FancyZonesData.h | 2 + .../FancyZonesData/LayoutTemplates.h | 26 ++++ .../FancyZonesLib/FancyZonesLib.vcxproj | 1 + .../FancyZonesLib.vcxproj.filters | 3 + .../fancyzones/FancyZonesLib/JsonHelpers.cpp | 36 +++-- .../fancyzones/FancyZonesLib/JsonHelpers.h | 6 +- .../UnitTests/JsonHelpers.Tests.cpp | 133 +++++------------- .../UnitTests/LayoutTemplatesTests.Spec.cpp | 95 +++++++++++++ .../UnitTests/UnitTests.vcxproj | 1 + .../UnitTests/UnitTests.vcxproj.filters | 3 + .../FancyZonesTests/UnitTests/Util.h | 70 +++++++++ .../editor/FancyZonesEditor/EditorWindow.cs | 2 +- .../FancyZonesEditor/MainWindow.xaml.cs | 4 + .../Properties/Resources.Designer.cs | 9 ++ .../Properties/Resources.resx | 3 + .../Utils/FancyZonesEditorIO.cs | 121 ++++++++++++---- 17 files changed, 380 insertions(+), 141 deletions(-) create mode 100644 src/modules/fancyzones/FancyZonesLib/FancyZonesData/LayoutTemplates.h create mode 100644 src/modules/fancyzones/FancyZonesTests/UnitTests/LayoutTemplatesTests.Spec.cpp diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesData.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZonesData.cpp index 95456c075f..6ddbcaf696 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesData.cpp +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesData.cpp @@ -170,6 +170,12 @@ void FancyZonesData::ReplaceZoneSettingsFileFromOlderVersions() //deviceInfoMap = JSONHelpers::ParseDeviceInfos(fancyZonesDataJSON); //customZoneSetsMap = JSONHelpers::ParseCustomZoneSets(fancyZonesDataJSON); + auto templates = JSONHelpers::ParseLayoutTemplates(fancyZonesDataJSON); + if (templates) + { + JSONHelpers::SaveLayoutTemplates(templates.value()); + } + auto quickKeysMap = JSONHelpers::ParseQuickKeys(fancyZonesDataJSON); if (quickKeysMap) { diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesData.h b/src/modules/fancyzones/FancyZonesLib/FancyZonesData.h index 0ac2311c2a..40ed9be2aa 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesData.h +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesData.h @@ -31,6 +31,7 @@ namespace FancyZonesUnitTests class WorkAreaUnitTests; class WorkAreaCreationUnitTests; class LayoutHotkeysUnitTests; + class LayoutTemplatesUnitTests; } #endif @@ -90,6 +91,7 @@ private: friend class FancyZonesUnitTests::WorkAreaCreationUnitTests; friend class FancyZonesUnitTests::ZoneSetCalculateZonesUnitTests; friend class FancyZonesUnitTests::LayoutHotkeysUnitTests; + friend class FancyZonesUnitTests::LayoutTemplatesUnitTests; inline void SetDeviceInfo(const FancyZonesDataTypes::DeviceIdData& deviceId, FancyZonesDataTypes::DeviceInfoData data) { diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/LayoutTemplates.h b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/LayoutTemplates.h new file mode 100644 index 0000000000..06ce93cd30 --- /dev/null +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/LayoutTemplates.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include + +namespace NonLocalizable +{ + namespace LayoutTemplatesIds + { + const static wchar_t* LayoutTemplatesArrayID = L"layout-templates"; + } +} + +class LayoutTemplates +{ +public: + inline static std::wstring LayoutTemplatesFileName() + { + std::wstring saveFolderPath = PTSettingsHelper::get_module_save_folder_location(NonLocalizable::ModuleKey); +#if defined(UNIT_TESTS) + return saveFolderPath + L"\\test-layout-templates.json"; +#endif + return saveFolderPath + L"\\layout-templates.json"; + } +}; \ No newline at end of file diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj index 2a10234956..68eeae71a9 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj @@ -39,6 +39,7 @@ + diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters index 80b3765f64..ffc0b99a1d 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters @@ -96,6 +96,9 @@ Header Files + + Header Files + diff --git a/src/modules/fancyzones/FancyZonesLib/JsonHelpers.cpp b/src/modules/fancyzones/FancyZonesLib/JsonHelpers.cpp index d0054b5d24..6f2f0d47ca 100644 --- a/src/modules/fancyzones/FancyZonesLib/JsonHelpers.cpp +++ b/src/modules/fancyzones/FancyZonesLib/JsonHelpers.cpp @@ -7,6 +7,7 @@ #include "util.h" #include +#include #include @@ -583,23 +584,9 @@ namespace JSONHelpers auto before = json::from_file(zonesSettingsFileName); json::JsonObject root{}; - json::JsonArray templates{}; - - try - { - if (before.has_value() && before->HasKey(NonLocalizable::Templates)) - { - templates = before->GetNamedArray(NonLocalizable::Templates); - } - } - catch (const winrt::hresult_error&) - { - - } root.SetNamedValue(NonLocalizable::DevicesStr, JSONHelpers::SerializeDeviceInfos(deviceInfoMap)); root.SetNamedValue(NonLocalizable::CustomZoneSetsStr, JSONHelpers::SerializeCustomZoneSets(customZoneSetsMap)); - root.SetNamedValue(NonLocalizable::Templates, templates); if (!before.has_value() || before.value().Stringify() != root.Stringify()) { @@ -769,4 +756,25 @@ namespace JSONHelpers root.SetNamedValue(NonLocalizable::LayoutHotkeysIds::LayoutHotkeysArrayID, keysArray); json::to_file(LayoutHotkeys::LayoutHotkeysFileName(), root); } + + std::optional ParseLayoutTemplates(const json::JsonObject& fancyZonesDataJSON) + { + try + { + return fancyZonesDataJSON.GetNamedArray(NonLocalizable::Templates); + } + catch (const winrt::hresult_error& e) + { + Logger::error(L"Parsing layout templates error: {}", e.message()); + } + + return std::nullopt; + } + + void SaveLayoutTemplates(const json::JsonArray& templates) + { + json::JsonObject root{}; + root.SetNamedValue(NonLocalizable::LayoutTemplatesIds::LayoutTemplatesArrayID, templates); + json::to_file(LayoutTemplates::LayoutTemplatesFileName(), root); + } } \ No newline at end of file diff --git a/src/modules/fancyzones/FancyZonesLib/JsonHelpers.h b/src/modules/fancyzones/FancyZonesLib/JsonHelpers.h index a6af69113e..604f848dc2 100644 --- a/src/modules/fancyzones/FancyZonesLib/JsonHelpers.h +++ b/src/modules/fancyzones/FancyZonesLib/JsonHelpers.h @@ -105,7 +105,11 @@ namespace JSONHelpers TCustomZoneSetsMap ParseCustomZoneSets(const json::JsonObject& fancyZonesDataJSON); json::JsonArray SerializeCustomZoneSets(const TCustomZoneSetsMap& customZoneSetsMap); - // replace zone-settings: layout hotkeys + // replace zones-settings: layout hotkeys std::optional ParseQuickKeys(const json::JsonObject& fancyZonesDataJSON); void SaveLayoutHotkeys(const TLayoutQuickKeysMap& quickKeysMap); + + // replace zones-settings: layout templates + std::optional ParseLayoutTemplates(const json::JsonObject& fancyZonesDataJSON); + void SaveLayoutTemplates(const json::JsonArray& templates); } diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/JsonHelpers.Tests.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/JsonHelpers.Tests.cpp index f8e9d043a4..5b1608dea3 100644 --- a/src/modules/fancyzones/FancyZonesTests/UnitTests/JsonHelpers.Tests.cpp +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/JsonHelpers.Tests.cpp @@ -19,46 +19,6 @@ using namespace Microsoft::VisualStudio::CppUnitTestFramework; namespace FancyZonesUnitTests { - void compareJsonObjects(const json::JsonObject& expected, const json::JsonObject& actual, bool recursive = true) - { - auto iter = expected.First(); - while (iter.HasCurrent()) - { - const auto key = iter.Current().Key(); - Assert::IsTrue(actual.HasKey(key), key.c_str()); - - const std::wstring expectedStringified = iter.Current().Value().Stringify().c_str(); - const std::wstring actualStringified = actual.GetNamedValue(key).Stringify().c_str(); - - if (recursive) - { - json::JsonObject expectedJson; - if (json::JsonObject::TryParse(expectedStringified, expectedJson)) - { - json::JsonObject actualJson; - if (json::JsonObject::TryParse(actualStringified, actualJson)) - { - compareJsonObjects(expectedJson, actualJson, true); - } - else - { - Assert::IsTrue(false, key.c_str()); - } - } - else - { - Assert::AreEqual(expectedStringified, actualStringified, key.c_str()); - } - } - else - { - Assert::AreEqual(expectedStringified, actualStringified, key.c_str()); - } - - iter.MoveNext(); - } - } - TEST_CLASS (IdValidationUnitTest) { TEST_METHOD (GuidValid) @@ -201,7 +161,8 @@ namespace FancyZonesUnitTests info.sensitivityRadius = 50; auto actual = CanvasLayoutInfoJSON::ToJson(info); - compareJsonObjects(m_json, actual); + auto res = CustomAssert::CompareJsonObjects(m_json, actual); + Assert::IsTrue(res.first, res.second.c_str()); } TEST_METHOD (FromJson) @@ -465,7 +426,8 @@ namespace FancyZonesUnitTests GridLayoutInfo info = m_info; auto actual = GridLayoutInfoJSON::ToJson(info); - compareJsonObjects(expected, actual); + auto res = CustomAssert::CompareJsonObjects(expected, actual); + Assert::IsTrue(res.first, res.second.c_str()); } @@ -486,7 +448,8 @@ namespace FancyZonesUnitTests info.m_spacing = 99; auto actual = GridLayoutInfoJSON::ToJson(info); - compareJsonObjects(expected, actual); + auto res = CustomAssert::CompareJsonObjects(expected, actual); + Assert::IsTrue(res.first, res.second.c_str()); } TEST_METHOD (FromJson) @@ -605,7 +568,8 @@ namespace FancyZonesUnitTests expected.SetNamedValue(L"info", GridLayoutInfoJSON::ToJson(std::get(zoneSet.data.info))); auto actual = CustomZoneSetJSON::ToJson(zoneSet); - compareJsonObjects(expected, actual); + auto res = CustomAssert::CompareJsonObjects(expected, actual); + Assert::IsTrue(res.first, res.second.c_str()); } TEST_METHOD (ToJsonCanvas) @@ -616,7 +580,8 @@ namespace FancyZonesUnitTests expected.SetNamedValue(L"info", CanvasLayoutInfoJSON::ToJson(std::get(zoneSet.data.info))); auto actual = CustomZoneSetJSON::ToJson(zoneSet); - compareJsonObjects(expected, actual); + auto res = CustomAssert::CompareJsonObjects(expected, actual); + Assert::IsTrue(res.first, res.second.c_str()); } TEST_METHOD (FromJsonGrid) @@ -718,7 +683,8 @@ namespace FancyZonesUnitTests json::JsonObject expected = json::JsonObject::Parse(L"{\"uuid\": \"{33A2B101-06E0-437B-A61E-CDBECF502906}\", \"type\": \"rows\"}"); ZoneSetData data{ L"{33A2B101-06E0-437B-A61E-CDBECF502906}", ZoneSetLayoutType::Rows }; const auto actual = ZoneSetDataJSON::ToJson(data); - compareJsonObjects(expected, actual); + auto res = CustomAssert::CompareJsonObjects(expected, actual); + Assert::IsTrue(res.first, res.second.c_str()); } TEST_METHOD (FromJsonGeneral) @@ -782,7 +748,8 @@ namespace FancyZonesUnitTests json::JsonObject expected = json::JsonObject::Parse(L"{\"app-path\": \"appPath\", \"history\":[{\"zone-index-set\": [54321], \"device-id\": \"device-id_0_0_{00000000-0000-0000-0000-000000000000}\", \"zoneset-uuid\": \"zoneset-uuid\"}]}"); auto actual = AppZoneHistoryJSON::ToJson(appZoneHistory); - compareJsonObjects(expected, actual); + auto res = CustomAssert::CompareJsonObjects(expected, actual); + Assert::IsTrue(res.first, res.second.c_str()); } TEST_METHOD (FromJson) @@ -860,7 +827,8 @@ namespace FancyZonesUnitTests auto actual = AppZoneHistoryJSON::ToJson(appZoneHistory); std::wstring s = actual.Stringify().c_str(); - compareJsonObjects(expected, actual); + auto res = CustomAssert::CompareJsonObjects(expected, actual); + Assert::IsTrue(res.first, res.second.c_str()); } TEST_METHOD (FromJsonMultipleDesktopAppHistory) @@ -912,7 +880,8 @@ namespace FancyZonesUnitTests json::JsonObject expected = m_defaultJson; auto actual = DeviceInfoJSON::ToJson(deviceInfo); - compareJsonObjects(expected, actual); + auto res = CustomAssert::CompareJsonObjects(expected, actual); + Assert::IsTrue(res.first, res.second.c_str()); } TEST_METHOD (FromJson) @@ -1036,15 +1005,6 @@ namespace FancyZonesUnitTests HINSTANCE m_hInst{}; FancyZonesData& m_fzData = FancyZonesDataInstance(); - void compareJsonArrays(const json::JsonArray& expected, const json::JsonArray& actual) - { - Assert::AreEqual(expected.Size(), actual.Size()); - for (uint32_t i = 0; i < expected.Size(); i++) - { - compareJsonObjects(expected.GetObjectAt(i), actual.GetObjectAt(i)); - } - } - TEST_METHOD_INITIALIZE(Init) { m_hInst = (HINSTANCE)GetModuleHandleW(nullptr); @@ -1181,7 +1141,8 @@ namespace FancyZonesUnitTests const auto& deviceInfoMap = ParseDeviceInfos(expected); auto actual = SerializeDeviceInfos(deviceInfoMap); - compareJsonArrays(expectedDevices, actual); + auto res = CustomAssert::CompareJsonArrays(expectedDevices, actual); + Assert::IsTrue(res.first, res.second.c_str()); } TEST_METHOD (AppZoneHistoryParseSingle) @@ -1353,7 +1314,8 @@ namespace FancyZonesUnitTests auto appZoneHistoryMap = ParseAppZoneHistory(json); const auto& actual = SerializeAppZoneHistory(appZoneHistoryMap); - compareJsonArrays(expected, actual); + auto res = CustomAssert::CompareJsonArrays(expected, actual); + Assert::IsTrue(res.first, res.second.c_str()); } TEST_METHOD (AppZoneHistorySerializeMany) @@ -1393,7 +1355,8 @@ namespace FancyZonesUnitTests const auto& appZoneHistoryMap = ParseAppZoneHistory(json); const auto& actual = SerializeAppZoneHistory(appZoneHistoryMap); - compareJsonArrays(expected, actual); + auto res = CustomAssert::CompareJsonArrays(expected, actual); + Assert::IsTrue(res.first, res.second.c_str()); } TEST_METHOD (AppZoneHistorySerializeEmpty) @@ -1405,7 +1368,8 @@ namespace FancyZonesUnitTests const auto& appZoneHistoryMap = ParseAppZoneHistory(json); const auto& actual = SerializeAppZoneHistory(appZoneHistoryMap); - compareJsonArrays(expected, actual); + auto res = CustomAssert::CompareJsonArrays(expected, actual); + Assert::IsTrue(res.first, res.second.c_str()); } TEST_METHOD (CustomZoneSetsParseSingle) @@ -1519,7 +1483,8 @@ namespace FancyZonesUnitTests const auto& customZoneSetsMap = ParseCustomZoneSets(json); auto actual = SerializeCustomZoneSets(customZoneSetsMap); - compareJsonArrays(expected, actual); + auto res = CustomAssert::CompareJsonArrays(expected, actual); + Assert::IsTrue(res.first, res.second.c_str()); } TEST_METHOD (CustomZoneSetsSerializeMany) @@ -1542,7 +1507,8 @@ namespace FancyZonesUnitTests const auto& customZoneSetsMap = ParseCustomZoneSets(json); auto actual = SerializeCustomZoneSets(customZoneSetsMap); - compareJsonArrays(expected, actual); + auto res = CustomAssert::CompareJsonArrays(expected, actual); + Assert::IsTrue(res.first, res.second.c_str()); } TEST_METHOD (CustomZoneSetsSerializeEmpty) @@ -1554,7 +1520,8 @@ namespace FancyZonesUnitTests const auto& customZoneSetsMap = ParseCustomZoneSets(json); auto actual = SerializeCustomZoneSets(customZoneSetsMap); - compareJsonArrays(expected, actual); + auto res = CustomAssert::CompareJsonArrays(expected, actual); + Assert::IsTrue(res.first, res.second.c_str()); } TEST_METHOD (SetActiveZoneSet) @@ -1756,36 +1723,6 @@ namespace FancyZonesUnitTests Assert::IsTrue(actual); } - TEST_METHOD (SaveFancyZonesDataWithTemplates) - { - FancyZonesData data; - data.SetSettingsModulePath(m_moduleName); - const auto& jsonPath = data.zonesSettingsFileName; - - // json with templates - json::JsonObject expectedJsonObj; - json::JsonObject templateObj = json::JsonObject::Parse(L"{\"type\": \"focus\", \"show-spacing\": false, \"spacing\": 15, \"zone-count\": 7, \"sensitivity-radius\": 25}"); - json::JsonArray templatesArray{}; - templatesArray.Append(templateObj); - expectedJsonObj.SetNamedValue(L"devices", json::JsonArray{}); - expectedJsonObj.SetNamedValue(L"custom-zone-sets", json::JsonArray{}); - expectedJsonObj.SetNamedValue(L"templates", templatesArray); - - // write json with templates to file - json::to_file(jsonPath, expectedJsonObj); - - data.SaveAppZoneHistoryAndZoneSettings(); - - // verify that file was written successfully - Assert::IsTrue(std::filesystem::exists(jsonPath)); - - // verify that templates were not changed after calling SaveFancyZonesData() - std::wstring str; - std::wifstream { jsonPath, std::ios::binary } >> str; - json::JsonObject actualJson = json::JsonObject::Parse(str); - compareJsonObjects(expectedJsonObj, actualJson); - } - TEST_METHOD (AppLastZoneIndex) { const FancyZonesDataTypes::DeviceIdData deviceId{ L"device-id" }; @@ -2240,7 +2177,8 @@ namespace FancyZonesUnitTests const auto actual = MonitorInfo::ToJson(monitor); - compareJsonObjects(expected, actual); + auto res = CustomAssert::CompareJsonObjects(expected, actual); + Assert::IsTrue(res.first, res.second.c_str()); } TEST_METHOD(EditorArgsToJson) @@ -2258,7 +2196,8 @@ namespace FancyZonesUnitTests const auto expected = json::JsonObject::Parse(expectedStr); const auto actual = EditorArgs::ToJson(args); - compareJsonObjects(expected, actual); + auto res = CustomAssert::CompareJsonObjects(expected, actual); + Assert::IsTrue(res.first, res.second.c_str()); } }; } \ No newline at end of file diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/LayoutTemplatesTests.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/LayoutTemplatesTests.Spec.cpp new file mode 100644 index 0000000000..f5951462b6 --- /dev/null +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/LayoutTemplatesTests.Spec.cpp @@ -0,0 +1,95 @@ +#include "pch.h" +#include + +#include +#include + +#include "util.h" + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; + +namespace FancyZonesUnitTests +{ + TEST_CLASS (LayoutTemplatesUnitTests) + { + FancyZonesData& m_fzData = FancyZonesDataInstance(); + std::wstring m_testFolder = L"FancyZonesUnitTests"; + + TEST_METHOD_INITIALIZE(Init) + { + m_fzData.clear_data(); + m_fzData.SetSettingsModulePath(L"FancyZonesUnitTests"); + } + + TEST_METHOD_CLEANUP(CleanUp) + { + std::filesystem::remove_all(LayoutTemplates::LayoutTemplatesFileName()); + std::filesystem::remove_all(PTSettingsHelper::get_module_save_folder_location(m_testFolder)); + } + + TEST_METHOD (MoveLayoutHotkeysFromZonesSettings) + { + // prepare + json::JsonObject root{}; + json::JsonArray devicesArray{}, customLayoutsArray{}, templateLayoutsArray{}, quickLayoutKeysArray{}; + root.SetNamedValue(L"devices", devicesArray); + root.SetNamedValue(L"custom-zone-sets", customLayoutsArray); + root.SetNamedValue(L"quick-layout-keys", quickLayoutKeysArray); + + { + json::JsonObject layout{}; + layout.SetNamedValue(L"type", json::value(L"blank")); + layout.SetNamedValue(L"show-spacing", json::value(false)); + layout.SetNamedValue(L"spacing", json::value(0)); + layout.SetNamedValue(L"zone-count", json::value(0)); + layout.SetNamedValue(L"sensitivity-radius", json::value(0)); + templateLayoutsArray.Append(layout); + } + { + json::JsonObject layout{}; + layout.SetNamedValue(L"type", json::value(L"grid")); + layout.SetNamedValue(L"show-spacing", json::value(true)); + layout.SetNamedValue(L"spacing", json::value(-10)); + layout.SetNamedValue(L"zone-count", json::value(4)); + layout.SetNamedValue(L"sensitivity-radius", json::value(30)); + templateLayoutsArray.Append(layout); + } + + root.SetNamedValue(L"templates", templateLayoutsArray); + json::to_file(m_fzData.GetZoneSettingsPath(m_testFolder), root); + + // test + m_fzData.ReplaceZoneSettingsFileFromOlderVersions(); + + auto result = json::from_file(LayoutTemplates::LayoutTemplatesFileName()); + Assert::IsTrue(result.has_value()); + Assert::IsTrue(result.value().HasKey(NonLocalizable::LayoutTemplatesIds::LayoutTemplatesArrayID)); + auto res = CustomAssert::CompareJsonArrays(templateLayoutsArray, result.value().GetNamedArray(NonLocalizable::LayoutTemplatesIds::LayoutTemplatesArrayID)); + Assert::IsTrue(res.first, res.second.c_str()); + } + + TEST_METHOD (MoveLayoutHotkeysFromZonesSettingsNoTemplates) + { + // prepare + json::JsonObject root{}; + json::JsonArray devicesArray{}, customLayoutsArray{}, quickLayoutKeysArray{}; + root.SetNamedValue(L"devices", devicesArray); + root.SetNamedValue(L"custom-zone-sets", customLayoutsArray); + root.SetNamedValue(L"quick-layout-keys", quickLayoutKeysArray); + json::to_file(m_fzData.GetZoneSettingsPath(m_testFolder), root); + + // test + m_fzData.ReplaceZoneSettingsFileFromOlderVersions(); + auto result = json::from_file(LayoutTemplates::LayoutTemplatesFileName()); + Assert::IsFalse(result.has_value()); + } + + TEST_METHOD (MoveLayoutHotkeysFromZonesSettingsNoFile) + { + // test + m_fzData.ReplaceZoneSettingsFileFromOlderVersions(); + auto result = json::from_file(LayoutTemplates::LayoutTemplatesFileName()); + Assert::IsFalse(result.has_value()); + } + }; +} \ No newline at end of file diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj b/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj index 9d40316ad8..ab799fd0e9 100644 --- a/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj @@ -45,6 +45,7 @@ + Create diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj.filters b/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj.filters index 93c37c8ca6..7d7690bbec 100644 --- a/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj.filters +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj.filters @@ -45,6 +45,9 @@ Source Files + + Source Files + diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/Util.h b/src/modules/fancyzones/FancyZonesTests/UnitTests/Util.h index b7956a2f83..c116d860a4 100644 --- a/src/modules/fancyzones/FancyZonesTests/UnitTests/Util.h +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/Util.h @@ -28,6 +28,76 @@ namespace CustomAssert Microsoft::VisualStudio::CppUnitTestFramework::Assert::IsTrue(a1[i].first == a2[i].first); } } + + static std::pair CompareJsonObjects(const json::JsonObject& expected, const json::JsonObject& actual, bool recursive = true) + { + auto iter = expected.First(); + while (iter.HasCurrent()) + { + const auto key = iter.Current().Key(); + if (!actual.HasKey(key)) + { + return std::make_pair(false, key.c_str()); + } + + const std::wstring expectedStringified = iter.Current().Value().Stringify().c_str(); + const std::wstring actualStringified = actual.GetNamedValue(key).Stringify().c_str(); + + if (recursive) + { + json::JsonObject expectedJson; + if (json::JsonObject::TryParse(expectedStringified, expectedJson)) + { + json::JsonObject actualJson; + if (json::JsonObject::TryParse(actualStringified, actualJson)) + { + CompareJsonObjects(expectedJson, actualJson, true); + } + else + { + return std::make_pair(false, key.c_str()); + } + } + else + { + if (expectedStringified != actualStringified) + { + return std::make_pair(false, key.c_str()); + } + } + } + else + { + if (expectedStringified != actualStringified) + { + return std::make_pair(false, key.c_str()); + } + } + + iter.MoveNext(); + } + + return std::make_pair(true, L""); + } + + static std::pair CompareJsonArrays(const json::JsonArray& expected, const json::JsonArray& actual) + { + if (expected.Size() != actual.Size()) + { + return std::make_pair(false, L"Array sizes don't match"); + } + + for (uint32_t i = 0; i < expected.Size(); i++) + { + auto res = CustomAssert::CompareJsonObjects(expected.GetObjectAt(i), actual.GetObjectAt(i)); + if (!res.first) + { + return res; + } + } + + return std::make_pair(true, L""); + } } namespace Mocks diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/EditorWindow.cs b/src/modules/fancyzones/editor/FancyZonesEditor/EditorWindow.cs index ca2e7a6433..b5fe1281fe 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/EditorWindow.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/EditorWindow.cs @@ -31,7 +31,7 @@ namespace FancyZonesEditor App.Overlay.SetLayoutSettings(App.Overlay.Monitors[App.Overlay.CurrentDesktop], model); } - App.FancyZonesEditorIO.SerializeZoneSettings(); + App.FancyZonesEditorIO.SerializeLayoutTemplates(); Close(); } diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs index e195d7fd3a..6dd61f3573 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs @@ -281,6 +281,8 @@ namespace FancyZonesEditor CancelLayoutChanges(); App.FancyZonesEditorIO.SerializeZoneSettings(); + App.FancyZonesEditorIO.SerializeLayoutHotkeys(); + App.FancyZonesEditorIO.SerializeLayoutTemplates(); App.Overlay.CloseLayoutWindow(); App.Current.Shutdown(); } @@ -424,6 +426,8 @@ namespace FancyZonesEditor } App.FancyZonesEditorIO.SerializeZoneSettings(); + App.FancyZonesEditorIO.SerializeLayoutTemplates(); + App.FancyZonesEditorIO.SerializeLayoutHotkeys(); // reset selected model Select(_settings.AppliedModel); diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.Designer.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.Designer.cs index cabcfad79c..aa45e59081 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.Designer.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.Designer.cs @@ -393,6 +393,15 @@ namespace FancyZonesEditor.Properties { } } + /// + /// Looks up a localized string similar to An error occurred while parsing template layouts.. + /// + public static string Error_Parsing_Layout_Templates_Message { + get { + return ResourceManager.GetString("Error_Parsing_Layout_Templates_Message", resourceCulture); + } + } + /// /// Looks up a localized string similar to A layout that contained invalid data has been removed.. /// diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.resx b/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.resx index b8054cbff8..a7f09d85a5 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.resx +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.resx @@ -386,4 +386,7 @@ An error occurred while parsing layout hotkeys. + + An error occurred while parsing template layouts. + \ No newline at end of file diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Utils/FancyZonesEditorIO.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Utils/FancyZonesEditorIO.cs index 1527bf90ed..a53e0989ad 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Utils/FancyZonesEditorIO.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Utils/FancyZonesEditorIO.cs @@ -30,6 +30,7 @@ namespace FancyZonesEditor.Utils // Non-localizable strings: Files private const string ZonesSettingsFile = "\\Microsoft\\PowerToys\\FancyZones\\zones-settings.json"; private const string LayoutHotkeysFile = "\\Microsoft\\PowerToys\\FancyZones\\layout-hotkeys.json"; + private const string LayoutTemplatesFile = "\\Microsoft\\PowerToys\\FancyZones\\layout-templates.json"; private const string ParamsFile = "\\Microsoft\\PowerToys\\FancyZones\\editor-parameters.json"; // Non-localizable string: Multi-monitor id @@ -52,6 +53,8 @@ namespace FancyZonesEditor.Utils public string FancyZonesLayoutHotkeysFile { get; private set; } + public string FancyZonesLayoutTemplatesFile { get; private set; } + public string FancyZonesEditorParamsFile { get; private set; } private enum CmdArgs @@ -182,7 +185,7 @@ namespace FancyZonesEditor.Utils public JsonElement Info { get; set; } // CanvasInfoWrapper or GridInfoWrapper } - // zones-settings: templates + // layout-templates: templates private struct TemplateLayoutWrapper { public string Type { get; set; } @@ -196,6 +199,12 @@ namespace FancyZonesEditor.Utils public int SensitivityRadius { get; set; } } + // layout-templates: layout-templates-wrapper + private struct TemplateLayoutsListWrapper + { + public List TemplatesList { get; set; } + } + // layout-hotkeys: layout-hotkeys-wrapper private struct LayoutHotkeyWrapper { @@ -216,8 +225,6 @@ namespace FancyZonesEditor.Utils public List Devices { get; set; } public List CustomZoneSets { get; set; } - - public List Templates { get; set; } } private struct EditorParams @@ -250,6 +257,7 @@ namespace FancyZonesEditor.Utils var localAppDataDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); FancyZonesSettingsFile = localAppDataDir + ZonesSettingsFile; FancyZonesLayoutHotkeysFile = localAppDataDir + LayoutHotkeysFile; + FancyZonesLayoutTemplatesFile = localAppDataDir + LayoutTemplatesFile; FancyZonesEditorParamsFile = localAppDataDir + ParamsFile; } @@ -523,8 +531,6 @@ namespace FancyZonesEditor.Utils { bool devicesParsingResult = SetDevices(zoneSettings.Devices); bool customZonesParsingResult = SetCustomLayouts(zoneSettings.CustomZoneSets); - bool templatesParsingResult = SetTemplateLayouts(zoneSettings.Templates); - if (!devicesParsingResult || !customZonesParsingResult) { return new ParsingResult(false, FancyZonesEditor.Properties.Resources.Error_Parsing_Zones_Settings_Message, settingsString); @@ -543,6 +549,12 @@ namespace FancyZonesEditor.Utils return parsingHotkeysResult; } + var parsingTemplatesResult = ParseLayoutTemplates(); + if (!parsingTemplatesResult.Result) + { + return parsingTemplatesResult; + } + return new ParsingResult(true); } @@ -585,6 +597,46 @@ namespace FancyZonesEditor.Utils return new ParsingResult(true); } + public ParsingResult ParseLayoutTemplates() + { + Logger.LogTrace(); + + if (_fileSystem.File.Exists(FancyZonesLayoutTemplatesFile)) + { + TemplateLayoutsListWrapper templates; + string dataString = string.Empty; + + try + { + dataString = ReadFile(FancyZonesLayoutTemplatesFile); + templates = JsonSerializer.Deserialize(dataString, _options); + } + catch (Exception ex) + { + Logger.LogError("Layout templates parsing error", ex); + return new ParsingResult(false, ex.Message, dataString); + } + + try + { + bool parsingResult = SetTemplateLayouts(templates.TemplatesList); + if (parsingResult) + { + return new ParsingResult(true); + } + + return new ParsingResult(false, FancyZonesEditor.Properties.Resources.Error_Parsing_Layout_Templates_Message, dataString); + } + catch (Exception ex) + { + Logger.LogError("Layout templates parsing error", ex); + return new ParsingResult(false, ex.Message, dataString); + } + } + + return new ParsingResult(false, FancyZonesEditor.Properties.Resources.Error_Parsing_Layout_Templates_Message); + } + public void SerializeZoneSettings() { Logger.LogTrace(); @@ -592,7 +644,6 @@ namespace FancyZonesEditor.Utils ZoneSettingsWrapper zoneSettings = new ZoneSettingsWrapper { }; zoneSettings.Devices = new List(); zoneSettings.CustomZoneSets = new List(); - zoneSettings.Templates = new List(); // Serialize used devices foreach (var monitor in App.Overlay.Monitors) @@ -710,25 +761,6 @@ namespace FancyZonesEditor.Utils zoneSettings.CustomZoneSets.Add(customLayout); } - // Serialize template layouts - foreach (LayoutModel layout in MainWindowSettingsModel.DefaultModels) - { - TemplateLayoutWrapper wrapper = new TemplateLayoutWrapper - { - Type = LayoutTypeToJsonTag(layout.Type), - SensitivityRadius = layout.SensitivityRadius, - ZoneCount = layout.TemplateZoneCount, - }; - - if (layout is GridLayoutModel grid) - { - wrapper.ShowSpacing = grid.ShowSpacing; - wrapper.Spacing = grid.Spacing; - } - - zoneSettings.Templates.Add(wrapper); - } - try { string jsonString = JsonSerializer.Serialize(zoneSettings, _options); @@ -739,11 +771,9 @@ namespace FancyZonesEditor.Utils Logger.LogError("Serialize zone settings error", ex); App.ShowExceptionMessageBox(Properties.Resources.Error_Applying_Layout, ex); } - - SerializeLayoutHotkeys(); } - private void SerializeLayoutHotkeys() + public void SerializeLayoutHotkeys() { LayoutHotkeysWrapper hotkeys = new LayoutHotkeysWrapper { }; hotkeys.LayoutHotkeys = new List(); @@ -781,6 +811,41 @@ namespace FancyZonesEditor.Utils } } + public void SerializeLayoutTemplates() + { + TemplateLayoutsListWrapper templates = new TemplateLayoutsListWrapper { }; + templates.TemplatesList = new List(); + + foreach (LayoutModel layout in MainWindowSettingsModel.DefaultModels) + { + TemplateLayoutWrapper wrapper = new TemplateLayoutWrapper + { + Type = LayoutTypeToJsonTag(layout.Type), + SensitivityRadius = layout.SensitivityRadius, + ZoneCount = layout.TemplateZoneCount, + }; + + if (layout is GridLayoutModel grid) + { + wrapper.ShowSpacing = grid.ShowSpacing; + wrapper.Spacing = grid.Spacing; + } + + templates.TemplatesList.Add(wrapper); + } + + try + { + string jsonString = JsonSerializer.Serialize(templates, _options); + _fileSystem.File.WriteAllText(FancyZonesLayoutTemplatesFile, jsonString); + } + catch (Exception ex) + { + Logger.LogError("Serialize layout templates error", ex); + App.ShowExceptionMessageBox(Properties.Resources.Error_Applying_Layout, ex); + } + } + private string ReadFile(string fileName) { Logger.LogTrace();