From 2ea301ff5d9da0357ccb70861686f8b707843d38 Mon Sep 17 00:00:00 2001 From: Nice3point Date: Sun, 29 Sep 2024 17:13:55 +0300 Subject: [PATCH 001/121] Disable Nuke for the feature branches --- .github/workflows/Nuke.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/Nuke.yml b/.github/workflows/Nuke.yml index 3c342fe6..c6776c55 100644 --- a/.github/workflows/Nuke.yml +++ b/.github/workflows/Nuke.yml @@ -1,7 +1,11 @@ name: Nuke + on: - pull_request: push: + branches: + - 'master' + - 'dev' + pull_request: jobs: windows: From 8983350eca2585aeb397b736ed7ba559ac42b693 Mon Sep 17 00:00:00 2001 From: Nice3point Date: Sun, 6 Oct 2024 15:14:37 +0300 Subject: [PATCH 002/121] Setup solution structure. Split projects --- Contributing.md | 5 +- RevitLookup.sln | 160 ------------ RevitLookup.slnx | 198 +++++++++++++++ build/Build.cs | 1 - build/Build.csproj | 3 +- global.json | 5 +- {doc => history}/Images/ja_1.png | Bin {doc => history}/Images/ja_2.png | Bin {doc => history}/Images/ja_3.png | Bin {doc => history}/Images/ja_4.png | Bin {doc => history}/Images/ja_5.png | Bin {doc => history}/Images/ja_6.png | Bin {doc => history}/Readme_2005-05-11.doc | Bin {doc => history}/Readme_2005-05-11.md | 240 +++++++++--------- {doc => history}/Wishlist.md | 0 .../LookupEngine.Abstractions.csproj | 10 + .../Benchmark.cs | 2 +- .../ClosureBenchmark.cs | 11 +- .../LookupEngine.Benchmarks.csproj} | 6 +- .../ResolveTypeBench.cs | 8 +- .../SortBench.cs | 10 +- .../TypeEqualBench.cs | 15 +- source/LookupEngine/LookupEngine.csproj | 10 + .../RevitLookup.Abstractions.csproj | 10 + source/RevitLookup.UI.Demo/App.xaml.cs | 61 ----- source/RevitLookup.UI.Demo/HostProvider.cs | 67 ----- .../Mock/Services/MockLookupService.cs | 116 --------- .../Mock/Services/MockSnoopVisualService.cs | 131 ---------- .../Mock/ViewModels/MockDashboardViewModel.cs | 143 ----------- .../Mock/ViewModels/MockEventsViewModel.cs | 163 ------------ .../Mock/ViewModels/MockSnoopViewModel.cs | 148 ----------- .../RevitLookup.UI.Demo.csproj | 32 --- source/RevitLookup.UI.Framework/App.xaml | 16 ++ .../RevitLookup.UI.Framework.csproj | 28 ++ .../App.xaml | 3 +- source/RevitLookup.UI.Playground/App.xaml.cs | 38 +++ source/RevitLookup.UI.Playground/Host.cs | 34 +++ .../RevitLookup.UI.Playground.csproj | 21 ++ .../RevitLookup.UI/Properties/AssemblyInfo.cs | 5 +- source/RevitLookup.UI/RevitLookup.UI.csproj | 24 +- .../LookupEngine.Tests.csproj | 15 ++ 41 files changed, 538 insertions(+), 1201 deletions(-) delete mode 100644 RevitLookup.sln create mode 100644 RevitLookup.slnx rename {doc => history}/Images/ja_1.png (100%) rename {doc => history}/Images/ja_2.png (100%) rename {doc => history}/Images/ja_3.png (100%) rename {doc => history}/Images/ja_4.png (100%) rename {doc => history}/Images/ja_5.png (100%) rename {doc => history}/Images/ja_6.png (100%) rename {doc => history}/Readme_2005-05-11.doc (100%) rename {doc => history}/Readme_2005-05-11.md (98%) rename {doc => history}/Wishlist.md (100%) create mode 100644 source/LookupEngine.Abstractions/LookupEngine.Abstractions.csproj rename source/{Benchmarks => LookupEngine.Benchmarks}/Benchmark.cs (70%) rename source/{Benchmarks => LookupEngine.Benchmarks}/ClosureBenchmark.cs (95%) rename source/{Benchmarks/Benchmarks.csproj => LookupEngine.Benchmarks/LookupEngine.Benchmarks.csproj} (76%) rename source/{Benchmarks => LookupEngine.Benchmarks}/ResolveTypeBench.cs (95%) rename source/{Benchmarks => LookupEngine.Benchmarks}/SortBench.cs (94%) rename source/{Benchmarks => LookupEngine.Benchmarks}/TypeEqualBench.cs (96%) create mode 100644 source/LookupEngine/LookupEngine.csproj create mode 100644 source/RevitLookup.Abstractions/RevitLookup.Abstractions.csproj delete mode 100644 source/RevitLookup.UI.Demo/App.xaml.cs delete mode 100644 source/RevitLookup.UI.Demo/HostProvider.cs delete mode 100644 source/RevitLookup.UI.Demo/Mock/Services/MockLookupService.cs delete mode 100644 source/RevitLookup.UI.Demo/Mock/Services/MockSnoopVisualService.cs delete mode 100644 source/RevitLookup.UI.Demo/Mock/ViewModels/MockDashboardViewModel.cs delete mode 100644 source/RevitLookup.UI.Demo/Mock/ViewModels/MockEventsViewModel.cs delete mode 100644 source/RevitLookup.UI.Demo/Mock/ViewModels/MockSnoopViewModel.cs delete mode 100644 source/RevitLookup.UI.Demo/RevitLookup.UI.Demo.csproj create mode 100644 source/RevitLookup.UI.Framework/App.xaml create mode 100644 source/RevitLookup.UI.Framework/RevitLookup.UI.Framework.csproj rename source/{RevitLookup.UI.Demo => RevitLookup.UI.Playground}/App.xaml (76%) create mode 100644 source/RevitLookup.UI.Playground/App.xaml.cs create mode 100644 source/RevitLookup.UI.Playground/Host.cs create mode 100644 source/RevitLookup.UI.Playground/RevitLookup.UI.Playground.csproj create mode 100644 tests/LookupEngine.Tests/LookupEngine.Tests.csproj diff --git a/Contributing.md b/Contributing.md index 0f6803b9..a5aea143 100644 --- a/Contributing.md +++ b/Contributing.md @@ -116,12 +116,13 @@ To execute NUKE build on GitHub, you can follow these steps: | Folder | Description | |----------|----------------------------------------------------------------------------| +| branding | Source files for logo, banner, installer background | +| history | Museum, storage of original RevitLookup documentation | | build | Nuke build system. Used to automate project builds | | install | Add-in installer, called implicitly by the Nuke build | | source | Project source code folder. Contains all solution projects | +| tools | Extra tools for RevitLookup development | | output | Folder of generated files by the build system, such as bundles, installers | -| branding | Source files for logo, banner, installer background | -| doc | Museum, storage of original RevitLookup documentation | ## Project structure diff --git a/RevitLookup.sln b/RevitLookup.sln deleted file mode 100644 index 42ff0e5d..00000000 --- a/RevitLookup.sln +++ /dev/null @@ -1,160 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{374DC381-78CC-4FC7-A566-EB8C71C6369F}" - ProjectSection(SolutionItems) = preProject - Readme.md = Readme.md - Changelog.md = Changelog.md - Contributing.md = Contributing.md - Build\Build.Configuration.cs = Build\Build.Configuration.cs - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Installer", "install\Installer.csproj", "{B3814652-9158-4056-A721-1A380DE74BAE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Build", "build\Build.csproj", "{64D9C223-D070-46CD-A635-04FD4E1BEC3C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RevitLookup", "source\RevitLookup\RevitLookup.csproj", "{05C77115-2277-4DFC-8F95-BE37E5F1130F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RevitLookup.UI", "source\RevitLookup.UI\RevitLookup.UI.csproj", "{CE5C104B-D76E-49DA-8BD6-BF472224AEC0}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RevitLookup.UI.Demo", "source\RevitLookup.UI.Demo\RevitLookup.UI.Demo.csproj", "{67B8906F-21B0-4CE3-82F7-8E608B9D53CA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmarks", "source\Benchmarks\Benchmarks.csproj", "{827646DF-D373-4D40-8F0B-1E9842E3CE9A}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug R21|Any CPU = Debug R21|Any CPU - Debug R22|Any CPU = Debug R22|Any CPU - Debug R23|Any CPU = Debug R23|Any CPU - Debug R24|Any CPU = Debug R24|Any CPU - Debug R25|Any CPU = Debug R25|Any CPU - Release R21|Any CPU = Release R21|Any CPU - Release R22|Any CPU = Release R22|Any CPU - Release R23|Any CPU = Release R23|Any CPU - Release R24|Any CPU = Release R24|Any CPU - Release R25|Any CPU = Release R25|Any CPU - Installer|Any CPU = Installer|Any CPU - UI.Demo Debug|Any CPU = UI.Demo Debug|Any CPU - UI.Demo Release|Any CPU = UI.Demo Release|Any CPU - Benchmark|Any CPU = Benchmark|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {05C77115-2277-4DFC-8F95-BE37E5F1130F}.UI.Demo Release|Any CPU.ActiveCfg = Release R22|Any CPU - {05C77115-2277-4DFC-8F95-BE37E5F1130F}.UI.Demo Release|Any CPU.Build.0 = Release R22|Any CPU - {05C77115-2277-4DFC-8F95-BE37E5F1130F}.Benchmark|Any CPU.ActiveCfg = Release R24|Any CPU - {05C77115-2277-4DFC-8F95-BE37E5F1130F}.Installer|Any CPU.ActiveCfg = Release R24|Any CPU - {05C77115-2277-4DFC-8F95-BE37E5F1130F}.Debug R21|Any CPU.ActiveCfg = Debug R21|Any CPU - {05C77115-2277-4DFC-8F95-BE37E5F1130F}.Debug R21|Any CPU.Build.0 = Debug R21|Any CPU - {05C77115-2277-4DFC-8F95-BE37E5F1130F}.Debug R22|Any CPU.ActiveCfg = Debug R22|Any CPU - {05C77115-2277-4DFC-8F95-BE37E5F1130F}.Debug R22|Any CPU.Build.0 = Debug R22|Any CPU - {05C77115-2277-4DFC-8F95-BE37E5F1130F}.Debug R23|Any CPU.ActiveCfg = Debug R23|Any CPU - {05C77115-2277-4DFC-8F95-BE37E5F1130F}.Debug R23|Any CPU.Build.0 = Debug R23|Any CPU - {05C77115-2277-4DFC-8F95-BE37E5F1130F}.Debug R24|Any CPU.ActiveCfg = Debug R24|Any CPU - {05C77115-2277-4DFC-8F95-BE37E5F1130F}.Debug R24|Any CPU.Build.0 = Debug R24|Any CPU - {05C77115-2277-4DFC-8F95-BE37E5F1130F}.Debug R25|Any CPU.ActiveCfg = Debug R25|Any CPU - {05C77115-2277-4DFC-8F95-BE37E5F1130F}.Debug R25|Any CPU.Build.0 = Debug R25|Any CPU - {05C77115-2277-4DFC-8F95-BE37E5F1130F}.Release R21|Any CPU.ActiveCfg = Release R21|Any CPU - {05C77115-2277-4DFC-8F95-BE37E5F1130F}.Release R21|Any CPU.Build.0 = Release R21|Any CPU - {05C77115-2277-4DFC-8F95-BE37E5F1130F}.Release R22|Any CPU.ActiveCfg = Release R22|Any CPU - {05C77115-2277-4DFC-8F95-BE37E5F1130F}.Release R22|Any CPU.Build.0 = Release R22|Any CPU - {05C77115-2277-4DFC-8F95-BE37E5F1130F}.Release R23|Any CPU.ActiveCfg = Release R23|Any CPU - {05C77115-2277-4DFC-8F95-BE37E5F1130F}.Release R23|Any CPU.Build.0 = Release R23|Any CPU - {05C77115-2277-4DFC-8F95-BE37E5F1130F}.Release R24|Any CPU.ActiveCfg = Release R24|Any CPU - {05C77115-2277-4DFC-8F95-BE37E5F1130F}.Release R24|Any CPU.Build.0 = Release R24|Any CPU - {05C77115-2277-4DFC-8F95-BE37E5F1130F}.Release R25|Any CPU.ActiveCfg = Release R25|Any CPU - {05C77115-2277-4DFC-8F95-BE37E5F1130F}.Release R25|Any CPU.Build.0 = Release R25|Any CPU - {05C77115-2277-4DFC-8F95-BE37E5F1130F}.UI.Demo Debug|Any CPU.ActiveCfg = Debug R25|Any CPU - {05C77115-2277-4DFC-8F95-BE37E5F1130F}.UI.Demo Debug|Any CPU.Build.0 = Debug R25|Any CPU - {64D9C223-D070-46CD-A635-04FD4E1BEC3C}.UI.Demo Debug|Any CPU.ActiveCfg = Debug|Any CPU - {64D9C223-D070-46CD-A635-04FD4E1BEC3C}.UI.Demo Release|Any CPU.ActiveCfg = Release|Any CPU - {64D9C223-D070-46CD-A635-04FD4E1BEC3C}.Installer|Any CPU.ActiveCfg = Release|Any CPU - {64D9C223-D070-46CD-A635-04FD4E1BEC3C}.Benchmark|Any CPU.ActiveCfg = Release|Any CPU - {64D9C223-D070-46CD-A635-04FD4E1BEC3C}.Debug R21|Any CPU.ActiveCfg = Debug|Any CPU - {64D9C223-D070-46CD-A635-04FD4E1BEC3C}.Debug R22|Any CPU.ActiveCfg = Debug|Any CPU - {64D9C223-D070-46CD-A635-04FD4E1BEC3C}.Debug R23|Any CPU.ActiveCfg = Debug|Any CPU - {64D9C223-D070-46CD-A635-04FD4E1BEC3C}.Debug R24|Any CPU.ActiveCfg = Debug|Any CPU - {64D9C223-D070-46CD-A635-04FD4E1BEC3C}.Debug R25|Any CPU.ActiveCfg = Debug|Any CPU - {64D9C223-D070-46CD-A635-04FD4E1BEC3C}.Release R21|Any CPU.ActiveCfg = Release|Any CPU - {64D9C223-D070-46CD-A635-04FD4E1BEC3C}.Release R22|Any CPU.ActiveCfg = Release|Any CPU - {64D9C223-D070-46CD-A635-04FD4E1BEC3C}.Release R23|Any CPU.ActiveCfg = Release|Any CPU - {64D9C223-D070-46CD-A635-04FD4E1BEC3C}.Release R24|Any CPU.ActiveCfg = Release|Any CPU - {64D9C223-D070-46CD-A635-04FD4E1BEC3C}.Release R25|Any CPU.ActiveCfg = Release|Any CPU - {B3814652-9158-4056-A721-1A380DE74BAE}.UI.Demo Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B3814652-9158-4056-A721-1A380DE74BAE}.UI.Demo Release|Any CPU.ActiveCfg = Release|Any CPU - {B3814652-9158-4056-A721-1A380DE74BAE}.Benchmark|Any CPU.ActiveCfg = Release|Any CPU - {B3814652-9158-4056-A721-1A380DE74BAE}.Installer|Any CPU.ActiveCfg = Release|Any CPU - {B3814652-9158-4056-A721-1A380DE74BAE}.Installer|Any CPU.Build.0 = Release|Any CPU - {B3814652-9158-4056-A721-1A380DE74BAE}.Debug R21|Any CPU.ActiveCfg = Debug|Any CPU - {B3814652-9158-4056-A721-1A380DE74BAE}.Debug R22|Any CPU.ActiveCfg = Debug|Any CPU - {B3814652-9158-4056-A721-1A380DE74BAE}.Debug R23|Any CPU.ActiveCfg = Debug|Any CPU - {B3814652-9158-4056-A721-1A380DE74BAE}.Debug R24|Any CPU.ActiveCfg = Debug|Any CPU - {B3814652-9158-4056-A721-1A380DE74BAE}.Debug R25|Any CPU.ActiveCfg = Debug|Any CPU - {B3814652-9158-4056-A721-1A380DE74BAE}.Release R21|Any CPU.ActiveCfg = Release|Any CPU - {B3814652-9158-4056-A721-1A380DE74BAE}.Release R22|Any CPU.ActiveCfg = Release|Any CPU - {B3814652-9158-4056-A721-1A380DE74BAE}.Release R23|Any CPU.ActiveCfg = Release|Any CPU - {B3814652-9158-4056-A721-1A380DE74BAE}.Release R24|Any CPU.ActiveCfg = Release|Any CPU - {B3814652-9158-4056-A721-1A380DE74BAE}.Release R25|Any CPU.ActiveCfg = Release|Any CPU - {CE5C104B-D76E-49DA-8BD6-BF472224AEC0}.UI.Demo Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CE5C104B-D76E-49DA-8BD6-BF472224AEC0}.UI.Demo Debug|Any CPU.Build.0 = Debug|Any CPU - {CE5C104B-D76E-49DA-8BD6-BF472224AEC0}.UI.Demo Release|Any CPU.ActiveCfg = Release|Any CPU - {CE5C104B-D76E-49DA-8BD6-BF472224AEC0}.UI.Demo Release|Any CPU.Build.0 = Release|Any CPU - {CE5C104B-D76E-49DA-8BD6-BF472224AEC0}.Benchmark|Any CPU.ActiveCfg = Release|Any CPU - {CE5C104B-D76E-49DA-8BD6-BF472224AEC0}.Debug R21|Any CPU.ActiveCfg = Debug|Any CPU - {CE5C104B-D76E-49DA-8BD6-BF472224AEC0}.Debug R21|Any CPU.Build.0 = Debug|Any CPU - {CE5C104B-D76E-49DA-8BD6-BF472224AEC0}.Debug R22|Any CPU.ActiveCfg = Debug|Any CPU - {CE5C104B-D76E-49DA-8BD6-BF472224AEC0}.Debug R23|Any CPU.ActiveCfg = Debug|Any CPU - {CE5C104B-D76E-49DA-8BD6-BF472224AEC0}.Debug R22|Any CPU.Build.0 = Debug|Any CPU - {CE5C104B-D76E-49DA-8BD6-BF472224AEC0}.Debug R23|Any CPU.Build.0 = Debug|Any CPU - {CE5C104B-D76E-49DA-8BD6-BF472224AEC0}.Debug R24|Any CPU.ActiveCfg = Debug|Any CPU - {CE5C104B-D76E-49DA-8BD6-BF472224AEC0}.Debug R24|Any CPU.Build.0 = Debug|Any CPU - {CE5C104B-D76E-49DA-8BD6-BF472224AEC0}.Debug R25|Any CPU.ActiveCfg = Debug|Any CPU - {CE5C104B-D76E-49DA-8BD6-BF472224AEC0}.Debug R25|Any CPU.Build.0 = Debug|Any CPU - {CE5C104B-D76E-49DA-8BD6-BF472224AEC0}.Release R21|Any CPU.ActiveCfg = Release|Any CPU - {CE5C104B-D76E-49DA-8BD6-BF472224AEC0}.Release R21|Any CPU.Build.0 = Release|Any CPU - {CE5C104B-D76E-49DA-8BD6-BF472224AEC0}.Release R22|Any CPU.ActiveCfg = Release|Any CPU - {CE5C104B-D76E-49DA-8BD6-BF472224AEC0}.Release R22|Any CPU.Build.0 = Release|Any CPU - {CE5C104B-D76E-49DA-8BD6-BF472224AEC0}.Release R23|Any CPU.ActiveCfg = Release|Any CPU - {CE5C104B-D76E-49DA-8BD6-BF472224AEC0}.Release R23|Any CPU.Build.0 = Release|Any CPU - {CE5C104B-D76E-49DA-8BD6-BF472224AEC0}.Release R24|Any CPU.ActiveCfg = Release|Any CPU - {CE5C104B-D76E-49DA-8BD6-BF472224AEC0}.Release R24|Any CPU.Build.0 = Release|Any CPU - {CE5C104B-D76E-49DA-8BD6-BF472224AEC0}.Release R25|Any CPU.ActiveCfg = Release|Any CPU - {CE5C104B-D76E-49DA-8BD6-BF472224AEC0}.Release R25|Any CPU.Build.0 = Release|Any CPU - {CE5C104B-D76E-49DA-8BD6-BF472224AEC0}.Installer|Any CPU.ActiveCfg = Release|Any CPU - {67B8906F-21B0-4CE3-82F7-8E608B9D53CA}.UI.Demo Debug|Any CPU.ActiveCfg = Debug|Any CPU - {67B8906F-21B0-4CE3-82F7-8E608B9D53CA}.UI.Demo Debug|Any CPU.Build.0 = Debug|Any CPU - {67B8906F-21B0-4CE3-82F7-8E608B9D53CA}.UI.Demo Release|Any CPU.ActiveCfg = Release|Any CPU - {67B8906F-21B0-4CE3-82F7-8E608B9D53CA}.UI.Demo Release|Any CPU.Build.0 = Release|Any CPU - {67B8906F-21B0-4CE3-82F7-8E608B9D53CA}.Installer|Any CPU.ActiveCfg = Release|Any CPU - {67B8906F-21B0-4CE3-82F7-8E608B9D53CA}.Benchmark|Any CPU.ActiveCfg = Release|Any CPU - {67B8906F-21B0-4CE3-82F7-8E608B9D53CA}.Debug R21|Any CPU.ActiveCfg = Debug|Any CPU - {67B8906F-21B0-4CE3-82F7-8E608B9D53CA}.Debug R22|Any CPU.ActiveCfg = Debug|Any CPU - {67B8906F-21B0-4CE3-82F7-8E608B9D53CA}.Debug R23|Any CPU.ActiveCfg = Debug|Any CPU - {67B8906F-21B0-4CE3-82F7-8E608B9D53CA}.Debug R24|Any CPU.ActiveCfg = Debug|Any CPU - {67B8906F-21B0-4CE3-82F7-8E608B9D53CA}.Debug R25|Any CPU.ActiveCfg = Debug|Any CPU - {67B8906F-21B0-4CE3-82F7-8E608B9D53CA}.Release R21|Any CPU.ActiveCfg = Release|Any CPU - {67B8906F-21B0-4CE3-82F7-8E608B9D53CA}.Release R22|Any CPU.ActiveCfg = Release|Any CPU - {67B8906F-21B0-4CE3-82F7-8E608B9D53CA}.Release R23|Any CPU.ActiveCfg = Release|Any CPU - {67B8906F-21B0-4CE3-82F7-8E608B9D53CA}.Release R24|Any CPU.ActiveCfg = Release|Any CPU - {67B8906F-21B0-4CE3-82F7-8E608B9D53CA}.Release R25|Any CPU.ActiveCfg = Release|Any CPU - {827646DF-D373-4D40-8F0B-1E9842E3CE9A}.UI.Demo Debug|Any CPU.ActiveCfg = Debug|Any CPU - {827646DF-D373-4D40-8F0B-1E9842E3CE9A}.UI.Demo Release|Any CPU.ActiveCfg = Release|Any CPU - {827646DF-D373-4D40-8F0B-1E9842E3CE9A}.Benchmark|Any CPU.ActiveCfg = Release|Any CPU - {827646DF-D373-4D40-8F0B-1E9842E3CE9A}.Benchmark|Any CPU.Build.0 = Release|Any CPU - {827646DF-D373-4D40-8F0B-1E9842E3CE9A}.Installer|Any CPU.ActiveCfg = Release|Any CPU - {827646DF-D373-4D40-8F0B-1E9842E3CE9A}.Debug R21|Any CPU.ActiveCfg = Debug|Any CPU - {827646DF-D373-4D40-8F0B-1E9842E3CE9A}.Debug R22|Any CPU.ActiveCfg = Debug|Any CPU - {827646DF-D373-4D40-8F0B-1E9842E3CE9A}.Debug R23|Any CPU.ActiveCfg = Debug|Any CPU - {827646DF-D373-4D40-8F0B-1E9842E3CE9A}.Debug R24|Any CPU.ActiveCfg = Debug|Any CPU - {827646DF-D373-4D40-8F0B-1E9842E3CE9A}.Debug R25|Any CPU.ActiveCfg = Debug|Any CPU - {827646DF-D373-4D40-8F0B-1E9842E3CE9A}.Release R21|Any CPU.ActiveCfg = Release|Any CPU - {827646DF-D373-4D40-8F0B-1E9842E3CE9A}.Release R22|Any CPU.ActiveCfg = Release|Any CPU - {827646DF-D373-4D40-8F0B-1E9842E3CE9A}.Release R23|Any CPU.ActiveCfg = Release|Any CPU - {827646DF-D373-4D40-8F0B-1E9842E3CE9A}.Release R24|Any CPU.ActiveCfg = Release|Any CPU - {827646DF-D373-4D40-8F0B-1E9842E3CE9A}.Release R25|Any CPU.ActiveCfg = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {DA402884-3505-43C8-8114-271D66001F6B} - EndGlobalSection -EndGlobal diff --git a/RevitLookup.slnx b/RevitLookup.slnx new file mode 100644 index 00000000..63788e25 --- /dev/null +++ b/RevitLookup.slnx @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build/Build.cs b/build/Build.cs index d0aab9f0..def828e2 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -1,4 +1,3 @@ -using Nuke.Common; using Nuke.Common.Git; using Nuke.Common.ProjectModel; diff --git a/build/Build.csproj b/build/Build.csproj index 55d9359a..e9fe3585 100644 --- a/build/Build.csproj +++ b/build/Build.csproj @@ -9,12 +9,11 @@ .. .. 1 - Release;Debug AnyCPU - + diff --git a/global.json b/global.json index 75a80e90..894573b7 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,7 @@ { "sdk": { - "version": "8.0.0", - "rollForward": "latestMinor" + "version": "9.0.0", + "rollForward": "latestMinor", + "allowPrerelease": true } } \ No newline at end of file diff --git a/doc/Images/ja_1.png b/history/Images/ja_1.png similarity index 100% rename from doc/Images/ja_1.png rename to history/Images/ja_1.png diff --git a/doc/Images/ja_2.png b/history/Images/ja_2.png similarity index 100% rename from doc/Images/ja_2.png rename to history/Images/ja_2.png diff --git a/doc/Images/ja_3.png b/history/Images/ja_3.png similarity index 100% rename from doc/Images/ja_3.png rename to history/Images/ja_3.png diff --git a/doc/Images/ja_4.png b/history/Images/ja_4.png similarity index 100% rename from doc/Images/ja_4.png rename to history/Images/ja_4.png diff --git a/doc/Images/ja_5.png b/history/Images/ja_5.png similarity index 100% rename from doc/Images/ja_5.png rename to history/Images/ja_5.png diff --git a/doc/Images/ja_6.png b/history/Images/ja_6.png similarity index 100% rename from doc/Images/ja_6.png rename to history/Images/ja_6.png diff --git a/doc/Readme_2005-05-11.doc b/history/Readme_2005-05-11.doc similarity index 100% rename from doc/Readme_2005-05-11.doc rename to history/Readme_2005-05-11.doc diff --git a/doc/Readme_2005-05-11.md b/history/Readme_2005-05-11.md similarity index 98% rename from doc/Readme_2005-05-11.md rename to history/Readme_2005-05-11.md index 88db5a3e..d205213b 100644 --- a/doc/Readme_2005-05-11.md +++ b/history/Readme_2005-05-11.md @@ -1,120 +1,120 @@ -# Revit Lookup (Formerly known as RvtMgdDbg) - -Jim Awe -
Autodesk, Inc. -
05/11/2005 - -
- - -Warning, this document is currently obsolete. - -08808006 [Revit SDK documentation outdated...] - -Contains outdated or non-functional information regarding creating the 'RevitLookup.addin' file and the associated file which belong in the addins folder. (If you cut and paste the suggested text as from RevitLookup.doc to create RevitLookup.addin it will fail when Revit starts) - -In 2012 SDK, there is a compiled DLL file, and a working Addin file. - -In the 2013 SDK, there is no compiled DLL file (or instructions on compiling DLL), and there is no Addin file. - -In the 2014 SDK there is no compiled DLL file (or instructions on compiling DLL), but there is an Addin file. - -In all three cases, the date listed for the Documentation (Word Doc) is 05/11/2005, and much of the information is out of date. - - -
- -Revit Lookup is a program designed with several goals: - -- To provide a comprehensive test of the Managed API of Revit -- To provide sample code and utility classes for 3rd Party developers -- To provide “scaffolding” for quick tests of issues when they arise -- To aid my own learning experience on the Revit API - -Currently, the following commands exist (all accessed from the “External Tools” menu): - - - -## Hello World... - -The classic bare-bones test. Just brings up an Alert box to show that the connection to the external module is working. - -## Snoop DB... - - - -This command allows you to browse all of the Elements within the current document and view their exposed properties. There has to be code written to extract all the individual properties of an object. As a result, when new classes or methods are added to the system, they will not be visible until code is explicitly written to display them. Because, the properties of an object are obtained in a top-down manner, all object types will at least be able to display common base class properties, even when newly added. - -When an item in the data list appears in bold typeface, it means that there is “Drill down” information. Click on that row and a nested Form (Dialog Box) will bring up more detailed information about that data item. For instance, clicking on any item that lists a ParameterSet will bring up a nested Form displaying the contents of the set. - -NOTE: when “Drilling down”, the Forms will stack on top of each other. You could keep drilling down a long way if you aren’t paying attention. It is up to you to make sure you don’t get lost in all of the stacked up Forms. - -The DrillDown information on a Class Separator (the light blue lines), will allow you to view information about the given class. - - - -Also, in the left-hand pane, you can right-click on a particular object and get additional information. - - - -Choosing “Browse Using Reflection...” will bring up a Generic PropertyGrid Form that uses .NET Reflection to browse all the properties. Here, there is no code written specifically in Revit Lookup to retrieve, format, and display the properties. - - - -NOTE: Because we have no control over how items are displayed, read-only values appear in Grey, and editable values appear in Bold. This form is not setup to correctly handle edits, so making changes is completely at your own risk. - -You can continue to browse generically using Reflection if you right-click on one of the items in the PropertyGrid. You get options to see either the Class info or the Object info. - -## Snoop Current Selection... - -Same as the above command, except that it starts you off Snooping only the elements that were part of the current selection set. - -## Snoop Application... - -Same as above, except that you start out at the Autodesk.Revit.Application object which is originally passed to the command. - -## Test Framework ... - -This command packages a set of individual tests into a single location. As new tests are written, they are plugged into this same Form without the need to define new external commands and hook them up to the system. The black “CLS” nodes of the tree represent which Class the tests concern. The grey check mark nodes represent an individual test. To run a test, simply choose a check mark node and press OK. - -As of now, there are only a few tests, but more will be added over time. - - - -## Set Up - -The Revit API now offers the ability to register API applications via an .addin manifest file. - -Manifest files will be read automatically by Revit when they are places in one of two locations on a user's system: - -- In a non-user specific location in "application data" - - For Windows XP – C:\Documents and Settings\All Users\Application Data\Autodesk\Revit\Addins\2012 - - For Vista/Windows 7 – C:\ProgramData\Autodesk\Revit\Addins\2012 -- In a user specific location in "application data" - - For Windows XP – C:\Documents and Settings\\Application Data\Autodesk\Revit\Addins\2012 - - For Vista/Windows 7 – C:\Users\\AppData\Roaming\Autodesk\Revit\Addins\2012 - -All files named `.addin` in these locations will be read and processed by Revit during startup. - -The content of RevitLookup.addin: - -``` - - - - RevitLookup - RevitLookup.dll - 6066CBF6-9034-41dd-A2A6-B3761C1362FF - RevitLookup.App - - -``` - -## Known Issues - -As of this date, there are a few known issues: - -- Random exceptions when browsing Forms. There is a strange work-around in some of the Form constructors that seems to get around a lot of the exceptions, but there is still the occasional problem. Usually, they give the exception message “Overflow or underflow operation”. The exceptions are caught and you will be returned to the editor window and can continue. -- The Snoop XML Form seems to be especially flaky. This was ported directly over from the AutoCAD version of MgdDbg, so I’m not sure what the deal is yet because it seems much more stable over there. -- Trying to get the Analytical model of walls if they aren’t a load-bearing wall causes an Assert. Simply click on “Ignore All” and it won’t bother you anymore. -- Trying to get the Room or analytical model of a FamilyInstance asserts that the TopologyId is incorrect. Simply click on “Ignore All” and it won’t bother you anymore. +# Revit Lookup (Formerly known as RvtMgdDbg) + +Jim Awe +
Autodesk, Inc. +
05/11/2005 + +
+ + +Warning, this document is currently obsolete. + +08808006 [Revit SDK documentation outdated...] + +Contains outdated or non-functional information regarding creating the 'RevitLookup.addin' file and the associated file which belong in the addins folder. (If you cut and paste the suggested text as from RevitLookup.doc to create RevitLookup.addin it will fail when Revit starts) + +In 2012 SDK, there is a compiled DLL file, and a working Addin file. + +In the 2013 SDK, there is no compiled DLL file (or instructions on compiling DLL), and there is no Addin file. + +In the 2014 SDK there is no compiled DLL file (or instructions on compiling DLL), but there is an Addin file. + +In all three cases, the date listed for the Documentation (Word Doc) is 05/11/2005, and much of the information is out of date. + + +
+ +Revit Lookup is a program designed with several goals: + +- To provide a comprehensive test of the Managed API of Revit +- To provide sample code and utility classes for 3rd Party developers +- To provide “scaffolding” for quick tests of issues when they arise +- To aid my own learning experience on the Revit API + +Currently, the following commands exist (all accessed from the “External Tools” menu): + + + +## Hello World... + +The classic bare-bones test. Just brings up an Alert box to show that the connection to the external module is working. + +## Snoop DB... + + + +This command allows you to browse all of the Elements within the current document and view their exposed properties. There has to be code written to extract all the individual properties of an object. As a result, when new classes or methods are added to the system, they will not be visible until code is explicitly written to display them. Because, the properties of an object are obtained in a top-down manner, all object types will at least be able to display common base class properties, even when newly added. + +When an item in the data list appears in bold typeface, it means that there is “Drill down” information. Click on that row and a nested Form (Dialog Box) will bring up more detailed information about that data item. For instance, clicking on any item that lists a ParameterSet will bring up a nested Form displaying the contents of the set. + +NOTE: when “Drilling down”, the Forms will stack on top of each other. You could keep drilling down a long way if you aren’t paying attention. It is up to you to make sure you don’t get lost in all of the stacked up Forms. + +The DrillDown information on a Class Separator (the light blue lines), will allow you to view information about the given class. + + + +Also, in the left-hand pane, you can right-click on a particular object and get additional information. + + + +Choosing “Browse Using Reflection...” will bring up a Generic PropertyGrid Form that uses .NET Reflection to browse all the properties. Here, there is no code written specifically in Revit Lookup to retrieve, format, and display the properties. + + + +NOTE: Because we have no control over how items are displayed, read-only values appear in Grey, and editable values appear in Bold. This form is not setup to correctly handle edits, so making changes is completely at your own risk. + +You can continue to browse generically using Reflection if you right-click on one of the items in the PropertyGrid. You get options to see either the Class info or the Object info. + +## Snoop Current Selection... + +Same as the above command, except that it starts you off Snooping only the elements that were part of the current selection set. + +## Snoop Application... + +Same as above, except that you start out at the Autodesk.Revit.Application object which is originally passed to the command. + +## Test Framework ... + +This command packages a set of individual tests into a single location. As new tests are written, they are plugged into this same Form without the need to define new external commands and hook them up to the system. The black “CLS” nodes of the tree represent which Class the tests concern. The grey check mark nodes represent an individual test. To run a test, simply choose a check mark node and press OK. + +As of now, there are only a few tests, but more will be added over time. + + + +## Set Up + +The Revit API now offers the ability to register API applications via an .addin manifest file. + +Manifest files will be read automatically by Revit when they are places in one of two locations on a user's system: + +- In a non-user specific location in "application data" + - For Windows XP – C:\Documents and Settings\All Users\Application Data\Autodesk\Revit\Addins\2012 + - For Vista/Windows 7 – C:\ProgramData\Autodesk\Revit\Addins\2012 +- In a user specific location in "application data" + - For Windows XP – C:\Documents and Settings\\Application Data\Autodesk\Revit\Addins\2012 + - For Vista/Windows 7 – C:\Users\\AppData\Roaming\Autodesk\Revit\Addins\2012 + +All files named `.addin` in these locations will be read and processed by Revit during startup. + +The content of RevitLookup.addin: + +``` + + + + RevitLookup + RevitLookup.dll + 6066CBF6-9034-41dd-A2A6-B3761C1362FF + RevitLookup.App + + +``` + +## Known Issues + +As of this date, there are a few known issues: + +- Random exceptions when browsing Forms. There is a strange work-around in some of the Form constructors that seems to get around a lot of the exceptions, but there is still the occasional problem. Usually, they give the exception message “Overflow or underflow operation”. The exceptions are caught and you will be returned to the editor window and can continue. +- The Snoop XML Form seems to be especially flaky. This was ported directly over from the AutoCAD version of MgdDbg, so I’m not sure what the deal is yet because it seems much more stable over there. +- Trying to get the Analytical model of walls if they aren’t a load-bearing wall causes an Assert. Simply click on “Ignore All” and it won’t bother you anymore. +- Trying to get the Room or analytical model of a FamilyInstance asserts that the TopologyId is incorrect. Simply click on “Ignore All” and it won’t bother you anymore. diff --git a/doc/Wishlist.md b/history/Wishlist.md similarity index 100% rename from doc/Wishlist.md rename to history/Wishlist.md diff --git a/source/LookupEngine.Abstractions/LookupEngine.Abstractions.csproj b/source/LookupEngine.Abstractions/LookupEngine.Abstractions.csproj new file mode 100644 index 00000000..3bd2fdff --- /dev/null +++ b/source/LookupEngine.Abstractions/LookupEngine.Abstractions.csproj @@ -0,0 +1,10 @@ + + + + enable + latest + enable + net48;net8.0-windows + + + diff --git a/source/Benchmarks/Benchmark.cs b/source/LookupEngine.Benchmarks/Benchmark.cs similarity index 70% rename from source/Benchmarks/Benchmark.cs rename to source/LookupEngine.Benchmarks/Benchmark.cs index f50e18ff..4835a8bc 100644 --- a/source/Benchmarks/Benchmark.cs +++ b/source/LookupEngine.Benchmarks/Benchmark.cs @@ -1,4 +1,4 @@ using BenchmarkDotNet.Running; -using Benchmarks; +using LookupEngine.Benchmarks; BenchmarkRunner.Run(); \ No newline at end of file diff --git a/source/Benchmarks/ClosureBenchmark.cs b/source/LookupEngine.Benchmarks/ClosureBenchmark.cs similarity index 95% rename from source/Benchmarks/ClosureBenchmark.cs rename to source/LookupEngine.Benchmarks/ClosureBenchmark.cs index bb1e03b1..acb688b0 100644 --- a/source/Benchmarks/ClosureBenchmark.cs +++ b/source/LookupEngine.Benchmarks/ClosureBenchmark.cs @@ -20,18 +20,18 @@ using BenchmarkDotNet.Attributes; -namespace Benchmarks; +namespace LookupEngine.Benchmarks; [ShortRunJob] [MemoryDiagnoser] -public class ClosureBenchmark +public sealed class ClosureBenchmark { [GlobalSetup] public void Setup() { Manager = new ExtensionManager("context"); } - + [Params("Text", 12d)] public object Parameter { get; set; } public ExtensionManager Manager { get; set; } @@ -44,10 +44,7 @@ public void ClosureMethod() [Benchmark] public void NonClosureMethod() { - Manager.Register("Extension", Parameter, extension => - { - extension.Result = extension.Value.ToString(); - }); + Manager.Register("Extension", Parameter, extension => { extension.Result = extension.Value.ToString(); }); } } diff --git a/source/Benchmarks/Benchmarks.csproj b/source/LookupEngine.Benchmarks/LookupEngine.Benchmarks.csproj similarity index 76% rename from source/Benchmarks/Benchmarks.csproj rename to source/LookupEngine.Benchmarks/LookupEngine.Benchmarks.csproj index 5321527a..c8fe66d4 100644 --- a/source/Benchmarks/Benchmarks.csproj +++ b/source/LookupEngine.Benchmarks/LookupEngine.Benchmarks.csproj @@ -1,11 +1,15 @@ + + enable Exe latest enable - net48 + net8.0-windows + + diff --git a/source/Benchmarks/ResolveTypeBench.cs b/source/LookupEngine.Benchmarks/ResolveTypeBench.cs similarity index 95% rename from source/Benchmarks/ResolveTypeBench.cs rename to source/LookupEngine.Benchmarks/ResolveTypeBench.cs index 773b1294..fdbc4b02 100644 --- a/source/Benchmarks/ResolveTypeBench.cs +++ b/source/LookupEngine.Benchmarks/ResolveTypeBench.cs @@ -20,14 +20,14 @@ using BenchmarkDotNet.Attributes; -namespace Benchmarks; +namespace LookupEngine.Benchmarks; [MediumRunJob] [MemoryDiagnoser] -public class ResolveTypeBench +public sealed class ResolveTypeBench { public object Obj { get; set; } - + public ResolveTypeBench() { Obj = "Text"; @@ -38,7 +38,7 @@ public bool TypeIsEquals() { return Obj is IDisposable; } - + [Benchmark] public bool NamespaceEquals() { diff --git a/source/Benchmarks/SortBench.cs b/source/LookupEngine.Benchmarks/SortBench.cs similarity index 94% rename from source/Benchmarks/SortBench.cs rename to source/LookupEngine.Benchmarks/SortBench.cs index 04b33aa4..775ce641 100644 --- a/source/Benchmarks/SortBench.cs +++ b/source/LookupEngine.Benchmarks/SortBench.cs @@ -21,11 +21,11 @@ using System.Reflection; using BenchmarkDotNet.Attributes; -namespace Benchmarks; +namespace LookupEngine.Benchmarks; [MediumRunJob] [MemoryDiagnoser(false)] -public class SortBench +public sealed class SortBench { private MethodInfo[] _methodInfos; @@ -55,7 +55,7 @@ public void SortComparer() var parameterInfos = methodInfo.GetParameters(); } } - + [Benchmark] public void SortComparison() { @@ -65,14 +65,14 @@ public void SortComparison() var parameterInfos = methodInfo.GetParameters(); } } - + private int Comparison(MethodInfo x, MethodInfo y) { return x.Name == y.Name ? 0 : string.Compare(x.Name, y.Name, StringComparison.OrdinalIgnoreCase); } } -public class MethodInfoComparer : IComparer +public sealed class MethodInfoComparer : IComparer { public int Compare(MethodInfo x, MethodInfo y) { diff --git a/source/Benchmarks/TypeEqualBench.cs b/source/LookupEngine.Benchmarks/TypeEqualBench.cs similarity index 96% rename from source/Benchmarks/TypeEqualBench.cs rename to source/LookupEngine.Benchmarks/TypeEqualBench.cs index 7667cbd0..9e8bb80c 100644 --- a/source/Benchmarks/TypeEqualBench.cs +++ b/source/LookupEngine.Benchmarks/TypeEqualBench.cs @@ -20,14 +20,14 @@ using BenchmarkDotNet.Attributes; -namespace Benchmarks; +namespace LookupEngine.Benchmarks; [MediumRunJob] [MemoryDiagnoser] -public class TypeEqualBench +public sealed class TypeEqualBench { public object Object { get; set; } = new RoundButton(); - + [Params(null, typeof(ButtonBase))] public Type Type { get; set; } @@ -85,15 +85,12 @@ public string FindValue3(object obj, Type type) public class ButtonBase { - } public class Button : ButtonBase { - -} -public class RoundButton : Button -{ - } +public sealed class RoundButton : Button +{ +} \ No newline at end of file diff --git a/source/LookupEngine/LookupEngine.csproj b/source/LookupEngine/LookupEngine.csproj new file mode 100644 index 00000000..3bd2fdff --- /dev/null +++ b/source/LookupEngine/LookupEngine.csproj @@ -0,0 +1,10 @@ + + + + enable + latest + enable + net48;net8.0-windows + + + diff --git a/source/RevitLookup.Abstractions/RevitLookup.Abstractions.csproj b/source/RevitLookup.Abstractions/RevitLookup.Abstractions.csproj new file mode 100644 index 00000000..22f47190 --- /dev/null +++ b/source/RevitLookup.Abstractions/RevitLookup.Abstractions.csproj @@ -0,0 +1,10 @@ + + + + enable + latest + enable + net48;net8.0-windows + + + \ No newline at end of file diff --git a/source/RevitLookup.UI.Demo/App.xaml.cs b/source/RevitLookup.UI.Demo/App.xaml.cs deleted file mode 100644 index 495e06a9..00000000 --- a/source/RevitLookup.UI.Demo/App.xaml.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2003-2024 by Autodesk, Inc. -// -// Permission to use, copy, modify, and distribute this software in -// object code form for any purpose and without fee is hereby granted, -// provided that the above copyright notice appears in all copies and -// that both that copyright notice and the limited warranty and -// restricted rights notice below appear in all supporting -// documentation. -// -// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. -// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF -// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. -// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE -// UNINTERRUPTED OR ERROR FREE. -// -// Use, duplication, or disclosure by the U.S. Government is subject to -// restrictions set forth in FAR 52.227-19 (Commercial Computer -// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) -// (Rights in Technical Data and Computer Software), as applicable. - -using System.IO; -using System.Reflection; -using System.Windows; -using RevitLookup.Services.Contracts; -using RevitLookup.Views.Pages; - -namespace RevitLookup.UI.Demo; - -public sealed partial class App -{ - private string _revitPath; - - private void OnStartup(object sender, StartupEventArgs e) - { - AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainOnAssemblyResolve; - var host = HostProvider.CreateHost(); - - Host.StartProxy(host); - Host.GetService().Show(); - } - - private void OnExit(object sender, ExitEventArgs e) - { - var settingsService = Host.GetService(); - settingsService.SaveSettings(); - - Host.Stop(); - } - - private Assembly CurrentDomainOnAssemblyResolve(object sender, ResolveEventArgs args) - { - var assemblyName = new AssemblyName(args.Name); - _revitPath ??= $@"C:\Program Files\Autodesk\Revit 20{assemblyName.Version!.Major}"; - if (!Directory.Exists(_revitPath)) return null; - - var assemblyPath = Path.Combine(_revitPath, $"{assemblyName.Name}.dll"); - if (!File.Exists(assemblyPath)) return null; - - return Assembly.LoadFrom(assemblyPath); - } -} \ No newline at end of file diff --git a/source/RevitLookup.UI.Demo/HostProvider.cs b/source/RevitLookup.UI.Demo/HostProvider.cs deleted file mode 100644 index 32489388..00000000 --- a/source/RevitLookup.UI.Demo/HostProvider.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System.IO; -using System.Reflection; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using RevitLookup.Config; -using RevitLookup.Services; -using RevitLookup.Services.Contracts; -using RevitLookup.UI.Demo.Mock.Services; -using RevitLookup.ViewModels.Contracts; -using RevitLookup.ViewModels.Pages; -using RevitLookup.Views; -using RevitLookup.Views.Pages; -using Wpf.Ui; -using MockDashboardViewModel = RevitLookup.UI.Demo.Mock.ViewModels.MockDashboardViewModel; -using MockEventsViewModel = RevitLookup.UI.Demo.Mock.ViewModels.MockEventsViewModel; -using MockSnoopViewModel = RevitLookup.UI.Demo.Mock.ViewModels.MockSnoopViewModel; - -namespace RevitLookup.UI.Demo; - -public static class HostProvider -{ - public static IHost CreateHost() - { - var builder = new HostApplicationBuilder(new HostApplicationBuilderSettings - { - ContentRootPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly()!.Location), - DisableDefaults = true - }); - - //Logging - builder.Logging.ClearProviders(); - builder.Logging.AddSerilogConfiguration(); - - //Configuration - builder.Services.AddOptions(builder.Configuration); - - //App services - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - - //UI services - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - - //Views - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - - //Startup view - builder.Services.AddTransient(); - - return builder.Build(); - } -} \ No newline at end of file diff --git a/source/RevitLookup.UI.Demo/Mock/Services/MockLookupService.cs b/source/RevitLookup.UI.Demo/Mock/Services/MockLookupService.cs deleted file mode 100644 index e6d520af..00000000 --- a/source/RevitLookup.UI.Demo/Mock/Services/MockLookupService.cs +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2003-2024 by Autodesk, Inc. -// -// Permission to use, copy, modify, and distribute this software in -// object code form for any purpose and without fee is hereby granted, -// provided that the above copyright notice appears in all copies and -// that both that copyright notice and the limited warranty and -// restricted rights notice below appear in all supporting -// documentation. -// -// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. -// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF -// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. -// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE -// UNINTERRUPTED OR ERROR FREE. -// -// Use, duplication, or disclosure by the U.S. Government is subject to -// restrictions set forth in FAR 52.227-19 (Commercial Computer -// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) -// (Rights in Technical Data and Computer Software), as applicable. - -using System.Windows; -using System.Windows.Controls; -using Microsoft.Extensions.DependencyInjection; -using RevitLookup.Core.Objects; -using RevitLookup.Services.Contracts; -using RevitLookup.Services.Enums; -using Wpf.Ui; - -namespace RevitLookup.UI.Demo.Mock.Services; - -public sealed class MockLookupService(IServiceScopeFactory scopeFactory) : ILookupService -{ - private Window _owner; - private Task _activeTask; - private readonly IServiceScope _scope = scopeFactory.CreateScope(); - - public ILookupServiceDependsStage Snoop(SnoopableType snoopableType) - { - _activeTask = _scope.ServiceProvider.GetRequiredService()!.SnoopAsync(snoopableType); - return this; - } - - public ILookupServiceDependsStage Snoop(SnoopableObject snoopableObject) - { - _scope.ServiceProvider.GetRequiredService()!.Snoop(snoopableObject); - return this; - } - - public ILookupServiceDependsStage Snoop(IList snoopableObjects) - { - _scope.ServiceProvider.GetRequiredService()!.Snoop(snoopableObjects); - return this; - } - - public ILookupServiceShowStage DependsOn(IServiceProvider provider) - { - _owner = (Window)provider.GetService(); - return this; - } - - public ILookupServiceExecuteStage Show() where T : Page - { - if (_activeTask is null) - { - ShowPage(); - } - else - { - _activeTask = _activeTask.ContinueWith(_ => ShowPage(), TaskScheduler.FromCurrentSynchronizationContext()); - } - - return this; - } - - public void Execute(Action handler) where T : class - { - if (_activeTask is null) - { - InvokeHandler(handler); - } - else - { - _activeTask = _activeTask.ContinueWith(_ => InvokeHandler(handler), TaskScheduler.FromCurrentSynchronizationContext()); - } - } - - private void ShowPage() where T : Page - { - var window = (Window)_scope.ServiceProvider.GetRequiredService(); - window.Closed += OnWindowClosed; - - if (_owner is null) - { - window.WindowStartupLocation = WindowStartupLocation.CenterScreen; - } - else - { - window.Left = _owner.Left + 47; - window.Top = _owner.Top + 49; - } - - window.Show(); - _scope.ServiceProvider.GetRequiredService().Navigate(typeof(T)); - } - - private void InvokeHandler(Action handler) where T : class - { - var service = _scope.ServiceProvider.GetRequiredService(); - handler.Invoke(service); - } - - private void OnWindowClosed(object sender, EventArgs e) - { - _scope.Dispose(); - } -} \ No newline at end of file diff --git a/source/RevitLookup.UI.Demo/Mock/Services/MockSnoopVisualService.cs b/source/RevitLookup.UI.Demo/Mock/Services/MockSnoopVisualService.cs deleted file mode 100644 index 0de6438c..00000000 --- a/source/RevitLookup.UI.Demo/Mock/Services/MockSnoopVisualService.cs +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright 2003-2024 by Autodesk, Inc. -// -// Permission to use, copy, modify, and distribute this software in -// object code form for any purpose and without fee is hereby granted, -// provided that the above copyright notice appears in all copies and -// that both that copyright notice and the limited warranty and -// restricted rights notice below appear in all supporting -// documentation. -// -// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. -// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF -// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. -// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE -// UNINTERRUPTED OR ERROR FREE. -// -// Use, duplication, or disclosure by the U.S. Government is subject to -// restrictions set forth in FAR 52.227-19 (Commercial Computer -// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) -// (Rights in Technical Data and Computer Software), as applicable. - -using Autodesk.Revit.DB; -using Bogus; -using RevitLookup.Core.Contracts; -using RevitLookup.Core.Objects; -using RevitLookup.Core.Utils; -using RevitLookup.Services; -using RevitLookup.Services.Contracts; -using RevitLookup.Services.Enums; -using RevitLookup.ViewModels.Contracts; -using Visibility = System.Windows.Visibility; - -namespace RevitLookup.UI.Demo.Mock.Services; - -public sealed class MockSnoopVisualService(NotificationService notificationService, ISnoopViewModel viewModel, IWindow window) : ISnoopVisualService -{ - public void Snoop(SnoopableObject snoopableObject) - { - try - { - if (snoopableObject.Descriptor is IDescriptorEnumerator { IsEmpty: false } descriptor) - { - viewModel.SnoopableObjects = descriptor.ParseEnumerable(snoopableObject); - } - else - { - viewModel.SnoopableObjects = new[] { snoopableObject }; - } - - viewModel.SnoopableData = Array.Empty(); - } - catch (Exception exception) - { - notificationService.ShowError("Invalid object", exception); - } - } - - public void Snoop(IList snoopableObjects) - { - viewModel.SnoopableObjects = snoopableObjects; - viewModel.SnoopableData = Array.Empty(); - } - - public async Task SnoopAsync(SnoopableType snoopableType) - { - switch (snoopableType) - { - case SnoopableType.Face: - case SnoopableType.Edge: - case SnoopableType.LinkedElement: - case SnoopableType.Point: - case SnoopableType.SubElement: - UpdateWindowVisibility(Visibility.Hidden); - await Task.Delay(TimeSpan.FromSeconds(2)); - break; - } - - var generationCount = snoopableType switch - { - SnoopableType.View => 50_000, - SnoopableType.Document => 25_000, - SnoopableType.Application => 10_000, - SnoopableType.UiApplication => 5_000, - SnoopableType.Database => 2_000, - SnoopableType.DependentElements => 1_000, - SnoopableType.Selection => 500, - SnoopableType.LinkedElement => 250, - SnoopableType.Face => 125, - SnoopableType.Edge => 60, - SnoopableType.Point => 30, - SnoopableType.SubElement => 15, - SnoopableType.ComponentManager => 8, - SnoopableType.PerformanceAdviser => 4, - SnoopableType.UpdaterRegistry => 2, - SnoopableType.Services => 1, - SnoopableType.Schemas => 0, - _ => throw new ArgumentOutOfRangeException(nameof(snoopableType), snoopableType, null) - }; - - var items = await GenerateObjectsAsync(generationCount); - Snoop(items); - UpdateWindowVisibility(Visibility.Visible); - } - - private void UpdateWindowVisibility(Visibility visibility) - { - if (!window.IsLoaded) return; - - window.Visibility = visibility; - } - - private static async Task> GenerateObjectsAsync(int generationCount) - { - if (generationCount == 0) return Array.Empty(); - - return await Task.Run(() => new Faker() - .CustomInstantiator(faker => - { - if (faker.IndexFaker % 2000 == 0) return new SnoopableObject((object)null); - if (faker.IndexFaker % 1000 == 0) return new SnoopableObject(string.Empty); - if (faker.IndexFaker % 700 == 0) return new SnoopableObject(faker.Make(150, () => faker.Internet.UserName())); - if (faker.IndexFaker % 500 == 0) return new SnoopableObject(typeof(DateTime)); - if (faker.IndexFaker % 200 == 0) return new SnoopableObject(faker.Lorem.Sentence()); - if (faker.IndexFaker % 100 == 0) return new SnoopableObject(faker.Make(150, () => new Color(faker.Random.Byte(), faker.Random.Byte(), faker.Random.Byte()))); - if (faker.IndexFaker % 5 == 0) return new SnoopableObject(faker.Random.Int(0)); - if (faker.IndexFaker % 3 == 0) return new SnoopableObject(faker.Random.Bool()); - - return new SnoopableObject(faker.Lorem.Word()); - }) - .Generate(generationCount)); - } -} \ No newline at end of file diff --git a/source/RevitLookup.UI.Demo/Mock/ViewModels/MockDashboardViewModel.cs b/source/RevitLookup.UI.Demo/Mock/ViewModels/MockDashboardViewModel.cs deleted file mode 100644 index af530954..00000000 --- a/source/RevitLookup.UI.Demo/Mock/ViewModels/MockDashboardViewModel.cs +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2003-2024 by Autodesk, Inc. -// -// Permission to use, copy, modify, and distribute this software in -// object code form for any purpose and without fee is hereby granted, -// provided that the above copyright notice appears in all copies and -// that both that copyright notice and the limited warranty and -// restricted rights notice below appear in all supporting -// documentation. -// -// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. -// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF -// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. -// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE -// UNINTERRUPTED OR ERROR FREE. -// -// Use, duplication, or disclosure by the U.S. Government is subject to -// restrictions set forth in FAR 52.227-19 (Commercial Computer -// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) -// (Rights in Technical Data and Computer Software), as applicable. - -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using RevitLookup.Services.Contracts; -using RevitLookup.Services.Enums; -using RevitLookup.ViewModels.Contracts; -using RevitLookup.Views.Dialogs; -using RevitLookup.Views.Pages; -using Wpf.Ui; - -namespace RevitLookup.UI.Demo.Mock.ViewModels; - -public partial class MockDashboardViewModel( - INavigationService navigationService, - ISnoopVisualService snoopVisualService, - IServiceProvider serviceProvider) - : ObservableObject, IDashboardViewModel -{ - [RelayCommand] - private async Task NavigateSnoopPage(string parameter) - { - switch (parameter) - { - case "view": - await snoopVisualService.SnoopAsync(SnoopableType.View); - navigationService.Navigate(typeof(SnoopPage)); - break; - case "document": - await snoopVisualService.SnoopAsync(SnoopableType.Document); - navigationService.Navigate(typeof(SnoopPage)); - break; - case "application": - await snoopVisualService.SnoopAsync(SnoopableType.Application); - navigationService.Navigate(typeof(SnoopPage)); - break; - case "uiApplication": - await snoopVisualService.SnoopAsync(SnoopableType.UiApplication); - navigationService.Navigate(typeof(SnoopPage)); - break; - case "database": - await snoopVisualService.SnoopAsync(SnoopableType.Database); - navigationService.Navigate(typeof(SnoopPage)); - break; - case "dependents": - await snoopVisualService.SnoopAsync(SnoopableType.DependentElements); - navigationService.Navigate(typeof(SnoopPage)); - break; - case "selection": - await snoopVisualService.SnoopAsync(SnoopableType.Selection); - navigationService.Navigate(typeof(SnoopPage)); - break; - case "linked": - await snoopVisualService.SnoopAsync(SnoopableType.LinkedElement); - navigationService.Navigate(typeof(SnoopPage)); - break; - case "face": - await snoopVisualService.SnoopAsync(SnoopableType.Face); - navigationService.Navigate(typeof(SnoopPage)); - break; - case "edge": - await snoopVisualService.SnoopAsync(SnoopableType.Edge); - navigationService.Navigate(typeof(SnoopPage)); - break; - case "point": - await snoopVisualService.SnoopAsync(SnoopableType.Point); - navigationService.Navigate(typeof(SnoopPage)); - break; - case "subElement": - await snoopVisualService.SnoopAsync(SnoopableType.SubElement); - navigationService.Navigate(typeof(SnoopPage)); - break; - case "components": - await snoopVisualService.SnoopAsync(SnoopableType.ComponentManager); - navigationService.Navigate(typeof(SnoopPage)); - break; - case "performance": - await snoopVisualService.SnoopAsync(SnoopableType.PerformanceAdviser); - navigationService.Navigate(typeof(SnoopPage)); - break; - case "updaters": - await snoopVisualService.SnoopAsync(SnoopableType.UpdaterRegistry); - navigationService.Navigate(typeof(SnoopPage)); - break; - case "services": - await snoopVisualService.SnoopAsync(SnoopableType.Services); - navigationService.Navigate(typeof(SnoopPage)); - break; - case "schemas": - await snoopVisualService.SnoopAsync(SnoopableType.Schemas); - navigationService.Navigate(typeof(SnoopPage)); - break; - case "events": - navigationService.Navigate(typeof(EventsPage)); - break; - default: - throw new ArgumentOutOfRangeException(nameof(parameter), parameter); - } - } - - [RelayCommand] - private Task OpenDialog(string parameter) - { - switch (parameter) - { - case "parameters": - var unitsDialog = new UnitsDialog(serviceProvider); - return unitsDialog.ShowParametersAsync(); - case "categories": - unitsDialog = new UnitsDialog(serviceProvider); - return unitsDialog.ShowCategoriesAsync(); - case "forge": - unitsDialog = new UnitsDialog(serviceProvider); - return unitsDialog.ShowForgeSchemaAsync(); - case "search": - var searchDialog = new SearchElementsDialog(serviceProvider); - return searchDialog.ShowAsync(); - case "modules": - var modulesDialog = new ModulesDialog(serviceProvider); - return modulesDialog.ShowAsync(); - } - - return Task.CompletedTask; - } -} \ No newline at end of file diff --git a/source/RevitLookup.UI.Demo/Mock/ViewModels/MockEventsViewModel.cs b/source/RevitLookup.UI.Demo/Mock/ViewModels/MockEventsViewModel.cs deleted file mode 100644 index f98cb49a..00000000 --- a/source/RevitLookup.UI.Demo/Mock/ViewModels/MockEventsViewModel.cs +++ /dev/null @@ -1,163 +0,0 @@ -using System.Windows.Media; -using Bogus; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using RevitLookup.Core.Enums; -using RevitLookup.Core.Objects; -using RevitLookup.Services; -using RevitLookup.Services.Contracts; -using RevitLookup.ViewModels.Contracts; -using RevitLookup.ViewModels.Utils; -using RevitLookup.Views.Pages; - -namespace RevitLookup.UI.Demo.Mock.ViewModels; - -public sealed partial class MockEventsViewModel( - NotificationService notificationService, - IServiceProvider provider) - : ObservableObject, IEventsViewModel -{ - private readonly CancellationTokenSource _cancellationTokenSource = new(); - private readonly Stack _events = new(); - - [ObservableProperty] private string _searchText = string.Empty; - [ObservableProperty] private IList _snoopableObjects = []; - [ObservableProperty] private IList _filteredSnoopableObjects = []; - [ObservableProperty] private IList _filteredSnoopableData; - [ObservableProperty] private IList _snoopableData; - - public SnoopableObject SelectedObject { get; set; } - public IServiceProvider ServiceProvider { get; } = provider; - - public void Navigate(SnoopableObject selectedItem) - { - Host.GetService() - .Snoop(selectedItem) - .DependsOn(ServiceProvider) - .Show(); - } - - public void Navigate(IList selectedItems) - { - Host.GetService() - .Snoop(selectedItems) - .DependsOn(ServiceProvider) - .Show(); - } - - public void RemoveObject(object obj) - { - var snoopableObject = obj switch - { - SnoopableObject snoopable => snoopable, - Descriptor descriptor => descriptor.Value.Descriptor.Value, - _ => throw new NotSupportedException($"Type {obj.GetType().Name} removing not supported") - }; - - SnoopableObjects.Remove(snoopableObject); - FilteredSnoopableObjects.Remove(snoopableObject); - } - - partial void OnSearchTextChanged(string value) - { - UpdateSearchResults(SearchOption.Objects); - } - - partial void OnSnoopableObjectsChanged(IList value) - { - SelectedObject = null; - UpdateSearchResults(SearchOption.Objects); - } - - partial void OnSnoopableDataChanged(IList value) - { - UpdateSearchResults(SearchOption.Selection); - } - - private void UpdateSearchResults(SearchOption option) - { - Task.Run(() => - { - if (string.IsNullOrEmpty(SearchText)) - { - FilteredSnoopableObjects = SnoopableObjects; - FilteredSnoopableData = SnoopableData; - return; - } - - var results = SearchEngine.Search(this, option); - if (results.Data is not null) FilteredSnoopableData = results.Data; - if (results.Objects is not null) FilteredSnoopableObjects = results.Objects; - }); - } - - [RelayCommand] - private Task FetchMembersAsync() - { - CollectMembers(true); - return Task.CompletedTask; - } - - [RelayCommand] - private Task RefreshMembersAsync() - { - CollectMembers(false); - return Task.CompletedTask; - } - - private void CollectMembers(bool useCached) - { - if (SelectedObject is null) - { - SnoopableData = Array.Empty(); - return; - } - - try - { - // ReSharper disable once MethodHasAsyncOverload - SnoopableData = SelectedObject.GetMembers(); - } - catch (Exception exception) - { - notificationService.ShowError("Snoop engine error", exception); - } - } - - public void OnNavigatedTo() - { - PushEvents(_cancellationTokenSource.Token); - } - - public void OnNavigatedFrom() - { - _cancellationTokenSource.Cancel(); - } - - private void PushEvents(CancellationToken cancellationToken) - { - Task.Run((Func)(async () => - { - var iteration = 0; - var faker = new Faker(); - while (!cancellationToken.IsCancellationRequested) - { - await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); - - var snoopableObject = GenerateEvent(faker, iteration); - _events.Push(snoopableObject); - SnoopableObjects = new List(_events); - iteration++; - } - }), cancellationToken); - } - - private static SnoopableObject GenerateEvent(Faker faker, int iteration) - { - if (iteration % 5 == 0) return new SnoopableObject(typeof(DateTime)); - if (iteration % 4 == 0) return new SnoopableObject(Color.FromRgb(faker.Random.Byte(), faker.Random.Byte(), faker.Random.Byte())); - if (iteration % 3 == 0) return new SnoopableObject(faker.Random.Int(0)); - if (iteration % 2 == 0) return new SnoopableObject(faker.Random.Bool()); - return new SnoopableObject(faker.Lorem.Word()); - } -} \ No newline at end of file diff --git a/source/RevitLookup.UI.Demo/Mock/ViewModels/MockSnoopViewModel.cs b/source/RevitLookup.UI.Demo/Mock/ViewModels/MockSnoopViewModel.cs deleted file mode 100644 index f7db4483..00000000 --- a/source/RevitLookup.UI.Demo/Mock/ViewModels/MockSnoopViewModel.cs +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2003-2024 by Autodesk, Inc. -// -// Permission to use, copy, modify, and distribute this software in -// object code form for any purpose and without fee is hereby granted, -// provided that the above copyright notice appears in all copies and -// that both that copyright notice and the limited warranty and -// restricted rights notice below appear in all supporting -// documentation. -// -// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. -// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF -// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. -// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE -// UNINTERRUPTED OR ERROR FREE. -// -// Use, duplication, or disclosure by the U.S. Government is subject to -// restrictions set forth in FAR 52.227-19 (Commercial Computer -// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) -// (Rights in Technical Data and Computer Software), as applicable. - -using System.Collections.ObjectModel; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using RevitLookup.Core.Enums; -using RevitLookup.Core.Objects; -using RevitLookup.Services; -using RevitLookup.Services.Contracts; -using RevitLookup.ViewModels.Contracts; -using RevitLookup.ViewModels.Utils; -using RevitLookup.Views.Pages; - -namespace RevitLookup.UI.Demo.Mock.ViewModels; - -public sealed partial class MockSnoopViewModel(NotificationService notificationService, IServiceProvider provider) : ObservableObject, ISnoopViewModel -{ - private Task _updatingTask = Task.CompletedTask; - - [ObservableProperty] private string _searchText = string.Empty; - [ObservableProperty] private IList _snoopableObjects = []; - [ObservableProperty] private IList _filteredSnoopableObjects = []; - [ObservableProperty] private IList _filteredSnoopableData; - [ObservableProperty] private IList _snoopableData; - - public SnoopableObject SelectedObject { get; set; } - public IServiceProvider ServiceProvider { get; } = provider; - - public void Navigate(SnoopableObject selectedItem) - { - Host.GetService() - .Snoop(selectedItem) - .DependsOn(ServiceProvider) - .Show(); - } - - public void Navigate(IList selectedItems) - { - Host.GetService() - .Snoop(selectedItems) - .DependsOn(ServiceProvider) - .Show(); - } - - async partial void OnSearchTextChanged(string value) - { - await _updatingTask; - UpdateSearchResults(SearchOption.Objects); - } - - async partial void OnSnoopableObjectsChanged(IList value) - { - SelectedObject = null; - await _updatingTask; - UpdateSearchResults(SearchOption.Objects); - } - - async partial void OnSnoopableDataChanged(IList value) - { - await _updatingTask; - UpdateSearchResults(SearchOption.Selection); - } - - private void UpdateSearchResults(SearchOption option) - { - _updatingTask = Task.Run(() => - { - if (string.IsNullOrEmpty(SearchText)) - { - if (option == SearchOption.Objects) - { - FilteredSnoopableObjects = SnoopableObjects; - } - - FilteredSnoopableData = SnoopableData; - return; - } - - var results = SearchEngine.Search(this, option); - if (results.Data is not null) FilteredSnoopableData = results.Data; - if (results.Objects is not null) FilteredSnoopableObjects = new ObservableCollection(results.Objects); - }); - } - - public void RemoveObject(object obj) - { - var snoopableObject = obj switch - { - SnoopableObject snoopable => snoopable, - Descriptor descriptor => descriptor.Value.Descriptor.Value, - _ => throw new NotSupportedException($"Type {obj.GetType().Name} removing not supported") - }; - - SnoopableObjects.Remove(snoopableObject); - FilteredSnoopableObjects.Remove(snoopableObject); - } - - [RelayCommand] - private Task FetchMembersAsync() - { - CollectMembers(true); - return Task.CompletedTask; - } - - [RelayCommand] - private Task RefreshMembersAsync() - { - CollectMembers(false); - return Task.CompletedTask; - } - - private void CollectMembers(bool useCached) - { - if (SelectedObject is null) - { - SnoopableData = Array.Empty(); - return; - } - - try - { - // ReSharper disable once MethodHasAsyncOverload - SnoopableData = SelectedObject.GetMembers(); - } - catch (Exception exception) - { - notificationService.ShowError("Snoop engine error", exception); - } - } -} \ No newline at end of file diff --git a/source/RevitLookup.UI.Demo/RevitLookup.UI.Demo.csproj b/source/RevitLookup.UI.Demo/RevitLookup.UI.Demo.csproj deleted file mode 100644 index b6e68b57..00000000 --- a/source/RevitLookup.UI.Demo/RevitLookup.UI.Demo.csproj +++ /dev/null @@ -1,32 +0,0 @@ - - - true - WinExe - latest - x64 - true - net8.0-windows - - - - true - full - $(DefineConstants);DEBUG - - - true - none - $(DefineConstants);RELEASE - - - - - - - - - - - - - diff --git a/source/RevitLookup.UI.Framework/App.xaml b/source/RevitLookup.UI.Framework/App.xaml new file mode 100644 index 00000000..ba678b77 --- /dev/null +++ b/source/RevitLookup.UI.Framework/App.xaml @@ -0,0 +1,16 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/RevitLookup.UI.Framework.csproj b/source/RevitLookup.UI.Framework/RevitLookup.UI.Framework.csproj new file mode 100644 index 00000000..f98444bd --- /dev/null +++ b/source/RevitLookup.UI.Framework/RevitLookup.UI.Framework.csproj @@ -0,0 +1,28 @@ + + + + true + enable + latest + enable + net48;net8.0-windows + + + + + + + + + + + + + + + + + + + + diff --git a/source/RevitLookup.UI.Demo/App.xaml b/source/RevitLookup.UI.Playground/App.xaml similarity index 76% rename from source/RevitLookup.UI.Demo/App.xaml rename to source/RevitLookup.UI.Playground/App.xaml index 20afa740..3fda360e 100644 --- a/source/RevitLookup.UI.Demo/App.xaml +++ b/source/RevitLookup.UI.Playground/App.xaml @@ -1,7 +1,6 @@ \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/App.xaml.cs b/source/RevitLookup.UI.Playground/App.xaml.cs new file mode 100644 index 00000000..9319dd33 --- /dev/null +++ b/source/RevitLookup.UI.Playground/App.xaml.cs @@ -0,0 +1,38 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +using System.Windows; + +namespace RevitLookup.UI.Playground; + +public sealed partial class App +{ + private void OnStartup(object sender, StartupEventArgs args) + { + // var scopeFactory = Host.GetService(); + // using var serviceScope = scopeFactory.CreateScope(); + // + // var view = serviceScope.ServiceProvider.GetRequiredService(); + // var navigationService = serviceScope.ServiceProvider.GetRequiredService(); + // + // view.Loaded += (_, _) => navigationService.Navigate(typeof(WindowsPage)); + // view.ShowDialog(); + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Host.cs b/source/RevitLookup.UI.Playground/Host.cs new file mode 100644 index 00000000..5c6383a8 --- /dev/null +++ b/source/RevitLookup.UI.Playground/Host.cs @@ -0,0 +1,34 @@ +using Microsoft.Extensions.DependencyInjection; +using Wpf.Ui; + +namespace RevitLookup.UI.Playground; + +/// +/// Provides a host for the application's services and manages their lifetimes. +/// +public static class Host +{ + private static readonly IServiceProvider ServiceProvider = RegisterServices(); + + /// + /// Gets a service of the specified type. + /// + /// The type of service object to get. + /// A service object of type T or null if there is no such service. + public static T GetService() where T : class + { + return ServiceProvider.GetRequiredService(); + } + + private static ServiceProvider RegisterServices() + { + var services = new ServiceCollection(); + + //Frontend services + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + return services.BuildServiceProvider(); + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/RevitLookup.UI.Playground.csproj b/source/RevitLookup.UI.Playground/RevitLookup.UI.Playground.csproj new file mode 100644 index 00000000..be9eef59 --- /dev/null +++ b/source/RevitLookup.UI.Playground/RevitLookup.UI.Playground.csproj @@ -0,0 +1,21 @@ + + + + true + enable + WinExe + latest + enable + net48;net8.0-windows + + + + + + + + + + + + diff --git a/source/RevitLookup.UI/Properties/AssemblyInfo.cs b/source/RevitLookup.UI/Properties/AssemblyInfo.cs index 13732af6..9dd19373 100644 --- a/source/RevitLookup.UI/Properties/AssemblyInfo.cs +++ b/source/RevitLookup.UI/Properties/AssemblyInfo.cs @@ -4,15 +4,14 @@ // All Rights Reserved. using System.Runtime.InteropServices; -using System.Windows; using System.Windows.Markup; [assembly: ComVisible(false)] [assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)] [assembly: Guid("072fb71f-784e-4113-9da0-a506ff1a0cd5")] -[assembly: XmlnsPrefix("http://revitlookup.com/xaml", "rl")] +[assembly: XmlnsPrefix("http://revitlookup.com/xaml", "ui")] [assembly: XmlnsDefinition("http://revitlookup.com/xaml", "Wpf.Ui")] [assembly: XmlnsDefinition("http://revitlookup.com/xaml", "Wpf.Ui.Controls")] [assembly: XmlnsDefinition("http://revitlookup.com/xaml", "Wpf.Ui.Markup")] -[assembly: XmlnsDefinition("http://revitlookup.com/xaml", "Wpf.Ui.Converters")] +[assembly: XmlnsDefinition("http://revitlookup.com/xaml", "Wpf.Ui.Converters")] \ No newline at end of file diff --git a/source/RevitLookup.UI/RevitLookup.UI.csproj b/source/RevitLookup.UI/RevitLookup.UI.csproj index 4f07590c..f7a6529e 100644 --- a/source/RevitLookup.UI/RevitLookup.UI.csproj +++ b/source/RevitLookup.UI/RevitLookup.UI.csproj @@ -2,29 +2,14 @@ true + enable latest x64 true - net48;net8.0-windows true - enable + net48;net8.0-windows CS8603;CS8618;CS8629;CS8600;CS0414;CS4014;CS8604;CS8765;CS8602;CS0168 - - - Wpf.Ui - true - - - - true - full - $(DefineConstants);DEBUG - - - true - none - $(DefineConstants);RELEASE @@ -33,10 +18,7 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + \ No newline at end of file diff --git a/tests/LookupEngine.Tests/LookupEngine.Tests.csproj b/tests/LookupEngine.Tests/LookupEngine.Tests.csproj new file mode 100644 index 00000000..1d244c3d --- /dev/null +++ b/tests/LookupEngine.Tests/LookupEngine.Tests.csproj @@ -0,0 +1,15 @@ + + + + true + enable + latest + enable + net8.0-windows + + + + + + + From 3f68550b78090bf6fc0cad59c1f4a0b93cbf4ff9 Mon Sep 17 00:00:00 2001 From: Nice3point Date: Sun, 27 Oct 2024 17:44:14 +0300 Subject: [PATCH 003/121] Split engine core --- RevitLookup.slnx | 38 +++-- RevitLookup.slnx.DotSettings | 2 + .../Collections/Variant.cs} | 27 +--- .../Collections/Variants.cs | 144 ++++++++++++++++++ .../ComponentModel}/IDescriptorCollector.cs | 2 +- .../ComponentModel}/IDescriptorConnector.cs | 21 +-- .../ComponentModel}/IDescriptorEnumerator.cs | 2 +- .../ComponentModel}/IDescriptorExtension.cs | 2 +- .../ComponentModel}/IDescriptorRedirection.cs | 4 +- .../ComponentModel}/IDescriptorResolver.cs | 4 +- .../ComponentModel}/IExtensionManager.cs | 5 +- .../ComponentModel}/IVariants.cs | 4 +- .../Enums/MemberAttributes.cs | 2 +- .../LookupEngine.Abstractions.csproj | 4 + .../Metadata}/Descriptor.cs | 16 +- .../Metadata}/ObjectDescriptor.cs | 6 +- .../Diagnostic/ClockDiagnoser.cs | 10 +- .../Diagnostic/MemoryDiagnoser.cs | 16 +- .../Formaters/ModifiersFormater.cs | 33 ++++ .../Formaters/ReflexionFormater.cs | 60 ++++++++ .../LookupComposer.Build.cs} | 84 +++++----- .../LookupComposer.Enumeration.cs} | 14 +- source/LookupEngine/LookupComposer.Events.cs | 19 +++ .../LookupEngine/LookupComposer.Extensions.cs | 66 ++++++++ .../LookupComposer.Fields.cs} | 25 +-- .../LookupComposer.Methods.cs} | 97 ++++++------ .../LookupComposer.Properties.cs} | 99 ++++++------ source/LookupEngine/LookupComposer.Write.cs | 92 +++++++++++ source/LookupEngine/LookupComposer.cs | 102 +++++++++++++ source/LookupEngine/LookupEngine.csproj | 9 ++ .../LookupEngine/Options/DecomposeOptions.cs | 33 ++++ source/LookupEngine/SnoopableObject.cs | 57 +++++++ .../Descriptors/DocumentDescriptor.cs | 25 +-- .../Descriptors/PipeDescriptor.cs | 6 +- .../Core/Engine/DescriptorBuilder.Api.cs | 54 ------- .../Core/Engine/DescriptorBuilder.Events.cs | 18 --- .../Engine/DescriptorBuilder.Extensions.cs | 60 -------- .../Core/Engine/DescriptorBuilder.Write.cs | 136 ----------------- .../LookupEngine.Tests.csproj | 4 + tests/LookupEngine.Tests/OptionsTests.cs | 26 ++++ 40 files changed, 904 insertions(+), 524 deletions(-) create mode 100644 RevitLookup.slnx.DotSettings rename source/{RevitLookup/Core/Engine/DescriptorBuilder.cs => LookupEngine.Abstractions/Collections/Variant.cs} (55%) create mode 100644 source/LookupEngine.Abstractions/Collections/Variants.cs rename source/{RevitLookup/Core/Contracts => LookupEngine.Abstractions/ComponentModel}/IDescriptorCollector.cs (95%) rename source/{RevitLookup/Core/Contracts => LookupEngine.Abstractions/ComponentModel}/IDescriptorConnector.cs (76%) rename source/{RevitLookup/Core/Contracts => LookupEngine.Abstractions/ComponentModel}/IDescriptorEnumerator.cs (95%) rename source/{RevitLookup/Core/Contracts => LookupEngine.Abstractions/ComponentModel}/IDescriptorExtension.cs (95%) rename source/{RevitLookup/Core/Contracts => LookupEngine.Abstractions/ComponentModel}/IDescriptorRedirection.cs (90%) rename source/{RevitLookup/Core/Contracts => LookupEngine.Abstractions/ComponentModel}/IDescriptorResolver.cs (90%) rename source/{RevitLookup/Core/Contracts => LookupEngine.Abstractions/ComponentModel}/IExtensionManager.cs (87%) rename source/{RevitLookup/Core/Contracts => LookupEngine.Abstractions/ComponentModel}/IVariants.cs (92%) rename source/{RevitLookup/Core => LookupEngine.Abstractions}/Enums/MemberAttributes.cs (96%) rename source/{RevitLookup/Core/Objects => LookupEngine.Abstractions/Metadata}/Descriptor.cs (74%) rename source/{RevitLookup/Core/ComponentModel/Descriptors => LookupEngine.Abstractions/Metadata}/ObjectDescriptor.cs (87%) rename source/{RevitLookup/Core => LookupEngine}/Diagnostic/ClockDiagnoser.cs (95%) rename source/{RevitLookup/Core => LookupEngine}/Diagnostic/MemoryDiagnoser.cs (96%) create mode 100644 source/LookupEngine/Formaters/ModifiersFormater.cs create mode 100644 source/LookupEngine/Formaters/ReflexionFormater.cs rename source/{RevitLookup/Core/Engine/DescriptorBuilder.Build.cs => LookupEngine/LookupComposer.Build.cs} (50%) rename source/{RevitLookup/Core/Engine/DescriptorBuilder.Enumeration.cs => LookupEngine/LookupComposer.Enumeration.cs} (87%) create mode 100644 source/LookupEngine/LookupComposer.Events.cs create mode 100644 source/LookupEngine/LookupComposer.Extensions.cs rename source/{RevitLookup/Core/Engine/DescriptorBuilder.Fields.cs => LookupEngine/LookupComposer.Fields.cs} (76%) rename source/{RevitLookup/Core/Engine/DescriptorBuilder.Methods.cs => LookupEngine/LookupComposer.Methods.cs} (64%) rename source/{RevitLookup/Core/Engine/DescriptorBuilder.Properties.cs => LookupEngine/LookupComposer.Properties.cs} (69%) create mode 100644 source/LookupEngine/LookupComposer.Write.cs create mode 100644 source/LookupEngine/LookupComposer.cs create mode 100644 source/LookupEngine/Options/DecomposeOptions.cs create mode 100644 source/LookupEngine/SnoopableObject.cs delete mode 100644 source/RevitLookup/Core/Engine/DescriptorBuilder.Api.cs delete mode 100644 source/RevitLookup/Core/Engine/DescriptorBuilder.Events.cs delete mode 100644 source/RevitLookup/Core/Engine/DescriptorBuilder.Extensions.cs delete mode 100644 source/RevitLookup/Core/Engine/DescriptorBuilder.Write.cs create mode 100644 tests/LookupEngine.Tests/OptionsTests.cs diff --git a/RevitLookup.slnx b/RevitLookup.slnx index 63788e25..f1e017c8 100644 --- a/RevitLookup.slnx +++ b/RevitLookup.slnx @@ -1,5 +1,6 @@  + @@ -7,7 +8,7 @@ - + @@ -16,6 +17,7 @@ + @@ -23,7 +25,7 @@ - + @@ -33,6 +35,7 @@ + @@ -40,7 +43,7 @@ - + @@ -48,6 +51,7 @@ + @@ -55,7 +59,7 @@ - + @@ -65,6 +69,7 @@ + @@ -72,7 +77,7 @@ - + @@ -80,6 +85,7 @@ + @@ -87,7 +93,7 @@ - + @@ -97,6 +103,7 @@ + @@ -104,7 +111,7 @@ - + @@ -112,6 +119,7 @@ + @@ -119,7 +127,7 @@ - + @@ -127,6 +135,7 @@ + @@ -134,7 +143,7 @@ - + @@ -150,14 +159,15 @@ + - - + + @@ -166,6 +176,7 @@ + @@ -173,7 +184,7 @@ - + @@ -181,6 +192,7 @@ + @@ -188,7 +200,7 @@ - + diff --git a/RevitLookup.slnx.DotSettings b/RevitLookup.slnx.DotSettings new file mode 100644 index 00000000..81f7b771 --- /dev/null +++ b/RevitLookup.slnx.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/source/RevitLookup/Core/Engine/DescriptorBuilder.cs b/source/LookupEngine.Abstractions/Collections/Variant.cs similarity index 55% rename from source/RevitLookup/Core/Engine/DescriptorBuilder.cs rename to source/LookupEngine.Abstractions/Collections/Variant.cs index c9ee6ae1..8b1303b1 100644 --- a/source/RevitLookup/Core/Engine/DescriptorBuilder.cs +++ b/source/LookupEngine.Abstractions/Collections/Variant.cs @@ -18,29 +18,10 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. -using RevitLookup.Core.Diagnostic; -using RevitLookup.Models.Settings; -using RevitLookup.Services.Contracts; +namespace LookupEngine.Abstractions.Collections; -namespace RevitLookup.Core.Engine; - -public sealed partial class DescriptorBuilder +public sealed class Variant { - private readonly List _descriptors; - private readonly GeneralSettings _settings; - private Descriptor _currentDescriptor; - private object _obj; - private Type _type; - private int _depth; - - private readonly ClockDiagnoser _clockDiagnoser = new(); - private readonly MemoryDiagnoser _memoryDiagnoser = new(); - - private DescriptorBuilder() - { - _descriptors = new List(16); - _settings = Host.GetService().GeneralSettings; - } - - public Document Context { get; private set; } + public required object Object { get; init; } + public string? Description { get; init; } } \ No newline at end of file diff --git a/source/LookupEngine.Abstractions/Collections/Variants.cs b/source/LookupEngine.Abstractions/Collections/Variants.cs new file mode 100644 index 00000000..5f462c20 --- /dev/null +++ b/source/LookupEngine.Abstractions/Collections/Variants.cs @@ -0,0 +1,144 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +using System.Collections; +using LookupEngine.Abstractions.ComponentModel; + +namespace LookupEngine.Abstractions.Collections; + +/// +/// Provides methods for creating variant collections +/// +/// Variants are provided for IDescriptorResolver +public static class Variants +{ + /// + /// Creates a variant collection with a single value + /// + /// A variant collection containing the specified value + public static IVariants Single(T value) + { + return new Variants(1).Add(value); + } + + /// + /// Creates a variant collection with a single value and description + /// + /// A variant collection containing the specified value + public static IVariants Single(T value, string description) + { + return new Variants(1).Add(value, description); + } + + /// + /// Creates an empty variant collection + /// + /// An empty variant collection + /// An empty collection is returned when there are no solutions for a member + public static IVariants Empty() + { + return new Variants(0); + } + + /// + /// A variant that disables the member calculation + /// + public static Func Disabled { get; } = () => new Variants(1).Add(new InvalidOperationException("Member execution disabled")); +} + +/// +/// Represents a collection of variants +/// +/// The type of the variants +/// The initial variants capacity. Required for atomic performance optimizations +public sealed class Variants(int capacity) : IVariants +{ + private readonly Queue _items = new(capacity); + + /// + /// Gets the number of variants + /// + public int Count => _items.Count; + + /// + /// Adds a new variant + /// + /// The variant collection with a new value + public Variants Add(T result) + { + if (result is null) return this; + if (result is ICollection { Count: 0 }) return this; + + _items.Enqueue(new Variant + { + Object = result + }); + + return this; + } + + /// + /// Adds a new variant with description + /// + /// The variant collection with a new value + public Variants Add(T result, string description) + { + if (result is null) return this; + if (result is ICollection { Count: 0 }) return this; + + _items.Enqueue(new Variant + { + Object = result, + Description = description + }); + + return this; + } + + /// + /// Returns a single value if exists + /// + /// Single variant + /// Used for internal purposes only and to display a single result instead of a list + public Variant Single() + { + if (_items.Count != 1) + { + throw new IndexOutOfRangeException("Variants contains more than one element or variants is empty"); + } + + return _items.Peek(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + private Queue.Enumerator GetEnumerator() + { + return _items.GetEnumerator(); + } +} \ No newline at end of file diff --git a/source/RevitLookup/Core/Contracts/IDescriptorCollector.cs b/source/LookupEngine.Abstractions/ComponentModel/IDescriptorCollector.cs similarity index 95% rename from source/RevitLookup/Core/Contracts/IDescriptorCollector.cs rename to source/LookupEngine.Abstractions/ComponentModel/IDescriptorCollector.cs index c47ad585..4ffe1f8a 100644 --- a/source/RevitLookup/Core/Contracts/IDescriptorCollector.cs +++ b/source/LookupEngine.Abstractions/ComponentModel/IDescriptorCollector.cs @@ -18,7 +18,7 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. -namespace RevitLookup.Core.Contracts; +namespace LookupEngine.Abstractions.ComponentModel; /// /// Indicates that the descriptor can retrieve object members by reflection diff --git a/source/RevitLookup/Core/Contracts/IDescriptorConnector.cs b/source/LookupEngine.Abstractions/ComponentModel/IDescriptorConnector.cs similarity index 76% rename from source/RevitLookup/Core/Contracts/IDescriptorConnector.cs rename to source/LookupEngine.Abstractions/ComponentModel/IDescriptorConnector.cs index e8369e2f..0f6bfd93 100644 --- a/source/RevitLookup/Core/Contracts/IDescriptorConnector.cs +++ b/source/LookupEngine.Abstractions/ComponentModel/IDescriptorConnector.cs @@ -18,14 +18,15 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. -using System.Windows.Controls; +// using System.Windows.Controls; +// +// namespace RevitLookup.Core.Contracts; +// +// /// +// /// Indicates that additional members can be added to the descriptor +// /// +// public interface IDescriptorConnector +// { +// void RegisterMenu(ContextMenu contextMenu); +// } -namespace RevitLookup.Core.Contracts; - -/// -/// Indicates that additional members can be added to the descriptor -/// -public interface IDescriptorConnector -{ - void RegisterMenu(ContextMenu contextMenu); -} \ No newline at end of file diff --git a/source/RevitLookup/Core/Contracts/IDescriptorEnumerator.cs b/source/LookupEngine.Abstractions/ComponentModel/IDescriptorEnumerator.cs similarity index 95% rename from source/RevitLookup/Core/Contracts/IDescriptorEnumerator.cs rename to source/LookupEngine.Abstractions/ComponentModel/IDescriptorEnumerator.cs index 54d76f4a..b59e7509 100644 --- a/source/RevitLookup/Core/Contracts/IDescriptorEnumerator.cs +++ b/source/LookupEngine.Abstractions/ComponentModel/IDescriptorEnumerator.cs @@ -20,7 +20,7 @@ using System.Collections; -namespace RevitLookup.Core.Contracts; +namespace LookupEngine.Abstractions.ComponentModel; /// /// Indicates that the descriptor is handled as a collection of descriptors diff --git a/source/RevitLookup/Core/Contracts/IDescriptorExtension.cs b/source/LookupEngine.Abstractions/ComponentModel/IDescriptorExtension.cs similarity index 95% rename from source/RevitLookup/Core/Contracts/IDescriptorExtension.cs rename to source/LookupEngine.Abstractions/ComponentModel/IDescriptorExtension.cs index dc44aa5d..ada1374b 100644 --- a/source/RevitLookup/Core/Contracts/IDescriptorExtension.cs +++ b/source/LookupEngine.Abstractions/ComponentModel/IDescriptorExtension.cs @@ -18,7 +18,7 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. -namespace RevitLookup.Core.Contracts; +namespace LookupEngine.Abstractions.ComponentModel; /// /// Indicates that the descriptor can interact with the UI and execute commands diff --git a/source/RevitLookup/Core/Contracts/IDescriptorRedirection.cs b/source/LookupEngine.Abstractions/ComponentModel/IDescriptorRedirection.cs similarity index 90% rename from source/RevitLookup/Core/Contracts/IDescriptorRedirection.cs rename to source/LookupEngine.Abstractions/ComponentModel/IDescriptorRedirection.cs index d603c242..6397f1c7 100644 --- a/source/RevitLookup/Core/Contracts/IDescriptorRedirection.cs +++ b/source/LookupEngine.Abstractions/ComponentModel/IDescriptorRedirection.cs @@ -18,12 +18,12 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. -namespace RevitLookup.Core.Contracts; +namespace LookupEngine.Abstractions.ComponentModel; /// /// Indicates that the object can be redirected to another /// public interface IDescriptorRedirection { - bool TryRedirect(Document context, string target, out object output); + bool TryRedirect(string target, out object output); } \ No newline at end of file diff --git a/source/RevitLookup/Core/Contracts/IDescriptorResolver.cs b/source/LookupEngine.Abstractions/ComponentModel/IDescriptorResolver.cs similarity index 90% rename from source/RevitLookup/Core/Contracts/IDescriptorResolver.cs rename to source/LookupEngine.Abstractions/ComponentModel/IDescriptorResolver.cs index 93a0dc89..cae08a25 100644 --- a/source/RevitLookup/Core/Contracts/IDescriptorResolver.cs +++ b/source/LookupEngine.Abstractions/ComponentModel/IDescriptorResolver.cs @@ -20,12 +20,12 @@ using System.Reflection; -namespace RevitLookup.Core.Contracts; +namespace LookupEngine.Abstractions.ComponentModel; /// /// Indicates that the descriptor can decide to call methods/properties with parameters or override their values /// public interface IDescriptorResolver : IDescriptorCollector { - Func Resolve(Document context, string target, ParameterInfo[] parameters); + Func? Resolve(string target, ParameterInfo[]? parameters); } \ No newline at end of file diff --git a/source/RevitLookup/Core/Contracts/IExtensionManager.cs b/source/LookupEngine.Abstractions/ComponentModel/IExtensionManager.cs similarity index 87% rename from source/RevitLookup/Core/Contracts/IExtensionManager.cs rename to source/LookupEngine.Abstractions/ComponentModel/IExtensionManager.cs index f5ef913e..87301925 100644 --- a/source/RevitLookup/Core/Contracts/IExtensionManager.cs +++ b/source/LookupEngine.Abstractions/ComponentModel/IExtensionManager.cs @@ -18,10 +18,9 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. -namespace RevitLookup.Core.Contracts; +namespace LookupEngine.Abstractions.ComponentModel; public interface IExtensionManager { - Document Context { get; } - void Register(string methodName, Func context); + void Register(string methodName, Func context); } \ No newline at end of file diff --git a/source/RevitLookup/Core/Contracts/IVariants.cs b/source/LookupEngine.Abstractions/ComponentModel/IVariants.cs similarity index 92% rename from source/RevitLookup/Core/Contracts/IVariants.cs rename to source/LookupEngine.Abstractions/ComponentModel/IVariants.cs index 554efdf0..a791d98a 100644 --- a/source/RevitLookup/Core/Contracts/IVariants.cs +++ b/source/LookupEngine.Abstractions/ComponentModel/IVariants.cs @@ -18,7 +18,9 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. -namespace RevitLookup.Core.Contracts; +using LookupEngine.Abstractions.Collections; + +namespace LookupEngine.Abstractions.ComponentModel; public interface IVariants : IReadOnlyCollection { diff --git a/source/RevitLookup/Core/Enums/MemberAttributes.cs b/source/LookupEngine.Abstractions/Enums/MemberAttributes.cs similarity index 96% rename from source/RevitLookup/Core/Enums/MemberAttributes.cs rename to source/LookupEngine.Abstractions/Enums/MemberAttributes.cs index 6e133005..5c819a8a 100644 --- a/source/RevitLookup/Core/Enums/MemberAttributes.cs +++ b/source/LookupEngine.Abstractions/Enums/MemberAttributes.cs @@ -18,7 +18,7 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. -namespace RevitLookup.Core.Enums; +namespace LookupEngine.Abstractions.Enums; [Flags] public enum MemberAttributes diff --git a/source/LookupEngine.Abstractions/LookupEngine.Abstractions.csproj b/source/LookupEngine.Abstractions/LookupEngine.Abstractions.csproj index 3bd2fdff..dac6a00a 100644 --- a/source/LookupEngine.Abstractions/LookupEngine.Abstractions.csproj +++ b/source/LookupEngine.Abstractions/LookupEngine.Abstractions.csproj @@ -7,4 +7,8 @@ net48;net8.0-windows + + + + diff --git a/source/RevitLookup/Core/Objects/Descriptor.cs b/source/LookupEngine.Abstractions/Metadata/Descriptor.cs similarity index 74% rename from source/RevitLookup/Core/Objects/Descriptor.cs rename to source/LookupEngine.Abstractions/Metadata/Descriptor.cs index 4d9e286c..431e3a00 100644 --- a/source/RevitLookup/Core/Objects/Descriptor.cs +++ b/source/LookupEngine.Abstractions/Metadata/Descriptor.cs @@ -18,19 +18,21 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. -using RevitLookup.Core.Enums; +using System.Diagnostics; +using LookupEngine.Abstractions.Enums; -namespace RevitLookup.Core.Objects; +namespace LookupEngine.Abstractions.Metadata; +[DebuggerDisplay("Name = {Name} Value = {Value} Description = {Description}")] public abstract class Descriptor { public int Depth { get; set; } - public string TypeFullName { get; set; } - public string Type { get; set; } - public string Name { get; set; } - public string Description { get; set; } + public string? TypeFullName { get; set; } + public string? Type { get; set; } + public string? Name { get; set; } + public string? Description { get; set; } public double ComputationTime { get; set; } public long AllocatedBytes { get; set; } public MemberAttributes MemberAttributes { get; set; } - public SnoopableObject Value { get; set; } + public object? Value { get; set; } } \ No newline at end of file diff --git a/source/RevitLookup/Core/ComponentModel/Descriptors/ObjectDescriptor.cs b/source/LookupEngine.Abstractions/Metadata/ObjectDescriptor.cs similarity index 87% rename from source/RevitLookup/Core/ComponentModel/Descriptors/ObjectDescriptor.cs rename to source/LookupEngine.Abstractions/Metadata/ObjectDescriptor.cs index 37fac280..8043b57e 100644 --- a/source/RevitLookup/Core/ComponentModel/Descriptors/ObjectDescriptor.cs +++ b/source/LookupEngine.Abstractions/Metadata/ObjectDescriptor.cs @@ -18,7 +18,7 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. -namespace RevitLookup.Core.ComponentModel.Descriptors; +namespace LookupEngine.Abstractions.Metadata; public sealed class ObjectDescriptor : Descriptor { @@ -26,8 +26,8 @@ public ObjectDescriptor() { } - public ObjectDescriptor(object value) + public ObjectDescriptor(object? value) { - Name = value.ToString(); + Name = value is null ? string.Empty : value.ToString(); } } \ No newline at end of file diff --git a/source/RevitLookup/Core/Diagnostic/ClockDiagnoser.cs b/source/LookupEngine/Diagnostic/ClockDiagnoser.cs similarity index 95% rename from source/RevitLookup/Core/Diagnostic/ClockDiagnoser.cs rename to source/LookupEngine/Diagnostic/ClockDiagnoser.cs index f16d47ee..854c8c6f 100644 --- a/source/RevitLookup/Core/Diagnostic/ClockDiagnoser.cs +++ b/source/LookupEngine/Diagnostic/ClockDiagnoser.cs @@ -20,27 +20,27 @@ using System.Diagnostics; -namespace RevitLookup.Core.Diagnostic; +namespace LookupEngine.Diagnostic; public sealed class ClockDiagnoser { private readonly Stopwatch _clock = new(); - + public void Start() { _clock.Start(); } - + public void Stop() { _clock.Stop(); } - + public TimeSpan GetElapsed() { var elapsed = _clock.Elapsed; _clock.Reset(); - + return elapsed; } } \ No newline at end of file diff --git a/source/RevitLookup/Core/Diagnostic/MemoryDiagnoser.cs b/source/LookupEngine/Diagnostic/MemoryDiagnoser.cs similarity index 96% rename from source/RevitLookup/Core/Diagnostic/MemoryDiagnoser.cs rename to source/LookupEngine/Diagnostic/MemoryDiagnoser.cs index d349346a..94d6516a 100644 --- a/source/RevitLookup/Core/Diagnostic/MemoryDiagnoser.cs +++ b/source/LookupEngine/Diagnostic/MemoryDiagnoser.cs @@ -18,33 +18,33 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. -namespace RevitLookup.Core.Diagnostic; +namespace LookupEngine.Diagnostic; public sealed class MemoryDiagnoser { private long _initialAllocatedBytes; private long _finalAllocatedBytes; - + public void Start() { _initialAllocatedBytes = GetTotalAllocatedBytes(); } - + public void Stop() { _finalAllocatedBytes = GetTotalAllocatedBytes(); } - + public long GetAllocatedBytes() { var allocatedBytes = _finalAllocatedBytes - _initialAllocatedBytes; - + _finalAllocatedBytes = 0; _initialAllocatedBytes = 0; - + return allocatedBytes; } - + private static long GetTotalAllocatedBytes() { // Ref: https://github.com/dotnet/BenchmarkDotNet/blob/master/src/BenchmarkDotNet/Engines/GcStats.cs @@ -52,7 +52,7 @@ private static long GetTotalAllocatedBytes() // GC.GetTotalAllocatedBytes() depends heavily on the garbage collection and gives inaccurate results; // AppDomain.MonitoringIsEnabled almost does not see memory changes when methods are called. // GetAllocatedBytesForCurrentThread is the perfect choice for reflexion calls - + return GC.GetAllocatedBytesForCurrentThread(); } } \ No newline at end of file diff --git a/source/LookupEngine/Formaters/ModifiersFormater.cs b/source/LookupEngine/Formaters/ModifiersFormater.cs new file mode 100644 index 00000000..52ff6d58 --- /dev/null +++ b/source/LookupEngine/Formaters/ModifiersFormater.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using LookupEngine.Abstractions.Enums; + +namespace LookupEngine.Formaters; + +internal static class ModifiersFormater +{ + internal static MemberAttributes FormatAttributes(MemberInfo member) + { + return member switch + { + MethodInfo info => MemberAttributes.Method.AddModifiers(info.Attributes), + PropertyInfo info => MemberAttributes.Property.AddModifiers(info.CanRead ? info.GetMethod!.Attributes : info.SetMethod!.Attributes), + FieldInfo info => MemberAttributes.Field.AddModifiers(info.Attributes), + EventInfo info => MemberAttributes.Event.AddModifiers(info.AddMethod!.Attributes), + _ => throw new ArgumentOutOfRangeException(nameof(member)) + }; + } + + private static MemberAttributes AddModifiers(this MemberAttributes attributes, MethodAttributes methodAttributes) + { + if ((methodAttributes & MethodAttributes.Static) != 0) attributes |= MemberAttributes.Static; + if ((methodAttributes & MethodAttributes.Private) != 0) attributes |= MemberAttributes.Private; + return attributes; + } + + private static MemberAttributes AddModifiers(this MemberAttributes attributes, FieldAttributes fieldAttributes) + { + if ((fieldAttributes & FieldAttributes.Static) != 0) attributes |= MemberAttributes.Static; + if ((fieldAttributes & FieldAttributes.Private) != 0) attributes |= MemberAttributes.Private; + return attributes; + } +} \ No newline at end of file diff --git a/source/LookupEngine/Formaters/ReflexionFormater.cs b/source/LookupEngine/Formaters/ReflexionFormater.cs new file mode 100644 index 00000000..25d8acf6 --- /dev/null +++ b/source/LookupEngine/Formaters/ReflexionFormater.cs @@ -0,0 +1,60 @@ +using System.Reflection; +using LookupEngine.Abstractions.Metadata; + +namespace LookupEngine.Formaters; + +public static class ReflexionFormater +{ + public static string FormatTypeName(Type? type) + { + if (type is null) return string.Empty; + if (!type.IsGenericType) return type.Name; + + var typeName = type.Name; + var apostropheIndex = typeName.IndexOf('`'); + if (apostropheIndex > 0) typeName = typeName[..apostropheIndex]; + typeName += "<"; + var genericArguments = type.GetGenericArguments(); + for (var i = 0; i < genericArguments.Length; i++) + { + typeName += FormatTypeName(genericArguments[i]); + if (i < genericArguments.Length - 1) typeName += ", "; + } + + typeName += ">"; + return typeName; + } + + public static string? FormatTypeFullName(Type type) + { + var fullName = type.FullName; + if (fullName is null) return null; + + return type.IsGenericType ? fullName[..fullName.IndexOf('[')] : fullName; + } + + public static string FormatMemberName(MemberInfo member, ParameterInfo[]? types) + { + if (types is null) return member.Name; + if (types.Length == 0) return member.Name; + + var parameterNames = types.Select(info => + { + return info.ParameterType.IsByRef switch + { + true => $"ref {FormatTypeName(info.ParameterType).Replace("&", string.Empty)}", + _ => FormatTypeName(info.ParameterType) + }; + }); + + var parameters = string.Join(", ", parameterNames); + return $"{member.Name} ({parameters})"; + } + + internal static void FormatDefaultProperties(Type type, Descriptor descriptor) + { + descriptor.Type = FormatTypeName(type); + descriptor.Name ??= descriptor.Type; + descriptor.TypeFullName = FormatTypeFullName(type); + } +} \ No newline at end of file diff --git a/source/RevitLookup/Core/Engine/DescriptorBuilder.Build.cs b/source/LookupEngine/LookupComposer.Build.cs similarity index 50% rename from source/RevitLookup/Core/Engine/DescriptorBuilder.Build.cs rename to source/LookupEngine/LookupComposer.Build.cs index f8056a95..eccc23f3 100644 --- a/source/RevitLookup/Core/Engine/DescriptorBuilder.Build.cs +++ b/source/LookupEngine/LookupComposer.Build.cs @@ -19,64 +19,64 @@ // (Rights in Technical Data and Computer Software), as applicable. using System.Reflection; -using RevitLookup.Core.Utils; +using LookupEngine.Abstractions.Metadata; -namespace RevitLookup.Core.Engine; +namespace LookupEngine; -public sealed partial class DescriptorBuilder +public sealed partial class LookupComposer { - private IList BuildInstanceObject(Type type) + private IList DecomposeInstanceObject(object instance) { - var types = GetTypeHierarchy(type); - - for (var i = types.Count - 1; i >= 0; i--) + _obj = instance; + var objectType = _obj.GetType(); + var objectTypeHierarchy = GetTypeHierarchy(objectType); + + for (var i = objectTypeHierarchy.Count - 1; i >= 0; i--) { - _type = types[i]; - _currentDescriptor = DescriptorUtils.FindSuitableDescriptor(_obj, _type); - + var type = objectTypeHierarchy[i]; + var descriptor = _options.TypeResolver.Invoke(_obj, type); + var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly; - if (_settings.IncludeStatic) flags |= BindingFlags.Static; - if (_settings.IncludePrivate) flags |= BindingFlags.NonPublic; + if (!_options.IgnoreStatic) flags |= BindingFlags.Static; + if (!_options.IgnorePrivate) flags |= BindingFlags.NonPublic; + + DecomposeFields(type, flags); + DecomposeProperties(type, descriptor, flags); + DecomposeMethods(type, descriptor, flags); + DecomposeEvents(type, descriptor, flags); + // AddExtensions(descriptor); - AddProperties(flags); - AddMethods(flags); - AddFields(flags); - AddEvents(flags); - AddExtensions(); - _depth--; } - + AddEnumerableItems(); - + return _descriptors; } - - private List GetTypeHierarchy(Type type) + + private IList DecomposeStaticObject(Type objectType) + { + var flags = BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly; + if (!_options.IgnorePrivate) flags |= BindingFlags.NonPublic; + + DecomposeFields(objectType, flags); + DecomposeProperties(objectType, null, flags); + DecomposeMethods(objectType, null, flags); + + return _descriptors; + } + + private List GetTypeHierarchy(Type inputType) { var types = new List(); - while (type.BaseType is not null) + while (inputType.BaseType is not null) { - types.Add(type); - type = type.BaseType; + types.Add(inputType); + inputType = inputType.BaseType; } - - if (_settings.IncludeRootHierarchy) types.Add(type); - + + if (!_options.IgnoreRoot) types.Add(inputType); + return types; } - - private IList BuildStaticObject(Type type) - { - _type = type; - - var flags = BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly; - if (_settings.IncludePrivate) flags |= BindingFlags.NonPublic; - - AddProperties(flags); - AddMethods(flags); - AddFields(flags); - - return _descriptors; - } } \ No newline at end of file diff --git a/source/RevitLookup/Core/Engine/DescriptorBuilder.Enumeration.cs b/source/LookupEngine/LookupComposer.Enumeration.cs similarity index 87% rename from source/RevitLookup/Core/Engine/DescriptorBuilder.Enumeration.cs rename to source/LookupEngine/LookupComposer.Enumeration.cs index 0ca44506..33c7cdbe 100644 --- a/source/RevitLookup/Core/Engine/DescriptorBuilder.Enumeration.cs +++ b/source/LookupEngine/LookupComposer.Enumeration.cs @@ -20,22 +20,22 @@ using System.Collections; -namespace RevitLookup.Core.Engine; +namespace LookupEngine; -public sealed partial class DescriptorBuilder +public sealed partial class LookupComposer { private void AddEnumerableItems() { if (_obj is not IEnumerable enumerable) return; - - _type = typeof(IEnumerable); + + var enumerableType = typeof(IEnumerable); var enumerator = enumerable.GetEnumerator(); - + while (enumerator.MoveNext()) { - WriteDescriptor(enumerator.Current); + WriteDescriptor(enumerator.Current, enumerableType); } - + if (enumerator is IDisposable disposable) { disposable.Dispose(); diff --git a/source/LookupEngine/LookupComposer.Events.cs b/source/LookupEngine/LookupComposer.Events.cs new file mode 100644 index 00000000..9c67ffa6 --- /dev/null +++ b/source/LookupEngine/LookupComposer.Events.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using LookupEngine.Abstractions.Metadata; +using LookupEngine.Formaters; + +namespace LookupEngine; + +public sealed partial class LookupComposer +{ + private void DecomposeEvents(Type inputType, Descriptor? descriptor, BindingFlags bindingFlags) + { + if (_options.IgnoreEvents) return; + + var members = inputType.GetEvents(bindingFlags); + foreach (var member in members) + { + WriteDescriptor(ReflexionFormater.FormatTypeName(member.EventHandlerType), inputType, member); + } + } +} \ No newline at end of file diff --git a/source/LookupEngine/LookupComposer.Extensions.cs b/source/LookupEngine/LookupComposer.Extensions.cs new file mode 100644 index 00000000..49e7b63c --- /dev/null +++ b/source/LookupEngine/LookupComposer.Extensions.cs @@ -0,0 +1,66 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +// using System.Reflection.Metadata; +// using LookupEngine.Abstractions.Metadata; +// using RevitLookup.Core.Contracts; +// +// namespace LookupEngine; +// +// public sealed partial class LookupComposer : IExtensionManager +// { +// private void AddExtensions(Descriptor descriptor) +// { +// if (!_options.HandleExtensions) return; +// if (descriptor is not IDescriptorExtension extension) return; +// +// extension.RegisterExtensions(this); +// } +// +// public void Register(string methodName, Func handler) +// { +// try +// { +// var result = Evaluate(handler); +// WriteDescriptor(methodName, result); +// } +// catch (Exception exception) +// { +// WriteDescriptor(methodName, exception); +// } +// } +// +// private object Evaluate(Func handler) +// { +// try +// { +// _clockDiagnoser.Start(); +// _memoryDiagnoser.Start(); +// +// return handler.Invoke(); +// } +// finally +// { +// _memoryDiagnoser.Stop(); +// _clockDiagnoser.Stop(); +// } +// } +// } + diff --git a/source/RevitLookup/Core/Engine/DescriptorBuilder.Fields.cs b/source/LookupEngine/LookupComposer.Fields.cs similarity index 76% rename from source/RevitLookup/Core/Engine/DescriptorBuilder.Fields.cs rename to source/LookupEngine/LookupComposer.Fields.cs index 682db978..9585328a 100644 --- a/source/RevitLookup/Core/Engine/DescriptorBuilder.Fields.cs +++ b/source/LookupEngine/LookupComposer.Fields.cs @@ -20,31 +20,34 @@ using System.Reflection; -namespace RevitLookup.Core.Engine; +namespace LookupEngine; -public sealed partial class DescriptorBuilder +public sealed partial class LookupComposer { - private void AddFields(BindingFlags bindingFlags) + private void DecomposeFields(Type type, BindingFlags bindingFlags) { - if (!_settings.IncludeFields) return; - - var members = _type.GetFields(bindingFlags); + if (_options.IgnoreFields) return; + + var members = type.GetFields(bindingFlags); foreach (var member in members) { if (member.IsSpecialName) continue; - - var value = Evaluate(member); - WriteDescriptor(member, value, null); + + var value = EvaluateValue(member); + WriteDescriptor(value, type, member); } } - - private object Evaluate(FieldInfo member) + + private object? EvaluateValue(FieldInfo member) { _clockDiagnoser.Start(); _memoryDiagnoser.Start(); + var value = member.GetValue(_obj); + _memoryDiagnoser.Stop(); _clockDiagnoser.Stop(); + return value; } } \ No newline at end of file diff --git a/source/RevitLookup/Core/Engine/DescriptorBuilder.Methods.cs b/source/LookupEngine/LookupComposer.Methods.cs similarity index 64% rename from source/RevitLookup/Core/Engine/DescriptorBuilder.Methods.cs rename to source/LookupEngine/LookupComposer.Methods.cs index 10598d1f..0f84c741 100644 --- a/source/RevitLookup/Core/Engine/DescriptorBuilder.Methods.cs +++ b/source/LookupEngine/LookupComposer.Methods.cs @@ -19,31 +19,29 @@ // (Rights in Technical Data and Computer Software), as applicable. using System.Reflection; +using LookupEngine.Abstractions.ComponentModel; +using LookupEngine.Abstractions.Metadata; -namespace RevitLookup.Core.Engine; +namespace LookupEngine; -public sealed partial class DescriptorBuilder +public sealed partial class LookupComposer { - private void AddMethods(BindingFlags bindingFlags) + private void DecomposeMethods(Type type, Descriptor? descriptor, BindingFlags bindingFlags) { - var members = _type.GetMethods(bindingFlags); + var members = type.GetMethods(bindingFlags); foreach (var member in members) { if (member.IsSpecialName) continue; - - object value; + + object? value; var parameters = member.GetParameters(); - + try { - if (!TryResolve(member, parameters, out value)) + if (!TryResolve(member, parameters, descriptor, out value)) { if (!IsMethodSupported(member, parameters, out value)) continue; - - if (value is null) - { - Evaluate(member, out value); - } + value ??= EvaluateValue(member); } } catch (TargetInvocationException exception) @@ -54,41 +52,54 @@ private void AddMethods(BindingFlags bindingFlags) { value = exception; } - - WriteDescriptor(member, value, parameters); + + WriteDescriptor(value, type, member, parameters); } } - - private bool TryResolve(MethodInfo member, ParameterInfo[] parameters, out object value) + + private bool TryResolve(MethodInfo member, ParameterInfo[] parameters, Descriptor? descriptor, out object? value) { value = null; - if (_currentDescriptor is not IDescriptorResolver resolver) return false; - - var handler = resolver.Resolve(Context, member.Name, parameters); + if (descriptor is not IDescriptorResolver resolver) return false; + + var handler = resolver.Resolve(member.Name, parameters); if (handler is null) return false; - - try + + value = EvaluateValue(handler); + + return true; + } + + private bool IsMethodSupported(MethodInfo member, ParameterInfo[] parameters, out object? value) + { + value = null; + if (member.ReturnType.Name == "Void") { - _clockDiagnoser.Start(); - _memoryDiagnoser.Start(); - value = handler.Invoke(); + if (_options.IgnoreUnsupported) return false; + + value = new InvalidOperationException("Method doesn't return a value"); + return true; } - finally + + if (parameters.Length > 0) { - _memoryDiagnoser.Stop(); - _clockDiagnoser.Stop(); + if (_options.IgnoreUnsupported) return false; + + value = new NotSupportedException("Unsupported method overload"); + return true; } - + return true; } - - private void Evaluate(MethodInfo member, out object value) + + private object? EvaluateValue(MethodInfo member) { try { _clockDiagnoser.Start(); _memoryDiagnoser.Start(); - value = member.Invoke(_obj, null); + + return member.Invoke(_obj, null); } finally { @@ -96,26 +107,4 @@ private void Evaluate(MethodInfo member, out object value) _clockDiagnoser.Stop(); } } - - private bool IsMethodSupported(MethodInfo member, ParameterInfo[] parameters, out object value) - { - value = null; - if (member.ReturnType.Name == "Void") - { - if (!_settings.IncludeUnsupported) return false; - - value = new InvalidOperationException("Method doesn't return a value"); - return true; - } - - if (parameters.Length > 0) - { - if (!_settings.IncludeUnsupported) return false; - - value = new NotSupportedException("Unsupported method overload"); - return true; - } - - return true; - } } \ No newline at end of file diff --git a/source/RevitLookup/Core/Engine/DescriptorBuilder.Properties.cs b/source/LookupEngine/LookupComposer.Properties.cs similarity index 69% rename from source/RevitLookup/Core/Engine/DescriptorBuilder.Properties.cs rename to source/LookupEngine/LookupComposer.Properties.cs index f7e8a07d..c847c0e3 100644 --- a/source/RevitLookup/Core/Engine/DescriptorBuilder.Properties.cs +++ b/source/LookupEngine/LookupComposer.Properties.cs @@ -19,31 +19,29 @@ // (Rights in Technical Data and Computer Software), as applicable. using System.Reflection; +using LookupEngine.Abstractions.ComponentModel; +using LookupEngine.Abstractions.Metadata; -namespace RevitLookup.Core.Engine; +namespace LookupEngine; -public sealed partial class DescriptorBuilder +public sealed partial class LookupComposer { - private void AddProperties(BindingFlags bindingFlags) + private void DecomposeProperties(Type type, Descriptor? descriptor, BindingFlags bindingFlags) { - var members = _type.GetProperties(bindingFlags); + var members = type.GetProperties(bindingFlags); foreach (var member in members) { if (member.IsSpecialName) continue; - - object value; + + object? value; var parameters = member.CanRead ? member.GetMethod!.GetParameters() : null; - + try { - if (!TryResolve(member, parameters, out value)) + if (!TryResolve(member, parameters, descriptor, out value)) { if (!IsPropertySupported(member, parameters, out value)) continue; - - if (value is null) - { - Evaluate(member, out value); - } + value ??= EvaluateValue(member); } } catch (TargetInvocationException exception) @@ -54,41 +52,53 @@ private void AddProperties(BindingFlags bindingFlags) { value = exception; } - - WriteDescriptor(member, value, parameters); + + WriteDescriptor(value, type, member, parameters); } } - - private bool TryResolve(PropertyInfo member, ParameterInfo[] parameters, out object value) + + private bool TryResolve(PropertyInfo member, ParameterInfo[]? parameters, Descriptor? descriptor, out object? value) { value = null; - if (_currentDescriptor is not IDescriptorResolver resolver) return false; - - var handler = resolver.Resolve(Context, member.Name, parameters); + if (descriptor is not IDescriptorResolver resolver) return false; + + var handler = resolver.Resolve(member.Name, parameters); if (handler is null) return false; - - try + + value = EvaluateValue(handler); + + return true; + } + + private bool IsPropertySupported(PropertyInfo member, ParameterInfo[]? parameters, out object? value) + { + value = null; + + if (!member.CanRead) { - _clockDiagnoser.Start(); - _memoryDiagnoser.Start(); - value = handler.Invoke(); + value = new InvalidOperationException("Property does not have a get accessor, it cannot be read"); + return true; } - finally + + if (parameters is not null && parameters.Length > 0) { - _memoryDiagnoser.Stop(); - _clockDiagnoser.Stop(); + if (_options.IgnoreUnsupported) return false; + + value = new NotSupportedException("Unsupported property overload"); + return true; } - + return true; } - - private void Evaluate(PropertyInfo member, out object value) + + private object? EvaluateValue(PropertyInfo member) { try { _clockDiagnoser.Start(); _memoryDiagnoser.Start(); - value = member.GetValue(_obj); + + return member.GetValue(_obj); } finally { @@ -96,25 +106,20 @@ private void Evaluate(PropertyInfo member, out object value) _clockDiagnoser.Stop(); } } - - private bool IsPropertySupported(PropertyInfo member, ParameterInfo[] parameters, out object value) + + private IVariants EvaluateValue(Func handler) { - value = null; - - if (!member.CanRead) + try { - value = new InvalidOperationException("Property does not have a get accessor, it cannot be read"); - return true; + _clockDiagnoser.Start(); + _memoryDiagnoser.Start(); + + return handler.Invoke(); } - - if (parameters.Length > 0) + finally { - if (!_settings.IncludeUnsupported) return false; - - value = new NotSupportedException("Unsupported property overload"); - return true; + _memoryDiagnoser.Stop(); + _clockDiagnoser.Stop(); } - - return true; } } \ No newline at end of file diff --git a/source/LookupEngine/LookupComposer.Write.cs b/source/LookupEngine/LookupComposer.Write.cs new file mode 100644 index 00000000..76c64a57 --- /dev/null +++ b/source/LookupEngine/LookupComposer.Write.cs @@ -0,0 +1,92 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +using System.Reflection; +using LookupEngine.Abstractions.Enums; +using LookupEngine.Abstractions.Metadata; +using LookupEngine.Formaters; + +namespace LookupEngine; + +public sealed partial class LookupComposer +{ + private void WriteDescriptor(object? value, Type inputType) + { + var descriptor = new ObjectDescriptor + { + Depth = _depth, + // Value = EvaluateValue(value), + TypeFullName = ReflexionFormater.FormatTypeFullName(inputType), + MemberAttributes = MemberAttributes.Property, + Type = ReflexionFormater.FormatTypeName(inputType) + }; + + // descriptor.Name = descriptor.Value.Descriptor.Type; + _descriptors.Add(descriptor); + } + + private void WriteDescriptor(object? value, Type inputType, string name) + { + var descriptor = new ObjectDescriptor + { + Depth = _depth, + Name = name, + // Value = EvaluateValue(value), + TypeFullName = ReflexionFormater.FormatTypeFullName(inputType), + MemberAttributes = MemberAttributes.Extension, + Type = ReflexionFormater.FormatTypeName(inputType), + ComputationTime = _clockDiagnoser.GetElapsed().TotalMilliseconds, + AllocatedBytes = _memoryDiagnoser.GetAllocatedBytes() + }; + + _descriptors.Add(descriptor); + } + + private void WriteDescriptor(object? value, Type inputType, MemberInfo member, ParameterInfo[]? parameters = null) + { + var descriptor = new ObjectDescriptor + { + Depth = _depth, + TypeFullName = ReflexionFormater.FormatTypeFullName(inputType), + // Value = EvaluateValue(member, value), + Name = ReflexionFormater.FormatMemberName(member, parameters), + MemberAttributes = ModifiersFormater.FormatAttributes(member), + Type = ReflexionFormater.FormatTypeName(inputType), + ComputationTime = _clockDiagnoser.GetElapsed().TotalMilliseconds, + AllocatedBytes = _memoryDiagnoser.GetAllocatedBytes() + }; + + _descriptors.Add(descriptor); + } + + // private SnoopableObject EvaluateValue(MemberInfo member, object? value) + // { + // var snoopableObject = new SnoopableObject(value, Context); + // SnoopUtils.Redirect(member.Name, snoopableObject); + // return snoopableObject; + // } + // + // private SnoopableObject EvaluateValue(object? value) + // { + // var snoopableObject = new SnoopableObject(value, Context); + // SnoopUtils.Redirect(snoopableObject); + // return snoopableObject; + // } +} \ No newline at end of file diff --git a/source/LookupEngine/LookupComposer.cs b/source/LookupEngine/LookupComposer.cs new file mode 100644 index 00000000..00767a4a --- /dev/null +++ b/source/LookupEngine/LookupComposer.cs @@ -0,0 +1,102 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +using JetBrains.Annotations; +using LookupEngine.Abstractions.Metadata; +using LookupEngine.Diagnostic; +using LookupEngine.Options; + +namespace LookupEngine; + +[PublicAPI] +public sealed partial class LookupComposer +{ + private readonly DecomposeOptions _options; + private readonly List _descriptors = new(32); + + private int _depth; + private object? _obj; + + private readonly ClockDiagnoser _clockDiagnoser = new(); + private readonly MemoryDiagnoser _memoryDiagnoser = new(); + + private LookupComposer(DecomposeOptions options) + { + _options = options; + } + + [Pure] + public static IList Decompose(object? value, DecomposeOptions? options = null) + { + if (value is null) return []; + + options ??= DecomposeOptions.Default; + var composer = new LookupComposer(options); + + return value switch + { + Type staticObjectType => composer.DecomposeStaticObject(staticObjectType), + _ => composer.DecomposeInstanceObject(value) + }; + } +} + +// private Descriptor FindSuitableDescriptor(object? obj) +// { +// var descriptor = _options.TypeResolver.Invoke(obj, null); +// +// if (obj is null) +// { +// descriptor.Type = nameof(Object); +// descriptor.TypeFullName = "System.Object"; +// } +// else +// { +// var type = obj.GetType(); +// FormatDefaultProperties(descriptor, type); +// } +// +// return descriptor; +// } +// +// private Descriptor FindSuitableDescriptor(Type? type) +// { +// var descriptor = new ObjectDescriptor(); +// +// if (type is null) +// { +// descriptor.Type = nameof(Object); +// descriptor.TypeFullName = "System.Object"; +// } +// else +// { +// FormatDefaultProperties(descriptor, type); +// } +// +// return descriptor; +// } +// +// private Descriptor FindSuitableDescriptor(object obj, Type type) +// { +// var descriptor = _options.TypeResolver.Invoke(obj, type); +// +// FormatDefaultProperties(descriptor, type); +// return descriptor; +// } \ No newline at end of file diff --git a/source/LookupEngine/LookupEngine.csproj b/source/LookupEngine/LookupEngine.csproj index 3bd2fdff..661fba31 100644 --- a/source/LookupEngine/LookupEngine.csproj +++ b/source/LookupEngine/LookupEngine.csproj @@ -7,4 +7,13 @@ net48;net8.0-windows + + + + + + + + + diff --git a/source/LookupEngine/Options/DecomposeOptions.cs b/source/LookupEngine/Options/DecomposeOptions.cs new file mode 100644 index 00000000..6acc0871 --- /dev/null +++ b/source/LookupEngine/Options/DecomposeOptions.cs @@ -0,0 +1,33 @@ +using LookupEngine.Abstractions.Metadata; + +namespace LookupEngine.Options; + +public sealed class DecomposeOptions +{ + private Func? _typeResolver; + + public Func TypeResolver + { + get { return _typeResolver ??= DefaultResolveMap; } + set => _typeResolver = value; + } + + public bool IgnoreFields { get; set; } = true; + public bool IgnoreRoot { get; set; } = false; + public bool IgnorePrivate { get; set; } = true; + public bool IgnoreStatic { get; set; } = false; + public bool IgnoreEvents { get; set; } = false; + public bool IgnoreUnsupported { get; set; } = true; + public bool HandleExtensions { get; set; } = false; + + public static DecomposeOptions Default => new(); + + private static Descriptor DefaultResolveMap(object? obj, Type? type) + { + return obj switch + { + null when type is null => new ObjectDescriptor(), + _ => new ObjectDescriptor(obj), + }; + } +} \ No newline at end of file diff --git a/source/LookupEngine/SnoopableObject.cs b/source/LookupEngine/SnoopableObject.cs new file mode 100644 index 00000000..11244543 --- /dev/null +++ b/source/LookupEngine/SnoopableObject.cs @@ -0,0 +1,57 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +namespace LookupEngine.Abstractions.Metadata; + +// public sealed class SnoopableObject +// { +// private IList _members; +// +// public SnoopableObject(Type type) +// { +// Object = type; +// Descriptor = DescriptorUtils.FindSuitableDescriptor(type); +// } +// +// public SnoopableObject(object obj) +// { +// Object = obj; +// Descriptor = DescriptorUtils.FindSuitableDescriptor(obj); +// } +// +// public object Object { get; set; } +// public Descriptor Descriptor { get; set; } +// +// public IList GetMembers() +// { +// return _members = LookupComposer.Decompose(Object); +// } +// +// public async Task> GetMembersAsync() +// { +// return _members = await RevitShell.ExternalDescriptorHandler.RaiseAsync(_ => LookupComposer.Decompose(Object)); +// } +// +// public async Task> GetCachedMembersAsync() +// { +// return _members ?? await GetMembersAsync(); +// } +// } \ No newline at end of file diff --git a/source/RevitLookup/Core/ComponentModel/Descriptors/DocumentDescriptor.cs b/source/RevitLookup/Core/ComponentModel/Descriptors/DocumentDescriptor.cs index f19c1234..e9f44749 100644 --- a/source/RevitLookup/Core/ComponentModel/Descriptors/DocumentDescriptor.cs +++ b/source/RevitLookup/Core/ComponentModel/Descriptors/DocumentDescriptor.cs @@ -23,6 +23,7 @@ #if REVIT2023_OR_GREATER using Autodesk.Revit.DB.Structure; #endif + #if !REVIT2025_OR_GREATER using Autodesk.Revit.DB.Macros; #endif @@ -32,13 +33,13 @@ namespace RevitLookup.Core.ComponentModel.Descriptors; public sealed class DocumentDescriptor : Descriptor, IDescriptorResolver, IDescriptorExtension { private readonly Document _document; - + public DocumentDescriptor(Document document) { _document = document; Name = document.Title; } - + public Func Resolve(Document context, string target, ParameterInfo[] parameters) { return target switch @@ -52,23 +53,24 @@ public Func Resolve(Document context, string target, ParameterInfo[] #endif _ => null }; - + IVariants ResolvePlanTopologies() { if (_document.IsReadOnly) return Variants.Empty(); - + var transaction = new Transaction(_document); transaction.Start("Calculating plan topologies"); var topologies = _document.PlanTopologies; transaction.Commit(); - + return Variants.Single(topologies); } + IVariants ResolveDefaultElementTypeId() { var values = Enum.GetValues(typeof(ElementTypeGroup)); var variants = new Variants(values.Length); - + foreach (ElementTypeGroup value in values) { var result = _document.GetDefaultElementTypeId(value); @@ -81,22 +83,23 @@ IVariants ResolveDefaultElementTypeId() variants.Add(result, $"{value.ToString()}: {result}"); } } - + return variants; } #if REVIT2024_OR_GREATER - + IVariants ResolveGetUnusedElements() { return Variants.Single(context.GetUnusedElements(new HashSet())); } - + IVariants ResolveGetAllUnusedElements() { return Variants.Single(context.GetAllUnusedElements(new HashSet())); } #endif } + public void RegisterExtensions(IExtensionManager manager) { manager.Register(nameof(GlobalParametersManager.GetAllGlobalParameters), GlobalParametersManager.GetAllGlobalParameters); @@ -113,14 +116,14 @@ public void RegisterExtensions(IExtensionManager manager) #endif if (_document.IsFamilyDocument) { - manager.Register(nameof(FamilySizeTableManager.GetFamilySizeTableManager), context => + manager.Register(nameof(FamilySizeTableManager.CreateFamilySizeTableManager), context => { var familyTableId = new ElementId(BuiltInParameter.RBS_LOOKUP_TABLE_NAME); return FamilySizeTableManager.GetFamilySizeTableManager(context, familyTableId); }); manager.Register(nameof(LightFamily.GetLightFamily), LightFamily.GetLightFamily); } - + // Disabled: slow performance. // manager.Register(nameof(WorksharingUtils.GetUserWorksetInfo), context => // { diff --git a/source/RevitLookup/Core/ComponentModel/Descriptors/PipeDescriptor.cs b/source/RevitLookup/Core/ComponentModel/Descriptors/PipeDescriptor.cs index ce950c46..69a34567 100644 --- a/source/RevitLookup/Core/ComponentModel/Descriptors/PipeDescriptor.cs +++ b/source/RevitLookup/Core/ComponentModel/Descriptors/PipeDescriptor.cs @@ -27,10 +27,10 @@ public class PipeDescriptor(Pipe pipe) : ElementDescriptor(pipe) { public override void RegisterExtensions(IExtensionManager manager) { - manager.Register(nameof(PlumbingUtils.HasOpenConnector), _ => PlumbingUtils.HasOpenConnector(pipe.Document, pipe.Id)); + manager.Register(nameof(PlumbingUtils.PlaceCapOnOpenEnds), _ => PlumbingUtils.HasOpenConnector(pipe.Document, pipe.Id)); } - - + + public override Func Resolve(Document context, string target, ParameterInfo[] parameters) { return null; diff --git a/source/RevitLookup/Core/Engine/DescriptorBuilder.Api.cs b/source/RevitLookup/Core/Engine/DescriptorBuilder.Api.cs deleted file mode 100644 index 0bc0fe1f..00000000 --- a/source/RevitLookup/Core/Engine/DescriptorBuilder.Api.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2003-2024 by Autodesk, Inc. -// -// Permission to use, copy, modify, and distribute this software in -// object code form for any purpose and without fee is hereby granted, -// provided that the above copyright notice appears in all copies and -// that both that copyright notice and the limited warranty and -// restricted rights notice below appear in all supporting -// documentation. -// -// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. -// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF -// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. -// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE -// UNINTERRUPTED OR ERROR FREE. -// -// Use, duplication, or disclosure by the U.S. Government is subject to -// restrictions set forth in FAR 52.227-19 (Commercial Computer -// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) -// (Rights in Technical Data and Computer Software), as applicable. - -namespace RevitLookup.Core.Engine; - -public sealed partial class DescriptorBuilder -{ - public static IList Build(Type type) - { - var builder = new DescriptorBuilder - { - _obj = null, - Context = Nice3point.Revit.Toolkit.Context.ActiveDocument - }; - - return builder.BuildStaticObject(type); - } - - public static IList Build(object obj, Document context) - { - if (obj is null) return Array.Empty(); - - var builder = new DescriptorBuilder(); - - switch (obj) - { - case Type staticObjectType: - builder._obj = null; - builder.Context = context; - return builder.BuildStaticObject(staticObjectType); - default: - builder._obj = obj; - builder.Context = context; - return builder.BuildInstanceObject(obj.GetType()); - } - } -} \ No newline at end of file diff --git a/source/RevitLookup/Core/Engine/DescriptorBuilder.Events.cs b/source/RevitLookup/Core/Engine/DescriptorBuilder.Events.cs deleted file mode 100644 index 845b1710..00000000 --- a/source/RevitLookup/Core/Engine/DescriptorBuilder.Events.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Reflection; -using RevitLookup.Core.Utils; - -namespace RevitLookup.Core.Engine; - -public sealed partial class DescriptorBuilder -{ - private void AddEvents(BindingFlags bindingFlags) - { - if (!_settings.IncludeEvents) return; - - var members = _type.GetEvents(bindingFlags); - foreach (var member in members) - { - WriteDescriptor(member, DescriptorUtils.MakeGenericTypeName(member.EventHandlerType), null); - } - } -} \ No newline at end of file diff --git a/source/RevitLookup/Core/Engine/DescriptorBuilder.Extensions.cs b/source/RevitLookup/Core/Engine/DescriptorBuilder.Extensions.cs deleted file mode 100644 index 157aedc0..00000000 --- a/source/RevitLookup/Core/Engine/DescriptorBuilder.Extensions.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2003-2024 by Autodesk, Inc. -// -// Permission to use, copy, modify, and distribute this software in -// object code form for any purpose and without fee is hereby granted, -// provided that the above copyright notice appears in all copies and -// that both that copyright notice and the limited warranty and -// restricted rights notice below appear in all supporting -// documentation. -// -// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. -// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF -// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. -// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE -// UNINTERRUPTED OR ERROR FREE. -// -// Use, duplication, or disclosure by the U.S. Government is subject to -// restrictions set forth in FAR 52.227-19 (Commercial Computer -// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) -// (Rights in Technical Data and Computer Software), as applicable. - -namespace RevitLookup.Core.Engine; - -public sealed partial class DescriptorBuilder : IExtensionManager -{ - private void AddExtensions() - { - if (!_settings.IncludeExtensions) return; - if (_currentDescriptor is not IDescriptorExtension extension) return; - - extension.RegisterExtensions(this); - } - - public void Register(string methodName, Func handler) - { - try - { - var result = Evaluate(handler); - WriteDescriptor(methodName, result); - } - catch (Exception exception) - { - WriteDescriptor(methodName, exception); - } - } - - private object Evaluate(Func handler) - { - try - { - _clockDiagnoser.Start(); - _memoryDiagnoser.Start(); - return handler.Invoke(Context); - } - finally - { - _memoryDiagnoser.Stop(); - _clockDiagnoser.Stop(); - } - } -} \ No newline at end of file diff --git a/source/RevitLookup/Core/Engine/DescriptorBuilder.Write.cs b/source/RevitLookup/Core/Engine/DescriptorBuilder.Write.cs deleted file mode 100644 index 3efe5e2d..00000000 --- a/source/RevitLookup/Core/Engine/DescriptorBuilder.Write.cs +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright 2003-2024 by Autodesk, Inc. -// -// Permission to use, copy, modify, and distribute this software in -// object code form for any purpose and without fee is hereby granted, -// provided that the above copyright notice appears in all copies and -// that both that copyright notice and the limited warranty and -// restricted rights notice below appear in all supporting -// documentation. -// -// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. -// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF -// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. -// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE -// UNINTERRUPTED OR ERROR FREE. -// -// Use, duplication, or disclosure by the U.S. Government is subject to -// restrictions set forth in FAR 52.227-19 (Commercial Computer -// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) -// (Rights in Technical Data and Computer Software), as applicable. - -using System.Reflection; -using RevitLookup.Core.ComponentModel.Descriptors; -using RevitLookup.Core.Enums; -using RevitLookup.Core.Utils; - -namespace RevitLookup.Core.Engine; - -public sealed partial class DescriptorBuilder -{ - private void WriteDescriptor(object value) - { - var descriptor = new ObjectDescriptor - { - Depth = _depth, - Value = EvaluateValue(value), - TypeFullName = DescriptorUtils.MakeGenericFullTypeName(_type), - MemberAttributes = MemberAttributes.Property, - Type = DescriptorUtils.MakeGenericTypeName(_type) - }; - - descriptor.Name = descriptor.Value.Descriptor.Type; - _descriptors.Add(descriptor); - } - - private void WriteDescriptor(string name, object value) - { - var descriptor = new ObjectDescriptor - { - Depth = _depth, - Name = name, - Value = EvaluateValue(value), - TypeFullName = DescriptorUtils.MakeGenericFullTypeName(_type), - MemberAttributes = MemberAttributes.Extension, - Type = DescriptorUtils.MakeGenericTypeName(_type), - ComputationTime = _clockDiagnoser.GetElapsed().TotalMilliseconds, - AllocatedBytes = _memoryDiagnoser.GetAllocatedBytes() - }; - - _descriptors.Add(descriptor); - } - - private void WriteDescriptor(MemberInfo member, object value, ParameterInfo[] parameters) - { - var descriptor = new ObjectDescriptor - { - Depth = _depth, - TypeFullName = DescriptorUtils.MakeGenericFullTypeName(_type), - Value = EvaluateValue(member, value), - Name = EvaluateName(member, parameters), - MemberAttributes = EvaluateAttributes(member), - Type = DescriptorUtils.MakeGenericTypeName(_type), - ComputationTime = _clockDiagnoser.GetElapsed().TotalMilliseconds, - AllocatedBytes = _memoryDiagnoser.GetAllocatedBytes() - }; - - _descriptors.Add(descriptor); - } - - private SnoopableObject EvaluateValue(MemberInfo member, object value) - { - var snoopableObject = new SnoopableObject(value, Context); - SnoopUtils.Redirect(member.Name, snoopableObject); - return snoopableObject; - } - - private SnoopableObject EvaluateValue(object value) - { - var snoopableObject = new SnoopableObject(value, Context); - SnoopUtils.Redirect(snoopableObject); - return snoopableObject; - } - - private static string EvaluateName(MemberInfo member, [CanBeNull] ParameterInfo[] types) - { - if (types is null) return member.Name; - if (types.Length == 0) return member.Name; - - var parameterNames = types.Select(info => - { - return info.ParameterType.IsByRef switch - { - true => $"ref {DescriptorUtils.MakeGenericTypeName(info.ParameterType).Replace("&", string.Empty)}", - _ => DescriptorUtils.MakeGenericTypeName(info.ParameterType) - }; - }); - - var parameters = string.Join(", ", parameterNames); - return $"{member.Name} ({parameters})"; - } - - private static MemberAttributes EvaluateAttributes(MemberInfo member) - { - return member switch - { - MethodInfo info => GetModifiers(MemberAttributes.Method, info.Attributes), - PropertyInfo info => GetModifiers(MemberAttributes.Property, info.CanRead ? info.GetMethod!.Attributes : info.SetMethod!.Attributes), - FieldInfo info => GetModifiers(MemberAttributes.Field, info.Attributes), - EventInfo info => GetModifiers(MemberAttributes.Event, info.AddMethod!.Attributes), - _ => throw new ArgumentOutOfRangeException(nameof(member)) - }; - } - - private static MemberAttributes GetModifiers(MemberAttributes attributes, MethodAttributes methodAttributes) - { - if ((methodAttributes & MethodAttributes.Static) != 0) attributes |= MemberAttributes.Static; - if ((methodAttributes & MethodAttributes.Private) != 0) attributes |= MemberAttributes.Private; - return attributes; - } - - private static MemberAttributes GetModifiers(MemberAttributes attributes, FieldAttributes fieldAttributes) - { - if ((fieldAttributes & FieldAttributes.Static) != 0) attributes |= MemberAttributes.Static; - if ((fieldAttributes & FieldAttributes.Private) != 0) attributes |= MemberAttributes.Private; - return attributes; - } -} \ No newline at end of file diff --git a/tests/LookupEngine.Tests/LookupEngine.Tests.csproj b/tests/LookupEngine.Tests/LookupEngine.Tests.csproj index 1d244c3d..7384f6bb 100644 --- a/tests/LookupEngine.Tests/LookupEngine.Tests.csproj +++ b/tests/LookupEngine.Tests/LookupEngine.Tests.csproj @@ -12,4 +12,8 @@ + + + + diff --git a/tests/LookupEngine.Tests/OptionsTests.cs b/tests/LookupEngine.Tests/OptionsTests.cs new file mode 100644 index 00000000..bd49aafc --- /dev/null +++ b/tests/LookupEngine.Tests/OptionsTests.cs @@ -0,0 +1,26 @@ +using LookupEngine.Options; + +namespace LookupEngine.Tests; + +public sealed class LookupEngineTests +{ + [Test] + public async Task Decompose_DefaultOptions() + { + var descriptors = LookupComposer.Decompose("Hello World"); + await Assert.That(descriptors).IsNotEmpty(); + } + + [Test] + public async Task Decompose_With_Fields() + { + var options = new DecomposeOptions + { + IgnoreFields = false + }; + + var defaultDescriptors = LookupComposer.Decompose("Hello World"); + var optionalDescriptors = LookupComposer.Decompose("Hello World", options); + await Assert.That(() => optionalDescriptors.Count > defaultDescriptors.Count).ThrowsNothing(); + } +} \ No newline at end of file From df62ab8e33acc4a07f306aa5bae85031efa02106 Mon Sep 17 00:00:00 2001 From: Nice3point Date: Sun, 3 Nov 2024 13:21:36 +0300 Subject: [PATCH 004/121] Restore original engine features --- .../IVariants.cs | 4 +- .../ComponentModel/BooleanDescriptor.cs} | 6 +- .../ComponentModel/Descriptor.cs} | 12 +- .../ComponentModel}/ExceptionDescriptor.cs | 2 +- .../ObjectDescriptor.cs | 6 +- .../IDescriptorCollector.cs | 2 +- .../IDescriptorConnector.cs | 0 .../IDescriptorEnumerator.cs | 2 +- .../IDescriptorExtension.cs | 2 +- .../IDescriptorRedirection.cs | 2 +- .../IDescriptorResolver.cs | 3 +- .../IExtensionManager.cs | 6 +- .../LookupEngine.Abstractions.csproj | 1 + .../Metadata/DecompositionMemberData.cs | 21 +++ .../Diagnostic/IEngineDiagnoser.cs | 7 + .../Diagnostic/MemoryDiagnoser.cs | 6 +- .../{ClockDiagnoser.cs => TimeDiagnoser.cs} | 23 ++- .../LookupComposer.Decompose.cs} | 43 +++--- .../Engine/LookupComposer.Diagnostic.cs | 93 ++++++++++++ .../LookupComposer.Enumeration.cs | 8 +- .../Engine/LookupComposer.Events.cs | 19 +++ .../Engine/LookupComposer.Extensions.cs} | 39 +++-- .../{ => Engine}/LookupComposer.Fields.cs | 22 +-- .../{ => Engine}/LookupComposer.Methods.cs | 40 ++--- .../{ => Engine}/LookupComposer.Properties.cs | 56 ++----- .../LookupComposer.WriteHelpers.cs} | 57 +++---- .../{ => Engine}/LookupComposer.cs | 103 +++++++------ .../Exceptions/EngineException.cs | 12 ++ .../Formaters/ModifiersFormater.cs | 12 +- .../Formaters/ReflexionFormater.cs | 21 +-- source/LookupEngine/IRedirect.cs | 3 + source/LookupEngine/LookupComposer.Events.cs | 19 --- .../LookupEngine/LookupComposer.Extensions.cs | 66 -------- .../LookupEngine/Options/DecomposeOptions.cs | 28 ++-- source/LookupEngine/Redirect.cs | 6 + source/LookupEngine/SnoopUtils.cs | 62 ++++++++ .../Core/Objects/SnoopableObject.cs | 69 --------- source/RevitLookup/Core/Objects/Variants.cs | 143 ------------------ .../RevitLookup/Core/Utils/DescriptorUtils.cs | 106 ------------- source/RevitLookup/Core/Utils/SnoopUtils.cs | 62 -------- tests/LookupEngine.Tests/OptionsTests.cs | 36 ++++- 41 files changed, 485 insertions(+), 745 deletions(-) rename source/LookupEngine.Abstractions/{ComponentModel => Collections}/IVariants.cs (92%) rename source/{RevitLookup/Core/ComponentModel/Descriptors/BoolDescriptor.cs => LookupEngine.Abstractions/ComponentModel/BooleanDescriptor.cs} (87%) rename source/{RevitLookup/Core/Objects/Variant.cs => LookupEngine.Abstractions/ComponentModel/Descriptor.cs} (81%) rename source/{RevitLookup/Core/ComponentModel/Descriptors => LookupEngine.Abstractions/ComponentModel}/ExceptionDescriptor.cs (96%) rename source/LookupEngine.Abstractions/{Metadata => ComponentModel}/ObjectDescriptor.cs (92%) rename source/LookupEngine.Abstractions/{ComponentModel => Configuration}/IDescriptorCollector.cs (95%) rename source/LookupEngine.Abstractions/{ComponentModel => Configuration}/IDescriptorConnector.cs (100%) rename source/LookupEngine.Abstractions/{ComponentModel => Configuration}/IDescriptorEnumerator.cs (95%) rename source/LookupEngine.Abstractions/{ComponentModel => Configuration}/IDescriptorExtension.cs (95%) rename source/LookupEngine.Abstractions/{ComponentModel => Configuration}/IDescriptorRedirection.cs (95%) rename source/LookupEngine.Abstractions/{ComponentModel => Configuration}/IDescriptorResolver.cs (92%) rename source/LookupEngine.Abstractions/{ComponentModel => Configuration}/IExtensionManager.cs (86%) create mode 100644 source/LookupEngine.Abstractions/Metadata/DecompositionMemberData.cs create mode 100644 source/LookupEngine/Diagnostic/IEngineDiagnoser.cs rename source/LookupEngine/Diagnostic/{ClockDiagnoser.cs => TimeDiagnoser.cs} (63%) rename source/LookupEngine/{LookupComposer.Build.cs => Engine/LookupComposer.Decompose.cs} (61%) create mode 100644 source/LookupEngine/Engine/LookupComposer.Diagnostic.cs rename source/LookupEngine/{ => Engine}/LookupComposer.Enumeration.cs (86%) create mode 100644 source/LookupEngine/Engine/LookupComposer.Events.cs rename source/{LookupEngine.Abstractions/Metadata/Descriptor.cs => LookupEngine/Engine/LookupComposer.Extensions.cs} (55%) rename source/LookupEngine/{ => Engine}/LookupComposer.Fields.cs (71%) rename source/LookupEngine/{ => Engine}/LookupComposer.Methods.cs (66%) rename source/LookupEngine/{ => Engine}/LookupComposer.Properties.cs (62%) rename source/LookupEngine/{LookupComposer.Write.cs => Engine/LookupComposer.WriteHelpers.cs} (55%) rename source/LookupEngine/{ => Engine}/LookupComposer.cs (52%) create mode 100644 source/LookupEngine/Exceptions/EngineException.cs create mode 100644 source/LookupEngine/IRedirect.cs delete mode 100644 source/LookupEngine/LookupComposer.Events.cs delete mode 100644 source/LookupEngine/LookupComposer.Extensions.cs create mode 100644 source/LookupEngine/Redirect.cs create mode 100644 source/LookupEngine/SnoopUtils.cs delete mode 100644 source/RevitLookup/Core/Objects/SnoopableObject.cs delete mode 100644 source/RevitLookup/Core/Objects/Variants.cs delete mode 100644 source/RevitLookup/Core/Utils/DescriptorUtils.cs delete mode 100644 source/RevitLookup/Core/Utils/SnoopUtils.cs diff --git a/source/LookupEngine.Abstractions/ComponentModel/IVariants.cs b/source/LookupEngine.Abstractions/Collections/IVariants.cs similarity index 92% rename from source/LookupEngine.Abstractions/ComponentModel/IVariants.cs rename to source/LookupEngine.Abstractions/Collections/IVariants.cs index a791d98a..525be05a 100644 --- a/source/LookupEngine.Abstractions/ComponentModel/IVariants.cs +++ b/source/LookupEngine.Abstractions/Collections/IVariants.cs @@ -18,9 +18,7 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. -using LookupEngine.Abstractions.Collections; - -namespace LookupEngine.Abstractions.ComponentModel; +namespace LookupEngine.Abstractions.Collections; public interface IVariants : IReadOnlyCollection { diff --git a/source/RevitLookup/Core/ComponentModel/Descriptors/BoolDescriptor.cs b/source/LookupEngine.Abstractions/ComponentModel/BooleanDescriptor.cs similarity index 87% rename from source/RevitLookup/Core/ComponentModel/Descriptors/BoolDescriptor.cs rename to source/LookupEngine.Abstractions/ComponentModel/BooleanDescriptor.cs index 1bfd003d..6b779638 100644 --- a/source/RevitLookup/Core/ComponentModel/Descriptors/BoolDescriptor.cs +++ b/source/LookupEngine.Abstractions/ComponentModel/BooleanDescriptor.cs @@ -18,11 +18,11 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. -namespace RevitLookup.Core.ComponentModel.Descriptors; +namespace LookupEngine.Abstractions.ComponentModel; -public sealed class BoolDescriptor : Descriptor +public sealed class BooleanDescriptor : Descriptor { - public BoolDescriptor(bool value) + public BooleanDescriptor(bool value) { Name = value ? "True" : "False"; } diff --git a/source/RevitLookup/Core/Objects/Variant.cs b/source/LookupEngine.Abstractions/ComponentModel/Descriptor.cs similarity index 81% rename from source/RevitLookup/Core/Objects/Variant.cs rename to source/LookupEngine.Abstractions/ComponentModel/Descriptor.cs index 8ffabc45..420a4bb4 100644 --- a/source/RevitLookup/Core/Objects/Variant.cs +++ b/source/LookupEngine.Abstractions/ComponentModel/Descriptor.cs @@ -18,10 +18,14 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. -namespace RevitLookup.Core.Objects; +using System.Diagnostics; +using JetBrains.Annotations; -public sealed class Variant +namespace LookupEngine.Abstractions.ComponentModel; + +[PublicAPI] +[DebuggerDisplay("Name = {Name}")] +public abstract class Descriptor { - public string Description { get; init; } - public object Object { get; init; } + public string? Name { get; set; } } \ No newline at end of file diff --git a/source/RevitLookup/Core/ComponentModel/Descriptors/ExceptionDescriptor.cs b/source/LookupEngine.Abstractions/ComponentModel/ExceptionDescriptor.cs similarity index 96% rename from source/RevitLookup/Core/ComponentModel/Descriptors/ExceptionDescriptor.cs rename to source/LookupEngine.Abstractions/ComponentModel/ExceptionDescriptor.cs index 22e1ca84..06185b9b 100644 --- a/source/RevitLookup/Core/ComponentModel/Descriptors/ExceptionDescriptor.cs +++ b/source/LookupEngine.Abstractions/ComponentModel/ExceptionDescriptor.cs @@ -18,7 +18,7 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. -namespace RevitLookup.Core.ComponentModel.Descriptors; +namespace LookupEngine.Abstractions.ComponentModel; public sealed class ExceptionDescriptor : Descriptor { diff --git a/source/LookupEngine.Abstractions/Metadata/ObjectDescriptor.cs b/source/LookupEngine.Abstractions/ComponentModel/ObjectDescriptor.cs similarity index 92% rename from source/LookupEngine.Abstractions/Metadata/ObjectDescriptor.cs rename to source/LookupEngine.Abstractions/ComponentModel/ObjectDescriptor.cs index 8043b57e..09b09f70 100644 --- a/source/LookupEngine.Abstractions/Metadata/ObjectDescriptor.cs +++ b/source/LookupEngine.Abstractions/ComponentModel/ObjectDescriptor.cs @@ -18,14 +18,10 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. -namespace LookupEngine.Abstractions.Metadata; +namespace LookupEngine.Abstractions.ComponentModel; public sealed class ObjectDescriptor : Descriptor { - public ObjectDescriptor() - { - } - public ObjectDescriptor(object? value) { Name = value is null ? string.Empty : value.ToString(); diff --git a/source/LookupEngine.Abstractions/ComponentModel/IDescriptorCollector.cs b/source/LookupEngine.Abstractions/Configuration/IDescriptorCollector.cs similarity index 95% rename from source/LookupEngine.Abstractions/ComponentModel/IDescriptorCollector.cs rename to source/LookupEngine.Abstractions/Configuration/IDescriptorCollector.cs index 4ffe1f8a..7183d0ff 100644 --- a/source/LookupEngine.Abstractions/ComponentModel/IDescriptorCollector.cs +++ b/source/LookupEngine.Abstractions/Configuration/IDescriptorCollector.cs @@ -18,7 +18,7 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. -namespace LookupEngine.Abstractions.ComponentModel; +namespace LookupEngine.Abstractions.Configuration; /// /// Indicates that the descriptor can retrieve object members by reflection diff --git a/source/LookupEngine.Abstractions/ComponentModel/IDescriptorConnector.cs b/source/LookupEngine.Abstractions/Configuration/IDescriptorConnector.cs similarity index 100% rename from source/LookupEngine.Abstractions/ComponentModel/IDescriptorConnector.cs rename to source/LookupEngine.Abstractions/Configuration/IDescriptorConnector.cs diff --git a/source/LookupEngine.Abstractions/ComponentModel/IDescriptorEnumerator.cs b/source/LookupEngine.Abstractions/Configuration/IDescriptorEnumerator.cs similarity index 95% rename from source/LookupEngine.Abstractions/ComponentModel/IDescriptorEnumerator.cs rename to source/LookupEngine.Abstractions/Configuration/IDescriptorEnumerator.cs index b59e7509..0349f203 100644 --- a/source/LookupEngine.Abstractions/ComponentModel/IDescriptorEnumerator.cs +++ b/source/LookupEngine.Abstractions/Configuration/IDescriptorEnumerator.cs @@ -20,7 +20,7 @@ using System.Collections; -namespace LookupEngine.Abstractions.ComponentModel; +namespace LookupEngine.Abstractions.Configuration; /// /// Indicates that the descriptor is handled as a collection of descriptors diff --git a/source/LookupEngine.Abstractions/ComponentModel/IDescriptorExtension.cs b/source/LookupEngine.Abstractions/Configuration/IDescriptorExtension.cs similarity index 95% rename from source/LookupEngine.Abstractions/ComponentModel/IDescriptorExtension.cs rename to source/LookupEngine.Abstractions/Configuration/IDescriptorExtension.cs index ada1374b..76aff48b 100644 --- a/source/LookupEngine.Abstractions/ComponentModel/IDescriptorExtension.cs +++ b/source/LookupEngine.Abstractions/Configuration/IDescriptorExtension.cs @@ -18,7 +18,7 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. -namespace LookupEngine.Abstractions.ComponentModel; +namespace LookupEngine.Abstractions.Configuration; /// /// Indicates that the descriptor can interact with the UI and execute commands diff --git a/source/LookupEngine.Abstractions/ComponentModel/IDescriptorRedirection.cs b/source/LookupEngine.Abstractions/Configuration/IDescriptorRedirection.cs similarity index 95% rename from source/LookupEngine.Abstractions/ComponentModel/IDescriptorRedirection.cs rename to source/LookupEngine.Abstractions/Configuration/IDescriptorRedirection.cs index 6397f1c7..776ccc16 100644 --- a/source/LookupEngine.Abstractions/ComponentModel/IDescriptorRedirection.cs +++ b/source/LookupEngine.Abstractions/Configuration/IDescriptorRedirection.cs @@ -18,7 +18,7 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. -namespace LookupEngine.Abstractions.ComponentModel; +namespace LookupEngine.Abstractions.Configuration; /// /// Indicates that the object can be redirected to another diff --git a/source/LookupEngine.Abstractions/ComponentModel/IDescriptorResolver.cs b/source/LookupEngine.Abstractions/Configuration/IDescriptorResolver.cs similarity index 92% rename from source/LookupEngine.Abstractions/ComponentModel/IDescriptorResolver.cs rename to source/LookupEngine.Abstractions/Configuration/IDescriptorResolver.cs index cae08a25..1ac9ad84 100644 --- a/source/LookupEngine.Abstractions/ComponentModel/IDescriptorResolver.cs +++ b/source/LookupEngine.Abstractions/Configuration/IDescriptorResolver.cs @@ -19,8 +19,9 @@ // (Rights in Technical Data and Computer Software), as applicable. using System.Reflection; +using LookupEngine.Abstractions.Collections; -namespace LookupEngine.Abstractions.ComponentModel; +namespace LookupEngine.Abstractions.Configuration; /// /// Indicates that the descriptor can decide to call methods/properties with parameters or override their values diff --git a/source/LookupEngine.Abstractions/ComponentModel/IExtensionManager.cs b/source/LookupEngine.Abstractions/Configuration/IExtensionManager.cs similarity index 86% rename from source/LookupEngine.Abstractions/ComponentModel/IExtensionManager.cs rename to source/LookupEngine.Abstractions/Configuration/IExtensionManager.cs index 87301925..c2944f85 100644 --- a/source/LookupEngine.Abstractions/ComponentModel/IExtensionManager.cs +++ b/source/LookupEngine.Abstractions/Configuration/IExtensionManager.cs @@ -18,9 +18,11 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. -namespace LookupEngine.Abstractions.ComponentModel; +using LookupEngine.Abstractions.Collections; + +namespace LookupEngine.Abstractions.Configuration; public interface IExtensionManager { - void Register(string methodName, Func context); + void Register(string methodName, Func context); } \ No newline at end of file diff --git a/source/LookupEngine.Abstractions/LookupEngine.Abstractions.csproj b/source/LookupEngine.Abstractions/LookupEngine.Abstractions.csproj index dac6a00a..a34a37dc 100644 --- a/source/LookupEngine.Abstractions/LookupEngine.Abstractions.csproj +++ b/source/LookupEngine.Abstractions/LookupEngine.Abstractions.csproj @@ -8,6 +8,7 @@ + diff --git a/source/LookupEngine.Abstractions/Metadata/DecompositionMemberData.cs b/source/LookupEngine.Abstractions/Metadata/DecompositionMemberData.cs new file mode 100644 index 00000000..2d464892 --- /dev/null +++ b/source/LookupEngine.Abstractions/Metadata/DecompositionMemberData.cs @@ -0,0 +1,21 @@ +using System.Diagnostics; +using JetBrains.Annotations; +using LookupEngine.Abstractions.Enums; + +// ReSharper disable once CheckNamespace +namespace LookupEngine.Abstractions; + +[PublicAPI] +[DebuggerDisplay("Name = {Name} Value = {Value}")] +public sealed class DecompositionMemberData +{ + public int Depth { get; set; } + public required string Name { get; set; } + public required object? Value { get; set; } + public required string Type { get; set; } + public required string? TypeFullName { get; set; } + public string? Description { get; set; } + public double ComputationTime { get; set; } + public long AllocatedBytes { get; set; } + public MemberAttributes MemberAttributes { get; set; } +} \ No newline at end of file diff --git a/source/LookupEngine/Diagnostic/IEngineDiagnoser.cs b/source/LookupEngine/Diagnostic/IEngineDiagnoser.cs new file mode 100644 index 00000000..1bfa09f3 --- /dev/null +++ b/source/LookupEngine/Diagnostic/IEngineDiagnoser.cs @@ -0,0 +1,7 @@ +namespace LookupEngine.Diagnostic; + +public interface IEngineDiagnoser +{ + void StartMonitoring(); + void StopMonitoring(); +} \ No newline at end of file diff --git a/source/LookupEngine/Diagnostic/MemoryDiagnoser.cs b/source/LookupEngine/Diagnostic/MemoryDiagnoser.cs index 94d6516a..388a50cd 100644 --- a/source/LookupEngine/Diagnostic/MemoryDiagnoser.cs +++ b/source/LookupEngine/Diagnostic/MemoryDiagnoser.cs @@ -20,17 +20,17 @@ namespace LookupEngine.Diagnostic; -public sealed class MemoryDiagnoser +public sealed class MemoryDiagnoser : IEngineDiagnoser { private long _initialAllocatedBytes; private long _finalAllocatedBytes; - public void Start() + public void StartMonitoring() { _initialAllocatedBytes = GetTotalAllocatedBytes(); } - public void Stop() + public void StopMonitoring() { _finalAllocatedBytes = GetTotalAllocatedBytes(); } diff --git a/source/LookupEngine/Diagnostic/ClockDiagnoser.cs b/source/LookupEngine/Diagnostic/TimeDiagnoser.cs similarity index 63% rename from source/LookupEngine/Diagnostic/ClockDiagnoser.cs rename to source/LookupEngine/Diagnostic/TimeDiagnoser.cs index 854c8c6f..c9c0038b 100644 --- a/source/LookupEngine/Diagnostic/ClockDiagnoser.cs +++ b/source/LookupEngine/Diagnostic/TimeDiagnoser.cs @@ -22,24 +22,31 @@ namespace LookupEngine.Diagnostic; -public sealed class ClockDiagnoser +public sealed class TimeDiagnoser : IEngineDiagnoser { - private readonly Stopwatch _clock = new(); + private long _startTimeStamp; + private long _endTimeStamp; - public void Start() + public void StartMonitoring() { - _clock.Start(); + _startTimeStamp = Stopwatch.GetTimestamp(); } - public void Stop() + public void StopMonitoring() { - _clock.Stop(); + _endTimeStamp = Stopwatch.GetTimestamp(); } public TimeSpan GetElapsed() { - var elapsed = _clock.Elapsed; - _clock.Reset(); +#if NETCOREAPP + var elapsed = Stopwatch.GetElapsedTime(_startTimeStamp, _endTimeStamp); +#else + var tickFrequency = (double) TimeSpan.TicksPerSecond / Stopwatch.Frequency; + var elapsed = new TimeSpan((long)((_startTimeStamp - _endTimeStamp) * tickFrequency)); +#endif + _startTimeStamp = default; + _endTimeStamp = default; return elapsed; } diff --git a/source/LookupEngine/LookupComposer.Build.cs b/source/LookupEngine/Engine/LookupComposer.Decompose.cs similarity index 61% rename from source/LookupEngine/LookupComposer.Build.cs rename to source/LookupEngine/Engine/LookupComposer.Decompose.cs index eccc23f3..19c25461 100644 --- a/source/LookupEngine/LookupComposer.Build.cs +++ b/source/LookupEngine/Engine/LookupComposer.Decompose.cs @@ -19,49 +19,54 @@ // (Rights in Technical Data and Computer Software), as applicable. using System.Reflection; -using LookupEngine.Abstractions.Metadata; +using LookupEngine.Abstractions; +// ReSharper disable once CheckNamespace namespace LookupEngine; public sealed partial class LookupComposer { - private IList DecomposeInstanceObject(object instance) + private List DecomposeInstanceObject(object instance) { - _obj = instance; - var objectType = _obj.GetType(); + InputObject = instance; + var objectType = instance.GetType(); var objectTypeHierarchy = GetTypeHierarchy(objectType); for (var i = objectTypeHierarchy.Count - 1; i >= 0; i--) { - var type = objectTypeHierarchy[i]; - var descriptor = _options.TypeResolver.Invoke(_obj, type); + Subtype = objectTypeHierarchy[i]; + SubtypeDescriptor = _options.TypeResolver.Invoke(instance, Subtype); var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly; - if (!_options.IgnoreStatic) flags |= BindingFlags.Static; - if (!_options.IgnorePrivate) flags |= BindingFlags.NonPublic; + if (!_options.IgnoreStaticMembers) flags |= BindingFlags.Static; + if (!_options.IgnorePrivateMembers) flags |= BindingFlags.NonPublic; - DecomposeFields(type, flags); - DecomposeProperties(type, descriptor, flags); - DecomposeMethods(type, descriptor, flags); - DecomposeEvents(type, descriptor, flags); - // AddExtensions(descriptor); + DecomposeFields(flags); + DecomposeProperties(flags); + DecomposeMethods(flags); + DecomposeEvents(flags); + ExecuteExtensions(); _depth--; } + Subtype = objectType; AddEnumerableItems(); return _descriptors; } - private IList DecomposeStaticObject(Type objectType) + private List DecomposeStaticObject(Type objectType) { var flags = BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly; - if (!_options.IgnorePrivate) flags |= BindingFlags.NonPublic; + if (!_options.IgnorePrivateMembers) flags |= BindingFlags.NonPublic; - DecomposeFields(objectType, flags); - DecomposeProperties(objectType, null, flags); - DecomposeMethods(objectType, null, flags); + Subtype = objectType; + SubtypeDescriptor = _options.TypeResolver.Invoke(null, Subtype); + + DecomposeFields(flags); + DecomposeProperties(flags); + DecomposeMethods(flags); return _descriptors; } @@ -75,7 +80,7 @@ private List GetTypeHierarchy(Type inputType) inputType = inputType.BaseType; } - if (!_options.IgnoreRoot) types.Add(inputType); + if (_options.IncludeRoot) types.Add(inputType); return types; } diff --git a/source/LookupEngine/Engine/LookupComposer.Diagnostic.cs b/source/LookupEngine/Engine/LookupComposer.Diagnostic.cs new file mode 100644 index 00000000..efd5cca5 --- /dev/null +++ b/source/LookupEngine/Engine/LookupComposer.Diagnostic.cs @@ -0,0 +1,93 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +using System.Reflection; +using LookupEngine.Abstractions.Collections; +using LookupEngine.Diagnostic; + +// ReSharper disable once CheckNamespace +namespace LookupEngine; + +public sealed partial class LookupComposer +{ + private readonly TimeDiagnoser _timeDiagnoser = new(); + private readonly MemoryDiagnoser _memoryDiagnoser = new(); + + private object? EvaluateValue(FieldInfo member) + { + _timeDiagnoser.StartMonitoring(); + _memoryDiagnoser.StartMonitoring(); + + var value = member.GetValue(InputObject); + + _memoryDiagnoser.StopMonitoring(); + _timeDiagnoser.StopMonitoring(); + + return value; + } + + private object? EvaluateValue(PropertyInfo member) + { + try + { + _timeDiagnoser.StartMonitoring(); + _memoryDiagnoser.StartMonitoring(); + + return member.GetValue(InputObject); + } + finally + { + _memoryDiagnoser.StopMonitoring(); + _timeDiagnoser.StopMonitoring(); + } + } + + private object? EvaluateValue(MethodInfo member) + { + try + { + _timeDiagnoser.StartMonitoring(); + _memoryDiagnoser.StartMonitoring(); + + return member.Invoke(InputObject, null); + } + finally + { + _memoryDiagnoser.StopMonitoring(); + _timeDiagnoser.StopMonitoring(); + } + } + + private IVariants EvaluateValue(Func handler) + { + try + { + _timeDiagnoser.StartMonitoring(); + _memoryDiagnoser.StartMonitoring(); + + return handler.Invoke(); + } + finally + { + _memoryDiagnoser.StopMonitoring(); + _timeDiagnoser.StopMonitoring(); + } + } +} \ No newline at end of file diff --git a/source/LookupEngine/LookupComposer.Enumeration.cs b/source/LookupEngine/Engine/LookupComposer.Enumeration.cs similarity index 86% rename from source/LookupEngine/LookupComposer.Enumeration.cs rename to source/LookupEngine/Engine/LookupComposer.Enumeration.cs index 33c7cdbe..c78032a0 100644 --- a/source/LookupEngine/LookupComposer.Enumeration.cs +++ b/source/LookupEngine/Engine/LookupComposer.Enumeration.cs @@ -20,20 +20,22 @@ using System.Collections; +// ReSharper disable once CheckNamespace namespace LookupEngine; public sealed partial class LookupComposer { private void AddEnumerableItems() { - if (_obj is not IEnumerable enumerable) return; + if (InputObject is not IEnumerable enumerable) return; - var enumerableType = typeof(IEnumerable); var enumerator = enumerable.GetEnumerator(); + var index = 0; while (enumerator.MoveNext()) { - WriteDescriptor(enumerator.Current, enumerableType); + WriteEnumerableResult(enumerator.Current, index); + index++; } if (enumerator is IDisposable disposable) diff --git a/source/LookupEngine/Engine/LookupComposer.Events.cs b/source/LookupEngine/Engine/LookupComposer.Events.cs new file mode 100644 index 00000000..bb5c3290 --- /dev/null +++ b/source/LookupEngine/Engine/LookupComposer.Events.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using LookupEngine.Formaters; + +// ReSharper disable once CheckNamespace +namespace LookupEngine; + +public sealed partial class LookupComposer +{ + private void DecomposeEvents(BindingFlags bindingFlags) + { + if (_options.IncludeEvents) return; + + var members = Subtype.GetEvents(bindingFlags); + foreach (var member in members) + { + WriteDecompositionResult(ReflexionFormater.FormatTypeName(member.EventHandlerType), member); + } + } +} \ No newline at end of file diff --git a/source/LookupEngine.Abstractions/Metadata/Descriptor.cs b/source/LookupEngine/Engine/LookupComposer.Extensions.cs similarity index 55% rename from source/LookupEngine.Abstractions/Metadata/Descriptor.cs rename to source/LookupEngine/Engine/LookupComposer.Extensions.cs index 431e3a00..2f1916de 100644 --- a/source/LookupEngine.Abstractions/Metadata/Descriptor.cs +++ b/source/LookupEngine/Engine/LookupComposer.Extensions.cs @@ -18,21 +18,32 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. -using System.Diagnostics; -using LookupEngine.Abstractions.Enums; +using LookupEngine.Abstractions.Collections; +using LookupEngine.Abstractions.Configuration; -namespace LookupEngine.Abstractions.Metadata; +//ReSharper disable once CheckNamespace +namespace LookupEngine; -[DebuggerDisplay("Name = {Name} Value = {Value} Description = {Description}")] -public abstract class Descriptor +public sealed partial class LookupComposer : IExtensionManager { - public int Depth { get; set; } - public string? TypeFullName { get; set; } - public string? Type { get; set; } - public string? Name { get; set; } - public string? Description { get; set; } - public double ComputationTime { get; set; } - public long AllocatedBytes { get; set; } - public MemberAttributes MemberAttributes { get; set; } - public object? Value { get; set; } + private void ExecuteExtensions() + { + if (!_options.EnableExtensions) return; + if (SubtypeDescriptor is not IDescriptorExtension extension) return; + + extension.RegisterExtensions(this); + } + + public void Register(string methodName, Func handler) + { + try + { + var result = EvaluateValue(handler); + WriteExtensionResult(result, methodName); + } + catch (Exception exception) + { + WriteExtensionResult(exception, methodName); + } + } } \ No newline at end of file diff --git a/source/LookupEngine/LookupComposer.Fields.cs b/source/LookupEngine/Engine/LookupComposer.Fields.cs similarity index 71% rename from source/LookupEngine/LookupComposer.Fields.cs rename to source/LookupEngine/Engine/LookupComposer.Fields.cs index 9585328a..70332d89 100644 --- a/source/LookupEngine/LookupComposer.Fields.cs +++ b/source/LookupEngine/Engine/LookupComposer.Fields.cs @@ -20,34 +20,22 @@ using System.Reflection; +// ReSharper disable once CheckNamespace namespace LookupEngine; public sealed partial class LookupComposer { - private void DecomposeFields(Type type, BindingFlags bindingFlags) + private void DecomposeFields(BindingFlags bindingFlags) { - if (_options.IgnoreFields) return; + if (!_options.IncludeFields) return; - var members = type.GetFields(bindingFlags); + var members = Subtype.GetFields(bindingFlags); foreach (var member in members) { if (member.IsSpecialName) continue; var value = EvaluateValue(member); - WriteDescriptor(value, type, member); + WriteDecompositionResult(value, member); } } - - private object? EvaluateValue(FieldInfo member) - { - _clockDiagnoser.Start(); - _memoryDiagnoser.Start(); - - var value = member.GetValue(_obj); - - _memoryDiagnoser.Stop(); - _clockDiagnoser.Stop(); - - return value; - } } \ No newline at end of file diff --git a/source/LookupEngine/LookupComposer.Methods.cs b/source/LookupEngine/Engine/LookupComposer.Methods.cs similarity index 66% rename from source/LookupEngine/LookupComposer.Methods.cs rename to source/LookupEngine/Engine/LookupComposer.Methods.cs index 0f84c741..ffcf182b 100644 --- a/source/LookupEngine/LookupComposer.Methods.cs +++ b/source/LookupEngine/Engine/LookupComposer.Methods.cs @@ -19,16 +19,16 @@ // (Rights in Technical Data and Computer Software), as applicable. using System.Reflection; -using LookupEngine.Abstractions.ComponentModel; -using LookupEngine.Abstractions.Metadata; +using LookupEngine.Abstractions.Configuration; +// ReSharper disable once CheckNamespace namespace LookupEngine; public sealed partial class LookupComposer { - private void DecomposeMethods(Type type, Descriptor? descriptor, BindingFlags bindingFlags) + private void DecomposeMethods(BindingFlags bindingFlags) { - var members = type.GetMethods(bindingFlags); + var members = Subtype.GetMethods(bindingFlags); foreach (var member in members) { if (member.IsSpecialName) continue; @@ -38,9 +38,9 @@ private void DecomposeMethods(Type type, Descriptor? descriptor, BindingFlags bi try { - if (!TryResolve(member, parameters, descriptor, out value)) + if (!TryResolve(member, parameters, out value)) { - if (!IsMethodSupported(member, parameters, out value)) continue; + if (!TryGetValue(member, parameters, out value)) continue; value ??= EvaluateValue(member); } } @@ -53,14 +53,14 @@ private void DecomposeMethods(Type type, Descriptor? descriptor, BindingFlags bi value = exception; } - WriteDescriptor(value, type, member, parameters); + WriteDecompositionResult(value, member, parameters); } } - private bool TryResolve(MethodInfo member, ParameterInfo[] parameters, Descriptor? descriptor, out object? value) + private bool TryResolve(MethodInfo member, ParameterInfo[] parameters, out object? value) { value = null; - if (descriptor is not IDescriptorResolver resolver) return false; + if (SubtypeDescriptor is not IDescriptorResolver resolver) return false; var handler = resolver.Resolve(member.Name, parameters); if (handler is null) return false; @@ -70,12 +70,12 @@ private bool TryResolve(MethodInfo member, ParameterInfo[] parameters, Descripto return true; } - private bool IsMethodSupported(MethodInfo member, ParameterInfo[] parameters, out object? value) + private bool TryGetValue(MethodInfo member, ParameterInfo[] parameters, out object? value) { value = null; if (member.ReturnType.Name == "Void") { - if (_options.IgnoreUnsupported) return false; + if (!_options.IncludeUnsupported) return false; value = new InvalidOperationException("Method doesn't return a value"); return true; @@ -83,7 +83,7 @@ private bool IsMethodSupported(MethodInfo member, ParameterInfo[] parameters, ou if (parameters.Length > 0) { - if (_options.IgnoreUnsupported) return false; + if (!_options.IncludeUnsupported) return false; value = new NotSupportedException("Unsupported method overload"); return true; @@ -91,20 +91,4 @@ private bool IsMethodSupported(MethodInfo member, ParameterInfo[] parameters, ou return true; } - - private object? EvaluateValue(MethodInfo member) - { - try - { - _clockDiagnoser.Start(); - _memoryDiagnoser.Start(); - - return member.Invoke(_obj, null); - } - finally - { - _memoryDiagnoser.Stop(); - _clockDiagnoser.Stop(); - } - } } \ No newline at end of file diff --git a/source/LookupEngine/LookupComposer.Properties.cs b/source/LookupEngine/Engine/LookupComposer.Properties.cs similarity index 62% rename from source/LookupEngine/LookupComposer.Properties.cs rename to source/LookupEngine/Engine/LookupComposer.Properties.cs index c847c0e3..bb361ab3 100644 --- a/source/LookupEngine/LookupComposer.Properties.cs +++ b/source/LookupEngine/Engine/LookupComposer.Properties.cs @@ -19,16 +19,18 @@ // (Rights in Technical Data and Computer Software), as applicable. using System.Reflection; -using LookupEngine.Abstractions.ComponentModel; -using LookupEngine.Abstractions.Metadata; +using JetBrains.Annotations; +using LookupEngine.Abstractions.Configuration; +// ReSharper disable once CheckNamespace namespace LookupEngine; +[UsedImplicitly] public sealed partial class LookupComposer { - private void DecomposeProperties(Type type, Descriptor? descriptor, BindingFlags bindingFlags) + private void DecomposeProperties(BindingFlags bindingFlags) { - var members = type.GetProperties(bindingFlags); + var members = Subtype.GetProperties(bindingFlags); foreach (var member in members) { if (member.IsSpecialName) continue; @@ -38,9 +40,9 @@ private void DecomposeProperties(Type type, Descriptor? descriptor, BindingFlags try { - if (!TryResolve(member, parameters, descriptor, out value)) + if (!TryResolve(member, parameters, out value)) { - if (!IsPropertySupported(member, parameters, out value)) continue; + if (!TryGetValue(member, parameters, out value)) continue; value ??= EvaluateValue(member); } } @@ -53,14 +55,14 @@ private void DecomposeProperties(Type type, Descriptor? descriptor, BindingFlags value = exception; } - WriteDescriptor(value, type, member, parameters); + WriteDecompositionResult(value, member, parameters); } } - private bool TryResolve(PropertyInfo member, ParameterInfo[]? parameters, Descriptor? descriptor, out object? value) + private bool TryResolve(PropertyInfo member, ParameterInfo[]? parameters, out object? value) { value = null; - if (descriptor is not IDescriptorResolver resolver) return false; + if (SubtypeDescriptor is not IDescriptorResolver resolver) return false; var handler = resolver.Resolve(member.Name, parameters); if (handler is null) return false; @@ -70,7 +72,7 @@ private bool TryResolve(PropertyInfo member, ParameterInfo[]? parameters, Descri return true; } - private bool IsPropertySupported(PropertyInfo member, ParameterInfo[]? parameters, out object? value) + private bool TryGetValue(PropertyInfo member, ParameterInfo[]? parameters, out object? value) { value = null; @@ -82,7 +84,7 @@ private bool IsPropertySupported(PropertyInfo member, ParameterInfo[]? parameter if (parameters is not null && parameters.Length > 0) { - if (_options.IgnoreUnsupported) return false; + if (!_options.IncludeUnsupported) return false; value = new NotSupportedException("Unsupported property overload"); return true; @@ -90,36 +92,4 @@ private bool IsPropertySupported(PropertyInfo member, ParameterInfo[]? parameter return true; } - - private object? EvaluateValue(PropertyInfo member) - { - try - { - _clockDiagnoser.Start(); - _memoryDiagnoser.Start(); - - return member.GetValue(_obj); - } - finally - { - _memoryDiagnoser.Stop(); - _clockDiagnoser.Stop(); - } - } - - private IVariants EvaluateValue(Func handler) - { - try - { - _clockDiagnoser.Start(); - _memoryDiagnoser.Start(); - - return handler.Invoke(); - } - finally - { - _memoryDiagnoser.Stop(); - _clockDiagnoser.Stop(); - } - } } \ No newline at end of file diff --git a/source/LookupEngine/LookupComposer.Write.cs b/source/LookupEngine/Engine/LookupComposer.WriteHelpers.cs similarity index 55% rename from source/LookupEngine/LookupComposer.Write.cs rename to source/LookupEngine/Engine/LookupComposer.WriteHelpers.cs index 76c64a57..464572d9 100644 --- a/source/LookupEngine/LookupComposer.Write.cs +++ b/source/LookupEngine/Engine/LookupComposer.WriteHelpers.cs @@ -18,75 +18,66 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. +using System.Collections; using System.Reflection; +using LookupEngine.Abstractions; using LookupEngine.Abstractions.Enums; -using LookupEngine.Abstractions.Metadata; using LookupEngine.Formaters; +// ReSharper disable once CheckNamespace namespace LookupEngine; public sealed partial class LookupComposer { - private void WriteDescriptor(object? value, Type inputType) + private void WriteEnumerableResult(object? value, int index) { - var descriptor = new ObjectDescriptor + var descriptor = new DecompositionMemberData { Depth = _depth, - // Value = EvaluateValue(value), - TypeFullName = ReflexionFormater.FormatTypeFullName(inputType), + Value = value, + // Value = RedirectValue(value), + Name = $"{Subtype.Name}[{index}]", + TypeFullName = "System.Runtime.IEnumerable", MemberAttributes = MemberAttributes.Property, - Type = ReflexionFormater.FormatTypeName(inputType) + Type = nameof(IEnumerable) }; - // descriptor.Name = descriptor.Value.Descriptor.Type; _descriptors.Add(descriptor); } - private void WriteDescriptor(object? value, Type inputType, string name) + private void WriteExtensionResult(object? value, string name) { - var descriptor = new ObjectDescriptor + var descriptor = new DecompositionMemberData { Depth = _depth, Name = name, - // Value = EvaluateValue(value), - TypeFullName = ReflexionFormater.FormatTypeFullName(inputType), + Value = value, + // Value = RedirectValue(value), + Type = ReflexionFormater.FormatTypeName(Subtype), + TypeFullName = ReflexionFormater.FormatTypeFullName(Subtype), MemberAttributes = MemberAttributes.Extension, - Type = ReflexionFormater.FormatTypeName(inputType), - ComputationTime = _clockDiagnoser.GetElapsed().TotalMilliseconds, + ComputationTime = _timeDiagnoser.GetElapsed().TotalMilliseconds, AllocatedBytes = _memoryDiagnoser.GetAllocatedBytes() }; _descriptors.Add(descriptor); } - private void WriteDescriptor(object? value, Type inputType, MemberInfo member, ParameterInfo[]? parameters = null) + private void WriteDecompositionResult(object? value, MemberInfo member, ParameterInfo[]? parameters = null) { - var descriptor = new ObjectDescriptor + var descriptor = new DecompositionMemberData { Depth = _depth, - TypeFullName = ReflexionFormater.FormatTypeFullName(inputType), - // Value = EvaluateValue(member, value), + Value = value, + // Value = RedirectValue(member, value), Name = ReflexionFormater.FormatMemberName(member, parameters), + Type = ReflexionFormater.FormatTypeName(Subtype), + TypeFullName = ReflexionFormater.FormatTypeFullName(Subtype), MemberAttributes = ModifiersFormater.FormatAttributes(member), - Type = ReflexionFormater.FormatTypeName(inputType), - ComputationTime = _clockDiagnoser.GetElapsed().TotalMilliseconds, + ComputationTime = _timeDiagnoser.GetElapsed().TotalMilliseconds, AllocatedBytes = _memoryDiagnoser.GetAllocatedBytes() }; _descriptors.Add(descriptor); } - - // private SnoopableObject EvaluateValue(MemberInfo member, object? value) - // { - // var snoopableObject = new SnoopableObject(value, Context); - // SnoopUtils.Redirect(member.Name, snoopableObject); - // return snoopableObject; - // } - // - // private SnoopableObject EvaluateValue(object? value) - // { - // var snoopableObject = new SnoopableObject(value, Context); - // SnoopUtils.Redirect(snoopableObject); - // return snoopableObject; - // } } \ No newline at end of file diff --git a/source/LookupEngine/LookupComposer.cs b/source/LookupEngine/Engine/LookupComposer.cs similarity index 52% rename from source/LookupEngine/LookupComposer.cs rename to source/LookupEngine/Engine/LookupComposer.cs index 00767a4a..3ce7fcd2 100644 --- a/source/LookupEngine/LookupComposer.cs +++ b/source/LookupEngine/Engine/LookupComposer.cs @@ -19,23 +19,23 @@ // (Rights in Technical Data and Computer Software), as applicable. using JetBrains.Annotations; -using LookupEngine.Abstractions.Metadata; -using LookupEngine.Diagnostic; -using LookupEngine.Options; +using LookupEngine.Abstractions; +using LookupEngine.Abstractions.ComponentModel; +using LookupEngine.Exceptions; +// ReSharper disable once CheckNamespace namespace LookupEngine; [PublicAPI] public sealed partial class LookupComposer { private readonly DecomposeOptions _options; - private readonly List _descriptors = new(32); + private readonly List _descriptors = new(32); private int _depth; - private object? _obj; - - private readonly ClockDiagnoser _clockDiagnoser = new(); - private readonly MemoryDiagnoser _memoryDiagnoser = new(); + private object? _inputObject; + private Type? _subtype; + private Descriptor? _subtypeDescriptor; private LookupComposer(DecomposeOptions options) { @@ -43,7 +43,7 @@ private LookupComposer(DecomposeOptions options) } [Pure] - public static IList Decompose(object? value, DecomposeOptions? options = null) + public static List Decompose(object? value, DecomposeOptions? options = null) { if (value is null) return []; @@ -56,47 +56,46 @@ public static IList Decompose(object? value, DecomposeOptions? optio _ => composer.DecomposeInstanceObject(value) }; } -} -// private Descriptor FindSuitableDescriptor(object? obj) -// { -// var descriptor = _options.TypeResolver.Invoke(obj, null); -// -// if (obj is null) -// { -// descriptor.Type = nameof(Object); -// descriptor.TypeFullName = "System.Object"; -// } -// else -// { -// var type = obj.GetType(); -// FormatDefaultProperties(descriptor, type); -// } -// -// return descriptor; -// } -// -// private Descriptor FindSuitableDescriptor(Type? type) -// { -// var descriptor = new ObjectDescriptor(); -// -// if (type is null) -// { -// descriptor.Type = nameof(Object); -// descriptor.TypeFullName = "System.Object"; -// } -// else -// { -// FormatDefaultProperties(descriptor, type); -// } -// -// return descriptor; -// } -// -// private Descriptor FindSuitableDescriptor(object obj, Type type) -// { -// var descriptor = _options.TypeResolver.Invoke(obj, type); -// -// FormatDefaultProperties(descriptor, type); -// return descriptor; -// } \ No newline at end of file + internal object InputObject + { + get + { + if (_inputObject is null) + { + EngineException.ThrowIfEngineNotInitialized(nameof(InputObject)); + } + + return _inputObject; + } + set => _inputObject = value; + } + + internal Type Subtype + { + get + { + if (_subtype is null) + { + EngineException.ThrowIfEngineNotInitialized(nameof(Subtype)); + } + + return _subtype; + } + set => _subtype = value; + } + + internal Descriptor SubtypeDescriptor + { + get + { + if (_subtypeDescriptor is null) + { + EngineException.ThrowIfEngineNotInitialized(nameof(SubtypeDescriptor)); + } + + return _subtypeDescriptor; + } + set => _subtypeDescriptor = value; + } +} \ No newline at end of file diff --git a/source/LookupEngine/Exceptions/EngineException.cs b/source/LookupEngine/Exceptions/EngineException.cs new file mode 100644 index 00000000..956fe53b --- /dev/null +++ b/source/LookupEngine/Exceptions/EngineException.cs @@ -0,0 +1,12 @@ +using System.Diagnostics.CodeAnalysis; + +namespace LookupEngine.Exceptions; + +public sealed class EngineException(string message) : Exception(message) +{ + [DoesNotReturn] + internal static void ThrowIfEngineNotInitialized(string propertyName) + { + throw new EngineException($"LookupEngine internal error. {propertyName} must be initialized before accessing it."); + } +} \ No newline at end of file diff --git a/source/LookupEngine/Formaters/ModifiersFormater.cs b/source/LookupEngine/Formaters/ModifiersFormater.cs index 52ff6d58..2c765ef3 100644 --- a/source/LookupEngine/Formaters/ModifiersFormater.cs +++ b/source/LookupEngine/Formaters/ModifiersFormater.cs @@ -9,22 +9,22 @@ internal static MemberAttributes FormatAttributes(MemberInfo member) { return member switch { - MethodInfo info => MemberAttributes.Method.AddModifiers(info.Attributes), - PropertyInfo info => MemberAttributes.Property.AddModifiers(info.CanRead ? info.GetMethod!.Attributes : info.SetMethod!.Attributes), - FieldInfo info => MemberAttributes.Field.AddModifiers(info.Attributes), - EventInfo info => MemberAttributes.Event.AddModifiers(info.AddMethod!.Attributes), + MethodInfo info => CombineModifiers(MemberAttributes.Method, info.Attributes), + PropertyInfo info => CombineModifiers(MemberAttributes.Property, info.CanRead ? info.GetMethod!.Attributes : info.SetMethod!.Attributes), + FieldInfo info => CombineModifiers(MemberAttributes.Field, info.Attributes), + EventInfo info => CombineModifiers(MemberAttributes.Event, info.AddMethod!.Attributes), _ => throw new ArgumentOutOfRangeException(nameof(member)) }; } - private static MemberAttributes AddModifiers(this MemberAttributes attributes, MethodAttributes methodAttributes) + private static MemberAttributes CombineModifiers(MemberAttributes attributes, MethodAttributes methodAttributes) { if ((methodAttributes & MethodAttributes.Static) != 0) attributes |= MemberAttributes.Static; if ((methodAttributes & MethodAttributes.Private) != 0) attributes |= MemberAttributes.Private; return attributes; } - private static MemberAttributes AddModifiers(this MemberAttributes attributes, FieldAttributes fieldAttributes) + private static MemberAttributes CombineModifiers(MemberAttributes attributes, FieldAttributes fieldAttributes) { if ((fieldAttributes & FieldAttributes.Static) != 0) attributes |= MemberAttributes.Static; if ((fieldAttributes & FieldAttributes.Private) != 0) attributes |= MemberAttributes.Private; diff --git a/source/LookupEngine/Formaters/ReflexionFormater.cs b/source/LookupEngine/Formaters/ReflexionFormater.cs index 25d8acf6..5e7d2ee0 100644 --- a/source/LookupEngine/Formaters/ReflexionFormater.cs +++ b/source/LookupEngine/Formaters/ReflexionFormater.cs @@ -1,5 +1,4 @@ using System.Reflection; -using LookupEngine.Abstractions.Metadata; namespace LookupEngine.Formaters; @@ -33,28 +32,20 @@ public static string FormatTypeName(Type? type) return type.IsGenericType ? fullName[..fullName.IndexOf('[')] : fullName; } - public static string FormatMemberName(MemberInfo member, ParameterInfo[]? types) + public static string FormatMemberName(MemberInfo member, ParameterInfo[]? parameters) { - if (types is null) return member.Name; - if (types.Length == 0) return member.Name; + if (parameters is null) return member.Name; + if (parameters.Length == 0) return member.Name; - var parameterNames = types.Select(info => + var formatedParameters = parameters.Select(info => { return info.ParameterType.IsByRef switch { true => $"ref {FormatTypeName(info.ParameterType).Replace("&", string.Empty)}", - _ => FormatTypeName(info.ParameterType) + false => FormatTypeName(info.ParameterType) }; }); - var parameters = string.Join(", ", parameterNames); - return $"{member.Name} ({parameters})"; - } - - internal static void FormatDefaultProperties(Type type, Descriptor descriptor) - { - descriptor.Type = FormatTypeName(type); - descriptor.Name ??= descriptor.Type; - descriptor.TypeFullName = FormatTypeFullName(type); + return $"{member.Name} ({string.Join(", ", formatedParameters)})"; } } \ No newline at end of file diff --git a/source/LookupEngine/IRedirect.cs b/source/LookupEngine/IRedirect.cs new file mode 100644 index 00000000..aee093c8 --- /dev/null +++ b/source/LookupEngine/IRedirect.cs @@ -0,0 +1,3 @@ +namespace LookupEngine; + +public interface IRedirect; \ No newline at end of file diff --git a/source/LookupEngine/LookupComposer.Events.cs b/source/LookupEngine/LookupComposer.Events.cs deleted file mode 100644 index 9c67ffa6..00000000 --- a/source/LookupEngine/LookupComposer.Events.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Reflection; -using LookupEngine.Abstractions.Metadata; -using LookupEngine.Formaters; - -namespace LookupEngine; - -public sealed partial class LookupComposer -{ - private void DecomposeEvents(Type inputType, Descriptor? descriptor, BindingFlags bindingFlags) - { - if (_options.IgnoreEvents) return; - - var members = inputType.GetEvents(bindingFlags); - foreach (var member in members) - { - WriteDescriptor(ReflexionFormater.FormatTypeName(member.EventHandlerType), inputType, member); - } - } -} \ No newline at end of file diff --git a/source/LookupEngine/LookupComposer.Extensions.cs b/source/LookupEngine/LookupComposer.Extensions.cs deleted file mode 100644 index 49e7b63c..00000000 --- a/source/LookupEngine/LookupComposer.Extensions.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2003-2024 by Autodesk, Inc. -// -// Permission to use, copy, modify, and distribute this software in -// object code form for any purpose and without fee is hereby granted, -// provided that the above copyright notice appears in all copies and -// that both that copyright notice and the limited warranty and -// restricted rights notice below appear in all supporting -// documentation. -// -// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. -// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF -// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. -// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE -// UNINTERRUPTED OR ERROR FREE. -// -// Use, duplication, or disclosure by the U.S. Government is subject to -// restrictions set forth in FAR 52.227-19 (Commercial Computer -// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) -// (Rights in Technical Data and Computer Software), as applicable. - -// using System.Reflection.Metadata; -// using LookupEngine.Abstractions.Metadata; -// using RevitLookup.Core.Contracts; -// -// namespace LookupEngine; -// -// public sealed partial class LookupComposer : IExtensionManager -// { -// private void AddExtensions(Descriptor descriptor) -// { -// if (!_options.HandleExtensions) return; -// if (descriptor is not IDescriptorExtension extension) return; -// -// extension.RegisterExtensions(this); -// } -// -// public void Register(string methodName, Func handler) -// { -// try -// { -// var result = Evaluate(handler); -// WriteDescriptor(methodName, result); -// } -// catch (Exception exception) -// { -// WriteDescriptor(methodName, exception); -// } -// } -// -// private object Evaluate(Func handler) -// { -// try -// { -// _clockDiagnoser.Start(); -// _memoryDiagnoser.Start(); -// -// return handler.Invoke(); -// } -// finally -// { -// _memoryDiagnoser.Stop(); -// _clockDiagnoser.Stop(); -// } -// } -// } - diff --git a/source/LookupEngine/Options/DecomposeOptions.cs b/source/LookupEngine/Options/DecomposeOptions.cs index 6acc0871..8aeb1784 100644 --- a/source/LookupEngine/Options/DecomposeOptions.cs +++ b/source/LookupEngine/Options/DecomposeOptions.cs @@ -1,7 +1,10 @@ -using LookupEngine.Abstractions.Metadata; +using JetBrains.Annotations; +using LookupEngine.Abstractions.ComponentModel; -namespace LookupEngine.Options; +// ReSharper disable once CheckNamespace +namespace LookupEngine; +[PublicAPI] public sealed class DecomposeOptions { private Func? _typeResolver; @@ -12,13 +15,15 @@ public sealed class DecomposeOptions set => _typeResolver = value; } - public bool IgnoreFields { get; set; } = true; - public bool IgnoreRoot { get; set; } = false; - public bool IgnorePrivate { get; set; } = true; - public bool IgnoreStatic { get; set; } = false; - public bool IgnoreEvents { get; set; } = false; - public bool IgnoreUnsupported { get; set; } = true; - public bool HandleExtensions { get; set; } = false; + public IRedirect[]? RedirectMap { get; set; } + + public bool IncludeRoot { get; set; } + public bool IncludeFields { get; set; } + public bool IncludeEvents { get; set; } + public bool IncludeUnsupported { get; set; } + public bool IgnorePrivateMembers { get; set; } = true; + public bool IgnoreStaticMembers { get; set; } = true; + public bool EnableExtensions { get; set; } public static DecomposeOptions Default => new(); @@ -26,8 +31,9 @@ private static Descriptor DefaultResolveMap(object? obj, Type? type) { return obj switch { - null when type is null => new ObjectDescriptor(), - _ => new ObjectDescriptor(obj), + bool value when type is null || type == typeof(bool) => new BooleanDescriptor(value), + Exception value when type is null || type == typeof(Exception) => new ExceptionDescriptor(value), + _ => new ObjectDescriptor(obj) }; } } \ No newline at end of file diff --git a/source/LookupEngine/Redirect.cs b/source/LookupEngine/Redirect.cs new file mode 100644 index 00000000..ef75ea8c --- /dev/null +++ b/source/LookupEngine/Redirect.cs @@ -0,0 +1,6 @@ +namespace LookupEngine; + +public sealed class Redirect(Func converter) : IRedirect +{ + public Func Converter { get; } = converter; +} \ No newline at end of file diff --git a/source/LookupEngine/SnoopUtils.cs b/source/LookupEngine/SnoopUtils.cs new file mode 100644 index 00000000..c29d1f2f --- /dev/null +++ b/source/LookupEngine/SnoopUtils.cs @@ -0,0 +1,62 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +namespace LookupEngine; + +public static class SnoopUtils +{ + // public static IList ParseEnumerable(this IDescriptorEnumerator descriptor, SnoopableObject snoopableObject) + // { + // var items = new List(); + // descriptor.Enumerator.Reset(); + // + // while (descriptor.Enumerator.MoveNext()) + // { + // var item = new SnoopableObject(descriptor.Enumerator, snoopableObject.Context); + // Redirect(item); + // items.Add(item); + // } + // + // if (descriptor.Enumerator is IDisposable disposable) + // { + // disposable.Dispose(); + // } + // + // return items; + // } + // + // public static void Redirect(string target, SnoopableObject snoopableObject) + // { + // while (snoopableObject.Descriptor is IDescriptorRedirection redirection) + // { + // if (!redirection.TryRedirect(snoopableObject.Context, target, out var output)) break; + // + // var descriptor = DescriptorUtils.FindSuitableDescriptor(output); + // descriptor.Description ??= snoopableObject.Descriptor.Description; + // snoopableObject.Descriptor = descriptor; + // snoopableObject.Object = output; + // } + // } + // + // public static void Redirect(SnoopableObject snoopableObject) + // { + // Redirect(null, snoopableObject); + // } +} \ No newline at end of file diff --git a/source/RevitLookup/Core/Objects/SnoopableObject.cs b/source/RevitLookup/Core/Objects/SnoopableObject.cs deleted file mode 100644 index abaa839e..00000000 --- a/source/RevitLookup/Core/Objects/SnoopableObject.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2003-2024 by Autodesk, Inc. -// Copyright 2003-2024 by Autodesk, Inc. -// -// Permission to use, copy, modify, and distribute this software in -// object code form for any purpose and without fee is hereby granted, -// provided that the above copyright notice appears in all copies and -// that both that copyright notice and the limited warranty and -// restricted rights notice below appear in all supporting -// documentation. -// -// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. -// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF -// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. -// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE -// UNINTERRUPTED OR ERROR FREE. -// -// Use, duplication, or disclosure by the U.S. Government is subject to -// restrictions set forth in FAR 52.227-19 (Commercial Computer -// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) -// (Rights in Technical Data and Computer Software), as applicable. - -using RevitLookup.Core.Engine; -using RevitLookup.Core.Utils; - -namespace RevitLookup.Core.Objects; - -public sealed class SnoopableObject -{ - private IList _members; - - public SnoopableObject(Type type) - { - Object = type; - Descriptor = DescriptorUtils.FindSuitableDescriptor(type); - } - - public SnoopableObject(object obj) - { - Object = obj; - Context = ContextUtils.FindSuitableContext(obj); - Descriptor = DescriptorUtils.FindSuitableDescriptor(obj); - } - - public SnoopableObject(object obj, Document context) - { - Object = obj; - Context = ContextUtils.FindSuitableContext(obj, context); - Descriptor = DescriptorUtils.FindSuitableDescriptor(obj); - } - - public object Object { get; set; } - public Descriptor Descriptor { get; set; } - public Document Context { get; } - - public IList GetMembers() - { - return _members = DescriptorBuilder.Build(Object, Context); - } - - public async Task> GetMembersAsync() - { - return _members = await RevitShell.ExternalDescriptorHandler.RaiseAsync(_ => DescriptorBuilder.Build(Object, Context)); - } - - public async Task> GetCachedMembersAsync() - { - return _members ?? await GetMembersAsync(); - } -} \ No newline at end of file diff --git a/source/RevitLookup/Core/Objects/Variants.cs b/source/RevitLookup/Core/Objects/Variants.cs deleted file mode 100644 index 914ed110..00000000 --- a/source/RevitLookup/Core/Objects/Variants.cs +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2003-2024 by Autodesk, Inc. -// -// Permission to use, copy, modify, and distribute this software in -// object code form for any purpose and without fee is hereby granted, -// provided that the above copyright notice appears in all copies and -// that both that copyright notice and the limited warranty and -// restricted rights notice below appear in all supporting -// documentation. -// -// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. -// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF -// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. -// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE -// UNINTERRUPTED OR ERROR FREE. -// -// Use, duplication, or disclosure by the U.S. Government is subject to -// restrictions set forth in FAR 52.227-19 (Commercial Computer -// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) -// (Rights in Technical Data and Computer Software), as applicable. - -using System.Collections; - -namespace RevitLookup.Core.Objects; - -/// -/// Provides methods for creating variant collections -/// -/// Variants are provided for IDescriptorResolver -public static class Variants -{ - /// - /// Creates a variant collection with a single value - /// - /// A variant collection containing the specified value - public static IVariants Single(T value) - { - return new Variants(1).Add(value); - } - - /// - /// Creates a variant collection with a single value and description - /// - /// A variant collection containing the specified value - public static IVariants Single(T value, string description) - { - return new Variants(1).Add(value, description); - } - - /// - /// Creates an empty variant collection - /// - /// An empty variant collection - /// An empty collection is returned when there are no solutions for a member - public static IVariants Empty() - { - return new Variants(0); - } - - /// - /// A variant that disables the member calculation - /// - public static Func Disabled { get; } = () => new Variants(1).Add(new InvalidOperationException("Member execution disabled")); -} - -/// -/// Represents a collection of variants -/// -/// The type of the variants -/// The initial variants capacity. Required for atomic performance optimizations -public sealed class Variants(int capacity) : IVariants -{ - private readonly Queue _items = new(capacity); - - /// - /// Gets the number of variants - /// - public int Count => _items.Count; - - /// - /// Adds a new variant - /// - /// The variant collection with a new value - public Variants Add(T result) - { - if (result is null) return this; - if (result is ICollection {Count: 0}) return this; - - _items.Enqueue(new Variant - { - Object = result - }); - - return this; - } - - /// - /// Adds a new variant with description - /// - /// The variant collection with a new value - public Variants Add(T result, string description) - { - if (result is null) return this; - if (result is ICollection {Count: 0}) return this; - - _items.Enqueue(new Variant - { - Object = result, - Description = description - }); - - return this; - } - - /// - /// Returns a single value if exists - /// - /// Single variant - /// Used for internal purposes only and to display a single result instead of a list - public Variant Single() - { - if (_items.Count != 1) - { - throw new IndexOutOfRangeException("Variants contains more than one element or variants is empty"); - } - - return _items.Peek(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - private Queue.Enumerator GetEnumerator() - { - return _items.GetEnumerator(); - } -} \ No newline at end of file diff --git a/source/RevitLookup/Core/Utils/DescriptorUtils.cs b/source/RevitLookup/Core/Utils/DescriptorUtils.cs deleted file mode 100644 index 24c1cbdb..00000000 --- a/source/RevitLookup/Core/Utils/DescriptorUtils.cs +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2003-2024 by Autodesk, Inc. -// -// Permission to use, copy, modify, and distribute this software in -// object code form for any purpose and without fee is hereby granted, -// provided that the above copyright notice appears in all copies and -// that both that copyright notice and the limited warranty and -// restricted rights notice below appear in all supporting -// documentation. -// -// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. -// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF -// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. -// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE -// UNINTERRUPTED OR ERROR FREE. -// -// Use, duplication, or disclosure by the U.S. Government is subject to -// restrictions set forth in FAR 52.227-19 (Commercial Computer -// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) -// (Rights in Technical Data and Computer Software), as applicable. - -using RevitLookup.Core.ComponentModel; -using RevitLookup.Core.ComponentModel.Descriptors; - -namespace RevitLookup.Core.Utils; - -public static class DescriptorUtils -{ - [NotNull] - public static Descriptor FindSuitableDescriptor(Type type) - { - var descriptor = new ObjectDescriptor(); - - if (type is null) - { - descriptor.Type = nameof(Object); - descriptor.TypeFullName = "System.Object"; - } - else - { - ValidateProperties(descriptor, type); - } - - return descriptor; - } - - [NotNull] - public static Descriptor FindSuitableDescriptor(object obj) - { - var descriptor = DescriptorMap.FindDescriptor(obj, null); - - if (obj is null) - { - descriptor.Type = nameof(Object); - descriptor.TypeFullName = "System.Object"; - } - else - { - var type = obj.GetType(); - ValidateProperties(descriptor, type); - } - - return descriptor; - } - - [CanBeNull] - public static Descriptor FindSuitableDescriptor(object obj, Type type) - { - var descriptor = DescriptorMap.FindDescriptor(obj, type); - if (descriptor is null) return null; - - ValidateProperties(descriptor, type); - return descriptor; - } - - public static string MakeGenericFullTypeName(Type type) - { - if (type.IsGenericType) return type.FullName![..type.FullName!.IndexOf('[')]; - return type.FullName; - } - - public static string MakeGenericTypeName(Type type) - { - if (!type.IsGenericType) return type.Name; - - var typeName = type.Name; - var apostropheIndex = typeName.IndexOf('`'); - if (apostropheIndex > 0) typeName = typeName.Substring(0, apostropheIndex); - typeName += "<"; - var genericArguments = type.GetGenericArguments(); - for (var i = 0; i < genericArguments.Length; i++) - { - typeName += MakeGenericTypeName(genericArguments[i]); - if (i < genericArguments.Length - 1) typeName += ", "; - } - - typeName += ">"; - return typeName; - } - - private static void ValidateProperties(Descriptor descriptor, Type type) - { - descriptor.Type = MakeGenericTypeName(type); - descriptor.Name ??= descriptor.Type; - descriptor.TypeFullName = MakeGenericFullTypeName(type); - } -} \ No newline at end of file diff --git a/source/RevitLookup/Core/Utils/SnoopUtils.cs b/source/RevitLookup/Core/Utils/SnoopUtils.cs deleted file mode 100644 index 85e0c73d..00000000 --- a/source/RevitLookup/Core/Utils/SnoopUtils.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2003-2024 by Autodesk, Inc. -// -// Permission to use, copy, modify, and distribute this software in -// object code form for any purpose and without fee is hereby granted, -// provided that the above copyright notice appears in all copies and -// that both that copyright notice and the limited warranty and -// restricted rights notice below appear in all supporting -// documentation. -// -// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. -// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF -// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. -// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE -// UNINTERRUPTED OR ERROR FREE. -// -// Use, duplication, or disclosure by the U.S. Government is subject to -// restrictions set forth in FAR 52.227-19 (Commercial Computer -// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) -// (Rights in Technical Data and Computer Software), as applicable. - -namespace RevitLookup.Core.Utils; - -public static class SnoopUtils -{ - public static IList ParseEnumerable(this IDescriptorEnumerator descriptor, SnoopableObject snoopableObject) - { - var items = new List(); - descriptor.Enumerator.Reset(); - - while (descriptor.Enumerator.MoveNext()) - { - var item = new SnoopableObject(descriptor.Enumerator, snoopableObject.Context); - Redirect(item); - items.Add(item); - } - - if (descriptor.Enumerator is IDisposable disposable) - { - disposable.Dispose(); - } - - return items; - } - - public static void Redirect(string target, SnoopableObject snoopableObject) - { - while (snoopableObject.Descriptor is IDescriptorRedirection redirection) - { - if (!redirection.TryRedirect(snoopableObject.Context, target, out var output)) break; - - var descriptor = DescriptorUtils.FindSuitableDescriptor(output); - descriptor.Description ??= snoopableObject.Descriptor.Description; - snoopableObject.Descriptor = descriptor; - snoopableObject.Object = output; - } - } - - public static void Redirect(SnoopableObject snoopableObject) - { - Redirect(null, snoopableObject); - } -} \ No newline at end of file diff --git a/tests/LookupEngine.Tests/OptionsTests.cs b/tests/LookupEngine.Tests/OptionsTests.cs index bd49aafc..ce7e28ba 100644 --- a/tests/LookupEngine.Tests/OptionsTests.cs +++ b/tests/LookupEngine.Tests/OptionsTests.cs @@ -1,26 +1,52 @@ -using LookupEngine.Options; - -namespace LookupEngine.Tests; +namespace LookupEngine.Tests; public sealed class LookupEngineTests { [Test] public async Task Decompose_DefaultOptions() { + //Act var descriptors = LookupComposer.Decompose("Hello World"); + + //Assert await Assert.That(descriptors).IsNotEmpty(); } [Test] - public async Task Decompose_With_Fields() + public async Task Decompose_Include_Fields() { + //Arrange var options = new DecomposeOptions { - IgnoreFields = false + IncludeFields = true }; + //Act var defaultDescriptors = LookupComposer.Decompose("Hello World"); var optionalDescriptors = LookupComposer.Decompose("Hello World", options); + + //Assert await Assert.That(() => optionalDescriptors.Count > defaultDescriptors.Count).ThrowsNothing(); } + + [Test] + public async Task Decompose_With_Redirection() + { + //Arrange + var options = new DecomposeOptions + { + RedirectMap = + [ + new Redirect(i => "d") + ] + }; + + //Act + var defaultDescriptors = LookupComposer.Decompose(69); + var optionalDescriptors = LookupComposer.Decompose(69, options); + + //Assert + await Assert.That(optionalDescriptors.Count(data => data.Value is string)) + .IsGreaterThan(defaultDescriptors.Count(data => data.Value is string)); + } } \ No newline at end of file From abba14cef7555e26c39738ee0280df043d2dd4cc Mon Sep 17 00:00:00 2001 From: Nice3point Date: Wed, 6 Nov 2024 00:31:25 +0300 Subject: [PATCH 005/121] Disable Qodana --- .github/workflows/Nuke.yml | 2 ++ .github/workflows/{Qodana.yml => Qodana.yml.disabled} | 0 2 files changed, 2 insertions(+) rename .github/workflows/{Qodana.yml => Qodana.yml.disabled} (100%) diff --git a/.github/workflows/Nuke.yml b/.github/workflows/Nuke.yml index c6776c55..e69f8597 100644 --- a/.github/workflows/Nuke.yml +++ b/.github/workflows/Nuke.yml @@ -6,6 +6,8 @@ on: - 'master' - 'dev' pull_request: + branches: + - 'dev' jobs: windows: diff --git a/.github/workflows/Qodana.yml b/.github/workflows/Qodana.yml.disabled similarity index 100% rename from .github/workflows/Qodana.yml rename to .github/workflows/Qodana.yml.disabled From dafbc9a6320f1e4751dc899ea95649e2f1c8c74c Mon Sep 17 00:00:00 2001 From: Nice3point Date: Wed, 6 Nov 2024 00:31:51 +0300 Subject: [PATCH 006/121] Bump Nuke version --- build/Build.Clean.cs | 4 ++-- build/Build.Configuration.cs | 2 +- build/Build.csproj | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/Build.Clean.cs b/build/Build.Clean.cs index 9e78c80c..f210d4af 100644 --- a/build/Build.Clean.cs +++ b/build/Build.Clean.cs @@ -8,12 +8,12 @@ sealed partial class Build .Executes(() => { CleanDirectory(ArtifactsDirectory); - foreach (var project in Solution.AllProjects.Where(project => project != Solution.Build)) + foreach (var project in Solution.AllProjects.Where(project => project != Solution.Build.Build)) { CleanDirectory(project.Directory / "bin"); CleanDirectory(project.Directory / "obj"); } - + foreach (var configuration in GlobBuildConfigurations()) DotNetClean(settings => settings .SetConfiguration(configuration) diff --git a/build/Build.Configuration.cs b/build/Build.Configuration.cs index 7b505efa..dc1119dc 100644 --- a/build/Build.Configuration.cs +++ b/build/Build.Configuration.cs @@ -13,7 +13,7 @@ protected override void OnBuildInitialized() InstallersMap = new() { - { Solution.Installer, Solution.RevitLookup } + { Solution.Build.Installer, Solution.RevitLookup } }; VersionMap = new() diff --git a/build/Build.csproj b/build/Build.csproj index e9fe3585..0cc0e8dd 100644 --- a/build/Build.csproj +++ b/build/Build.csproj @@ -13,7 +13,7 @@ - + From 9e207b49f8920c12c5a57c8dd26e97f9f59542cd Mon Sep 17 00:00:00 2001 From: Nice3point Date: Wed, 6 Nov 2024 00:33:51 +0300 Subject: [PATCH 007/121] Fetch UI fork --- .../Controls/INavigableView.cs | 19 ++++++++ .../Controls/INavigationAware.cs | 24 ++++++++++ .../Controls/NavigationAware.cs | 44 +++++++++++++++++++ .../GlobalUsings.cs | 7 +++ .../INavigationViewPageProvider.cs | 19 ++++++++ .../NavigationException.cs | 31 +++++++++++++ .../NavigationViewPageProviderExtensions.cs | 39 ++++++++++++++++ .../RevitLookup.UI.Abstractions.csproj | 16 +++++++ 8 files changed, 199 insertions(+) create mode 100644 source/RevitLookup.UI.Abstractions/Controls/INavigableView.cs create mode 100644 source/RevitLookup.UI.Abstractions/Controls/INavigationAware.cs create mode 100644 source/RevitLookup.UI.Abstractions/Controls/NavigationAware.cs create mode 100644 source/RevitLookup.UI.Abstractions/GlobalUsings.cs create mode 100644 source/RevitLookup.UI.Abstractions/INavigationViewPageProvider.cs create mode 100644 source/RevitLookup.UI.Abstractions/NavigationException.cs create mode 100644 source/RevitLookup.UI.Abstractions/NavigationViewPageProviderExtensions.cs create mode 100644 source/RevitLookup.UI.Abstractions/RevitLookup.UI.Abstractions.csproj diff --git a/source/RevitLookup.UI.Abstractions/Controls/INavigableView.cs b/source/RevitLookup.UI.Abstractions/Controls/INavigableView.cs new file mode 100644 index 00000000..2a325216 --- /dev/null +++ b/source/RevitLookup.UI.Abstractions/Controls/INavigableView.cs @@ -0,0 +1,19 @@ +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. +// Copyright (C) Leszek Pomianowski and WPF UI Contributors. +// All Rights Reserved. + +namespace Wpf.Ui.Abstractions.Controls; + +/// +/// A component whose ViewModel is separate from the DataContext and can be navigated by INavigationView. +/// +/// The type of the ViewModel associated with the view. This type optionally may implement to participate in navigation processes. +public interface INavigableView +{ + /// + /// Gets the view model used by the view. + /// Optionally, it may implement and be navigated by INavigationView. + /// + T ViewModel { get; } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Abstractions/Controls/INavigationAware.cs b/source/RevitLookup.UI.Abstractions/Controls/INavigationAware.cs new file mode 100644 index 00000000..50f195af --- /dev/null +++ b/source/RevitLookup.UI.Abstractions/Controls/INavigationAware.cs @@ -0,0 +1,24 @@ +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. +// Copyright (C) Leszek Pomianowski and WPF UI Contributors. +// All Rights Reserved. + +namespace Wpf.Ui.Abstractions.Controls; + +/// +/// Notifies class about being navigated. +/// +public interface INavigationAware +{ + /// + /// Asynchronously handles the event that is fired after the component is navigated to. + /// + /// A task that represents the asynchronous operation. + Task OnNavigatedToAsync(); + + /// + /// Asynchronously handles the event that is fired before the component is navigated from. + /// + /// A task that represents the asynchronous operation. + Task OnNavigatedFromAsync(); +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Abstractions/Controls/NavigationAware.cs b/source/RevitLookup.UI.Abstractions/Controls/NavigationAware.cs new file mode 100644 index 00000000..3b609ebc --- /dev/null +++ b/source/RevitLookup.UI.Abstractions/Controls/NavigationAware.cs @@ -0,0 +1,44 @@ +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. +// Copyright (C) Leszek Pomianowski and WPF UI Contributors. +// All Rights Reserved. + +namespace Wpf.Ui.Abstractions.Controls; + +/// +/// Provides a base class for navigation-aware components. +/// +public abstract class NavigationAware : INavigationAware +{ + /// + public virtual Task OnNavigatedToAsync() + { + OnNavigatedTo(); + + return Task.CompletedTask; + } + + /// + /// Handles the event that is fired after the component is navigated to. + /// + // ReSharper disable once MemberCanBeProtected.Global + public virtual void OnNavigatedTo() + { + } + + /// + public virtual Task OnNavigatedFromAsync() + { + OnNavigatedFrom(); + + return Task.CompletedTask; + } + + /// + /// Handles the event that is fired before the component is navigated from. + /// + // ReSharper disable once MemberCanBeProtected.Global + public virtual void OnNavigatedFrom() + { + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Abstractions/GlobalUsings.cs b/source/RevitLookup.UI.Abstractions/GlobalUsings.cs new file mode 100644 index 00000000..8fcccdc8 --- /dev/null +++ b/source/RevitLookup.UI.Abstractions/GlobalUsings.cs @@ -0,0 +1,7 @@ +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. +// Copyright (C) Leszek Pomianowski and WPF UI Contributors. +// All Rights Reserved. + +global using System; +global using System.Threading.Tasks; \ No newline at end of file diff --git a/source/RevitLookup.UI.Abstractions/INavigationViewPageProvider.cs b/source/RevitLookup.UI.Abstractions/INavigationViewPageProvider.cs new file mode 100644 index 00000000..cecfeb55 --- /dev/null +++ b/source/RevitLookup.UI.Abstractions/INavigationViewPageProvider.cs @@ -0,0 +1,19 @@ +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. +// Copyright (C) Leszek Pomianowski and WPF UI Contributors. +// All Rights Reserved. + +namespace Wpf.Ui.Abstractions; + +/// +/// Defines a service that provides pages for navigation. +/// +public interface INavigationViewPageProvider +{ + /// + /// Retrieves a page of the specified type. + /// + /// The type of the page to retrieve. + /// An instance of the specified page type, or null if the page is not found. + public object? GetPage(Type pageType); +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Abstractions/NavigationException.cs b/source/RevitLookup.UI.Abstractions/NavigationException.cs new file mode 100644 index 00000000..1f1eee3b --- /dev/null +++ b/source/RevitLookup.UI.Abstractions/NavigationException.cs @@ -0,0 +1,31 @@ +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. +// Copyright (C) Leszek Pomianowski and WPF UI Contributors. +// All Rights Reserved. + +namespace Wpf.Ui.Abstractions; + +/// +/// Represents errors that occur during navigation. +/// +public sealed class NavigationException : Exception +{ + /// + /// Initializes a new instance of the NavigationException class with a specified error message. + /// + /// The message that describes the error. + public NavigationException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the NavigationException class with a specified error message and a reference to the inner exception that is the cause of this exception. + /// + /// The exception that is the cause of the current exception. + /// The message that describes the error. + public NavigationException(Exception e, string message) + : base(message, e) + { + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Abstractions/NavigationViewPageProviderExtensions.cs b/source/RevitLookup.UI.Abstractions/NavigationViewPageProviderExtensions.cs new file mode 100644 index 00000000..391b53d3 --- /dev/null +++ b/source/RevitLookup.UI.Abstractions/NavigationViewPageProviderExtensions.cs @@ -0,0 +1,39 @@ +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. +// Copyright (C) Leszek Pomianowski and WPF UI Contributors. +// All Rights Reserved. + +namespace Wpf.Ui.Abstractions; + +/// +/// Provides extension methods for the INavigationViewPageProvider interface. +/// +public static class NavigationViewPageProviderExtensions +{ + /// + /// Retrieves a page of the specified type from the page service. + /// + /// The type of the page to retrieve. + /// The page service instance. + /// An instance of the specified page type, or null if the page is not found. + public static TPage? GetPage(this INavigationViewPageProvider navigationViewPageProvider) + where TPage : class + { + return navigationViewPageProvider.GetPage(typeof(TPage)) as TPage; + } + + /// + /// Retrieves a page of the specified type from the page service. + /// Throws a NavigationException if the page is not found. + /// + /// The type of the page to retrieve. + /// The page service instance. + /// An instance of the specified page type. + /// Thrown when the specified page type is not found. + public static TPage GetRequiredPage(this INavigationViewPageProvider navigationViewPageProvider) + where TPage : class + { + return navigationViewPageProvider.GetPage(typeof(TPage)) as TPage + ?? throw new NavigationException($"{typeof(TPage)} page not found."); + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Abstractions/RevitLookup.UI.Abstractions.csproj b/source/RevitLookup.UI.Abstractions/RevitLookup.UI.Abstractions.csproj new file mode 100644 index 00000000..1568afbe --- /dev/null +++ b/source/RevitLookup.UI.Abstractions/RevitLookup.UI.Abstractions.csproj @@ -0,0 +1,16 @@ + + + + true + enable + latest + enable + net48;net8.0-windows + Wpf.Ui.Abstractions + + + + + + + From d4994d23fac75429c02a237c668c39f17f3feebe Mon Sep 17 00:00:00 2001 From: Nice3point Date: Wed, 6 Nov 2024 00:34:48 +0300 Subject: [PATCH 008/121] Add shared project --- .../RevitLookup.Common.csproj | 19 +++++ .../RevitLookup.Common/Tools/ProcessTasks.cs | 70 +++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 source/RevitLookup.Common/RevitLookup.Common.csproj create mode 100644 source/RevitLookup.Common/Tools/ProcessTasks.cs diff --git a/source/RevitLookup.Common/RevitLookup.Common.csproj b/source/RevitLookup.Common/RevitLookup.Common.csproj new file mode 100644 index 00000000..ce28a998 --- /dev/null +++ b/source/RevitLookup.Common/RevitLookup.Common.csproj @@ -0,0 +1,19 @@ + + + + true + enable + latest + enable + net48;net8.0-windows + + + + + + + + + + + \ No newline at end of file diff --git a/source/RevitLookup.Common/Tools/ProcessTasks.cs b/source/RevitLookup.Common/Tools/ProcessTasks.cs new file mode 100644 index 00000000..bfbe343d --- /dev/null +++ b/source/RevitLookup.Common/Tools/ProcessTasks.cs @@ -0,0 +1,70 @@ +using System.Diagnostics; +using System.Text; + +namespace RevitLookup.Common.Tools; + +public static class ProcessTasks +{ + public static Process? StartProcess(string toolPath, string arguments = "", Action? logger = null) + { + var startInfo = new ProcessStartInfo + { + FileName = toolPath, + Arguments = arguments, + CreateNoWindow = true, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + StandardOutputEncoding = Encoding.UTF8, + StandardErrorEncoding = Encoding.UTF8 + }; + + var process = Process.Start(startInfo); + if (process == null) return null; + + RedirectProcessOutput(process, logger); + return process; + } + + public static Process? StartShell(string toolPath, string arguments = "") + { + var startInfo = new ProcessStartInfo + { + FileName = toolPath, + Arguments = arguments, + CreateNoWindow = true, + UseShellExecute = true + }; + + return Process.Start(startInfo); + } + + private static void RedirectProcessOutput(Process process, Action? logger) + { + logger ??= DefaultLogger; + process.OutputDataReceived += (_, args) => + { + if (string.IsNullOrEmpty(args.Data)) return; + logger.Invoke(OutputType.Standard, args.Data); + }; + process.ErrorDataReceived += (_, args) => + { + if (string.IsNullOrEmpty(args.Data)) return; + logger.Invoke(OutputType.Error, args.Data); + }; + + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + } + + private static void DefaultLogger(OutputType type, string output) + { + Console.WriteLine(output); + } +} + +public enum OutputType +{ + Standard, + Error +} \ No newline at end of file From dcbcc4bcb349f5a1ed775d0148177715e6e3c8c2 Mon Sep 17 00:00:00 2001 From: Nice3point Date: Wed, 6 Nov 2024 00:35:19 +0300 Subject: [PATCH 009/121] Migrate few components --- .../Models/OpenSourceSoftware.cs | 9 + .../RevitLookup.Abstractions.csproj | 18 + .../Services/INotificationService.cs | 9 + .../Services}/ISoftwareUpdateService.cs | 4 +- .../Services/IWindowIntercomService.cs | 12 + .../States}/SoftwareUpdateState.cs | 2 +- .../AboutProgram/IAboutViewModel.cs | 20 + .../AboutProgram/IOpenSourceViewModel.cs | 8 + .../Settings/ISettingsViewModel.cs} | 22 +- source/RevitLookup.UI.Framework/App.xaml | 1 + source/RevitLookup.UI.Framework/App.xaml.cs | 3 + .../ColorPicker/ColorPickerControl.xaml | 2 +- .../ColorPicker/ColorPickerControl.xaml.cs | 4 +- .../Controls/ColorPicker/HSVColor.cs | 2 +- .../ContentPlaceholder.xaml | 0 .../ContentPlaceholder.xaml.cs | 0 .../BooleanCollapsedVisibilityConverter.cs | 25 + .../BooleanHiddenVisibilityConverter.cs | 25 + .../EmptyCollectionVisibilityConverter.cs | 26 + .../Converters/EnumBoolConverter.cs | 40 + .../EnumCollapsedVisibilityConverter.cs | 36 + .../EnumHiddenVisibilityConverter.cs | 36 + .../Converters/InverseBoolConverter.cs | 24 + ...erseBooleanCollapsedVisibilityConverter.cs | 25 + ...InverseBooleanHiddenVisibilityConverter.cs | 25 + .../Converters/StringVisibilityConverter.cs | 6 +- .../ApplicationThemeConverter.cs | 33 + .../BackgroundTypeConverter.cs} | 36 +- .../ErrorCardVisibilityConverter.cs | 46 + ...erseUpdateDownloadedVisibilityConverter.cs | 26 + .../UpToDateVisibilityConverter.cs | 28 + .../UpdateAvailableCardVisibilityConverter.cs | 26 + .../UpdateDownloadedVisibilityConverter.cs | 26 + .../Extensions/CollectionExtensions.cs | 29 + .../Extensions/StringExtensions.cs | 25 + .../Resources/Images/ProductLogo200.png} | Bin .../Resources/Images/ShellIcon.ico | Bin .../RevitLookup.UI.Framework.csproj | 19 +- .../Services/NotificationService.cs | 62 +- .../Services/WindowIntercomService.cs | 27 + .../Utils/ColorFormatUtils.cs | 270 + .../Utils/VisualExtensions.cs | 71 + .../Views/AboutProgram}/AboutPage.xaml | 94 +- .../Views/AboutProgram}/AboutPage.xaml.cs | 12 +- .../Views/AboutProgram/OpenSourceDialog.xaml | 84 + .../AboutProgram/OpenSourceDialog.xaml.cs | 42 + .../Views/Settings/ResetSettingsDialog.xaml | 40 + .../Settings/ResetSettingsDialog.xaml.cs | 43 + .../Views/Settings/SettingsPage.xaml | 172 + .../Views/Settings}/SettingsPage.xaml.cs | 12 +- source/RevitLookup.UI.Playground/App.xaml | 15 +- source/RevitLookup.UI.Playground/App.xaml.cs | 35 +- .../Client/Controls/ControlExample.xaml | 237 + .../Client/Controls/ControlExample.xaml.cs | 172 + .../Client/Controls/PageViewer.xaml | 34 + .../Client/Controls/PageViewer.xaml.cs | 47 + .../Helpers/NullToVisibilityConverter.cs | 27 + .../Client/Helpers/SymbolIconXamlConverter.cs | 43 + .../Client/Models/FontIconData.cs | 15 + .../Client/Models/FontIcons.json | 5614 +++++++++++++++++ .../Client/Models/SymbolIconData.cs | 14 + .../Client/Resources/Images/ProductIcon.png | Bin 0 -> 24234 bytes .../Client/Resources/Images/ProductLogo.png | Bin 0 -> 75424 bytes ...encyInjectionNavigationViewPageProvider.cs | 19 + .../ViewModelsRegistrationExtensions.cs | 15 + .../Services/ViewsRegistrationExtensions.cs | 20 + .../ViewModels/Pages/DashboardViewModel.cs | 29 + .../DesignGuidance/FontIconsPageViewModel.cs | 67 + .../SymbolIconsPageViewModel.cs | 62 + .../ViewModels/Pages/DialogsViewModel.cs | 18 + .../Client/ViewModels/Pages/PagesViewModel.cs | 26 + .../ViewModels/Pages/WindowsViewModel.cs | 16 + .../Client/ViewModels/PlaygroundViewModel.cs | 29 + .../Client/Views/Pages/DashboardPage.xaml | 89 + .../Client/Views/Pages/DashboardPage.xaml.cs | 12 + .../Pages/DesignGuidance/FontIconsPage.xaml | 326 + .../DesignGuidance/FontIconsPage.xaml.cs | 37 + .../Pages/DesignGuidance/SymbolIconsPage.xaml | 339 + .../DesignGuidance/SymbolIconsPage.xaml.cs | 37 + .../Pages/DesignGuidance/TypographyPage.xaml | 379 ++ .../DesignGuidance/TypographyPage.xaml.cs | 10 + .../Client/Views/Pages/DialogsPage.xaml | 27 + .../Client/Views/Pages/DialogsPage.xaml.cs | 16 + .../Client/Views/Pages/PagesPage.xaml | 31 + .../Client/Views/Pages/PagesPage.xaml.cs | 16 + .../Client/Views/Pages/WindowsPage.xaml | 26 + .../Client/Views/Pages/WindowsPage.xaml.cs | 16 + .../Client/Views/PlaygroundView.xaml | 64 + .../Client/Views/PlaygroundView.xaml.cs | 44 + .../Config/OptionsConfiguration.cs | 22 + source/RevitLookup.UI.Playground/Host.cs | 47 +- .../RevitLookup.UI.Playground.csproj | 10 +- .../AboutProgram/MockAboutViewModel.cs | 93 + .../AboutProgram/MockOpenSourceViewModel.cs | 97 + .../Settings/MockSettingsViewModel.cs | 71 + 95 files changed, 9788 insertions(+), 182 deletions(-) create mode 100644 source/RevitLookup.Abstractions/Models/OpenSourceSoftware.cs create mode 100644 source/RevitLookup.Abstractions/Services/INotificationService.cs rename source/{RevitLookup/Services/Contracts => RevitLookup.Abstractions/Services}/ISoftwareUpdateService.cs (93%) create mode 100644 source/RevitLookup.Abstractions/Services/IWindowIntercomService.cs rename source/{RevitLookup/Services/Enums => RevitLookup.Abstractions/States}/SoftwareUpdateState.cs (96%) create mode 100644 source/RevitLookup.Abstractions/ViewModels/AboutProgram/IAboutViewModel.cs create mode 100644 source/RevitLookup.Abstractions/ViewModels/AboutProgram/IOpenSourceViewModel.cs rename source/{RevitLookup/Models/SearchResults.cs => RevitLookup.Abstractions/ViewModels/Settings/ISettingsViewModel.cs} (59%) create mode 100644 source/RevitLookup.UI.Framework/App.xaml.cs rename source/{RevitLookup/Views => RevitLookup.UI.Framework}/Controls/ColorPicker/ColorPickerControl.xaml (99%) rename source/{RevitLookup/Views => RevitLookup.UI.Framework}/Controls/ColorPicker/ColorPickerControl.xaml.cs (99%) rename source/{RevitLookup/Views => RevitLookup.UI.Framework}/Controls/ColorPicker/HSVColor.cs (96%) rename source/{RevitLookup/Views => RevitLookup.UI.Framework}/Controls/ContentPlaceholder/ContentPlaceholder.xaml (100%) rename source/{RevitLookup/Views => RevitLookup.UI.Framework}/Controls/ContentPlaceholder/ContentPlaceholder.xaml.cs (100%) create mode 100644 source/RevitLookup.UI.Framework/Converters/BooleanCollapsedVisibilityConverter.cs create mode 100644 source/RevitLookup.UI.Framework/Converters/BooleanHiddenVisibilityConverter.cs create mode 100644 source/RevitLookup.UI.Framework/Converters/EmptyCollectionVisibilityConverter.cs create mode 100644 source/RevitLookup.UI.Framework/Converters/EnumBoolConverter.cs create mode 100644 source/RevitLookup.UI.Framework/Converters/EnumCollapsedVisibilityConverter.cs create mode 100644 source/RevitLookup.UI.Framework/Converters/EnumHiddenVisibilityConverter.cs create mode 100644 source/RevitLookup.UI.Framework/Converters/InverseBoolConverter.cs create mode 100644 source/RevitLookup.UI.Framework/Converters/InverseBooleanCollapsedVisibilityConverter.cs create mode 100644 source/RevitLookup.UI.Framework/Converters/InverseBooleanHiddenVisibilityConverter.cs rename source/{RevitLookup/ViewModels => RevitLookup.UI.Framework}/Converters/StringVisibilityConverter.cs (86%) create mode 100644 source/RevitLookup.UI.Framework/Converters/ValueConverters/ApplicationThemeConverter.cs rename source/{RevitLookup/ViewModels/Converters/ThemeConverters.cs => RevitLookup.UI.Framework/Converters/ValueConverters/BackgroundTypeConverter.cs} (57%) create mode 100644 source/RevitLookup.UI.Framework/Converters/ValueConverters/ErrorCardVisibilityConverter.cs create mode 100644 source/RevitLookup.UI.Framework/Converters/ValueConverters/InverseUpdateDownloadedVisibilityConverter.cs create mode 100644 source/RevitLookup.UI.Framework/Converters/ValueConverters/UpToDateVisibilityConverter.cs create mode 100644 source/RevitLookup.UI.Framework/Converters/ValueConverters/UpdateAvailableCardVisibilityConverter.cs create mode 100644 source/RevitLookup.UI.Framework/Converters/ValueConverters/UpdateDownloadedVisibilityConverter.cs create mode 100644 source/RevitLookup.UI.Framework/Extensions/CollectionExtensions.cs create mode 100644 source/RevitLookup.UI.Framework/Extensions/StringExtensions.cs rename source/{RevitLookup/Resources/Images/AboutLogo200.png => RevitLookup.UI.Framework/Resources/Images/ProductLogo200.png} (100%) rename source/{RevitLookup => RevitLookup.UI.Framework}/Resources/Images/ShellIcon.ico (100%) rename source/{RevitLookup => RevitLookup.UI.Framework}/Services/NotificationService.cs (72%) create mode 100644 source/RevitLookup.UI.Framework/Services/WindowIntercomService.cs create mode 100644 source/RevitLookup.UI.Framework/Utils/ColorFormatUtils.cs create mode 100644 source/RevitLookup.UI.Framework/Utils/VisualExtensions.cs rename source/{RevitLookup/Views/Pages => RevitLookup.UI.Framework/Views/AboutProgram}/AboutPage.xaml (79%) rename source/{RevitLookup/Views/Pages => RevitLookup.UI.Framework/Views/AboutProgram}/AboutPage.xaml.cs (77%) create mode 100644 source/RevitLookup.UI.Framework/Views/AboutProgram/OpenSourceDialog.xaml create mode 100644 source/RevitLookup.UI.Framework/Views/AboutProgram/OpenSourceDialog.xaml.cs create mode 100644 source/RevitLookup.UI.Framework/Views/Settings/ResetSettingsDialog.xaml create mode 100644 source/RevitLookup.UI.Framework/Views/Settings/ResetSettingsDialog.xaml.cs create mode 100644 source/RevitLookup.UI.Framework/Views/Settings/SettingsPage.xaml rename source/{RevitLookup/Views/Pages => RevitLookup.UI.Framework/Views/Settings}/SettingsPage.xaml.cs (76%) create mode 100644 source/RevitLookup.UI.Playground/Client/Controls/ControlExample.xaml create mode 100644 source/RevitLookup.UI.Playground/Client/Controls/ControlExample.xaml.cs create mode 100644 source/RevitLookup.UI.Playground/Client/Controls/PageViewer.xaml create mode 100644 source/RevitLookup.UI.Playground/Client/Controls/PageViewer.xaml.cs create mode 100644 source/RevitLookup.UI.Playground/Client/Helpers/NullToVisibilityConverter.cs create mode 100644 source/RevitLookup.UI.Playground/Client/Helpers/SymbolIconXamlConverter.cs create mode 100644 source/RevitLookup.UI.Playground/Client/Models/FontIconData.cs create mode 100644 source/RevitLookup.UI.Playground/Client/Models/FontIcons.json create mode 100644 source/RevitLookup.UI.Playground/Client/Models/SymbolIconData.cs create mode 100644 source/RevitLookup.UI.Playground/Client/Resources/Images/ProductIcon.png create mode 100644 source/RevitLookup.UI.Playground/Client/Resources/Images/ProductLogo.png create mode 100644 source/RevitLookup.UI.Playground/Client/Services/DependencyInjectionNavigationViewPageProvider.cs create mode 100644 source/RevitLookup.UI.Playground/Client/Services/ViewModelsRegistrationExtensions.cs create mode 100644 source/RevitLookup.UI.Playground/Client/Services/ViewsRegistrationExtensions.cs create mode 100644 source/RevitLookup.UI.Playground/Client/ViewModels/Pages/DashboardViewModel.cs create mode 100644 source/RevitLookup.UI.Playground/Client/ViewModels/Pages/DesignGuidance/FontIconsPageViewModel.cs create mode 100644 source/RevitLookup.UI.Playground/Client/ViewModels/Pages/DesignGuidance/SymbolIconsPageViewModel.cs create mode 100644 source/RevitLookup.UI.Playground/Client/ViewModels/Pages/DialogsViewModel.cs create mode 100644 source/RevitLookup.UI.Playground/Client/ViewModels/Pages/PagesViewModel.cs create mode 100644 source/RevitLookup.UI.Playground/Client/ViewModels/Pages/WindowsViewModel.cs create mode 100644 source/RevitLookup.UI.Playground/Client/ViewModels/PlaygroundViewModel.cs create mode 100644 source/RevitLookup.UI.Playground/Client/Views/Pages/DashboardPage.xaml create mode 100644 source/RevitLookup.UI.Playground/Client/Views/Pages/DashboardPage.xaml.cs create mode 100644 source/RevitLookup.UI.Playground/Client/Views/Pages/DesignGuidance/FontIconsPage.xaml create mode 100644 source/RevitLookup.UI.Playground/Client/Views/Pages/DesignGuidance/FontIconsPage.xaml.cs create mode 100644 source/RevitLookup.UI.Playground/Client/Views/Pages/DesignGuidance/SymbolIconsPage.xaml create mode 100644 source/RevitLookup.UI.Playground/Client/Views/Pages/DesignGuidance/SymbolIconsPage.xaml.cs create mode 100644 source/RevitLookup.UI.Playground/Client/Views/Pages/DesignGuidance/TypographyPage.xaml create mode 100644 source/RevitLookup.UI.Playground/Client/Views/Pages/DesignGuidance/TypographyPage.xaml.cs create mode 100644 source/RevitLookup.UI.Playground/Client/Views/Pages/DialogsPage.xaml create mode 100644 source/RevitLookup.UI.Playground/Client/Views/Pages/DialogsPage.xaml.cs create mode 100644 source/RevitLookup.UI.Playground/Client/Views/Pages/PagesPage.xaml create mode 100644 source/RevitLookup.UI.Playground/Client/Views/Pages/PagesPage.xaml.cs create mode 100644 source/RevitLookup.UI.Playground/Client/Views/Pages/WindowsPage.xaml create mode 100644 source/RevitLookup.UI.Playground/Client/Views/Pages/WindowsPage.xaml.cs create mode 100644 source/RevitLookup.UI.Playground/Client/Views/PlaygroundView.xaml create mode 100644 source/RevitLookup.UI.Playground/Client/Views/PlaygroundView.xaml.cs create mode 100644 source/RevitLookup.UI.Playground/Config/OptionsConfiguration.cs create mode 100644 source/RevitLookup.UI.Playground/ViewModels/AboutProgram/MockAboutViewModel.cs create mode 100644 source/RevitLookup.UI.Playground/ViewModels/AboutProgram/MockOpenSourceViewModel.cs create mode 100644 source/RevitLookup.UI.Playground/ViewModels/Settings/MockSettingsViewModel.cs diff --git a/source/RevitLookup.Abstractions/Models/OpenSourceSoftware.cs b/source/RevitLookup.Abstractions/Models/OpenSourceSoftware.cs new file mode 100644 index 00000000..efe524af --- /dev/null +++ b/source/RevitLookup.Abstractions/Models/OpenSourceSoftware.cs @@ -0,0 +1,9 @@ +namespace RevitLookup.Abstractions.Models; + +public sealed class OpenSourceSoftware +{ + public required string SoftwareName { get; set; } + public required string SoftwareUri { get; set; } + public required string LicenseName { get; set; } + public required string LicenseUri { get; set; } +} \ No newline at end of file diff --git a/source/RevitLookup.Abstractions/RevitLookup.Abstractions.csproj b/source/RevitLookup.Abstractions/RevitLookup.Abstractions.csproj index 22f47190..43c6df0e 100644 --- a/source/RevitLookup.Abstractions/RevitLookup.Abstractions.csproj +++ b/source/RevitLookup.Abstractions/RevitLookup.Abstractions.csproj @@ -7,4 +7,22 @@ net48;net8.0-windows + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/RevitLookup.Abstractions/Services/INotificationService.cs b/source/RevitLookup.Abstractions/Services/INotificationService.cs new file mode 100644 index 00000000..c3dc35e9 --- /dev/null +++ b/source/RevitLookup.Abstractions/Services/INotificationService.cs @@ -0,0 +1,9 @@ +namespace RevitLookup.Abstractions.Services; + +public interface INotificationService +{ + void ShowSuccess(string title, string message); + void ShowWarning(string title, string message); + void ShowError(string title, string message); + void ShowError(string title, Exception exception); +} \ No newline at end of file diff --git a/source/RevitLookup/Services/Contracts/ISoftwareUpdateService.cs b/source/RevitLookup.Abstractions/Services/ISoftwareUpdateService.cs similarity index 93% rename from source/RevitLookup/Services/Contracts/ISoftwareUpdateService.cs rename to source/RevitLookup.Abstractions/Services/ISoftwareUpdateService.cs index 1352655e..415f9cae 100644 --- a/source/RevitLookup/Services/Contracts/ISoftwareUpdateService.cs +++ b/source/RevitLookup.Abstractions/Services/ISoftwareUpdateService.cs @@ -18,9 +18,9 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. -using RevitLookup.Services.Enums; +using RevitLookup.Abstractions.States; -namespace RevitLookup.Services.Contracts; +namespace RevitLookup.Abstractions.Services; public interface ISoftwareUpdateService { diff --git a/source/RevitLookup.Abstractions/Services/IWindowIntercomService.cs b/source/RevitLookup.Abstractions/Services/IWindowIntercomService.cs new file mode 100644 index 00000000..294a7164 --- /dev/null +++ b/source/RevitLookup.Abstractions/Services/IWindowIntercomService.cs @@ -0,0 +1,12 @@ +using System.Windows; +using System.Windows.Threading; + +namespace RevitLookup.Abstractions.Services; + +public interface IWindowIntercomService +{ + Dispatcher Dispatcher { get; } + Window Host { get; } + + void SetHost(Window host); +} \ No newline at end of file diff --git a/source/RevitLookup/Services/Enums/SoftwareUpdateState.cs b/source/RevitLookup.Abstractions/States/SoftwareUpdateState.cs similarity index 96% rename from source/RevitLookup/Services/Enums/SoftwareUpdateState.cs rename to source/RevitLookup.Abstractions/States/SoftwareUpdateState.cs index 305b455c..988b0c8b 100644 --- a/source/RevitLookup/Services/Enums/SoftwareUpdateState.cs +++ b/source/RevitLookup.Abstractions/States/SoftwareUpdateState.cs @@ -18,7 +18,7 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. -namespace RevitLookup.Services.Enums; +namespace RevitLookup.Abstractions.States; public enum SoftwareUpdateState { diff --git a/source/RevitLookup.Abstractions/ViewModels/AboutProgram/IAboutViewModel.cs b/source/RevitLookup.Abstractions/ViewModels/AboutProgram/IAboutViewModel.cs new file mode 100644 index 00000000..eb8c5ed4 --- /dev/null +++ b/source/RevitLookup.Abstractions/ViewModels/AboutProgram/IAboutViewModel.cs @@ -0,0 +1,20 @@ +using CommunityToolkit.Mvvm.Input; +using RevitLookup.Abstractions.States; + +namespace RevitLookup.Abstractions.ViewModels.AboutProgram; + +public interface IAboutViewModel +{ + SoftwareUpdateState State { get; set; } + bool IsUpdateChecked { get; set; } + Version CurrentVersion { get; set; } + string NewVersion { get; set; } + string ErrorMessage { get; set; } + string ReleaseNotesUrl { get; set; } + string LatestCheckDate { get; set; } + string Runtime { get; set; } + + IAsyncRelayCommand CheckUpdatesCommand { get; } + IAsyncRelayCommand DownloadUpdateCommand { get; } + IAsyncRelayCommand ShowSoftwareDialogCommand { get; } +} \ No newline at end of file diff --git a/source/RevitLookup.Abstractions/ViewModels/AboutProgram/IOpenSourceViewModel.cs b/source/RevitLookup.Abstractions/ViewModels/AboutProgram/IOpenSourceViewModel.cs new file mode 100644 index 00000000..3b8a8951 --- /dev/null +++ b/source/RevitLookup.Abstractions/ViewModels/AboutProgram/IOpenSourceViewModel.cs @@ -0,0 +1,8 @@ +using RevitLookup.Abstractions.Models; + +namespace RevitLookup.Abstractions.ViewModels.AboutProgram; + +public interface IOpenSourceViewModel +{ + List Software { get; } +} \ No newline at end of file diff --git a/source/RevitLookup/Models/SearchResults.cs b/source/RevitLookup.Abstractions/ViewModels/Settings/ISettingsViewModel.cs similarity index 59% rename from source/RevitLookup/Models/SearchResults.cs rename to source/RevitLookup.Abstractions/ViewModels/Settings/ISettingsViewModel.cs index c62eb9dc..a17ef330 100644 --- a/source/RevitLookup/Models/SearchResults.cs +++ b/source/RevitLookup.Abstractions/ViewModels/Settings/ISettingsViewModel.cs @@ -1,4 +1,4 @@ -// Copyright 2003-2024 by Autodesk, Inc. +// Copyright 2003-2024 by Autodesk, Inc. // // Permission to use, copy, modify, and distribute this software in // object code form for any purpose and without fee is hereby granted, @@ -18,10 +18,22 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. -namespace RevitLookup.Models; +using CommunityToolkit.Mvvm.Input; +using Wpf.Ui.Appearance; +using Wpf.Ui.Controls; -public sealed class SearchResults +namespace RevitLookup.Abstractions.ViewModels.Settings; + +public interface ISettingsViewModel { - public IList Data { get; init; } - public IList Objects { get; init; } + ApplicationTheme Theme { get; set; } + List Themes { get; } + WindowBackdropType Background { get; set; } + List BackgroundEffects { get; } + bool UseTransition { get; set; } + bool UseHardwareRendering { get; set; } + bool UseSizeRestoring { get; set; } + bool UseModifyTab { get; set; } + + IAsyncRelayCommand ResetSettingsCommand { get; } } \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/App.xaml b/source/RevitLookup.UI.Framework/App.xaml index ba678b77..1238ec2a 100644 --- a/source/RevitLookup.UI.Framework/App.xaml +++ b/source/RevitLookup.UI.Framework/App.xaml @@ -1,4 +1,5 @@ diff --git a/source/RevitLookup.UI.Framework/App.xaml.cs b/source/RevitLookup.UI.Framework/App.xaml.cs new file mode 100644 index 00000000..f428252d --- /dev/null +++ b/source/RevitLookup.UI.Framework/App.xaml.cs @@ -0,0 +1,3 @@ +namespace RevitLookup.UI.Framework; + +public partial class App; \ No newline at end of file diff --git a/source/RevitLookup/Views/Controls/ColorPicker/ColorPickerControl.xaml b/source/RevitLookup.UI.Framework/Controls/ColorPicker/ColorPickerControl.xaml similarity index 99% rename from source/RevitLookup/Views/Controls/ColorPicker/ColorPickerControl.xaml rename to source/RevitLookup.UI.Framework/Controls/ColorPicker/ColorPickerControl.xaml index e2d0e73b..d2b328b6 100644 --- a/source/RevitLookup/Views/Controls/ColorPicker/ColorPickerControl.xaml +++ b/source/RevitLookup.UI.Framework/Controls/ColorPicker/ColorPickerControl.xaml @@ -1,5 +1,5 @@  : MarkupExtension, IValueConverter where TEnum : Enum +{ + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is not TEnum valueEnum) + { + throw new ArgumentException($"{nameof(value)} is not type: {typeof(TEnum)}"); + } + + if (parameter is not TEnum parameterEnum) + { + throw new ArgumentException($"{nameof(parameter)} is not type: {typeof(TEnum)}"); + } + + return EqualityComparer.Default.Equals(valueEnum, parameterEnum); + } + + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (parameter is not TEnum parameterEnum) + { + throw new ArgumentException($"{nameof(parameter)} is not type: {typeof(TEnum)}"); + } + + return parameterEnum; + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + return this; + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Converters/EnumCollapsedVisibilityConverter.cs b/source/RevitLookup.UI.Framework/Converters/EnumCollapsedVisibilityConverter.cs new file mode 100644 index 00000000..1fb8f41d --- /dev/null +++ b/source/RevitLookup.UI.Framework/Converters/EnumCollapsedVisibilityConverter.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Windows.Data; +using System.Windows.Markup; +using Visibility = System.Windows.Visibility; + +namespace RevitLookup.UI.Framework.Converters; + +public class EnumCollapsedVisibilityConverter : MarkupExtension, IValueConverter where TEnum : Enum +{ + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is not TEnum valueEnum) + { + throw new ArgumentException($"{nameof(value)} is not type: {typeof(TEnum)}"); + } + + if (parameter is not TEnum parameterEnum) + { + throw new ArgumentException($"{nameof(parameter)} is not type: {typeof(TEnum)}"); + } + + return EqualityComparer.Default.Equals(valueEnum, parameterEnum) ? Visibility.Visible : Visibility.Collapsed; + } + + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + return this; + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Converters/EnumHiddenVisibilityConverter.cs b/source/RevitLookup.UI.Framework/Converters/EnumHiddenVisibilityConverter.cs new file mode 100644 index 00000000..dae14dcc --- /dev/null +++ b/source/RevitLookup.UI.Framework/Converters/EnumHiddenVisibilityConverter.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Windows.Data; +using System.Windows.Markup; +using Visibility = System.Windows.Visibility; + +namespace RevitLookup.UI.Framework.Converters; + +public class EnumVisibilityConverter : MarkupExtension, IValueConverter where TEnum : Enum +{ + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is not TEnum valueEnum) + { + throw new ArgumentException($"{nameof(value)} is not type: {typeof(TEnum)}"); + } + + if (parameter is not TEnum parameterEnum) + { + throw new ArgumentException($"{nameof(parameter)} is not type: {typeof(TEnum)}"); + } + + return EqualityComparer.Default.Equals(valueEnum, parameterEnum) ? Visibility.Visible : Visibility.Hidden; + } + + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + return this; + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Converters/InverseBoolConverter.cs b/source/RevitLookup.UI.Framework/Converters/InverseBoolConverter.cs new file mode 100644 index 00000000..d713ed0c --- /dev/null +++ b/source/RevitLookup.UI.Framework/Converters/InverseBoolConverter.cs @@ -0,0 +1,24 @@ +using System; +using System.Globalization; +using System.Windows.Data; +using System.Windows.Markup; + +namespace RevitLookup.UI.Framework.Converters; + +public sealed class InverseBoolConverter : MarkupExtension, IValueConverter +{ + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + return !(bool)value!; + } + + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + return !(bool)value!; + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + return this; + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Converters/InverseBooleanCollapsedVisibilityConverter.cs b/source/RevitLookup.UI.Framework/Converters/InverseBooleanCollapsedVisibilityConverter.cs new file mode 100644 index 00000000..fb087dac --- /dev/null +++ b/source/RevitLookup.UI.Framework/Converters/InverseBooleanCollapsedVisibilityConverter.cs @@ -0,0 +1,25 @@ +using System; +using System.Globalization; +using System.Windows.Data; +using System.Windows.Markup; +using Visibility = System.Windows.Visibility; + +namespace RevitLookup.UI.Framework.Converters; + +public sealed class InverseBooleanCollapsedVisibilityConverter : MarkupExtension, IValueConverter +{ + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + return (bool)value! == false ? Visibility.Visible : Visibility.Collapsed; + } + + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + return (Visibility)value! != Visibility.Visible; + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + return this; + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Converters/InverseBooleanHiddenVisibilityConverter.cs b/source/RevitLookup.UI.Framework/Converters/InverseBooleanHiddenVisibilityConverter.cs new file mode 100644 index 00000000..dcf4cd0b --- /dev/null +++ b/source/RevitLookup.UI.Framework/Converters/InverseBooleanHiddenVisibilityConverter.cs @@ -0,0 +1,25 @@ +using System; +using System.Globalization; +using System.Windows.Data; +using System.Windows.Markup; +using Visibility = System.Windows.Visibility; + +namespace RevitLookup.UI.Framework.Converters; + +public class InverseBooleanHiddenVisibilityConverter : MarkupExtension, IValueConverter +{ + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + return (bool)value! == false ? Visibility.Visible : Visibility.Hidden; + } + + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + return (Visibility)value! != Visibility.Visible; + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + return this; + } +} \ No newline at end of file diff --git a/source/RevitLookup/ViewModels/Converters/StringVisibilityConverter.cs b/source/RevitLookup.UI.Framework/Converters/StringVisibilityConverter.cs similarity index 86% rename from source/RevitLookup/ViewModels/Converters/StringVisibilityConverter.cs rename to source/RevitLookup.UI.Framework/Converters/StringVisibilityConverter.cs index 8fc94c6f..0c070d4c 100644 --- a/source/RevitLookup/ViewModels/Converters/StringVisibilityConverter.cs +++ b/source/RevitLookup.UI.Framework/Converters/StringVisibilityConverter.cs @@ -23,17 +23,17 @@ using System.Windows.Markup; using Visibility = System.Windows.Visibility; -namespace RevitLookup.ViewModels.Converters; +namespace RevitLookup.UI.Framework.Converters; public sealed class StringVisibilityConverter : MarkupExtension, IValueConverter { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { if (value is not string text) return Visibility.Collapsed; return string.IsNullOrEmpty(text) ? Visibility.Collapsed : Visibility.Visible; } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { return Convert(value, targetType, parameter, culture); } diff --git a/source/RevitLookup.UI.Framework/Converters/ValueConverters/ApplicationThemeConverter.cs b/source/RevitLookup.UI.Framework/Converters/ValueConverters/ApplicationThemeConverter.cs new file mode 100644 index 00000000..9f4a983a --- /dev/null +++ b/source/RevitLookup.UI.Framework/Converters/ValueConverters/ApplicationThemeConverter.cs @@ -0,0 +1,33 @@ +using System.Globalization; +using System.Windows.Data; +using System.Windows.Markup; +using Wpf.Ui.Appearance; + +namespace RevitLookup.UI.Framework.Converters.ValueConverters; + +public sealed class ApplicationThemeConverter : MarkupExtension, IValueConverter +{ + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + var applicationTheme = (ApplicationTheme) value!; + return applicationTheme switch + { + ApplicationTheme.Auto => "Auto", + ApplicationTheme.Light => "Light", + ApplicationTheme.Dark => "Dark", + ApplicationTheme.HighContrast => "High contrast", + ApplicationTheme.Unknown => throw new NotSupportedException(), + _ => throw new ArgumentOutOfRangeException() + }; + } + + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + return this; + } +} \ No newline at end of file diff --git a/source/RevitLookup/ViewModels/Converters/ThemeConverters.cs b/source/RevitLookup.UI.Framework/Converters/ValueConverters/BackgroundTypeConverter.cs similarity index 57% rename from source/RevitLookup/ViewModels/Converters/ThemeConverters.cs rename to source/RevitLookup.UI.Framework/Converters/ValueConverters/BackgroundTypeConverter.cs index a26588b1..683d112d 100644 --- a/source/RevitLookup/ViewModels/Converters/ThemeConverters.cs +++ b/source/RevitLookup.UI.Framework/Converters/ValueConverters/BackgroundTypeConverter.cs @@ -21,15 +21,13 @@ using System.Globalization; using System.Windows.Data; using System.Windows.Markup; -using Wpf.Ui.Appearance; using Wpf.Ui.Controls; -namespace RevitLookup.ViewModels.Converters; +namespace RevitLookup.UI.Framework.Converters.ValueConverters; -[ValueConversion(typeof(WindowBackdropType), typeof(string))] public sealed class BackgroundTypeConverter : MarkupExtension, IValueConverter { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { var backgroundType = (WindowBackdropType) value!; return backgroundType switch @@ -43,35 +41,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn }; } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotSupportedException(); - } - - public override object ProvideValue(IServiceProvider serviceProvider) - { - return this; - } -} - -[ValueConversion(typeof(ApplicationTheme), typeof(string))] -public sealed class ApplicationThemeConverter : MarkupExtension, IValueConverter -{ - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - var applicationTheme = (ApplicationTheme) value!; - return applicationTheme switch - { - ApplicationTheme.Auto => "Auto", - ApplicationTheme.Light => "Light", - ApplicationTheme.Dark => "Dark", - ApplicationTheme.HighContrast => "High contrast", - ApplicationTheme.Unknown => throw new NotSupportedException(), - _ => throw new ArgumentOutOfRangeException() - }; - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { throw new NotSupportedException(); } diff --git a/source/RevitLookup.UI.Framework/Converters/ValueConverters/ErrorCardVisibilityConverter.cs b/source/RevitLookup.UI.Framework/Converters/ValueConverters/ErrorCardVisibilityConverter.cs new file mode 100644 index 00000000..8af1dc3d --- /dev/null +++ b/source/RevitLookup.UI.Framework/Converters/ValueConverters/ErrorCardVisibilityConverter.cs @@ -0,0 +1,46 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +using System.Globalization; +using System.Windows.Data; +using System.Windows.Markup; +using RevitLookup.Abstractions.States; +using Visibility = System.Windows.Visibility; + +namespace RevitLookup.UI.Framework.Converters.ValueConverters; + +public sealed class ErrorCardVisibilityConverter : MarkupExtension, IValueConverter +{ + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + var state = (SoftwareUpdateState) value!; + return state is SoftwareUpdateState.ErrorChecking or SoftwareUpdateState.ErrorDownloading ? Visibility.Visible : Visibility.Collapsed; + } + + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + return this; + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Converters/ValueConverters/InverseUpdateDownloadedVisibilityConverter.cs b/source/RevitLookup.UI.Framework/Converters/ValueConverters/InverseUpdateDownloadedVisibilityConverter.cs new file mode 100644 index 00000000..960e2fd4 --- /dev/null +++ b/source/RevitLookup.UI.Framework/Converters/ValueConverters/InverseUpdateDownloadedVisibilityConverter.cs @@ -0,0 +1,26 @@ +using System.Globalization; +using System.Windows; +using System.Windows.Data; +using System.Windows.Markup; +using RevitLookup.Abstractions.States; + +namespace RevitLookup.UI.Framework.Converters.ValueConverters; + +public class InverseUpdateDownloadedVisibilityConverter : MarkupExtension, IValueConverter +{ + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + var state = (SoftwareUpdateState) value!; + return state is SoftwareUpdateState.ReadyToInstall ? Visibility.Collapsed : Visibility.Visible; + } + + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + return this; + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Converters/ValueConverters/UpToDateVisibilityConverter.cs b/source/RevitLookup.UI.Framework/Converters/ValueConverters/UpToDateVisibilityConverter.cs new file mode 100644 index 00000000..07ac0c91 --- /dev/null +++ b/source/RevitLookup.UI.Framework/Converters/ValueConverters/UpToDateVisibilityConverter.cs @@ -0,0 +1,28 @@ +using System.Globalization; +using System.Windows; +using System.Windows.Data; +using System.Windows.Markup; +using RevitLookup.Abstractions.States; + +namespace RevitLookup.UI.Framework.Converters.ValueConverters; + +public sealed class UpToDateVisibilityConverter : MarkupExtension, IMultiValueConverter +{ + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + var state = (SoftwareUpdateState) values[0]; + var isUpdating = (bool) values[1]; + var isUpdateChecked = (bool) values[2]; + return state == SoftwareUpdateState.UpToDate && !isUpdating && isUpdateChecked ? Visibility.Visible : Visibility.Collapsed; + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + return this; + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Converters/ValueConverters/UpdateAvailableCardVisibilityConverter.cs b/source/RevitLookup.UI.Framework/Converters/ValueConverters/UpdateAvailableCardVisibilityConverter.cs new file mode 100644 index 00000000..bf7daa40 --- /dev/null +++ b/source/RevitLookup.UI.Framework/Converters/ValueConverters/UpdateAvailableCardVisibilityConverter.cs @@ -0,0 +1,26 @@ +using System.Globalization; +using System.Windows; +using System.Windows.Data; +using System.Windows.Markup; +using RevitLookup.Abstractions.States; + +namespace RevitLookup.UI.Framework.Converters.ValueConverters; + +public sealed class UpdateAvailableCardVisibilityConverter : MarkupExtension, IValueConverter +{ + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + var state = (SoftwareUpdateState) value!; + return state is SoftwareUpdateState.ReadyToDownload or SoftwareUpdateState.ErrorDownloading ? Visibility.Visible : Visibility.Collapsed; + } + + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + return this; + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Converters/ValueConverters/UpdateDownloadedVisibilityConverter.cs b/source/RevitLookup.UI.Framework/Converters/ValueConverters/UpdateDownloadedVisibilityConverter.cs new file mode 100644 index 00000000..abb269fc --- /dev/null +++ b/source/RevitLookup.UI.Framework/Converters/ValueConverters/UpdateDownloadedVisibilityConverter.cs @@ -0,0 +1,26 @@ +using System.Globalization; +using System.Windows; +using System.Windows.Data; +using System.Windows.Markup; +using RevitLookup.Abstractions.States; + +namespace RevitLookup.UI.Framework.Converters.ValueConverters; + +public sealed class UpdateDownloadedVisibilityConverter : MarkupExtension, IValueConverter +{ + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + var state = (SoftwareUpdateState) value!; + return state is SoftwareUpdateState.ReadyToInstall ? Visibility.Visible : Visibility.Collapsed; + } + + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + return this; + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Extensions/CollectionExtensions.cs b/source/RevitLookup.UI.Framework/Extensions/CollectionExtensions.cs new file mode 100644 index 00000000..1bcedb2f --- /dev/null +++ b/source/RevitLookup.UI.Framework/Extensions/CollectionExtensions.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace RevitLookup.UI.Framework.Extensions; + +public static class CollectionExtensions +{ + /// + /// Converts an to an . + /// + /// The type of elements in the collection. + /// The source to convert. + /// An containing the elements of the source. + public static ObservableCollection ToObservableCollection(this IEnumerable source) + { + return new ObservableCollection(source); + } + + /// + /// Converts an to an . + /// + /// The type of elements in the collection. + /// The source to convert. + /// An containing the elements of the source. + public static ObservableCollection ToObservableCollection(this List source) + { + return new ObservableCollection(source); + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Extensions/StringExtensions.cs b/source/RevitLookup.UI.Framework/Extensions/StringExtensions.cs new file mode 100644 index 00000000..cec0e78d --- /dev/null +++ b/source/RevitLookup.UI.Framework/Extensions/StringExtensions.cs @@ -0,0 +1,25 @@ +using System; +using JetBrains.Annotations; + +namespace RevitLookup.UI.Framework.Extensions; + +public static class StringExtensions +{ +#if !NETCORE + /// + /// Returns a value indicating whether a specified substring occurs within this string. + /// + /// Source string + /// The string to seek + /// One of the enumeration values that specifies the rules for the search + /// True if the value parameter occurs within this string, or if value is the empty string (""); otherwise, false + [Pure] + [ContractAnnotation("source:null => false; value:null => false")] + public static bool Contains(this string? source, string? value, StringComparison comparison) + { + if (source is null) return false; + if (value is null) return false; + return source.IndexOf(value, comparison) >= 0; + } +#endif +} \ No newline at end of file diff --git a/source/RevitLookup/Resources/Images/AboutLogo200.png b/source/RevitLookup.UI.Framework/Resources/Images/ProductLogo200.png similarity index 100% rename from source/RevitLookup/Resources/Images/AboutLogo200.png rename to source/RevitLookup.UI.Framework/Resources/Images/ProductLogo200.png diff --git a/source/RevitLookup/Resources/Images/ShellIcon.ico b/source/RevitLookup.UI.Framework/Resources/Images/ShellIcon.ico similarity index 100% rename from source/RevitLookup/Resources/Images/ShellIcon.ico rename to source/RevitLookup.UI.Framework/Resources/Images/ShellIcon.ico diff --git a/source/RevitLookup.UI.Framework/RevitLookup.UI.Framework.csproj b/source/RevitLookup.UI.Framework/RevitLookup.UI.Framework.csproj index f98444bd..c6a45da7 100644 --- a/source/RevitLookup.UI.Framework/RevitLookup.UI.Framework.csproj +++ b/source/RevitLookup.UI.Framework/RevitLookup.UI.Framework.csproj @@ -14,15 +14,26 @@ - - - - + + + + + + + + + + + + + + + diff --git a/source/RevitLookup/Services/NotificationService.cs b/source/RevitLookup.UI.Framework/Services/NotificationService.cs similarity index 72% rename from source/RevitLookup/Services/NotificationService.cs rename to source/RevitLookup.UI.Framework/Services/NotificationService.cs index d1d6a41b..5ab93468 100644 --- a/source/RevitLookup/Services/NotificationService.cs +++ b/source/RevitLookup.UI.Framework/Services/NotificationService.cs @@ -19,69 +19,69 @@ // (Rights in Technical Data and Computer Software), as applicable. using System.Windows; -using RevitLookup.Services.Contracts; +using RevitLookup.Abstractions.Services; using Wpf.Ui; using Wpf.Ui.Controls; -namespace RevitLookup.Services; +namespace RevitLookup.UI.Framework.Services; -public sealed class NotificationService(ISnackbarService snackbarService, IWindow window) +public sealed class NotificationService(ISnackbarService snackbarService, IWindowIntercomService intercomService) : INotificationService { - private Action _pendingNotifications; - + private Action? _pendingNotifications; + public void ShowSuccess(string title, string message) { - if (window.Dispatcher.CheckAccess()) + if (intercomService.Dispatcher.CheckAccess()) { PushSuccessMessage(title, message); } else { - window.Dispatcher.Invoke(() => PushSuccessMessage(title, message)); + intercomService.Dispatcher.Invoke(() => PushSuccessMessage(title, message)); } } - + public void ShowWarning(string title, string message) { - if (window.Dispatcher.CheckAccess()) + if (intercomService.Dispatcher.CheckAccess()) { PushWarningMessage(title, message); } else { - window.Dispatcher.Invoke(() => PushWarningMessage(title, message)); + intercomService.Dispatcher.Invoke(() => PushWarningMessage(title, message)); } } - + public void ShowError(string title, string message) { - if (window.Dispatcher.CheckAccess()) + if (intercomService.Dispatcher.CheckAccess()) { PushErrorMessage(title, message); } else { - window.Dispatcher.Invoke(() => PushErrorMessage(title, message)); + intercomService.Dispatcher.Invoke(() => PushErrorMessage(title, message)); } } - + public void ShowError(string title, Exception exception) { - if (window.Dispatcher.CheckAccess()) + if (intercomService.Dispatcher.CheckAccess()) { PushErrorMessage(title, exception.Message); } else { - window.Dispatcher.Invoke(() => PushErrorMessage(title, exception.Message)); + intercomService.Dispatcher.Invoke(() => PushErrorMessage(title, exception.Message)); } } - + private void PushSuccessMessage(string title, string message) { - if (!window.IsLoaded) + if (!intercomService.Host.IsLoaded) { - if (_pendingNotifications is null) window.Loaded += ShowPendingNotifications; + if (_pendingNotifications is null) intercomService.Host.Loaded += ShowPendingNotifications; _pendingNotifications += () => ShowSuccessBar(title, message); } else @@ -89,12 +89,12 @@ private void PushSuccessMessage(string title, string message) ShowSuccessBar(title, message); } } - + private void PushWarningMessage(string title, string message) { - if (!window.IsLoaded) + if (!intercomService.Host.IsLoaded) { - if (_pendingNotifications is null) window.Loaded += ShowPendingNotifications; + if (_pendingNotifications is null) intercomService.Host.Loaded += ShowPendingNotifications; _pendingNotifications += () => ShowWarningBar(title, message); } else @@ -102,12 +102,12 @@ private void PushWarningMessage(string title, string message) ShowWarningBar(title, message); } } - + private void PushErrorMessage(string title, string message) { - if (!window.IsLoaded) + if (!intercomService.Host.IsLoaded) { - if (_pendingNotifications is null) window.Loaded += ShowPendingNotifications; + if (_pendingNotifications is null) intercomService.Host.Loaded += ShowPendingNotifications; _pendingNotifications += () => ShowErrorBar(title, message); } else @@ -115,7 +115,7 @@ private void PushErrorMessage(string title, string message) ShowErrorBar(title, message); } } - + private void ShowSuccessBar(string title, string message) { snackbarService.Show( @@ -125,7 +125,7 @@ private void ShowSuccessBar(string title, string message) new SymbolIcon(SymbolRegular.ChatWarning24, 24), snackbarService.DefaultTimeOut); } - + private void ShowWarningBar(string title, string message) { snackbarService.Show( @@ -135,7 +135,7 @@ private void ShowWarningBar(string title, string message) new SymbolIcon(SymbolRegular.Warning24, 24), snackbarService.DefaultTimeOut); } - + private void ShowErrorBar(string title, string message) { snackbarService.Show( @@ -145,10 +145,12 @@ private void ShowErrorBar(string title, string message) new SymbolIcon(SymbolRegular.ErrorCircle24, 24), snackbarService.DefaultTimeOut); } - + private void ShowPendingNotifications(object sender, RoutedEventArgs args) { - window.Loaded -= ShowPendingNotifications; + intercomService.Host.Loaded -= ShowPendingNotifications; + if (_pendingNotifications is null) return; + _pendingNotifications.Invoke(); _pendingNotifications = null; } diff --git a/source/RevitLookup.UI.Framework/Services/WindowIntercomService.cs b/source/RevitLookup.UI.Framework/Services/WindowIntercomService.cs new file mode 100644 index 00000000..e2ed9f0e --- /dev/null +++ b/source/RevitLookup.UI.Framework/Services/WindowIntercomService.cs @@ -0,0 +1,27 @@ +using System.Windows; +using System.Windows.Threading; +using RevitLookup.Abstractions.Services; + +namespace RevitLookup.UI.Framework.Services; + +public sealed class WindowIntercomService : IWindowIntercomService +{ + private Window? _host; + + public Window Host + { + get + { + if (_host is null) throw new InvalidOperationException("The Host was never set."); + return _host; + } + private set => _host = value; + } + + public Dispatcher Dispatcher => Host.Dispatcher; + + public void SetHost(Window host) + { + Host = host; + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Utils/ColorFormatUtils.cs b/source/RevitLookup.UI.Framework/Utils/ColorFormatUtils.cs new file mode 100644 index 00000000..e34c40a9 --- /dev/null +++ b/source/RevitLookup.UI.Framework/Utils/ColorFormatUtils.cs @@ -0,0 +1,270 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +using System.Drawing; + +namespace RevitLookup.UI.Framework.Utils; + +/// +/// Helper class to easier work with color formats +/// +/// +/// Implementation: https://github.com/microsoft/PowerToys/blob/main/src/common/ManagedCommon/ColorFormatHelper.cs +/// +public static class ColorFormatUtils +{ + /// + /// Return a drawing color of a given + /// + public static Color GetDrawingColor(this System.Windows.Media.Color color) + { + return Color.FromArgb(1, color.R, color.G, color.B); + } + + /// + /// Convert a given to a CMYK color (cyan, magenta, yellow, black key) + /// + /// The to convert + /// The cyan[0..1], magenta[0..1], yellow[0..1] and black key[0..1] of the converted color + public static (double Cyan, double Magenta, double Yellow, double BlackKey) ConvertToCmykColor(Color color) + { + // special case for black (avoid division by zero) + if (color is { R: 0, G: 0, B: 0}) + { + return (0d, 0d, 0d, 1d); + } + + var red = color.R / 255d; + var green = color.G / 255d; + var blue = color.B / 255d; + + var blackKey = 1d - Math.Max(Math.Max(red, green), blue); + + // special case for black (avoid division by zero) + if (1d - blackKey == 0d) + { + return (0d, 0d, 0d, 1d); + } + + var cyan = (1d - red - blackKey) / (1d - blackKey); + var magenta = (1d - green - blackKey) / (1d - blackKey); + var yellow = (1d - blue - blackKey) / (1d - blackKey); + + return (cyan, magenta, yellow, blackKey); + } + + /// + /// Convert a given to a HSB color (hue, saturation, brightness) + /// + /// The to convert + /// The hue [0°..360°], saturation [0..1] and brightness [0..1] of the converted color + public static (double Hue, double Saturation, double Brightness) ConvertToHsbColor(Color color) + { + // HSB and HSV represents the same color space + return ConvertToHsvColor(color); + } + + /// + /// Convert a given to a HSV color (hue, saturation, value) + /// + /// The to convert + /// The hue [0°..360°], saturation [0..1] and value [0..1] of the converted color + public static (double Hue, double Saturation, double Value) ConvertToHsvColor(Color color) + { + var min = Math.Min(Math.Min(color.R, color.G), color.B) / 255d; + var max = Math.Max(Math.Max(color.R, color.G), color.B) / 255d; + + return (color.GetHue(), max == 0d ? 0d : (max - min) / max, max); + } + + /// + /// Convert a given to a HSI color (hue, saturation, intensity) + /// + /// The to convert + /// The hue [0°..360°], saturation [0..1] and intensity [0..1] of the converted color + public static (double Hue, double Saturation, double Intensity) ConvertToHsiColor(Color color) + { + // special case for black + if (color.R == 0 && color.G == 0 && color.B == 0) + { + return (0d, 0d, 0d); + } + + var red = color.R / 255d; + var green = color.G / 255d; + var blue = color.B / 255d; + + var intensity = (red + green + blue) / 3d; + + var min = Math.Min(Math.Min(color.R, color.G), color.B) / 255d; + + return (color.GetHue(), 1d - (min / intensity), intensity); + } + + /// + /// Convert a given to a HSL color (hue, saturation, lightness) + /// + /// The to convert + /// The hue [0°..360°], saturation [0..1] and lightness [0..1] values of the converted color + public static (double Hue, double Saturation, double Lightness) ConvertToHslColor(Color color) + { + var min = Math.Min(Math.Min(color.R, color.G), color.B) / 255d; + var max = Math.Max(Math.Max(color.R, color.G), color.B) / 255d; + + var lightness = (max + min) / 2d; + + if (lightness == 0d || Math.Abs(min - max) < 1e-9) + { + return (color.GetHue(), 0d, lightness); + } + + if (lightness is > 0d and <= 0.5d) + { + return (color.GetHue(), (max - min) / (max + min), lightness); + } + + return (color.GetHue(), (max - min) / (2d - (max + min)), lightness); + } + + /// + /// Convert a given to a HWB color (hue, whiteness, blackness) + /// + /// The to convert + /// The hue [0°..360°], whiteness [0..1] and blackness [0..1] of the converted color + public static (double Hue, double Whiteness, double Blackness) ConvertToHwbColor(Color color) + { + var min = Math.Min(Math.Min(color.R, color.G), color.B) / 255d; + var max = Math.Max(Math.Max(color.R, color.G), color.B) / 255d; + + return (color.GetHue(), min, 1 - max); + } + + /// + /// Convert a given to a CIE LAB color (LAB) + /// + /// The to convert + /// The lightness [0..100] and two chromaticities [-128..127] + public static (double Lightness, double ChromaticityA, double ChromaticityB) ConvertToCielabColor(Color color) + { + var xyz = ConvertToCiexyzColor(color); + var lab = GetCielabColorFromCieXyz(xyz.X, xyz.Y, xyz.Z); + + return lab; + } + + /// + /// Convert a given to a CIE XYZ color (XYZ) + /// The constants of the formula matches this Wikipedia page, but at a higher precision: + /// https://en.wikipedia.org/wiki/SRGB#The_reverse_transformation_(sRGB_to_CIE_XYZ) + /// This page provides a method to calculate the constants: + /// http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html + /// + /// The to convert + /// The X [0..1], Y [0..1] and Z [0..1] + public static (double X, double Y, double Z) ConvertToCiexyzColor(Color color) + { + var r = color.R / 255d; + var g = color.G / 255d; + var b = color.B / 255d; + + // inverse companding, gamma correction must be undone + var rLinear = (r > 0.04045) ? Math.Pow((r + 0.055) / 1.055, 2.4) : (r / 12.92); + var gLinear = (g > 0.04045) ? Math.Pow((g + 0.055) / 1.055, 2.4) : (g / 12.92); + var bLinear = (b > 0.04045) ? Math.Pow((b + 0.055) / 1.055, 2.4) : (b / 12.92); + + return ( + (rLinear * 0.41239079926595948) + (gLinear * 0.35758433938387796) + (bLinear * 0.18048078840183429), + (rLinear * 0.21263900587151036) + (gLinear * 0.71516867876775593) + (bLinear * 0.07219231536073372), + (rLinear * 0.01933081871559185) + (gLinear * 0.11919477979462599) + (bLinear * 0.95053215224966058) + ); + } + + /// + /// Convert a CIE XYZ color to a CIE LAB color (LAB) adapted to sRGB D65 white point + /// The constants of the formula used come from this wikipedia page: + /// https://en.wikipedia.org/wiki/CIELAB_color_space#Converting_between_CIELAB_and_CIEXYZ_coordinates + /// + /// The represents a mix of the three CIE RGB curves + /// The represents the luminance + /// The is quasi-equal to blue (of CIE RGB) + /// The lightness [0..100] and two chromaticities [-128..127] + private static (double Lightness, double ChromaticityA, double ChromaticityB) GetCielabColorFromCieXyz(double x, double y, double z) + { + // sRGB reference white (x=0.3127, y=0.3290, Y=1.0), actually CIE Standard Illuminant D65 truncated to 4 decimal places, + // then converted to XYZ using the formula: + // X = x * (Y / y) + // Y = Y + // Z = (1 - x - y) * (Y / y) + const double xN = 0.9504559270516717; + const double yN = 1.0; + const double zN = 1.0890577507598784; + + // Scale XYZ values relative to reference white + x /= xN; + y /= yN; + z /= zN; + + // XYZ to CIELab transformation + const double delta = 6d / 29; + var m = (1d / 3) * Math.Pow(delta, -2); + var t = Math.Pow(delta, 3); + + var fx = (x > t) ? Math.Pow(x, 1.0 / 3.0) : (x * m) + (16.0 / 116.0); + var fy = (y > t) ? Math.Pow(y, 1.0 / 3.0) : (y * m) + (16.0 / 116.0); + var fz = (z > t) ? Math.Pow(z, 1.0 / 3.0) : (z * m) + (16.0 / 116.0); + + var l = (116 * fy) - 16; + var a = 500 * (fx - fy); + var b = 200 * (fy - fz); + + return (l, a, b); + } + + /// + /// Convert a given to a natural color (hue, whiteness, blackness) + /// + /// The to convert + /// The hue, whiteness [0..1] and blackness [0..1] of the converted color + public static (string Hue, double Whiteness, double Blackness) ConvertToNaturalColor(Color color) + { + var min = Math.Min(Math.Min(color.R, color.G), color.B) / 255d; + var max = Math.Max(Math.Max(color.R, color.G), color.B) / 255d; + + return (GetNaturalColorFromHue(color.GetHue()), min, 1 - max); + } + + /// + /// Return the natural color for the given hue value + /// + /// The hue value to convert + /// A natural color + private static string GetNaturalColorFromHue(double hue) + { + return hue switch + { + < 60d => $"R{Math.Round(hue / 0.6d, 0)}", + < 120d => $"Y{Math.Round((hue - 60d) / 0.6d, 0)}", + < 180d => $"G{Math.Round((hue - 120d) / 0.6d, 0)}", + < 240d => $"C{Math.Round((hue - 180d) / 0.6d, 0)}", + < 300d => $"B{Math.Round((hue - 240d) / 0.6d, 0)}", + _ => $"M{Math.Round((hue - 300d) / 0.6d, 0)}" + }; + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Utils/VisualExtensions.cs b/source/RevitLookup.UI.Framework/Utils/VisualExtensions.cs new file mode 100644 index 00000000..10b3655f --- /dev/null +++ b/source/RevitLookup.UI.Framework/Utils/VisualExtensions.cs @@ -0,0 +1,71 @@ +using System.Windows; +using System.Windows.Media; + +namespace RevitLookup.UI.Framework.Utils; + +public static class VisualExtensions +{ + public static T? FindVisualParent(this FrameworkElement element) where T : FrameworkElement + { + var parentElement = (FrameworkElement?)VisualTreeHelper.GetParent(element); + while (parentElement != null) + { + if (parentElement is T parent) + return parent; + + parentElement = (FrameworkElement?)VisualTreeHelper.GetParent(parentElement); + } + + return null; + } + + public static T? FindVisualParent(this FrameworkElement element, string name) where T : FrameworkElement + { + var parentElement = (FrameworkElement?)VisualTreeHelper.GetParent(element); + while (parentElement != null) + { + if (parentElement is T parent) + if (parentElement.Name == name) + return parent; + + parentElement = (FrameworkElement?)VisualTreeHelper.GetParent(parentElement); + } + + return null; + } + + public static T? FindVisualChild(this FrameworkElement element) where T : Visual + { + for (var i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++) + { + var childElement = (FrameworkElement?)VisualTreeHelper.GetChild(element, i); + if (childElement is null) return null; + + if (childElement is T child) + return child; + + var descendent = FindVisualChild(childElement); + if (descendent != null) return descendent; + } + + return null; + } + + public static T? FindVisualChild(this FrameworkElement element, string name) where T : Visual + { + for (var i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++) + { + var childElement = (FrameworkElement?)VisualTreeHelper.GetChild(element, i); + if (childElement is null) return null; + + if (childElement is T child) + if (childElement.Name == name) + return child; + + var descendent = FindVisualChild(childElement, name); + if (descendent != null) return descendent; + } + + return null; + } +} \ No newline at end of file diff --git a/source/RevitLookup/Views/Pages/AboutPage.xaml b/source/RevitLookup.UI.Framework/Views/AboutProgram/AboutPage.xaml similarity index 79% rename from source/RevitLookup/Views/Pages/AboutPage.xaml rename to source/RevitLookup.UI.Framework/Views/AboutProgram/AboutPage.xaml index cafcd7d5..eef8cdb3 100644 --- a/source/RevitLookup/Views/Pages/AboutPage.xaml +++ b/source/RevitLookup.UI.Framework/Views/AboutProgram/AboutPage.xaml @@ -1,14 +1,16 @@  + d:DesignWidth="800" + d:DataContext="{d:DesignInstance aboutProgram:AboutPage}"> + Source="pack://application:,,,/RevitLookup.UI.Framework;component/Resources/Images/ProductLogo200.png" /> - - + - + Visibility="{Binding ViewModel.CheckUpdatesCommand.IsRunning, Converter={converters:BooleanCollapsedVisibilityConverter}}" /> + Text="{Binding ViewModel.CurrentVersion, FallbackValue='1.0.0'}" /> + Text="{Binding ViewModel.LatestCheckDate, StringFormat='Latest check: {0}', FallbackValue='Latest check: 2030.01.01'}" /> + Visibility="{Binding ViewModel.State, Converter={valueConverters:UpdateAvailableCardVisibilityConverter}}"> @@ -81,7 +83,7 @@ Height="20" IsIndeterminate="True" VerticalAlignment="Center" - Visibility="{Binding ViewModel.DownloadUpdateCommand.IsRunning, Converter={converters:BooleanVisibilityConverter}}" /> + Visibility="{Binding ViewModel.DownloadUpdateCommand.IsRunning, Converter={converters:BooleanCollapsedVisibilityConverter}}" /> + Text="{Binding ViewModel.NewVersion, FallbackValue='1.0.0'}" /> - + Visibility="{Binding ViewModel.State, Converter={valueConverters:UpdateDownloadedVisibilityConverter}}"> + - - + + Converter="{valueConverters:UpToDateVisibilityConverter}"> - + - - + - + Visibility="{Binding ViewModel.State, Converter={valueConverters:ErrorCardVisibilityConverter}}"> + - - + TextWrapping="WrapWithOverflow" + Text="{Binding ViewModel.ErrorMessage, FallbackValue='Error message'}" /> + - - + +public sealed partial class AboutPage : INavigableView { - public AboutPage(AboutViewModel viewModel) + public AboutPage(IAboutViewModel viewModel) { ViewModel = viewModel; InitializeComponent(); DataContext = this; } - public AboutViewModel ViewModel { get; } + public IAboutViewModel ViewModel { get; } } \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/AboutProgram/OpenSourceDialog.xaml b/source/RevitLookup.UI.Framework/Views/AboutProgram/OpenSourceDialog.xaml new file mode 100644 index 00000000..3e36804d --- /dev/null +++ b/source/RevitLookup.UI.Framework/Views/AboutProgram/OpenSourceDialog.xaml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/AboutProgram/OpenSourceDialog.xaml.cs b/source/RevitLookup.UI.Framework/Views/AboutProgram/OpenSourceDialog.xaml.cs new file mode 100644 index 00000000..399be1ea --- /dev/null +++ b/source/RevitLookup.UI.Framework/Views/AboutProgram/OpenSourceDialog.xaml.cs @@ -0,0 +1,42 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +using System.Windows; +using System.Windows.Documents; +using RevitLookup.Abstractions.ViewModels.AboutProgram; +using RevitLookup.Common.Tools; +using Wpf.Ui; + +namespace RevitLookup.UI.Framework.Views.AboutProgram; + +public sealed partial class OpenSourceDialog +{ + public OpenSourceDialog(IContentDialogService dialogService, IOpenSourceViewModel viewModel) : base(dialogService.GetDialogHost()) + { + InitializeComponent(); + DataContext = viewModel; + } + + private void OpenLink(object sender, RoutedEventArgs args) + { + var link = (Hyperlink) args.OriginalSource; + ProcessTasks.StartShell(link.NavigateUri.OriginalString); + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/Settings/ResetSettingsDialog.xaml b/source/RevitLookup.UI.Framework/Views/Settings/ResetSettingsDialog.xaml new file mode 100644 index 00000000..d6a8a9e8 --- /dev/null +++ b/source/RevitLookup.UI.Framework/Views/Settings/ResetSettingsDialog.xaml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/Controls/ControlExample.xaml.cs b/source/RevitLookup.UI.Playground/Client/Controls/ControlExample.xaml.cs new file mode 100644 index 00000000..b62b2e96 --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/Controls/ControlExample.xaml.cs @@ -0,0 +1,172 @@ +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Windows; +using System.Windows.Automation; +using System.Windows.Automation.Peers; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Markup; + +namespace RevitLookup.UI.Playground.Client.Controls; + +/// +/// A control that displays an example of a control +/// +[ContentProperty(nameof(ExampleContent))] +public sealed class ControlExample : Control +{ + static ControlExample() + { + CommandManager.RegisterClassCommandBinding(typeof(ControlExample), new CommandBinding(ApplicationCommands.Copy, Copy_SourceCode)); + } + + public static readonly DependencyProperty HeaderTextProperty = DependencyProperty.Register( + nameof(HeaderText), + typeof(string), + typeof(ControlExample), + new PropertyMetadata(null) + ); + + public static readonly DependencyProperty ExampleContentProperty = DependencyProperty.Register( + nameof(ExampleContent), + typeof(object), + typeof(ControlExample), + new PropertyMetadata(null) + ); + + public static readonly DependencyProperty XamlCodeProperty = DependencyProperty.Register( + nameof(XamlCode), + typeof(string), + typeof(ControlExample), + new PropertyMetadata(null) + ); + + public static readonly DependencyProperty XamlCodeSourceProperty = DependencyProperty.Register( + nameof(XamlCodeSource), + typeof(Uri), + typeof(ControlExample), + new PropertyMetadata( + null, + static (o, args) => ((ControlExample)o).OnXamlCodeSourceChanged((Uri)args.NewValue) + ) + ); + + public static readonly DependencyProperty CsharpCodeProperty = DependencyProperty.Register( + nameof(CsharpCode), + typeof(string), + typeof(ControlExample), + new PropertyMetadata(null) + ); + + public static readonly DependencyProperty CsharpCodeSourceProperty = DependencyProperty.Register( + nameof(CsharpCodeSource), + typeof(Uri), + typeof(ControlExample), + new PropertyMetadata( + null, + static (o, args) => ((ControlExample)o).OnCsharpCodeSourceChanged((Uri)args.NewValue) + ) + ); + + public string? HeaderText + { + get => (string)GetValue(HeaderTextProperty); + set => SetValue(HeaderTextProperty, value); + } + + public object? ExampleContent + { + get => GetValue(ExampleContentProperty); + set => SetValue(ExampleContentProperty, value); + } + + public string? XamlCode + { + get => (string)GetValue(XamlCodeProperty); + set => SetValue(XamlCodeProperty, value); + } + + public Uri? XamlCodeSource + { + get => (Uri)GetValue(XamlCodeSourceProperty); + set => SetValue(XamlCodeSourceProperty, value); + } + + public string? CsharpCode + { + get => (string)GetValue(CsharpCodeProperty); + set => SetValue(CsharpCodeProperty, value); + } + + public Uri? CsharpCodeSource + { + get => (Uri)GetValue(CsharpCodeSourceProperty); + set => SetValue(CsharpCodeSourceProperty, value); + } + + private void OnXamlCodeSourceChanged(Uri uri) + { + XamlCode = LoadResource(uri); + } + + private void OnCsharpCodeSourceChanged(Uri uri) + { + CsharpCode = LoadResource(uri); + } + + private static void Copy_SourceCode(object sender, RoutedEventArgs e) + { + var controlExample = (ControlExample)sender; + + try + { + switch (((ExecutedRoutedEventArgs)e).Parameter.ToString()) + { + case "Copy_XamlCode": + if (string.IsNullOrEmpty(controlExample.XamlCode)) break; + + Clipboard.SetText(controlExample.XamlCode); + var peer = UIElementAutomationPeer.CreatePeerForElement((Button)e.OriginalSource); + peer.RaiseNotificationEvent( + AutomationNotificationKind.Other, + AutomationNotificationProcessing.ImportantMostRecent, + "Source Code Copied", + "ButtonClickedActivity" + ); + + break; + case "Copy_CsharpCode": + if (string.IsNullOrEmpty(controlExample.CsharpCode)) break; + + Clipboard.SetText(controlExample.CsharpCode); + break; + default: + throw new InvalidOperationException(); + } + } + catch + { + // ignored + } + } + + private static string LoadResource(Uri uri) + { + try + { + if (Application.GetResourceStream(uri) is not { } steamInfo) + { + return string.Empty; + } + + using StreamReader streamReader = new(steamInfo.Stream, Encoding.UTF8); + return streamReader.ReadToEnd(); + } + catch (Exception exception) + { + Debug.WriteLine(exception); + return exception.ToString(); + } + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/Controls/PageViewer.xaml b/source/RevitLookup.UI.Playground/Client/Controls/PageViewer.xaml new file mode 100644 index 00000000..9a1599cd --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/Controls/PageViewer.xaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/Controls/PageViewer.xaml.cs b/source/RevitLookup.UI.Playground/Client/Controls/PageViewer.xaml.cs new file mode 100644 index 00000000..5fbb3e53 --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/Controls/PageViewer.xaml.cs @@ -0,0 +1,47 @@ +using System.Windows; +using System.Windows.Controls; +using Microsoft.Extensions.DependencyInjection; +using RevitLookup.Abstractions.Services; +using Wpf.Ui; + +namespace RevitLookup.UI.Playground.Client.Controls; + +public sealed partial class PageViewer +{ + private readonly IServiceProvider _serviceProvider; + + public PageViewer( + IServiceProvider serviceProvider, + ISnackbarService snackbarService, + IContentDialogService dialogService, + IWindowIntercomService intercomService) + { + _serviceProvider = serviceProvider; + InitializeComponent(); + + intercomService.SetHost(this); + dialogService.SetDialogHost(RootContentDialog); + snackbarService.SetSnackbarPresenter(RootSnackbar); + } + + public bool? ShowPage() where T : Page + { + var page = _serviceProvider.GetRequiredService(); + Viewer.Navigate(page); + + if (WindowStartupLocation == WindowStartupLocation.CenterScreen) Viewer.SizeChanged += OnViewerFrameResized; + return ShowDialog(); + } + + private void OnViewerFrameResized(object sender, SizeChangedEventArgs args) + { + if (args.PreviousSize.Height == 0 || args.PreviousSize.Width == 0) return; + + var self = (Frame)sender; + self.SizeChanged -= OnViewerFrameResized; + + //Move the owner to the screen center after navigation + Left -= (ActualWidth - MinWidth) / 2; + Top -= (ActualHeight - MinHeight) / 2; + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/Helpers/NullToVisibilityConverter.cs b/source/RevitLookup.UI.Playground/Client/Helpers/NullToVisibilityConverter.cs new file mode 100644 index 00000000..6d3e485b --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/Helpers/NullToVisibilityConverter.cs @@ -0,0 +1,27 @@ +using System.Globalization; +using System.Windows; +using System.Windows.Data; +using System.Windows.Markup; + +namespace RevitLookup.UI.Playground.Client.Helpers; + +/// +/// Converts a null value to Visibility.Collapsed +/// +internal sealed class NullToVisibilityConverter : MarkupExtension, IValueConverter +{ + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + return value is null ? Visibility.Collapsed : Visibility.Visible; + } + + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + + public override object? ProvideValue(IServiceProvider serviceProvider) + { + return this; + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/Helpers/SymbolIconXamlConverter.cs b/source/RevitLookup.UI.Playground/Client/Helpers/SymbolIconXamlConverter.cs new file mode 100644 index 00000000..0dcb966b --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/Helpers/SymbolIconXamlConverter.cs @@ -0,0 +1,43 @@ +using System.Globalization; +using System.Text; +using System.Windows.Data; +using System.Windows.Markup; +using Wpf.Ui.Controls; + +namespace RevitLookup.UI.Playground.Client.Helpers; + +public sealed class SymbolIconXamlConverter : MarkupExtension, IMultiValueConverter +{ + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + if (values[0] is string text) return text; + + var icon = (SymbolRegular)values[0]; + var filled = (bool)values[1]; + + var builder = new StringBuilder(); + builder.Append(""); + + return builder.ToString(); + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + return this; + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/Models/FontIconData.cs b/source/RevitLookup.UI.Playground/Client/Models/FontIconData.cs new file mode 100644 index 00000000..edf024ee --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/Models/FontIconData.cs @@ -0,0 +1,15 @@ +namespace RevitLookup.UI.Playground.Client.Models; + +/// +/// IconData class for icons in icon page +/// +[Serializable] +public class FontIconData +{ + public required string Name { get; set; } + public required string Code { get; set; } + + public string Character => char.ConvertFromUtf32(Convert.ToInt32(Code, 16)); + public string CodeGlyph => "\\x" + Code; + public string TextGlyph => "&#x" + Code + ";"; +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/Models/FontIcons.json b/source/RevitLookup.UI.Playground/Client/Models/FontIcons.json new file mode 100644 index 00000000..f234337b --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/Models/FontIcons.json @@ -0,0 +1,5614 @@ +[ + { + "Code": "E700", + "Name": "GlobalNavButton" + }, + { + "Code": "E701", + "Name": "Wifi" + }, + { + "Code": "E702", + "Name": "Bluetooth" + }, + { + "Code": "E703", + "Name": "Connect" + }, + { + "Code": "E704", + "Name": "InternetSharing" + }, + { + "Code": "E705", + "Name": "VPN" + }, + { + "Code": "E706", + "Name": "Brightness" + }, + { + "Code": "E707", + "Name": "MapPin" + }, + { + "Code": "E708", + "Name": "QuietHours" + }, + { + "Code": "E709", + "Name": "Airplane" + }, + { + "Code": "E70A", + "Name": "Tablet" + }, + { + "Code": "E70B", + "Name": "QuickNote" + }, + { + "Code": "E70C", + "Name": "RememberedDevice" + }, + { + "Code": "E70D", + "Name": "ChevronDown" + }, + { + "Code": "E70E", + "Name": "ChevronUp" + }, + { + "Code": "E70F", + "Name": "Edit" + }, + { + "Code": "E710", + "Name": "Add" + }, + { + "Code": "E711", + "Name": "Cancel" + }, + { + "Code": "E712", + "Name": "More" + }, + { + "Code": "E713", + "Name": "Settings" + }, + { + "Code": "E714", + "Name": "Video" + }, + { + "Code": "E715", + "Name": "Mail" + }, + { + "Code": "E716", + "Name": "People" + }, + { + "Code": "E717", + "Name": "Phone" + }, + { + "Code": "E718", + "Name": "Pin" + }, + { + "Code": "E719", + "Name": "Shop" + }, + { + "Code": "E71A", + "Name": "Stop" + }, + { + "Code": "E71B", + "Name": "Link" + }, + { + "Code": "E71C", + "Name": "Filter" + }, + { + "Code": "E71D", + "Name": "AllApps" + }, + { + "Code": "E71E", + "Name": "Zoom" + }, + { + "Code": "E71F", + "Name": "ZoomOut" + }, + { + "Code": "E720", + "Name": "Microphone" + }, + { + "Code": "E721", + "Name": "Search" + }, + { + "Code": "E722", + "Name": "Camera" + }, + { + "Code": "E723", + "Name": "Attach" + }, + { + "Code": "E724", + "Name": "Send" + }, + { + "Code": "E725", + "Name": "SendFill" + }, + { + "Code": "E726", + "Name": "WalkSolid" + }, + { + "Code": "E727", + "Name": "InPrivate" + }, + { + "Code": "E728", + "Name": "FavoriteList" + }, + { + "Code": "E729", + "Name": "PageSolid" + }, + { + "Code": "E72A", + "Name": "Forward" + }, + { + "Code": "E72B", + "Name": "Back" + }, + { + "Code": "E72C", + "Name": "Refresh" + }, + { + "Code": "E72D", + "Name": "Share" + }, + { + "Code": "E72E", + "Name": "Lock" + }, + { + "Code": "E730", + "Name": "ReportHacked" + }, + { + "Code": "E731", + "Name": "EMI" + }, + { + "Code": "E734", + "Name": "FavoriteStar" + }, + { + "Code": "E735", + "Name": "FavoriteStarFill" + }, + { + "Code": "E736", + "Name": "ReadingMode" + }, + { + "Code": "E737", + "Name": "Favicon" + }, + { + "Code": "E738", + "Name": "Remove" + }, + { + "Code": "E739", + "Name": "Checkbox" + }, + { + "Code": "E73A", + "Name": "CheckboxComposite" + }, + { + "Code": "E73B", + "Name": "CheckboxFill" + }, + { + "Code": "E73C", + "Name": "CheckboxIndeterminate" + }, + { + "Code": "E73D", + "Name": "CheckboxCompositeReversed" + }, + { + "Code": "E73E", + "Name": "CheckMark" + }, + { + "Code": "E73F", + "Name": "BackToWindow" + }, + { + "Code": "E740", + "Name": "FullScreen" + }, + { + "Code": "E741", + "Name": "ResizeTouchLarger" + }, + { + "Code": "E742", + "Name": "ResizeTouchSmaller" + }, + { + "Code": "E743", + "Name": "ResizeMouseSmall" + }, + { + "Code": "E744", + "Name": "ResizeMouseMedium" + }, + { + "Code": "E745", + "Name": "ResizeMouseWide" + }, + { + "Code": "E746", + "Name": "ResizeMouseTall" + }, + { + "Code": "E747", + "Name": "ResizeMouseLarge" + }, + { + "Code": "E748", + "Name": "SwitchUser" + }, + { + "Code": "E749", + "Name": "Print" + }, + { + "Code": "E74A", + "Name": "Up" + }, + { + "Code": "E74B", + "Name": "Down" + }, + { + "Code": "E74C", + "Name": "OEM" + }, + { + "Code": "E74D", + "Name": "Delete" + }, + { + "Code": "E74E", + "Name": "Save" + }, + { + "Code": "E74F", + "Name": "Mute" + }, + { + "Code": "E750", + "Name": "BackSpaceQWERTY" + }, + { + "Code": "E751", + "Name": "ReturnKey" + }, + { + "Code": "E752", + "Name": "UpArrowShiftKey" + }, + { + "Code": "E753", + "Name": "Cloud" + }, + { + "Code": "E754", + "Name": "Flashlight" + }, + { + "Code": "E755", + "Name": "RotationLock" + }, + { + "Code": "E756", + "Name": "CommandPrompt" + }, + { + "Code": "E759", + "Name": "SIPMove" + }, + { + "Code": "E75A", + "Name": "SIPUndock" + }, + { + "Code": "E75B", + "Name": "SIPRedock" + }, + { + "Code": "E75C", + "Name": "EraseTool" + }, + { + "Code": "E75D", + "Name": "UnderscoreSpace" + }, + { + "Code": "E75E", + "Name": "GripperTool" + }, + { + "Code": "E75F", + "Name": "Dialpad" + }, + { + "Code": "E760", + "Name": "PageLeft" + }, + { + "Code": "E761", + "Name": "PageRight" + }, + { + "Code": "E762", + "Name": "MultiSelect" + }, + { + "Code": "E763", + "Name": "KeyboardLeftHanded" + }, + { + "Code": "E764", + "Name": "KeyboardRightHanded" + }, + { + "Code": "E765", + "Name": "KeyboardClassic" + }, + { + "Code": "E766", + "Name": "KeyboardSplit" + }, + { + "Code": "E767", + "Name": "Volume" + }, + { + "Code": "E768", + "Name": "Play" + }, + { + "Code": "E769", + "Name": "Pause" + }, + { + "Code": "E76B", + "Name": "ChevronLeft" + }, + { + "Code": "E76C", + "Name": "ChevronRight" + }, + { + "Code": "E76D", + "Name": "InkingTool" + }, + { + "Code": "E76E", + "Name": "Emoji2" + }, + { + "Code": "E76F", + "Name": "GripperBarHorizontal" + }, + { + "Code": "E770", + "Name": "System" + }, + { + "Code": "E771", + "Name": "Personalize" + }, + { + "Code": "E772", + "Name": "Devices" + }, + { + "Code": "E773", + "Name": "SearchAndApps" + }, + { + "Code": "E774", + "Name": "Globe" + }, + { + "Code": "E775", + "Name": "TimeLanguage" + }, + { + "Code": "E776", + "Name": "EaseOfAccess" + }, + { + "Code": "E777", + "Name": "UpdateRestore" + }, + { + "Code": "E778", + "Name": "HangUp" + }, + { + "Code": "E779", + "Name": "ContactInfo" + }, + { + "Code": "E77A", + "Name": "Unpin" + }, + { + "Code": "E77B", + "Name": "Contact" + }, + { + "Code": "E77C", + "Name": "Memo" + }, + { + "Code": "E77E", + "Name": "IncomingCall" + }, + { + "Code": "E77F", + "Name": "Paste" + }, + { + "Code": "E780", + "Name": "PhoneBook" + }, + { + "Code": "E781", + "Name": "LEDLight" + }, + { + "Code": "E783", + "Name": "Error" + }, + { + "Code": "E784", + "Name": "GripperBarVertical" + }, + { + "Code": "E785", + "Name": "Unlock" + }, + { + "Code": "E786", + "Name": "Slideshow" + }, + { + "Code": "E787", + "Name": "Calendar" + }, + { + "Code": "E788", + "Name": "GripperResize" + }, + { + "Code": "E789", + "Name": "Megaphone" + }, + { + "Code": "E78A", + "Name": "Trim" + }, + { + "Code": "E78B", + "Name": "NewWindow" + }, + { + "Code": "E78C", + "Name": "SaveLocal" + }, + { + "Code": "E790", + "Name": "Color" + }, + { + "Code": "E791", + "Name": "DataSense" + }, + { + "Code": "E792", + "Name": "SaveAs" + }, + { + "Code": "E793", + "Name": "Light" + }, + { + "Code": "E799", + "Name": "AspectRatio" + }, + { + "Code": "E7A5", + "Name": "DataSenseBar" + }, + { + "Code": "E7A6", + "Name": "Redo" + }, + { + "Code": "E7A7", + "Name": "Undo" + }, + { + "Code": "E7A8", + "Name": "Crop" + }, + { + "Code": "E7AC", + "Name": "OpenWith" + }, + { + "Code": "E7AD", + "Name": "Rotate" + }, + { + "Code": "E7B3", + "Name": "RedEye" + }, + { + "Code": "E7B5", + "Name": "SetlockScreen" + }, + { + "Code": "E7B7", + "Name": "MapPin2" + }, + { + "Code": "E7B8", + "Name": "Package" + }, + { + "Code": "E7BA", + "Name": "Warning" + }, + { + "Code": "E7BC", + "Name": "ReadingList" + }, + { + "Code": "E7BE", + "Name": "Education" + }, + { + "Code": "E7BF", + "Name": "ShoppingCart" + }, + { + "Code": "E7C0", + "Name": "Train" + }, + { + "Code": "E7C1", + "Name": "Flag" + }, + { + "Code": "E7C2", + "Name": "Move" + }, + { + "Code": "E7C3", + "Name": "Page" + }, + { + "Code": "E7C4", + "Name": "TaskView" + }, + { + "Code": "E7C5", + "Name": "BrowsePhotos" + }, + { + "Code": "E7C6", + "Name": "HalfStarLeft" + }, + { + "Code": "E7C7", + "Name": "HalfStarRight" + }, + { + "Code": "E7C8", + "Name": "Record" + }, + { + "Code": "E7C9", + "Name": "TouchPointer" + }, + { + "Code": "E7DE", + "Name": "LangJPN" + }, + { + "Code": "E7E3", + "Name": "Ferry" + }, + { + "Code": "E7E6", + "Name": "Highlight" + }, + { + "Code": "E7E7", + "Name": "ActionCenterNotification" + }, + { + "Code": "E7E8", + "Name": "PowerButton" + }, + { + "Code": "E7EA", + "Name": "ResizeTouchNarrower" + }, + { + "Code": "E7EB", + "Name": "ResizeTouchShorter" + }, + { + "Code": "E7EC", + "Name": "DrivingMode" + }, + { + "Code": "E7ED", + "Name": "RingerSilent" + }, + { + "Code": "E7EE", + "Name": "OtherUser" + }, + { + "Code": "E7EF", + "Name": "Admin" + }, + { + "Code": "E7F0", + "Name": "CC" + }, + { + "Code": "E7F1", + "Name": "SDCard" + }, + { + "Code": "E7F2", + "Name": "CallForwarding" + }, + { + "Code": "E7F3", + "Name": "SettingsDisplaySound" + }, + { + "Code": "E7F4", + "Name": "TVMonitor" + }, + { + "Code": "E7F5", + "Name": "Speakers" + }, + { + "Code": "E7F6", + "Name": "Headphone" + }, + { + "Code": "E7F7", + "Name": "DeviceLaptopPic" + }, + { + "Code": "E7F8", + "Name": "DeviceLaptopNoPic" + }, + { + "Code": "E7F9", + "Name": "DeviceMonitorRightPic" + }, + { + "Code": "E7FA", + "Name": "DeviceMonitorLeftPic" + }, + { + "Code": "E7FB", + "Name": "DeviceMonitorNoPic" + }, + { + "Code": "E7FC", + "Name": "Game" + }, + { + "Code": "E7FD", + "Name": "HorizontalTabKey" + }, + { + "Code": "E802", + "Name": "StreetsideSplitMinimize" + }, + { + "Code": "E803", + "Name": "StreetsideSplitExpand" + }, + { + "Code": "E804", + "Name": "Car" + }, + { + "Code": "E805", + "Name": "Walk" + }, + { + "Code": "E806", + "Name": "Bus" + }, + { + "Code": "E809", + "Name": "TiltUp" + }, + { + "Code": "E80A", + "Name": "TiltDown" + }, + { + "Code": "E80B", + "Name": "CallControl" + }, + { + "Code": "E80C", + "Name": "RotateMapRight" + }, + { + "Code": "E80D", + "Name": "RotateMapLeft" + }, + { + "Code": "E80F", + "Name": "Home" + }, + { + "Code": "E811", + "Name": "ParkingLocation" + }, + { + "Code": "E812", + "Name": "MapCompassTop" + }, + { + "Code": "E813", + "Name": "MapCompassBottom" + }, + { + "Code": "E814", + "Name": "IncidentTriangle" + }, + { + "Code": "E815", + "Name": "Touch" + }, + { + "Code": "E816", + "Name": "MapDirections" + }, + { + "Code": "E819", + "Name": "StartPoint" + }, + { + "Code": "E81A", + "Name": "StopPoint" + }, + { + "Code": "E81B", + "Name": "EndPoint" + }, + { + "Code": "E81C", + "Name": "History" + }, + { + "Code": "E81D", + "Name": "Location" + }, + { + "Code": "E81E", + "Name": "MapLayers" + }, + { + "Code": "E81F", + "Name": "Accident" + }, + { + "Code": "E821", + "Name": "Work" + }, + { + "Code": "E822", + "Name": "Construction" + }, + { + "Code": "E823", + "Name": "Recent" + }, + { + "Code": "E825", + "Name": "Bank" + }, + { + "Code": "E826", + "Name": "DownloadMap" + }, + { + "Code": "E829", + "Name": "InkingToolFill2" + }, + { + "Code": "E82A", + "Name": "HighlightFill2" + }, + { + "Code": "E82B", + "Name": "EraseToolFill" + }, + { + "Code": "E82C", + "Name": "EraseToolFill2" + }, + { + "Code": "E82D", + "Name": "Dictionary" + }, + { + "Code": "E82E", + "Name": "DictionaryAdd" + }, + { + "Code": "E82F", + "Name": "ToolTip" + }, + { + "Code": "E830", + "Name": "ChromeBack" + }, + { + "Code": "E835", + "Name": "ProvisioningPackage" + }, + { + "Code": "E836", + "Name": "AddRemoteDevice" + }, + { + "Code": "E838", + "Name": "FolderOpen" + }, + { + "Code": "E839", + "Name": "Ethernet" + }, + { + "Code": "E83A", + "Name": "ShareBroadband" + }, + { + "Code": "E83B", + "Name": "DirectAccess" + }, + { + "Code": "E83C", + "Name": "DialUp" + }, + { + "Code": "E83D", + "Name": "DefenderApp" + }, + { + "Code": "E83E", + "Name": "BatteryCharging9" + }, + { + "Code": "E83F", + "Name": "Battery10" + }, + { + "Code": "E840", + "Name": "Pinned" + }, + { + "Code": "E841", + "Name": "PinFill" + }, + { + "Code": "E842", + "Name": "PinnedFill" + }, + { + "Code": "E843", + "Name": "PeriodKey" + }, + { + "Code": "E844", + "Name": "PuncKey" + }, + { + "Code": "E845", + "Name": "RevToggleKey" + }, + { + "Code": "E846", + "Name": "RightArrowKeyTime1" + }, + { + "Code": "E847", + "Name": "RightArrowKeyTime2" + }, + { + "Code": "E848", + "Name": "LeftQuote" + }, + { + "Code": "E849", + "Name": "RightQuote" + }, + { + "Code": "E84A", + "Name": "DownShiftKey" + }, + { + "Code": "E84B", + "Name": "UpShiftKey" + }, + { + "Code": "E84C", + "Name": "PuncKey0" + }, + { + "Code": "E84D", + "Name": "PuncKeyLeftBottom" + }, + { + "Code": "E84E", + "Name": "RightArrowKeyTime3" + }, + { + "Code": "E84F", + "Name": "RightArrowKeyTime4" + }, + { + "Code": "E850", + "Name": "Battery0" + }, + { + "Code": "E851", + "Name": "Battery1" + }, + { + "Code": "E852", + "Name": "Battery2" + }, + { + "Code": "E853", + "Name": "Battery3" + }, + { + "Code": "E854", + "Name": "Battery4" + }, + { + "Code": "E855", + "Name": "Battery5" + }, + { + "Code": "E856", + "Name": "Battery6" + }, + { + "Code": "E857", + "Name": "Battery7" + }, + { + "Code": "E858", + "Name": "Battery8" + }, + { + "Code": "E859", + "Name": "Battery9" + }, + { + "Code": "E85A", + "Name": "BatteryCharging0" + }, + { + "Code": "E85B", + "Name": "BatteryCharging1" + }, + { + "Code": "E85C", + "Name": "BatteryCharging2" + }, + { + "Code": "E85D", + "Name": "BatteryCharging3" + }, + { + "Code": "E85E", + "Name": "BatteryCharging4" + }, + { + "Code": "E85F", + "Name": "BatteryCharging5" + }, + { + "Code": "E860", + "Name": "BatteryCharging6" + }, + { + "Code": "E861", + "Name": "BatteryCharging7" + }, + { + "Code": "E862", + "Name": "BatteryCharging8" + }, + { + "Code": "E863", + "Name": "BatterySaver0" + }, + { + "Code": "E864", + "Name": "BatterySaver1" + }, + { + "Code": "E865", + "Name": "BatterySaver2" + }, + { + "Code": "E866", + "Name": "BatterySaver3" + }, + { + "Code": "E867", + "Name": "BatterySaver4" + }, + { + "Code": "E868", + "Name": "BatterySaver5" + }, + { + "Code": "E869", + "Name": "BatterySaver6" + }, + { + "Code": "E86A", + "Name": "BatterySaver7" + }, + { + "Code": "E86B", + "Name": "BatterySaver8" + }, + { + "Code": "E86C", + "Name": "SignalBars1" + }, + { + "Code": "E86D", + "Name": "SignalBars2" + }, + { + "Code": "E86E", + "Name": "SignalBars3" + }, + { + "Code": "E86F", + "Name": "SignalBars4" + }, + { + "Code": "E870", + "Name": "SignalBars5" + }, + { + "Code": "E871", + "Name": "SignalNotConnected" + }, + { + "Code": "E872", + "Name": "Wifi1" + }, + { + "Code": "E873", + "Name": "Wifi2" + }, + { + "Code": "E874", + "Name": "Wifi3" + }, + { + "Code": "E875", + "Name": "MobSIMLock" + }, + { + "Code": "E876", + "Name": "MobSIMMissing" + }, + { + "Code": "E877", + "Name": "Vibrate" + }, + { + "Code": "E878", + "Name": "RoamingInternational" + }, + { + "Code": "E879", + "Name": "RoamingDomestic" + }, + { + "Code": "E87A", + "Name": "CallForwardInternational" + }, + { + "Code": "E87B", + "Name": "CallForwardRoaming" + }, + { + "Code": "E87C", + "Name": "JpnRomanji" + }, + { + "Code": "E87D", + "Name": "JpnRomanjiLock" + }, + { + "Code": "E87E", + "Name": "JpnRomanjiShift" + }, + { + "Code": "E87F", + "Name": "JpnRomanjiShiftLock" + }, + { + "Code": "E880", + "Name": "StatusDataTransfer" + }, + { + "Code": "E881", + "Name": "StatusDataTransferVPN" + }, + { + "Code": "E882", + "Name": "StatusDualSIM2" + }, + { + "Code": "E883", + "Name": "StatusDualSIM2VPN" + }, + { + "Code": "E884", + "Name": "StatusDualSIM1" + }, + { + "Code": "E885", + "Name": "StatusDualSIM1VPN" + }, + { + "Code": "E886", + "Name": "StatusSGLTE" + }, + { + "Code": "E887", + "Name": "StatusSGLTECell" + }, + { + "Code": "E888", + "Name": "StatusSGLTEDataVPN" + }, + { + "Code": "E889", + "Name": "StatusVPN" + }, + { + "Code": "E88A", + "Name": "WifiHotspot" + }, + { + "Code": "E88B", + "Name": "LanguageKor" + }, + { + "Code": "E88C", + "Name": "LanguageCht" + }, + { + "Code": "E88D", + "Name": "LanguageChs" + }, + { + "Code": "E88E", + "Name": "USB" + }, + { + "Code": "E88F", + "Name": "InkingToolFill" + }, + { + "Code": "E890", + "Name": "View" + }, + { + "Code": "E891", + "Name": "HighlightFill" + }, + { + "Code": "E892", + "Name": "Previous" + }, + { + "Code": "E893", + "Name": "Next" + }, + { + "Code": "E894", + "Name": "Clear" + }, + { + "Code": "E895", + "Name": "Sync" + }, + { + "Code": "E896", + "Name": "Download" + }, + { + "Code": "E897", + "Name": "Help" + }, + { + "Code": "E898", + "Name": "Upload" + }, + { + "Code": "E899", + "Name": "Emoji" + }, + { + "Code": "E89A", + "Name": "TwoPage" + }, + { + "Code": "E89B", + "Name": "LeaveChat" + }, + { + "Code": "E89C", + "Name": "MailForward" + }, + { + "Code": "E89E", + "Name": "RotateCamera" + }, + { + "Code": "E89F", + "Name": "ClosePane" + }, + { + "Code": "E8A0", + "Name": "OpenPane" + }, + { + "Code": "E8A1", + "Name": "PreviewLink" + }, + { + "Code": "E8A2", + "Name": "AttachCamera" + }, + { + "Code": "E8A3", + "Name": "ZoomIn" + }, + { + "Code": "E8A4", + "Name": "Bookmarks" + }, + { + "Code": "E8A5", + "Name": "Document" + }, + { + "Code": "E8A6", + "Name": "ProtectedDocument" + }, + { + "Code": "E8A7", + "Name": "OpenInNewWindow" + }, + { + "Code": "E8A8", + "Name": "MailFill" + }, + { + "Code": "E8A9", + "Name": "ViewAll" + }, + { + "Code": "E8AA", + "Name": "VideoChat" + }, + { + "Code": "E8AB", + "Name": "Switch" + }, + { + "Code": "E8AC", + "Name": "Rename" + }, + { + "Code": "E8AD", + "Name": "Go" + }, + { + "Code": "E8AE", + "Name": "SurfaceHub" + }, + { + "Code": "E8AF", + "Name": "Remote" + }, + { + "Code": "E8B0", + "Name": "Click" + }, + { + "Code": "E8B1", + "Name": "Shuffle" + }, + { + "Code": "E8B2", + "Name": "Movies" + }, + { + "Code": "E8B3", + "Name": "SelectAll" + }, + { + "Code": "E8B4", + "Name": "Orientation" + }, + { + "Code": "E8B5", + "Name": "Import" + }, + { + "Code": "E8B6", + "Name": "ImportAll" + }, + { + "Code": "E8B7", + "Name": "Folder" + }, + { + "Code": "E8B8", + "Name": "Webcam" + }, + { + "Code": "E8B9", + "Name": "Picture" + }, + { + "Code": "E8BA", + "Name": "Caption" + }, + { + "Code": "E8BB", + "Name": "ChromeClose" + }, + { + "Code": "E8BC", + "Name": "ShowResults" + }, + { + "Code": "E8BD", + "Name": "Message" + }, + { + "Code": "E8BE", + "Name": "Leaf" + }, + { + "Code": "E8BF", + "Name": "CalendarDay" + }, + { + "Code": "E8C0", + "Name": "CalendarWeek" + }, + { + "Code": "E8C1", + "Name": "Characters" + }, + { + "Code": "E8C2", + "Name": "MailReplyAll" + }, + { + "Code": "E8C3", + "Name": "Read" + }, + { + "Code": "E8C4", + "Name": "ShowBcc" + }, + { + "Code": "E8C5", + "Name": "HideBcc" + }, + { + "Code": "E8C6", + "Name": "Cut" + }, + { + "Code": "E8C7", + "Name": "PaymentCard" + }, + { + "Code": "E8C8", + "Name": "Copy" + }, + { + "Code": "E8C9", + "Name": "Important" + }, + { + "Code": "E8CA", + "Name": "MailReply" + }, + { + "Code": "E8CB", + "Name": "Sort" + }, + { + "Code": "E8CC", + "Name": "MobileTablet" + }, + { + "Code": "E8CD", + "Name": "DisconnectDrive" + }, + { + "Code": "E8CE", + "Name": "MapDrive" + }, + { + "Code": "E8CF", + "Name": "ContactPresence" + }, + { + "Code": "E8D0", + "Name": "Priority" + }, + { + "Code": "E8D1", + "Name": "GotoToday" + }, + { + "Code": "E8D2", + "Name": "Font" + }, + { + "Code": "E8D3", + "Name": "FontColor" + }, + { + "Code": "E8D4", + "Name": "Contact2" + }, + { + "Code": "E8D5", + "Name": "FolderFill" + }, + { + "Code": "E8D6", + "Name": "Audio" + }, + { + "Code": "E8D7", + "Name": "Permissions" + }, + { + "Code": "E8D8", + "Name": "DisableUpdates" + }, + { + "Code": "E8D9", + "Name": "Unfavorite" + }, + { + "Code": "E8DA", + "Name": "OpenLocal" + }, + { + "Code": "E8DB", + "Name": "Italic" + }, + { + "Code": "E8DC", + "Name": "Underline" + }, + { + "Code": "E8DD", + "Name": "Bold" + }, + { + "Code": "E8DE", + "Name": "MoveToFolder" + }, + { + "Code": "E8DF", + "Name": "LikeDislike" + }, + { + "Code": "E8E0", + "Name": "Dislike" + }, + { + "Code": "E8E1", + "Name": "Like" + }, + { + "Code": "E8E2", + "Name": "AlignRight" + }, + { + "Code": "E8E3", + "Name": "AlignCenter" + }, + { + "Code": "E8E4", + "Name": "AlignLeft" + }, + { + "Code": "E8E5", + "Name": "OpenFile" + }, + { + "Code": "E8E6", + "Name": "ClearSelection" + }, + { + "Code": "E8E7", + "Name": "FontDecrease" + }, + { + "Code": "E8E8", + "Name": "FontIncrease" + }, + { + "Code": "E8E9", + "Name": "FontSize" + }, + { + "Code": "E8EA", + "Name": "CellPhone" + }, + { + "Code": "E8EB", + "Name": "Reshare" + }, + { + "Code": "E8EC", + "Name": "Tag" + }, + { + "Code": "E8ED", + "Name": "RepeatOne" + }, + { + "Code": "E8EE", + "Name": "RepeatAll" + }, + { + "Code": "E8EF", + "Name": "Calculator" + }, + { + "Code": "E8F0", + "Name": "Directions" + }, + { + "Code": "E8F1", + "Name": "Library" + }, + { + "Code": "E8F2", + "Name": "ChatBubbles" + }, + { + "Code": "E8F3", + "Name": "PostUpdate" + }, + { + "Code": "E8F4", + "Name": "NewFolder" + }, + { + "Code": "E8F5", + "Name": "CalendarReply" + }, + { + "Code": "E8F6", + "Name": "UnsyncFolder" + }, + { + "Code": "E8F7", + "Name": "SyncFolder" + }, + { + "Code": "E8F8", + "Name": "BlockContact" + }, + { + "Code": "E8F9", + "Name": "SwitchApps" + }, + { + "Code": "E8FA", + "Name": "AddFriend" + }, + { + "Code": "E8FB", + "Name": "Accept" + }, + { + "Code": "E8FC", + "Name": "GoToStart" + }, + { + "Code": "E8FD", + "Name": "BulletedList" + }, + { + "Code": "E8FE", + "Name": "Scan" + }, + { + "Code": "E8FF", + "Name": "Preview" + }, + { + "Code": "E902", + "Name": "Group" + }, + { + "Code": "E904", + "Name": "ZeroBars" + }, + { + "Code": "E905", + "Name": "OneBar" + }, + { + "Code": "E906", + "Name": "TwoBars" + }, + { + "Code": "E907", + "Name": "ThreeBars" + }, + { + "Code": "E908", + "Name": "FourBars" + }, + { + "Code": "E909", + "Name": "World" + }, + { + "Code": "E90A", + "Name": "Comment" + }, + { + "Code": "E90B", + "Name": "MusicInfo" + }, + { + "Code": "E90C", + "Name": "DockLeft" + }, + { + "Code": "E90D", + "Name": "DockRight" + }, + { + "Code": "E90E", + "Name": "DockBottom" + }, + { + "Code": "E90F", + "Name": "Repair" + }, + { + "Code": "E910", + "Name": "Accounts" + }, + { + "Code": "E911", + "Name": "DullSound" + }, + { + "Code": "E912", + "Name": "Manage" + }, + { + "Code": "E913", + "Name": "Street" + }, + { + "Code": "E914", + "Name": "Printer3D" + }, + { + "Code": "E915", + "Name": "RadioBullet" + }, + { + "Code": "E916", + "Name": "Stopwatch" + }, + { + "Code": "E91B", + "Name": "Photo" + }, + { + "Code": "E91C", + "Name": "ActionCenter" + }, + { + "Code": "E91F", + "Name": "FullCircleMask" + }, + { + "Code": "E921", + "Name": "ChromeMinimize" + }, + { + "Code": "E922", + "Name": "ChromeMaximize" + }, + { + "Code": "E923", + "Name": "ChromeRestore" + }, + { + "Code": "E924", + "Name": "Annotation" + }, + { + "Code": "E925", + "Name": "BackSpaceQWERTYSm" + }, + { + "Code": "E926", + "Name": "BackSpaceQWERTYMd" + }, + { + "Code": "E927", + "Name": "Swipe" + }, + { + "Code": "E928", + "Name": "Fingerprint" + }, + { + "Code": "E929", + "Name": "Handwriting" + }, + { + "Code": "E92C", + "Name": "ChromeBackToWindow" + }, + { + "Code": "E92D", + "Name": "ChromeFullScreen" + }, + { + "Code": "E92E", + "Name": "KeyboardStandard" + }, + { + "Code": "E92F", + "Name": "KeyboardDismiss" + }, + { + "Code": "E930", + "Name": "Completed" + }, + { + "Code": "E931", + "Name": "ChromeAnnotate" + }, + { + "Code": "E932", + "Name": "Label" + }, + { + "Code": "E933", + "Name": "IBeam" + }, + { + "Code": "E934", + "Name": "IBeamOutline" + }, + { + "Code": "E935", + "Name": "FlickDown" + }, + { + "Code": "E936", + "Name": "FlickUp" + }, + { + "Code": "E937", + "Name": "FlickLeft" + }, + { + "Code": "E938", + "Name": "FlickRight" + }, + { + "Code": "E939", + "Name": "FeedbackApp" + }, + { + "Code": "E93C", + "Name": "MusicAlbum" + }, + { + "Code": "E93E", + "Name": "Streaming" + }, + { + "Code": "E943", + "Name": "Code" + }, + { + "Code": "E944", + "Name": "ReturnToWindow" + }, + { + "Code": "E945", + "Name": "LightningBolt" + }, + { + "Code": "E946", + "Name": "Info" + }, + { + "Code": "E947", + "Name": "CalculatorMultiply" + }, + { + "Code": "E948", + "Name": "CalculatorAddition" + }, + { + "Code": "E949", + "Name": "CalculatorSubtract" + }, + { + "Code": "E94A", + "Name": "CalculatorDivide" + }, + { + "Code": "E94B", + "Name": "CalculatorSquareroot" + }, + { + "Code": "E94C", + "Name": "CalculatorPercentage" + }, + { + "Code": "E94D", + "Name": "CalculatorNegate" + }, + { + "Code": "E94E", + "Name": "CalculatorEqualTo" + }, + { + "Code": "E94F", + "Name": "CalculatorBackspace" + }, + { + "Code": "E950", + "Name": "Component" + }, + { + "Code": "E951", + "Name": "DMC" + }, + { + "Code": "E952", + "Name": "Dock" + }, + { + "Code": "E953", + "Name": "MultimediaDMS" + }, + { + "Code": "E954", + "Name": "MultimediaDVR" + }, + { + "Code": "E955", + "Name": "MultimediaPMP" + }, + { + "Code": "E956", + "Name": "PrintfaxPrinterFile" + }, + { + "Code": "E957", + "Name": "Sensor" + }, + { + "Code": "E958", + "Name": "StorageOptical" + }, + { + "Code": "E95A", + "Name": "Communications" + }, + { + "Code": "E95B", + "Name": "Headset" + }, + { + "Code": "E95D", + "Name": "Projector" + }, + { + "Code": "E95E", + "Name": "Health" + }, + { + "Code": "E95F", + "Name": "Wire" + }, + { + "Code": "E960", + "Name": "Webcam2" + }, + { + "Code": "E961", + "Name": "Input" + }, + { + "Code": "E962", + "Name": "Mouse" + }, + { + "Code": "E963", + "Name": "Smartcard" + }, + { + "Code": "E964", + "Name": "SmartcardVirtual" + }, + { + "Code": "E965", + "Name": "MediaStorageTower" + }, + { + "Code": "E966", + "Name": "ReturnKeySm" + }, + { + "Code": "E967", + "Name": "GameConsole" + }, + { + "Code": "E968", + "Name": "Network" + }, + { + "Code": "E969", + "Name": "StorageNetworkWireless" + }, + { + "Code": "E96A", + "Name": "StorageTape" + }, + { + "Code": "E96D", + "Name": "ChevronUpSmall" + }, + { + "Code": "E96E", + "Name": "ChevronDownSmall" + }, + { + "Code": "E96F", + "Name": "ChevronLeftSmall" + }, + { + "Code": "E970", + "Name": "ChevronRightSmall" + }, + { + "Code": "E971", + "Name": "ChevronUpMed" + }, + { + "Code": "E972", + "Name": "ChevronDownMed" + }, + { + "Code": "E973", + "Name": "ChevronLeftMed" + }, + { + "Code": "E974", + "Name": "ChevronRightMed" + }, + { + "Code": "E975", + "Name": "Devices2" + }, + { + "Code": "E976", + "Name": "ExpandTile" + }, + { + "Code": "E977", + "Name": "PC1" + }, + { + "Code": "E978", + "Name": "PresenceChicklet" + }, + { + "Code": "E979", + "Name": "PresenceChickletVideo" + }, + { + "Code": "E97A", + "Name": "Reply" + }, + { + "Code": "E97B", + "Name": "SetTile" + }, + { + "Code": "E97C", + "Name": "Type" + }, + { + "Code": "E97D", + "Name": "Korean" + }, + { + "Code": "E97E", + "Name": "HalfAlpha" + }, + { + "Code": "E97F", + "Name": "FullAlpha" + }, + { + "Code": "E980", + "Name": "Key12On" + }, + { + "Code": "E981", + "Name": "ChineseChangjie" + }, + { + "Code": "E982", + "Name": "QWERTYOn" + }, + { + "Code": "E983", + "Name": "QWERTYOff" + }, + { + "Code": "E984", + "Name": "ChineseQuick" + }, + { + "Code": "E985", + "Name": "Japanese" + }, + { + "Code": "E986", + "Name": "FullHiragana" + }, + { + "Code": "E987", + "Name": "FullKatakana" + }, + { + "Code": "E988", + "Name": "HalfKatakana" + }, + { + "Code": "E989", + "Name": "ChineseBoPoMoFo" + }, + { + "Code": "E98A", + "Name": "ChinesePinyin" + }, + { + "Code": "E98F", + "Name": "ConstructionCone" + }, + { + "Code": "E990", + "Name": "XboxOneConsole" + }, + { + "Code": "E992", + "Name": "Volume0" + }, + { + "Code": "E993", + "Name": "Volume1" + }, + { + "Code": "E994", + "Name": "Volume2" + }, + { + "Code": "E995", + "Name": "Volume3" + }, + { + "Code": "E996", + "Name": "BatteryUnknown" + }, + { + "Code": "E998", + "Name": "WifiAttentionOverlay" + }, + { + "Code": "E99A", + "Name": "Robot" + }, + { + "Code": "E9A1", + "Name": "TapAndSend" + }, + { + "Code": "E9A6", + "Name": "FitPage" + }, + { + "Code": "E9A8", + "Name": "PasswordKeyShow" + }, + { + "Code": "E9A9", + "Name": "PasswordKeyHide" + }, + { + "Code": "E9AA", + "Name": "BidiLtr" + }, + { + "Code": "E9AB", + "Name": "BidiRtl" + }, + { + "Code": "E9AC", + "Name": "ForwardSm" + }, + { + "Code": "E9AD", + "Name": "CommaKey" + }, + { + "Code": "E9AE", + "Name": "DashKey" + }, + { + "Code": "E9AF", + "Name": "DullSoundKey" + }, + { + "Code": "E9B0", + "Name": "HalfDullSound" + }, + { + "Code": "E9B1", + "Name": "RightDoubleQuote" + }, + { + "Code": "E9B2", + "Name": "LeftDoubleQuote" + }, + { + "Code": "E9B3", + "Name": "PuncKeyRightBottom" + }, + { + "Code": "E9B4", + "Name": "PuncKey1" + }, + { + "Code": "E9B5", + "Name": "PuncKey2" + }, + { + "Code": "E9B6", + "Name": "PuncKey3" + }, + { + "Code": "E9B7", + "Name": "PuncKey4" + }, + { + "Code": "E9B8", + "Name": "PuncKey5" + }, + { + "Code": "E9B9", + "Name": "PuncKey6" + }, + { + "Code": "E9BA", + "Name": "PuncKey9" + }, + { + "Code": "E9BB", + "Name": "PuncKey7" + }, + { + "Code": "E9BC", + "Name": "PuncKey8" + }, + { + "Code": "E9CA", + "Name": "Frigid" + }, + { + "Code": "E9CE", + "Name": "Unknown" + }, + { + "Code": "E9D2", + "Name": "AreaChart" + }, + { + "Code": "E9D5", + "Name": "CheckList" + }, + { + "Code": "E9D9", + "Name": "Diagnostic" + }, + { + "Code": "E9E9", + "Name": "Equalizer" + }, + { + "Code": "E9F3", + "Name": "Process" + }, + { + "Code": "E9F5", + "Name": "Processing" + }, + { + "Code": "E9F9", + "Name": "ReportDocument" + }, + { + "Code": "EA0C", + "Name": "VideoSolid" + }, + { + "Code": "EA0D", + "Name": "MixedMediaBadge" + }, + { + "Code": "EA14", + "Name": "DisconnectDisplay" + }, + { + "Code": "EA18", + "Name": "Shield" + }, + { + "Code": "EA1F", + "Name": "Info2" + }, + { + "Code": "EA21", + "Name": "ActionCenterAsterisk" + }, + { + "Code": "EA24", + "Name": "Beta" + }, + { + "Code": "EA35", + "Name": "SaveCopy" + }, + { + "Code": "EA37", + "Name": "List" + }, + { + "Code": "EA38", + "Name": "Asterisk" + }, + { + "Code": "EA39", + "Name": "ErrorBadge" + }, + { + "Code": "EA3A", + "Name": "CircleRing" + }, + { + "Code": "EA3B", + "Name": "CircleFill" + }, + { + "Code": "EA3C", + "Name": "MergeCall" + }, + { + "Code": "EA3D", + "Name": "PrivateCall" + }, + { + "Code": "EA3F", + "Name": "Record2" + }, + { + "Code": "EA40", + "Name": "AllAppsMirrored" + }, + { + "Code": "EA41", + "Name": "BookmarksMirrored" + }, + { + "Code": "EA42", + "Name": "BulletedListMirrored" + }, + { + "Code": "EA43", + "Name": "CallForwardInternationalMirrored" + }, + { + "Code": "EA44", + "Name": "CallForwardRoamingMirrored" + }, + { + "Code": "EA47", + "Name": "ChromeBackMirrored" + }, + { + "Code": "EA48", + "Name": "ClearSelectionMirrored" + }, + { + "Code": "EA49", + "Name": "ClosePaneMirrored" + }, + { + "Code": "EA4A", + "Name": "ContactInfoMirrored" + }, + { + "Code": "EA4B", + "Name": "DockRightMirrored" + }, + { + "Code": "EA4C", + "Name": "DockLeftMirrored" + }, + { + "Code": "EA4E", + "Name": "ExpandTileMirrored" + }, + { + "Code": "EA4F", + "Name": "GoMirrored" + }, + { + "Code": "EA50", + "Name": "GripperResizeMirrored" + }, + { + "Code": "EA51", + "Name": "HelpMirrored" + }, + { + "Code": "EA52", + "Name": "ImportMirrored" + }, + { + "Code": "EA53", + "Name": "ImportAllMirrored" + }, + { + "Code": "EA54", + "Name": "LeaveChatMirrored" + }, + { + "Code": "EA55", + "Name": "ListMirrored" + }, + { + "Code": "EA56", + "Name": "MailForwardMirrored" + }, + { + "Code": "EA57", + "Name": "MailReplyMirrored" + }, + { + "Code": "EA58", + "Name": "MailReplyAllMirrored" + }, + { + "Code": "EA5B", + "Name": "OpenPaneMirrored" + }, + { + "Code": "EA5C", + "Name": "OpenWithMirrored" + }, + { + "Code": "EA5E", + "Name": "ParkingLocationMirrored" + }, + { + "Code": "EA5F", + "Name": "ResizeMouseMediumMirrored" + }, + { + "Code": "EA60", + "Name": "ResizeMouseSmallMirrored" + }, + { + "Code": "EA61", + "Name": "ResizeMouseTallMirrored" + }, + { + "Code": "EA62", + "Name": "ResizeTouchNarrowerMirrored" + }, + { + "Code": "EA63", + "Name": "SendMirrored" + }, + { + "Code": "EA64", + "Name": "SendFillMirrored" + }, + { + "Code": "EA65", + "Name": "ShowResultsMirrored" + }, + { + "Code": "EA69", + "Name": "Media" + }, + { + "Code": "EA6A", + "Name": "SyncError" + }, + { + "Code": "EA6C", + "Name": "Devices3" + }, + { + "Code": "EA79", + "Name": "SlowMotionOn" + }, + { + "Code": "EA80", + "Name": "Lightbulb" + }, + { + "Code": "EA81", + "Name": "StatusCircle" + }, + { + "Code": "EA82", + "Name": "StatusTriangle" + }, + { + "Code": "EA83", + "Name": "StatusError" + }, + { + "Code": "EA84", + "Name": "StatusWarning" + }, + { + "Code": "EA86", + "Name": "Puzzle" + }, + { + "Code": "EA89", + "Name": "CalendarSolid" + }, + { + "Code": "EA8A", + "Name": "HomeSolid" + }, + { + "Code": "EA8B", + "Name": "ParkingLocationSolid" + }, + { + "Code": "EA8C", + "Name": "ContactSolid" + }, + { + "Code": "EA8D", + "Name": "ConstructionSolid" + }, + { + "Code": "EA8E", + "Name": "AccidentSolid" + }, + { + "Code": "EA8F", + "Name": "Ringer" + }, + { + "Code": "EA90", + "Name": "PDF" + }, + { + "Code": "EA91", + "Name": "ThoughtBubble" + }, + { + "Code": "EA92", + "Name": "HeartBroken" + }, + { + "Code": "EA93", + "Name": "BatteryCharging10" + }, + { + "Code": "EA94", + "Name": "BatterySaver9" + }, + { + "Code": "EA95", + "Name": "BatterySaver10" + }, + { + "Code": "EA97", + "Name": "CallForwardingMirrored" + }, + { + "Code": "EA98", + "Name": "MultiSelectMirrored" + }, + { + "Code": "EA99", + "Name": "Broom" + }, + { + "Code": "EAC2", + "Name": "ForwardCall" + }, + { + "Code": "EADF", + "Name": "Trackers" + }, + { + "Code": "EAFC", + "Name": "Market" + }, + { + "Code": "EB05", + "Name": "PieSingle" + }, + { + "Code": "EB0F", + "Name": "StockUp" + }, + { + "Code": "EB11", + "Name": "StockDown" + }, + { + "Code": "EB3C", + "Name": "Design" + }, + { + "Code": "EB41", + "Name": "Website" + }, + { + "Code": "EB42", + "Name": "Drop" + }, + { + "Code": "EB44", + "Name": "Radar" + }, + { + "Code": "EB47", + "Name": "BusSolid" + }, + { + "Code": "EB48", + "Name": "FerrySolid" + }, + { + "Code": "EB49", + "Name": "StartPointSolid" + }, + { + "Code": "EB4A", + "Name": "StopPointSolid" + }, + { + "Code": "EB4B", + "Name": "EndPointSolid" + }, + { + "Code": "EB4C", + "Name": "AirplaneSolid" + }, + { + "Code": "EB4D", + "Name": "TrainSolid" + }, + { + "Code": "EB4E", + "Name": "WorkSolid" + }, + { + "Code": "EB4F", + "Name": "ReminderFill" + }, + { + "Code": "EB50", + "Name": "Reminder" + }, + { + "Code": "EB51", + "Name": "Heart" + }, + { + "Code": "EB52", + "Name": "HeartFill" + }, + { + "Code": "EB55", + "Name": "EthernetError" + }, + { + "Code": "EB56", + "Name": "EthernetWarning" + }, + { + "Code": "EB57", + "Name": "StatusConnecting1" + }, + { + "Code": "EB58", + "Name": "StatusConnecting2" + }, + { + "Code": "EB59", + "Name": "StatusUnsecure" + }, + { + "Code": "EB5A", + "Name": "WifiError0" + }, + { + "Code": "EB5B", + "Name": "WifiError1" + }, + { + "Code": "EB5C", + "Name": "WifiError2" + }, + { + "Code": "EB5D", + "Name": "WifiError3" + }, + { + "Code": "EB5E", + "Name": "WifiError4" + }, + { + "Code": "EB5F", + "Name": "WifiWarning0" + }, + { + "Code": "EB60", + "Name": "WifiWarning1" + }, + { + "Code": "EB61", + "Name": "WifiWarning2" + }, + { + "Code": "EB62", + "Name": "WifiWarning3" + }, + { + "Code": "EB63", + "Name": "WifiWarning4" + }, + { + "Code": "EB66", + "Name": "Devices4" + }, + { + "Code": "EB67", + "Name": "NUIIris" + }, + { + "Code": "EB68", + "Name": "NUIFace" + }, + { + "Code": "EB77", + "Name": "GatewayRouter" + }, + { + "Code": "EB7E", + "Name": "EditMirrored" + }, + { + "Code": "EB82", + "Name": "NUIFPStartSlideHand" + }, + { + "Code": "EB83", + "Name": "NUIFPStartSlideAction" + }, + { + "Code": "EB84", + "Name": "NUIFPContinueSlideHand" + }, + { + "Code": "EB85", + "Name": "NUIFPContinueSlideAction" + }, + { + "Code": "EB86", + "Name": "NUIFPRollRightHand" + }, + { + "Code": "EB87", + "Name": "NUIFPRollRightHandAction" + }, + { + "Code": "EB88", + "Name": "NUIFPRollLeftHand" + }, + { + "Code": "EB89", + "Name": "NUIFPRollLeftAction" + }, + { + "Code": "EB8A", + "Name": "NUIFPPressHand" + }, + { + "Code": "EB8B", + "Name": "NUIFPPressAction" + }, + { + "Code": "EB8C", + "Name": "NUIFPPressRepeatHand" + }, + { + "Code": "EB8D", + "Name": "NUIFPPressRepeatAction" + }, + { + "Code": "EB90", + "Name": "StatusErrorFull" + }, + { + "Code": "EB91", + "Name": "TaskViewExpanded" + }, + { + "Code": "EB95", + "Name": "Certificate" + }, + { + "Code": "EB96", + "Name": "BackSpaceQWERTYLg" + }, + { + "Code": "EB97", + "Name": "ReturnKeyLg" + }, + { + "Code": "EB9D", + "Name": "FastForward" + }, + { + "Code": "EB9E", + "Name": "Rewind" + }, + { + "Code": "EB9F", + "Name": "Photo2" + }, + { + "Code": "EBA0", + "Name": "MobBattery0" + }, + { + "Code": "EBA1", + "Name": "MobBattery1" + }, + { + "Code": "EBA2", + "Name": "MobBattery2" + }, + { + "Code": "EBA3", + "Name": "MobBattery3" + }, + { + "Code": "EBA4", + "Name": "MobBattery4" + }, + { + "Code": "EBA5", + "Name": "MobBattery5" + }, + { + "Code": "EBA6", + "Name": "MobBattery6" + }, + { + "Code": "EBA7", + "Name": "MobBattery7" + }, + { + "Code": "EBA8", + "Name": "MobBattery8" + }, + { + "Code": "EBA9", + "Name": "MobBattery9" + }, + { + "Code": "EBAA", + "Name": "MobBattery10" + }, + { + "Code": "EBAB", + "Name": "MobBatteryCharging0" + }, + { + "Code": "EBAC", + "Name": "MobBatteryCharging1" + }, + { + "Code": "EBAD", + "Name": "MobBatteryCharging2" + }, + { + "Code": "EBAE", + "Name": "MobBatteryCharging3" + }, + { + "Code": "EBAF", + "Name": "MobBatteryCharging4" + }, + { + "Code": "EBB0", + "Name": "MobBatteryCharging5" + }, + { + "Code": "EBB1", + "Name": "MobBatteryCharging6" + }, + { + "Code": "EBB2", + "Name": "MobBatteryCharging7" + }, + { + "Code": "EBB3", + "Name": "MobBatteryCharging8" + }, + { + "Code": "EBB4", + "Name": "MobBatteryCharging9" + }, + { + "Code": "EBB5", + "Name": "MobBatteryCharging10" + }, + { + "Code": "EBB6", + "Name": "MobBatterySaver0" + }, + { + "Code": "EBB7", + "Name": "MobBatterySaver1" + }, + { + "Code": "EBB8", + "Name": "MobBatterySaver2" + }, + { + "Code": "EBB9", + "Name": "MobBatterySaver3" + }, + { + "Code": "EBBA", + "Name": "MobBatterySaver4" + }, + { + "Code": "EBBB", + "Name": "MobBatterySaver5" + }, + { + "Code": "EBBC", + "Name": "MobBatterySaver6" + }, + { + "Code": "EBBD", + "Name": "MobBatterySaver7" + }, + { + "Code": "EBBE", + "Name": "MobBatterySaver8" + }, + { + "Code": "EBBF", + "Name": "MobBatterySaver9" + }, + { + "Code": "EBC0", + "Name": "MobBatterySaver10" + }, + { + "Code": "EBC3", + "Name": "DictionaryCloud" + }, + { + "Code": "EBC4", + "Name": "ResetDrive" + }, + { + "Code": "EBC5", + "Name": "VolumeBars" + }, + { + "Code": "EBC6", + "Name": "Project" + }, + { + "Code": "EBD2", + "Name": "AdjustHologram" + }, + { + "Code": "EBD3", + "Name": "CloudDownload" + }, + { + "Code": "EBD4", + "Name": "MobWifiCallBars" + }, + { + "Code": "EBD5", + "Name": "MobWifiCall0" + }, + { + "Code": "EBD6", + "Name": "MobWifiCall1" + }, + { + "Code": "EBD7", + "Name": "MobWifiCall2" + }, + { + "Code": "EBD8", + "Name": "MobWifiCall3" + }, + { + "Code": "EBD9", + "Name": "MobWifiCall4" + }, + { + "Code": "EBDA", + "Name": "Family" + }, + { + "Code": "EBDB", + "Name": "LockFeedback" + }, + { + "Code": "EBDE", + "Name": "DeviceDiscovery" + }, + { + "Code": "EBE6", + "Name": "WindDirection" + }, + { + "Code": "EBE7", + "Name": "RightArrowKeyTime0" + }, + { + "Code": "EBE8", + "Name": "Bug" + }, + { + "Code": "EBFC", + "Name": "TabletMode" + }, + { + "Code": "EBFD", + "Name": "StatusCircleLeft" + }, + { + "Code": "EBFE", + "Name": "StatusTriangleLeft" + }, + { + "Code": "EBFF", + "Name": "StatusErrorLeft" + }, + { + "Code": "EC00", + "Name": "StatusWarningLeft" + }, + { + "Code": "EC02", + "Name": "MobBatteryUnknown" + }, + { + "Code": "EC05", + "Name": "NetworkTower" + }, + { + "Code": "EC06", + "Name": "CityNext" + }, + { + "Code": "EC07", + "Name": "CityNext2" + }, + { + "Code": "EC08", + "Name": "Courthouse" + }, + { + "Code": "EC09", + "Name": "Groceries" + }, + { + "Code": "EC0A", + "Name": "Sustainable" + }, + { + "Code": "EC0B", + "Name": "BuildingEnergy" + }, + { + "Code": "EC11", + "Name": "ToggleFilled" + }, + { + "Code": "EC12", + "Name": "ToggleBorder" + }, + { + "Code": "EC13", + "Name": "SliderThumb" + }, + { + "Code": "EC14", + "Name": "ToggleThumb" + }, + { + "Code": "EC15", + "Name": "MiracastLogoSmall" + }, + { + "Code": "EC16", + "Name": "MiracastLogoLarge" + }, + { + "Code": "EC19", + "Name": "PLAP" + }, + { + "Code": "EC1B", + "Name": "Badge" + }, + { + "Code": "EC1E", + "Name": "SignalRoaming" + }, + { + "Code": "EC20", + "Name": "MobileLocked" + }, + { + "Code": "EC24", + "Name": "InsiderHubApp" + }, + { + "Code": "EC25", + "Name": "PersonalFolder" + }, + { + "Code": "EC26", + "Name": "HomeGroup" + }, + { + "Code": "EC27", + "Name": "MyNetwork" + }, + { + "Code": "EC31", + "Name": "KeyboardFull" + }, + { + "Code": "EC32", + "Name": "Cafe" + }, + { + "Code": "EC37", + "Name": "MobSignal1" + }, + { + "Code": "EC38", + "Name": "MobSignal2" + }, + { + "Code": "EC39", + "Name": "MobSignal3" + }, + { + "Code": "EC3A", + "Name": "MobSignal4" + }, + { + "Code": "EC3B", + "Name": "MobSignal5" + }, + { + "Code": "EC3C", + "Name": "MobWifi1" + }, + { + "Code": "EC3D", + "Name": "MobWifi2" + }, + { + "Code": "EC3E", + "Name": "MobWifi3" + }, + { + "Code": "EC3F", + "Name": "MobWifi4" + }, + { + "Code": "EC40", + "Name": "MobAirplane" + }, + { + "Code": "EC41", + "Name": "MobBluetooth" + }, + { + "Code": "EC42", + "Name": "MobActionCenter" + }, + { + "Code": "EC43", + "Name": "MobLocation" + }, + { + "Code": "EC44", + "Name": "MobWifiHotspot" + }, + { + "Code": "EC45", + "Name": "LanguageJpn" + }, + { + "Code": "EC46", + "Name": "MobQuietHours" + }, + { + "Code": "EC47", + "Name": "MobDrivingMode" + }, + { + "Code": "EC48", + "Name": "SpeedOff" + }, + { + "Code": "EC49", + "Name": "SpeedMedium" + }, + { + "Code": "EC4A", + "Name": "SpeedHigh" + }, + { + "Code": "EC4E", + "Name": "ThisPC" + }, + { + "Code": "EC4F", + "Name": "MusicNote" + }, + { + "Code": "EC50", + "Name": "FileExplorer" + }, + { + "Code": "EC51", + "Name": "FileExplorerApp" + }, + { + "Code": "EC52", + "Name": "LeftArrowKeyTime0" + }, + { + "Code": "EC54", + "Name": "MicOff" + }, + { + "Code": "EC55", + "Name": "MicSleep" + }, + { + "Code": "EC56", + "Name": "MicError" + }, + { + "Code": "EC57", + "Name": "PlaybackRate1x" + }, + { + "Code": "EC58", + "Name": "PlaybackRateOther" + }, + { + "Code": "EC59", + "Name": "CashDrawer" + }, + { + "Code": "EC5A", + "Name": "BarcodeScanner" + }, + { + "Code": "EC5B", + "Name": "ReceiptPrinter" + }, + { + "Code": "EC5C", + "Name": "MagStripeReader" + }, + { + "Code": "EC61", + "Name": "CompletedSolid" + }, + { + "Code": "EC64", + "Name": "CompanionApp" + }, + { + "Code": "EC6C", + "Name": "Favicon2" + }, + { + "Code": "EC6D", + "Name": "SwipeRevealArt" + }, + { + "Code": "EC71", + "Name": "MicOn" + }, + { + "Code": "EC72", + "Name": "MicClipping" + }, + { + "Code": "EC74", + "Name": "TabletSelected" + }, + { + "Code": "EC75", + "Name": "MobileSelected" + }, + { + "Code": "EC76", + "Name": "LaptopSelected" + }, + { + "Code": "EC77", + "Name": "TVMonitorSelected" + }, + { + "Code": "EC7A", + "Name": "DeveloperTools" + }, + { + "Code": "EC7E", + "Name": "MobCallForwarding" + }, + { + "Code": "EC7F", + "Name": "MobCallForwardingMirrored" + }, + { + "Code": "EC80", + "Name": "BodyCam" + }, + { + "Code": "EC81", + "Name": "PoliceCar" + }, + { + "Code": "EC87", + "Name": "Draw" + }, + { + "Code": "EC88", + "Name": "DrawSolid" + }, + { + "Code": "EC8A", + "Name": "LowerBrightness" + }, + { + "Code": "EC8F", + "Name": "ScrollUpDown" + }, + { + "Code": "EC92", + "Name": "DateTime" + }, + { + "Code": "EC94", + "Name": "HoloLens" + }, + { + "Code": "ECA5", + "Name": "Tiles" + }, + { + "Code": "ECA7", + "Name": "PartyLeader" + }, + { + "Code": "ECAA", + "Name": "AppIconDefault" + }, + { + "Code": "ECAD", + "Name": "Calories" + }, + { + "Code": "ECAF", + "Name": "POI" + }, + { + "Code": "ECB9", + "Name": "BandBattery0" + }, + { + "Code": "ECBA", + "Name": "BandBattery1" + }, + { + "Code": "ECBB", + "Name": "BandBattery2" + }, + { + "Code": "ECBC", + "Name": "BandBattery3" + }, + { + "Code": "ECBD", + "Name": "BandBattery4" + }, + { + "Code": "ECBE", + "Name": "BandBattery5" + }, + { + "Code": "ECBF", + "Name": "BandBattery6" + }, + { + "Code": "ECC4", + "Name": "AddSurfaceHub" + }, + { + "Code": "ECC5", + "Name": "DevUpdate" + }, + { + "Code": "ECC6", + "Name": "Unit" + }, + { + "Code": "ECC8", + "Name": "AddTo" + }, + { + "Code": "ECC9", + "Name": "RemoveFrom" + }, + { + "Code": "ECCA", + "Name": "RadioBtnOff" + }, + { + "Code": "ECCB", + "Name": "RadioBtnOn" + }, + { + "Code": "ECCC", + "Name": "RadioBullet2" + }, + { + "Code": "ECCD", + "Name": "ExploreContent" + }, + { + "Code": "ECE4", + "Name": "Blocked2" + }, + { + "Code": "ECE7", + "Name": "ScrollMode" + }, + { + "Code": "ECE8", + "Name": "ZoomMode" + }, + { + "Code": "ECE9", + "Name": "PanMode" + }, + { + "Code": "ECF0", + "Name": "WiredUSB" + }, + { + "Code": "ECF1", + "Name": "WirelessUSB" + }, + { + "Code": "ECF3", + "Name": "USBSafeConnect" + }, + { + "Code": "ED0C", + "Name": "ActionCenterNotificationMirrored" + }, + { + "Code": "ED0D", + "Name": "ActionCenterMirrored" + }, + { + "Code": "ED0E", + "Name": "SubscriptionAdd" + }, + { + "Code": "ED10", + "Name": "ResetDevice" + }, + { + "Code": "ED11", + "Name": "SubscriptionAddMirrored" + }, + { + "Code": "ED14", + "Name": "QRCode" + }, + { + "Code": "ED15", + "Name": "Feedback" + }, + { + "Code": "ED1A", + "Name": "Hide" + }, + { + "Code": "ED1E", + "Name": "Subtitles" + }, + { + "Code": "ED1F", + "Name": "SubtitlesAudio" + }, + { + "Code": "ED25", + "Name": "OpenFolderHorizontal" + }, + { + "Code": "ED28", + "Name": "CalendarMirrored" + }, + { + "Code": "ED2A", + "Name": "MobeSIM" + }, + { + "Code": "ED2B", + "Name": "MobeSIMNoProfile" + }, + { + "Code": "ED2C", + "Name": "MobeSIMLocked" + }, + { + "Code": "ED2D", + "Name": "MobeSIMBusy" + }, + { + "Code": "ED2E", + "Name": "SignalError" + }, + { + "Code": "ED2F", + "Name": "StreamingEnterprise" + }, + { + "Code": "ED30", + "Name": "Headphone0" + }, + { + "Code": "ED31", + "Name": "Headphone1" + }, + { + "Code": "ED32", + "Name": "Headphone2" + }, + { + "Code": "ED33", + "Name": "Headphone3" + }, + { + "Code": "ED35", + "Name": "Apps" + }, + { + "Code": "ED39", + "Name": "KeyboardBrightness" + }, + { + "Code": "ED3A", + "Name": "KeyboardLowerBrightness" + }, + { + "Code": "ED3C", + "Name": "SkipBack10" + }, + { + "Code": "ED3D", + "Name": "SkipForward30" + }, + { + "Code": "ED41", + "Name": "TreeFolderFolder" + }, + { + "Code": "ED42", + "Name": "TreeFolderFolderFill" + }, + { + "Code": "ED43", + "Name": "TreeFolderFolderOpen" + }, + { + "Code": "ED44", + "Name": "TreeFolderFolderOpenFill" + }, + { + "Code": "ED47", + "Name": "MultimediaDMP" + }, + { + "Code": "ED4C", + "Name": "KeyboardOneHanded" + }, + { + "Code": "ED4D", + "Name": "Narrator" + }, + { + "Code": "ED53", + "Name": "EmojiTabPeople" + }, + { + "Code": "ED54", + "Name": "EmojiTabSmilesAnimals" + }, + { + "Code": "ED55", + "Name": "EmojiTabCelebrationObjects" + }, + { + "Code": "ED56", + "Name": "EmojiTabFoodPlants" + }, + { + "Code": "ED57", + "Name": "EmojiTabTransitPlaces" + }, + { + "Code": "ED58", + "Name": "EmojiTabSymbols" + }, + { + "Code": "ED59", + "Name": "EmojiTabTextSmiles" + }, + { + "Code": "ED5A", + "Name": "EmojiTabFavorites" + }, + { + "Code": "ED5B", + "Name": "EmojiSwatch" + }, + { + "Code": "ED5C", + "Name": "ConnectApp" + }, + { + "Code": "ED5D", + "Name": "CompanionDeviceFramework" + }, + { + "Code": "ED5E", + "Name": "Ruler" + }, + { + "Code": "ED5F", + "Name": "FingerInking" + }, + { + "Code": "ED60", + "Name": "StrokeErase" + }, + { + "Code": "ED61", + "Name": "PointErase" + }, + { + "Code": "ED62", + "Name": "ClearAllInk" + }, + { + "Code": "ED63", + "Name": "Pencil" + }, + { + "Code": "ED64", + "Name": "Marker" + }, + { + "Code": "ED65", + "Name": "InkingCaret" + }, + { + "Code": "ED66", + "Name": "InkingColorOutline" + }, + { + "Code": "ED67", + "Name": "InkingColorFill" + }, + { + "Code": "EDA2", + "Name": "HardDrive" + }, + { + "Code": "EDA3", + "Name": "NetworkAdapter" + }, + { + "Code": "EDA4", + "Name": "Touchscreen" + }, + { + "Code": "EDA5", + "Name": "NetworkPrinter" + }, + { + "Code": "EDA6", + "Name": "CloudPrinter" + }, + { + "Code": "EDA7", + "Name": "KeyboardShortcut" + }, + { + "Code": "EDA8", + "Name": "BrushSize" + }, + { + "Code": "EDA9", + "Name": "NarratorForward" + }, + { + "Code": "EDAA", + "Name": "NarratorForwardMirrored" + }, + { + "Code": "EDAB", + "Name": "SyncBadge12" + }, + { + "Code": "EDAC", + "Name": "RingerBadge12" + }, + { + "Code": "EDAD", + "Name": "AsteriskBadge12" + }, + { + "Code": "EDAE", + "Name": "ErrorBadge12" + }, + { + "Code": "EDAF", + "Name": "CircleRingBadge12" + }, + { + "Code": "EDB0", + "Name": "CircleFillBadge12" + }, + { + "Code": "EDB1", + "Name": "ImportantBadge12" + }, + { + "Code": "EDB3", + "Name": "MailBadge12" + }, + { + "Code": "EDB4", + "Name": "PauseBadge12" + }, + { + "Code": "EDB5", + "Name": "PlayBadge12" + }, + { + "Code": "EDC6", + "Name": "PenWorkspace" + }, + { + "Code": "EDD5", + "Name": "CaretLeft8" + }, + { + "Code": "EDD6", + "Name": "CaretRight8" + }, + { + "Code": "EDD7", + "Name": "CaretUp8" + }, + { + "Code": "EDD8", + "Name": "CaretDown8" + }, + { + "Code": "EDD9", + "Name": "CaretLeftSolid8" + }, + { + "Code": "EDDA", + "Name": "CaretRightSolid8" + }, + { + "Code": "EDDB", + "Name": "CaretUpSolid8" + }, + { + "Code": "EDDC", + "Name": "CaretDownSolid8" + }, + { + "Code": "EDE0", + "Name": "Strikethrough" + }, + { + "Code": "EDE1", + "Name": "Export" + }, + { + "Code": "EDE2", + "Name": "ExportMirrored" + }, + { + "Code": "EDE3", + "Name": "ButtonMenu" + }, + { + "Code": "EDE4", + "Name": "CloudSearch" + }, + { + "Code": "EDE5", + "Name": "PinyinIMELogo" + }, + { + "Code": "EDFB", + "Name": "CalligraphyPen" + }, + { + "Code": "EE35", + "Name": "ReplyMirrored" + }, + { + "Code": "EE3F", + "Name": "LockscreenDesktop" + }, + { + "Code": "EE40", + "Name": "TaskViewSettings" + }, + { + "Code": "EE47", + "Name": "MiniExpand2Mirrored" + }, + { + "Code": "EE49", + "Name": "MiniContract2Mirrored" + }, + { + "Code": "EE4A", + "Name": "Play36" + }, + { + "Code": "EE56", + "Name": "PenPalette" + }, + { + "Code": "EE57", + "Name": "GuestUser" + }, + { + "Code": "EE63", + "Name": "SettingsBattery" + }, + { + "Code": "EE64", + "Name": "TaskbarPhone" + }, + { + "Code": "EE65", + "Name": "LockScreenGlance" + }, + { + "Code": "EE6F", + "Name": "GenericScan" + }, + { + "Code": "EE71", + "Name": "ImageExport" + }, + { + "Code": "EE77", + "Name": "WifiEthernet" + }, + { + "Code": "EE79", + "Name": "ActionCenterQuiet" + }, + { + "Code": "EE7A", + "Name": "ActionCenterQuietNotification" + }, + { + "Code": "EE92", + "Name": "TrackersMirrored" + }, + { + "Code": "EE93", + "Name": "DateTimeMirrored" + }, + { + "Code": "EE94", + "Name": "Wheel" + }, + { + "Code": "EEA3", + "Name": "VirtualMachineGroup" + }, + { + "Code": "EECA", + "Name": "ButtonView2" + }, + { + "Code": "EF15", + "Name": "PenWorkspaceMirrored" + }, + { + "Code": "EF16", + "Name": "PenPaletteMirrored" + }, + { + "Code": "EF17", + "Name": "StrokeEraseMirrored" + }, + { + "Code": "EF18", + "Name": "PointEraseMirrored" + }, + { + "Code": "EF19", + "Name": "ClearAllInkMirrored" + }, + { + "Code": "EF1F", + "Name": "BackgroundToggle" + }, + { + "Code": "EF20", + "Name": "Marquee" + }, + { + "Code": "EF2C", + "Name": "ChromeCloseContrast" + }, + { + "Code": "EF2D", + "Name": "ChromeMinimizeContrast" + }, + { + "Code": "EF2E", + "Name": "ChromeMaximizeContrast" + }, + { + "Code": "EF2F", + "Name": "ChromeRestoreContrast" + }, + { + "Code": "EF31", + "Name": "TrafficLight" + }, + { + "Code": "EF3B", + "Name": "Replay" + }, + { + "Code": "EF3C", + "Name": "Eyedropper" + }, + { + "Code": "EF3D", + "Name": "LineDisplay" + }, + { + "Code": "EF3E", + "Name": "PINPad" + }, + { + "Code": "EF3F", + "Name": "SignatureCapture" + }, + { + "Code": "EF40", + "Name": "ChipCardCreditCardReader" + }, + { + "Code": "EF42", + "Name": "MarketDown" + }, + { + "Code": "EF58", + "Name": "PlayerSettings" + }, + { + "Code": "EF6B", + "Name": "LandscapeOrientation" + }, + { + "Code": "EF90", + "Name": "Flow" + }, + { + "Code": "EFA5", + "Name": "Touchpad" + }, + { + "Code": "EFA9", + "Name": "Speech" + }, + { + "Code": "F000", + "Name": "KnowledgeArticle" + }, + { + "Code": "F003", + "Name": "Relationship" + }, + { + "Code": "F012", + "Name": "ZipFolder" + }, + { + "Code": "F080", + "Name": "DefaultAPN" + }, + { + "Code": "F081", + "Name": "UserAPN" + }, + { + "Code": "F085", + "Name": "DoublePinyin" + }, + { + "Code": "F08C", + "Name": "BlueLight" + }, + { + "Code": "F08D", + "Name": "CaretSolidLeft" + }, + { + "Code": "F08E", + "Name": "CaretSolidDown" + }, + { + "Code": "F08F", + "Name": "CaretSolidRight" + }, + { + "Code": "F090", + "Name": "CaretSolidUp" + }, + { + "Code": "F093", + "Name": "ButtonA" + }, + { + "Code": "F094", + "Name": "ButtonB" + }, + { + "Code": "F095", + "Name": "ButtonY" + }, + { + "Code": "F096", + "Name": "ButtonX" + }, + { + "Code": "F0AD", + "Name": "ArrowUp8" + }, + { + "Code": "F0AE", + "Name": "ArrowDown8" + }, + { + "Code": "F0AF", + "Name": "ArrowRight8" + }, + { + "Code": "F0B0", + "Name": "ArrowLeft8" + }, + { + "Code": "F0B2", + "Name": "QuarentinedItems" + }, + { + "Code": "F0B3", + "Name": "QuarentinedItemsMirrored" + }, + { + "Code": "F0B4", + "Name": "Protractor" + }, + { + "Code": "F0B5", + "Name": "ChecklistMirrored" + }, + { + "Code": "F0B6", + "Name": "StatusCircle7" + }, + { + "Code": "F0B7", + "Name": "StatusCheckmark7" + }, + { + "Code": "F0B8", + "Name": "StatusErrorCircle7" + }, + { + "Code": "F0B9", + "Name": "Connected" + }, + { + "Code": "F0C6", + "Name": "PencilFill" + }, + { + "Code": "F0C7", + "Name": "CalligraphyFill" + }, + { + "Code": "F0CA", + "Name": "QuarterStarLeft" + }, + { + "Code": "F0CB", + "Name": "QuarterStarRight" + }, + { + "Code": "F0CC", + "Name": "ThreeQuarterStarLeft" + }, + { + "Code": "F0CD", + "Name": "ThreeQuarterStarRight" + }, + { + "Code": "F0CE", + "Name": "QuietHoursBadge12" + }, + { + "Code": "F0D2", + "Name": "BackMirrored" + }, + { + "Code": "F0D3", + "Name": "ForwardMirrored" + }, + { + "Code": "F0D5", + "Name": "ChromeBackContrast" + }, + { + "Code": "F0D6", + "Name": "ChromeBackContrastMirrored" + }, + { + "Code": "F0D7", + "Name": "ChromeBackToWindowContrast" + }, + { + "Code": "F0D8", + "Name": "ChromeFullScreenContrast" + }, + { + "Code": "F0E2", + "Name": "GridView" + }, + { + "Code": "F0E3", + "Name": "ClipboardList" + }, + { + "Code": "F0E4", + "Name": "ClipboardListMirrored" + }, + { + "Code": "F0E5", + "Name": "OutlineQuarterStarLeft" + }, + { + "Code": "F0E6", + "Name": "OutlineQuarterStarRight" + }, + { + "Code": "F0E7", + "Name": "OutlineHalfStarLeft" + }, + { + "Code": "F0E8", + "Name": "OutlineHalfStarRight" + }, + { + "Code": "F0E9", + "Name": "OutlineThreeQuarterStarLeft" + }, + { + "Code": "F0EA", + "Name": "OutlineThreeQuarterStarRight" + }, + { + "Code": "F0EB", + "Name": "SpatialVolume0" + }, + { + "Code": "F0EC", + "Name": "SpatialVolume1" + }, + { + "Code": "F0ED", + "Name": "SpatialVolume2" + }, + { + "Code": "F0EE", + "Name": "SpatialVolume3" + }, + { + "Code": "F0EF", + "Name": "ApplicationGuard" + }, + { + "Code": "F0F7", + "Name": "OutlineStarLeftHalf" + }, + { + "Code": "F0F8", + "Name": "OutlineStarRightHalf" + }, + { + "Code": "F0F9", + "Name": "ChromeAnnotateContrast" + }, + { + "Code": "F0FB", + "Name": "DefenderBadge12" + }, + { + "Code": "F103", + "Name": "DetachablePC" + }, + { + "Code": "F108", + "Name": "LeftStick" + }, + { + "Code": "F109", + "Name": "RightStick" + }, + { + "Code": "F10A", + "Name": "TriggerLeft" + }, + { + "Code": "F10B", + "Name": "TriggerRight" + }, + { + "Code": "F10C", + "Name": "BumperLeft" + }, + { + "Code": "F10D", + "Name": "BumperRight" + }, + { + "Code": "F10E", + "Name": "Dpad" + }, + { + "Code": "F110", + "Name": "EnglishPunctuation" + }, + { + "Code": "F111", + "Name": "ChinesePunctuation" + }, + { + "Code": "F119", + "Name": "HMD" + }, + { + "Code": "F11B", + "Name": "CtrlSpatialRight" + }, + { + "Code": "F126", + "Name": "PaginationDotOutline10" + }, + { + "Code": "F127", + "Name": "PaginationDotSolid10" + }, + { + "Code": "F128", + "Name": "StrokeErase2" + }, + { + "Code": "F129", + "Name": "SmallErase" + }, + { + "Code": "F12A", + "Name": "LargeErase" + }, + { + "Code": "F12B", + "Name": "FolderHorizontal" + }, + { + "Code": "F12E", + "Name": "MicrophoneListening" + }, + { + "Code": "F12F", + "Name": "StatusExclamationCircle7" + }, + { + "Code": "F131", + "Name": "Video360" + }, + { + "Code": "F133", + "Name": "GiftboxOpen" + }, + { + "Code": "F136", + "Name": "StatusCircleOuter" + }, + { + "Code": "F137", + "Name": "StatusCircleInner" + }, + { + "Code": "F138", + "Name": "StatusCircleRing" + }, + { + "Code": "F139", + "Name": "StatusTriangleOuter" + }, + { + "Code": "F13A", + "Name": "StatusTriangleInner" + }, + { + "Code": "F13B", + "Name": "StatusTriangleExclamation" + }, + { + "Code": "F13C", + "Name": "StatusCircleExclamation" + }, + { + "Code": "F13D", + "Name": "StatusCircleErrorX" + }, + { + "Code": "F13E", + "Name": "StatusCircleCheckmark" + }, + { + "Code": "F13F", + "Name": "StatusCircleInfo" + }, + { + "Code": "F140", + "Name": "StatusCircleBlock" + }, + { + "Code": "F141", + "Name": "StatusCircleBlock2" + }, + { + "Code": "F142", + "Name": "StatusCircleQuestionMark" + }, + { + "Code": "F143", + "Name": "StatusCircleSync" + }, + { + "Code": "F146", + "Name": "Dial1" + }, + { + "Code": "F147", + "Name": "Dial2" + }, + { + "Code": "F148", + "Name": "Dial3" + }, + { + "Code": "F149", + "Name": "Dial4" + }, + { + "Code": "F14A", + "Name": "Dial5" + }, + { + "Code": "F14B", + "Name": "Dial6" + }, + { + "Code": "F14C", + "Name": "Dial7" + }, + { + "Code": "F14D", + "Name": "Dial8" + }, + { + "Code": "F14E", + "Name": "Dial9" + }, + { + "Code": "F14F", + "Name": "Dial10" + }, + { + "Code": "F150", + "Name": "Dial11" + }, + { + "Code": "F151", + "Name": "Dial12" + }, + { + "Code": "F152", + "Name": "Dial13" + }, + { + "Code": "F153", + "Name": "Dial14" + }, + { + "Code": "F154", + "Name": "Dial15" + }, + { + "Code": "F155", + "Name": "Dial16" + }, + { + "Code": "F156", + "Name": "DialShape1" + }, + { + "Code": "F157", + "Name": "DialShape2" + }, + { + "Code": "F158", + "Name": "DialShape3" + }, + { + "Code": "F159", + "Name": "DialShape4" + }, + { + "Code": "F15F", + "Name": "ClosedCaptionsInternational" + }, + { + "Code": "F161", + "Name": "TollSolid" + }, + { + "Code": "F163", + "Name": "TrafficCongestionSolid" + }, + { + "Code": "F164", + "Name": "ExploreContentSingle" + }, + { + "Code": "F165", + "Name": "CollapseContent" + }, + { + "Code": "F166", + "Name": "CollapseContentSingle" + }, + { + "Code": "F167", + "Name": "InfoSolid" + }, + { + "Code": "F168", + "Name": "GroupList" + }, + { + "Code": "F169", + "Name": "CaretBottomRightSolidCenter8" + }, + { + "Code": "F16A", + "Name": "ProgressRingDots" + }, + { + "Code": "F16B", + "Name": "Checkbox14" + }, + { + "Code": "F16C", + "Name": "CheckboxComposite14" + }, + { + "Code": "F16D", + "Name": "CheckboxIndeterminateCombo14" + }, + { + "Code": "F16E", + "Name": "CheckboxIndeterminateCombo" + }, + { + "Code": "F175", + "Name": "StatusPause7" + }, + { + "Code": "F17F", + "Name": "CharacterAppearance" + }, + { + "Code": "F180", + "Name": "Lexicon" + }, + { + "Code": "F182", + "Name": "ScreenTime" + }, + { + "Code": "F191", + "Name": "HeadlessDevice" + }, + { + "Code": "F193", + "Name": "NetworkSharing" + }, + { + "Code": "F19D", + "Name": "EyeGaze" + }, + { + "Code": "F19E", + "Name": "ToggleLeft" + }, + { + "Code": "F19F", + "Name": "ToggleRight" + }, + { + "Code": "F1AD", + "Name": "WindowsInsider" + }, + { + "Code": "F1CB", + "Name": "ChromeSwitch" + }, + { + "Code": "F1CC", + "Name": "ChromeSwitchContast" + }, + { + "Code": "F1D8", + "Name": "StatusCheckmark" + }, + { + "Code": "F1D9", + "Name": "StatusCheckmarkLeft" + }, + { + "Code": "F20C", + "Name": "KeyboardLeftAligned" + }, + { + "Code": "F20D", + "Name": "KeyboardRightAligned" + }, + { + "Code": "F210", + "Name": "KeyboardSettings" + }, + { + "Code": "F211", + "Name": "NetworkPhysical" + }, + { + "Code": "F22C", + "Name": "IOT" + }, + { + "Code": "F22E", + "Name": "UnknownMirrored" + }, + { + "Code": "F246", + "Name": "ViewDashboard" + }, + { + "Code": "F259", + "Name": "ExploitProtectionSettings" + }, + { + "Code": "F260", + "Name": "KeyboardNarrow" + }, + { + "Code": "F261", + "Name": "Keyboard12Key" + }, + { + "Code": "F26B", + "Name": "KeyboardDock" + }, + { + "Code": "F26C", + "Name": "KeyboardUndock" + }, + { + "Code": "F26D", + "Name": "KeyboardLeftDock" + }, + { + "Code": "F26E", + "Name": "KeyboardRightDock" + }, + { + "Code": "F270", + "Name": "Ear" + }, + { + "Code": "F271", + "Name": "PointerHand" + }, + { + "Code": "F272", + "Name": "Bullseye" + }, + { + "Code": "F28B", + "Name": "DocumentApproval" + }, + { + "Code": "F2B7", + "Name": "LocaleLanguage" + }, + { + "Code": "F32A", + "Name": "PassiveAuthentication" + }, + { + "Code": "F354", + "Name": "ColorSolid" + }, + { + "Code": "F384", + "Name": "NetworkOffline" + }, + { + "Code": "F385", + "Name": "NetworkConnected" + }, + { + "Code": "F386", + "Name": "NetworkConnectedCheckmark" + }, + { + "Code": "F3B1", + "Name": "SignOut" + }, + { + "Code": "F3CC", + "Name": "StatusInfo" + }, + { + "Code": "F3CD", + "Name": "StatusInfoLeft" + }, + { + "Code": "F3E2", + "Name": "NearbySharing" + }, + { + "Code": "F3E7", + "Name": "CtrlSpatialLeft" + }, + { + "Code": "F404", + "Name": "InteractiveDashboard" + }, + { + "Code": "F405", + "Name": "DeclineCall" + }, + { + "Code": "F406", + "Name": "ClippingTool" + }, + { + "Code": "F407", + "Name": "RectangularClipping" + }, + { + "Code": "F408", + "Name": "FreeFormClipping" + }, + { + "Code": "F413", + "Name": "CopyTo" + }, + { + "Code": "F427", + "Name": "IDBadge" + }, + { + "Code": "F439", + "Name": "DynamicLock" + }, + { + "Code": "F45E", + "Name": "PenTips" + }, + { + "Code": "F45F", + "Name": "PenTipsMirrored" + }, + { + "Code": "F460", + "Name": "HWPJoin" + }, + { + "Code": "F461", + "Name": "HWPInsert" + }, + { + "Code": "F462", + "Name": "HWPStrikeThrough" + }, + { + "Code": "F463", + "Name": "HWPScratchOut" + }, + { + "Code": "F464", + "Name": "HWPSplit" + }, + { + "Code": "F465", + "Name": "HWPNewLine" + }, + { + "Code": "F466", + "Name": "HWPOverwrite" + }, + { + "Code": "F473", + "Name": "MobWifiWarning1" + }, + { + "Code": "F474", + "Name": "MobWifiWarning2" + }, + { + "Code": "F475", + "Name": "MobWifiWarning3" + }, + { + "Code": "F476", + "Name": "MobWifiWarning4" + }, + { + "Code": "F47F", + "Name": "MicLocationCombo" + }, + { + "Code": "F49A", + "Name": "Globe2" + }, + { + "Code": "F4A5", + "Name": "SpecialEffectSize" + }, + { + "Code": "F4A9", + "Name": "GIF" + }, + { + "Code": "F4AA", + "Name": "Sticker2" + }, + { + "Code": "F4BE", + "Name": "SurfaceHubSelected" + }, + { + "Code": "F4BF", + "Name": "HoloLensSelected" + }, + { + "Code": "F4C0", + "Name": "Earbud" + }, + { + "Code": "F4C3", + "Name": "MixVolumes" + }, + { + "Code": "F540", + "Name": "Safe" + }, + { + "Code": "F552", + "Name": "LaptopSecure" + }, + { + "Code": "F56D", + "Name": "PrintDefault" + }, + { + "Code": "F56E", + "Name": "PageMirrored" + }, + { + "Code": "F56F", + "Name": "LandscapeOrientationMirrored" + }, + { + "Code": "F570", + "Name": "ColorOff" + }, + { + "Code": "F571", + "Name": "PrintAllPages" + }, + { + "Code": "F572", + "Name": "PrintCustomRange" + }, + { + "Code": "F573", + "Name": "PageMarginPortraitNarrow" + }, + { + "Code": "F574", + "Name": "PageMarginPortraitNormal" + }, + { + "Code": "F575", + "Name": "PageMarginPortraitModerate" + }, + { + "Code": "F576", + "Name": "PageMarginPortraitWide" + }, + { + "Code": "F577", + "Name": "PageMarginLandscapeNarrow" + }, + { + "Code": "F578", + "Name": "PageMarginLandscapeNormal" + }, + { + "Code": "F579", + "Name": "PageMarginLandscapeModerate" + }, + { + "Code": "F57A", + "Name": "PageMarginLandscapeWide" + }, + { + "Code": "F57B", + "Name": "CollateLandscape" + }, + { + "Code": "F57C", + "Name": "CollatePortrait" + }, + { + "Code": "F57D", + "Name": "CollatePortraitSeparated" + }, + { + "Code": "F57E", + "Name": "DuplexLandscapeOneSided" + }, + { + "Code": "F57F", + "Name": "DuplexLandscapeOneSidedMirrored" + }, + { + "Code": "F580", + "Name": "DuplexLandscapeTwoSidedLongEdge" + }, + { + "Code": "F581", + "Name": "DuplexLandscapeTwoSidedLongEdgeMirrored" + }, + { + "Code": "F582", + "Name": "DuplexLandscapeTwoSidedShortEdge" + }, + { + "Code": "F583", + "Name": "DuplexLandscapeTwoSidedShortEdgeMirrored" + }, + { + "Code": "F584", + "Name": "DuplexPortraitOneSided" + }, + { + "Code": "F585", + "Name": "DuplexPortraitOneSidedMirrored" + }, + { + "Code": "F586", + "Name": "DuplexPortraitTwoSidedLongEdge" + }, + { + "Code": "F587", + "Name": "DuplexPortraitTwoSidedLongEdgeMirrored" + }, + { + "Code": "F588", + "Name": "DuplexPortraitTwoSidedShortEdge" + }, + { + "Code": "F589", + "Name": "DuplexPortraitTwoSidedShortEdgeMirrored" + }, + { + "Code": "F58A", + "Name": "PPSOneLandscape" + }, + { + "Code": "F58B", + "Name": "PPSTwoLandscape" + }, + { + "Code": "F58C", + "Name": "PPSTwoPortrait" + }, + { + "Code": "F58D", + "Name": "PPSFourLandscape" + }, + { + "Code": "F58E", + "Name": "PPSFourPortrait" + }, + { + "Code": "F58F", + "Name": "HolePunchOff" + }, + { + "Code": "F590", + "Name": "HolePunchPortraitLeft" + }, + { + "Code": "F591", + "Name": "HolePunchPortraitRight" + }, + { + "Code": "F592", + "Name": "HolePunchPortraitTop" + }, + { + "Code": "F593", + "Name": "HolePunchPortraitBottom" + }, + { + "Code": "F594", + "Name": "HolePunchLandscapeLeft" + }, + { + "Code": "F595", + "Name": "HolePunchLandscapeRight" + }, + { + "Code": "F596", + "Name": "HolePunchLandscapeTop" + }, + { + "Code": "F597", + "Name": "HolePunchLandscapeBottom" + }, + { + "Code": "F598", + "Name": "StaplingOff" + }, + { + "Code": "F599", + "Name": "StaplingPortraitTopLeft" + }, + { + "Code": "F59A", + "Name": "StaplingPortraitTopRight" + }, + { + "Code": "F59B", + "Name": "StaplingPortraitBottomRight" + }, + { + "Code": "F59C", + "Name": "StaplingPortraitTwoLeft" + }, + { + "Code": "F59D", + "Name": "StaplingPortraitTwoRight" + }, + { + "Code": "F59E", + "Name": "StaplingPortraitTwoTop" + }, + { + "Code": "F59F", + "Name": "StaplingPortraitTwoBottom" + }, + { + "Code": "F5A0", + "Name": "StaplingPortraitBookBinding" + }, + { + "Code": "F5A1", + "Name": "StaplingLandscapeTopLeft" + }, + { + "Code": "F5A2", + "Name": "StaplingLandscapeTopRight" + }, + { + "Code": "F5A3", + "Name": "StaplingLandscapeBottomLeft" + }, + { + "Code": "F5A4", + "Name": "StaplingLandscapeBottomRight" + }, + { + "Code": "F5A5", + "Name": "StaplingLandscapeTwoLeft" + }, + { + "Code": "F5A6", + "Name": "StaplingLandscapeTwoRight" + }, + { + "Code": "F5A7", + "Name": "StaplingLandscapeTwoTop" + }, + { + "Code": "F5A8", + "Name": "StaplingLandscapeTwoBottom" + }, + { + "Code": "F5A9", + "Name": "StaplingLandscapeBookBinding" + }, + { + "Code": "F5AA", + "Name": "StatusDataTransferRoaming" + }, + { + "Code": "F5AB", + "Name": "MobSIMError" + }, + { + "Code": "F5AC", + "Name": "CollateLandscapeSeparated" + }, + { + "Code": "F5AD", + "Name": "PPSOnePortrait" + }, + { + "Code": "F5AE", + "Name": "StaplingPortraitBottomLeft" + }, + { + "Code": "F5B0", + "Name": "PlaySolid" + }, + { + "Code": "F5E7", + "Name": "RepeatOff" + }, + { + "Code": "F5ED", + "Name": "Set" + }, + { + "Code": "F5EE", + "Name": "SetSolid" + }, + { + "Code": "F5EF", + "Name": "FuzzyReading" + }, + { + "Code": "F5F2", + "Name": "VerticalBattery0" + }, + { + "Code": "F5F3", + "Name": "VerticalBattery1" + }, + { + "Code": "F5F4", + "Name": "VerticalBattery2" + }, + { + "Code": "F5F5", + "Name": "VerticalBattery3" + }, + { + "Code": "F5F6", + "Name": "VerticalBattery4" + }, + { + "Code": "F5F7", + "Name": "VerticalBattery5" + }, + { + "Code": "F5F8", + "Name": "VerticalBattery6" + }, + { + "Code": "F5F9", + "Name": "VerticalBattery7" + }, + { + "Code": "F5FA", + "Name": "VerticalBattery8" + }, + { + "Code": "F5FB", + "Name": "VerticalBattery9" + }, + { + "Code": "F5FC", + "Name": "VerticalBattery10" + }, + { + "Code": "F5FD", + "Name": "VerticalBatteryCharging0" + }, + { + "Code": "F5FE", + "Name": "VerticalBatteryCharging1" + }, + { + "Code": "F5FF", + "Name": "VerticalBatteryCharging2" + }, + { + "Code": "F600", + "Name": "VerticalBatteryCharging3" + }, + { + "Code": "F601", + "Name": "VerticalBatteryCharging4" + }, + { + "Code": "F602", + "Name": "VerticalBatteryCharging5" + }, + { + "Code": "F603", + "Name": "VerticalBatteryCharging6" + }, + { + "Code": "F604", + "Name": "VerticalBatteryCharging7" + }, + { + "Code": "F605", + "Name": "VerticalBatteryCharging8" + }, + { + "Code": "F606", + "Name": "VerticalBatteryCharging9" + }, + { + "Code": "F607", + "Name": "VerticalBatteryCharging10" + }, + { + "Code": "F608", + "Name": "VerticalBatteryUnknown" + }, + { + "Code": "F618", + "Name": "SIMError" + }, + { + "Code": "F619", + "Name": "SIMMissing" + }, + { + "Code": "F61A", + "Name": "SIMLock" + }, + { + "Code": "F61B", + "Name": "eSIM" + }, + { + "Code": "F61C", + "Name": "eSIMNoProfile" + }, + { + "Code": "F61D", + "Name": "eSIMLocked" + }, + { + "Code": "F61E", + "Name": "eSIMBusy" + }, + { + "Code": "F61F", + "Name": "NoiseCancelation" + }, + { + "Code": "F620", + "Name": "NoiseCancelationOff" + }, + { + "Code": "F623", + "Name": "MusicSharing" + }, + { + "Code": "F624", + "Name": "MusicSharingOff" + }, + { + "Code": "F63C", + "Name": "CircleShapeSolid" + }, + { + "Code": "F657", + "Name": "WifiCallBars" + }, + { + "Code": "F658", + "Name": "WifiCall0" + }, + { + "Code": "F659", + "Name": "WifiCall1" + }, + { + "Code": "F65A", + "Name": "WifiCall2" + }, + { + "Code": "F65B", + "Name": "WifiCall3" + }, + { + "Code": "F65C", + "Name": "WifiCall4" + }, + { + "Code": "F69E", + "Name": "CHTLanguageBar" + }, + { + "Code": "F6A9", + "Name": "ComposeMode" + }, + { + "Code": "F6B8", + "Name": "ExpressiveInputEntry" + }, + { + "Code": "F6BA", + "Name": "EmojiTabMoreSymbols" + }, + { + "Code": "F6FA", + "Name": "WebSearch" + }, + { + "Code": "F712", + "Name": "Kiosk" + }, + { + "Code": "F714", + "Name": "RTTLogo" + }, + { + "Code": "F715", + "Name": "VoiceCall" + }, + { + "Code": "F716", + "Name": "GoToMessage" + }, + { + "Code": "F71A", + "Name": "ReturnToCall" + }, + { + "Code": "F71C", + "Name": "StartPresenting" + }, + { + "Code": "F71D", + "Name": "StopPresenting" + }, + { + "Code": "F71E", + "Name": "ProductivityMode" + }, + { + "Code": "F738", + "Name": "SetHistoryStatus" + }, + { + "Code": "F739", + "Name": "SetHistoryStatus2" + }, + { + "Code": "F73D", + "Name": "Keyboardsettings20" + }, + { + "Code": "F73E", + "Name": "OneHandedRight20" + }, + { + "Code": "F73F", + "Name": "OneHandedLeft20" + }, + { + "Code": "F740", + "Name": "Split20" + }, + { + "Code": "F741", + "Name": "Full20" + }, + { + "Code": "F742", + "Name": "Handwriting20" + }, + { + "Code": "F743", + "Name": "ChevronLeft20" + }, + { + "Code": "F744", + "Name": "ChevronLeft32" + }, + { + "Code": "F745", + "Name": "ChevronRight20" + }, + { + "Code": "F746", + "Name": "ChevronRight32" + }, + { + "Code": "F763", + "Name": "Event12" + }, + { + "Code": "F781", + "Name": "MicOff2" + }, + { + "Code": "F785", + "Name": "DeliveryOptimization" + }, + { + "Code": "F78A", + "Name": "CancelMedium" + }, + { + "Code": "F78B", + "Name": "SearchMedium" + }, + { + "Code": "F78C", + "Name": "AcceptMedium" + }, + { + "Code": "F78D", + "Name": "RevealPasswordMedium" + }, + { + "Code": "F7AD", + "Name": "DeleteWord" + }, + { + "Code": "F7AE", + "Name": "DeleteWordFill" + }, + { + "Code": "F7AF", + "Name": "DeleteLines" + }, + { + "Code": "F7B0", + "Name": "DeleteLinesFill" + }, + { + "Code": "F7B1", + "Name": "InstertWords" + }, + { + "Code": "F7B2", + "Name": "InstertWordsFill" + }, + { + "Code": "F7B3", + "Name": "JoinWords" + }, + { + "Code": "F7B4", + "Name": "JoinWordsFill" + }, + { + "Code": "F7B5", + "Name": "OverwriteWords" + }, + { + "Code": "F7B6", + "Name": "OverwriteWordsFill" + }, + { + "Code": "F7B7", + "Name": "AddNewLine" + }, + { + "Code": "F7B8", + "Name": "AddNewLineFill" + }, + { + "Code": "F7B9", + "Name": "OverwriteWordsKorean" + }, + { + "Code": "F7BA", + "Name": "OverwriteWordsFillKorean" + }, + { + "Code": "F7BB", + "Name": "EducationIcon" + }, + { + "Code": "F7ED", + "Name": "WindowSnipping" + }, + { + "Code": "F7EE", + "Name": "VideoCapture" + }, + { + "Code": "F809", + "Name": "StatusSecured" + }, + { + "Code": "F83B", + "Name": "NarratorApp" + }, + { + "Code": "F83D", + "Name": "PowerButtonUpdate" + }, + { + "Code": "F83E", + "Name": "RestartUpdate" + }, + { + "Code": "F83F", + "Name": "UpdateStatusDot" + }, + { + "Code": "F847", + "Name": "Eject" + }, + { + "Code": "F87B", + "Name": "Spelling" + }, + { + "Code": "F87C", + "Name": "SpellingKorean" + }, + { + "Code": "F87D", + "Name": "SpellingSerbian" + }, + { + "Code": "F87E", + "Name": "SpellingChinese" + }, + { + "Code": "F89A", + "Name": "FolderSelect" + }, + { + "Code": "F8A5", + "Name": "SmartScreen" + }, + { + "Code": "F8A6", + "Name": "ExploitProtection" + }, + { + "Code": "F8AA", + "Name": "AddBold" + }, + { + "Code": "F8AB", + "Name": "SubtractBold" + }, + { + "Code": "F8AC", + "Name": "BackSolidBold" + }, + { + "Code": "F8AD", + "Name": "ForwardSolidBold" + }, + { + "Code": "F8AE", + "Name": "PauseBold" + }, + { + "Code": "F8AF", + "Name": "ClickSolid" + }, + { + "Code": "F8B0", + "Name": "SettingsSolid" + }, + { + "Code": "F8B1", + "Name": "MicrophoneSolidBold" + }, + { + "Code": "F8B2", + "Name": "SpeechSolidBold" + }, + { + "Code": "F8B3", + "Name": "ClickedOutLoudSolidBold" + } +] \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/Models/SymbolIconData.cs b/source/RevitLookup.UI.Playground/Client/Models/SymbolIconData.cs new file mode 100644 index 00000000..3c54bf25 --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/Models/SymbolIconData.cs @@ -0,0 +1,14 @@ +using Wpf.Ui.Controls; + +namespace RevitLookup.UI.Playground.Client.Models; + +/// +/// IconData class for icons in icon page +/// +public sealed class SymbolIconData +{ + public required string Name { get; init; } + public required SymbolRegular Icon { get; init; } + public required string Code { get; init; } + public string TextGlyph => $"&#x{Code};"; +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/Resources/Images/ProductIcon.png b/source/RevitLookup.UI.Playground/Client/Resources/Images/ProductIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..8e012ed13dbd2ebab3f05d0f8d91fe023a286888 GIT binary patch literal 24234 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 Rsn!4h002ovPDHLkV1lo1aIgRX literal 0 HcmV?d00001 diff --git a/source/RevitLookup.UI.Playground/Client/Resources/Images/ProductLogo.png b/source/RevitLookup.UI.Playground/Client/Resources/Images/ProductLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..7236de89ecddb4a3e399a30aa12aff7089be0776 GIT binary patch literal 75424 zcmV)SK(fDyP)kLRWR3o}4u!jdDgvNQfj6L=s?v zjWJ+jf=zHD7>v0Fj7>1+0tTCKNhXUVfdENJD2Gv)ku;;pGd-O{v^8Fwy@Uno8vdfZ;oGl$p6zP@SEc|$N$ZT-hE{J*La(y zuTbtBDYn{!Uw$Z(~PPLlk)zc|YbVL71DoF{HJDTD>GI7SHZOaDe- zvDP4@LJ9%KV1z&bh72qSsmQFw2nj*}f((NaAcRLI6~^-j1&9o~!a~+TB{7-s(OE*A zqy$k!xmZTnE@>uFLZL)TY6QkwLQi7inE7T410Vz$8A?br5`#rpNUR}H(9uQOMo{u4 zCQFb>7pr|Tq4451p7tP!Xt!HP6=1C)%Q{G@C`JL_yKeh)@7aC$inXKFy8|OT)oP`2 z$|-BE85t>0cjE+M1v<`H=vqpVk7wIt+Q;e$VJym zNDLT*l!`1fWLDA=2`y_8O5+)ewJ}CV#A$}WqO~AUnlwxCO-9u0;1@zNKcwBNF*`fO z_*92;w{0f!EpcL)nV-R!%sp2ji3$~@lsqb;F$R>vW`@95B-W6ohIOMubgiWmXKo(+ zHy--G_0xZI{KpP!NWB7DDFO^xCK(#4J{aG(_d`Fr=fI2C3|6T2}7Uxmnh(9D3kSUUdEmvvZ5I=3B&BiWCy5G)8Iq z%Oz^vqpP3y<-2|W-o!IkmA-YwnX7(y(TOX5ync9~0gwUniJ{dJh~L=DuRQ*rHxO$D z+FAwzNx84g{6d|{PE7D?{k1?SNgzakltV)JMhUS+39($lln^2YymFu#w@=J2-ZRx& zHP&D76ebBJS;0?d%f8+_q-AZS==1hZ-Se@J-#YQ)6N~bm7hJODpRYJ~?YHhcFt>c$ ziL3T3ABx)6S{j)~dl7Ng!4rnq3OuE$L>`L^GhA`{%Aee|a_E*X-hK4qzq@PCYrpxv zAHSAT(0a=0D}VBYO+!~-dg_|1wrv{vR+Vt#{U{?gFX;-i{K@6x(V}hT_zS5mtDWBap8)A z@V=FO=_^hh?YsDlD^BKpSDeh96Z5?0b2k^R`IkGMa*a)%LO&lm@8o57J^h@G|Ld~H zp8mPhPgph!l6sP{&@sP(HGk&#f5t$pwJ0U176PhKMAszjm|R?b^`5EA-n!%9Gub)! zG_o#Ve94(l94ZWwDxX3j1j76i470Vx(j)sPPk!*g?C_zvIdjDs>;LGKwacE6wmNxy zpW#wshn^W1W8rn1y#ECki9fjG01cb5KJxL6B>@(U9X`_T4o}rPjD?D)p590IoI&D_ zl{?yLwDuX>*ngz9;**CKU(BJ!7vJ9Pd~YTCf!Rmqs?Rug*(aXy_>=D(f9Sxn@9duZ zaxDwlPz+c48>`Abu}PV27_#3u72y8?1IdiV_av(;1-e%8;MC&EtM~1_;>~yMeHM=# zy_irbRxjgSSDwWy&sz7%;n4pf7)ZL@bzuDD+xE?zf9Svt$k>9@Z zqJLb!VrVu4W){1onFaNK;>rErafn@qkNg^M`|smH>dkpfyG3qRgH{-Sr@eee$s?mY5XjD!!)d-hw9Ob+XLu^|;NPnS^tlJ`KHIP#0=XX4y>Wsz!##$l?LMd*Z ztaH`g8Fn^W)RFXS4Y&k9HI^>U+qJQ@E$H0n+SY=BOj-Jb#1KYbnCegsBCP`(>vpsX|SL*uaj0IVLguYAt= zpMK5nJ?$MQZ(4cK0(%!*EMys4d;ibzApZvqB*Tk5w3p%w!{TCxtkXrRUuGZ{tif9Q z>lp}c1z$7R$NR2-@EL#li639ZiK}?=xtnVYZ5HEHg+F}t zZ~f)jC#~GmGF=X|ELmz1zcG-XJ$~Lm>dhACZ##jJYK2bs-)|s^F{~H}I6OVK>39F& zt*`o{S3UoOkGtTkgL88WKmYC4A_OQQer*q8ttB%SPr_ZZt*0DrbMYz*DZ zV3U;8SUT2ukRIBUK>oWV!Qx>tRg;tl=RMM|)Hu|Zc9NVD|+ z^9J%C&OwBbkYzMmO^Am5Q0$*3C*Gs^o1VM)07GxqmhSS3h47Zf;3~X-JumH zbgV%kC>VPI1!MARoT)1kTvD0zS1J77eT$gLVk> zGcGp~`Mt9l#2NI3cq3a$HlGajfhWG|GdrI6s@27NzIguF8=kXm==wx@>|98(=GUJy z|NnUWhYUmtL7HV8nVIE`4XY8>VvGT6e=Sjil#+7j^Nnv_d+AGFaM54?&0Amnm;1-Z zX}7z`<8lyd!IR*5%0*be!aA(ABw2=(QdPs?cS?c&lThg`#>6CzPM#K6mvdS{EEJLq zNci&I`*`H0Juq5%|5q$HWog8Si+1KbY(QL8Uis__=R0s-E zqJ_W;5L%Ncjb|;91>Zo^SY}eg8etgoJ#KAgFw=sWCbSJR^#p1iW(^<}(?Q6-VgOa; z!lHo!D~8LguJ)m{Vy4@n)oRnu48C=!nYLgs6s%i>>U}jUY-ihij2Fg|zsZ+(9Eb!;5J{_0EC{{9m- zj_jIDQ>L3S_Wz@a{C_CKI{n1WG+Qk?NyfR`Hge+1LArpn;ECfBRhOrd*`HSfX#q2f z3q*m(;$nlvMw7_bm@e5U|8qf)a{y82(Pt_ut`5xfYv|TD!y; z8zHbbuVE0mH?oADfs*H^GQ78tq+FfQ^ErhWo0t&`?^C@9+SD8PH zu7be`kZ{`pxN{%0f&MZy>M(mS54jXzKq&O?oup5C0zC5t(3pc%z~KhOO)fa2|BD}b z!p7I0xvV;SBr#02JCv;XO^5{+r4%yGxc8C$sESj#+OWj@OpW!PN9bv+^6GLE7PZ4>NKni>*(E_A!*N_-PY+<7mT&PukVG@)}?rmzR$0?0g%xp7Z z|9qPrQ!O4iw7~X<4zpwDLHNnN)S@!AGd9A;L0CJ;@)ZL#eMz>^Vk%BSSjtwCSW8D3 z_dQ_>^(OcV9=itC4Z+QOVAmuRi!inhj_!lFlLHNqc|IIE3Z@I)6gnA9C^p3gT zI_rVkp7&>uU-gE!KVi#1jP^zBsWr$_Ls1Ee1`yDS~=xbD}o9B{gOLHKH)u5zGP!gU+`W|6tyZ&j7>eMEQ{dil zxP33wG8kG3GY4IwkHr=$U=*|sgax1uji$5sbpufAs`q~Fkq^E1?#Y*5^UTdJe)5(T z_t9786Ib1E+4ciR&wT4kFMoHnRLZ*DCgngAXEDbI;zEFwf@Y&ZyW8b>hv54j_uqGT zOCj{trH?&p$5gY)!a{@pooSDH?Hk_sYrQe>J!a}1_8*>PYt6eBV}!t*u%^6BrgdechQj}2lR*o2Z$YcvrUq?9B{iYFD?6G$bn)?$T& zYI{Z^g!5FHu_&ozC5qnGABA5J6enj{M%J6j2!ypx4{O0w8X5RxRxsUcuxDn5-ILQC znOh)969xjGu~NuDK@)kJ$Ww%&rWp8C0*|3$z-TdId8xo?Ib^6DG8p-kd=Fm>w3G-T zF$gj%$xv7{NDxvWg+NHB+Y(ZugnJFC@Q_F)Kq&VfA+bnOqv;w*lZ;NiORLernv9`x z#M-sXxbniyoOkAO80+Ky`3C%CC)}_Hj?^JeVX%ZVqTA(a+tq?^O?WT7y@fwESv6 zNk?Axr~mx9ukPA&&Py&l>GR*e?!iri)pD?I#c)GN!R-73LgdfXWF$#KR4C?1pF{iB zu3pYJzkTgffAd{muJ!_p^%k94i;}0f?D5;U?D41Y z?n9IO_})W&<(30{_j?b(-`oQytc9m+gU76eu|b$$fKCFa9L~+XNoHWK2BiR=b~3Ce z!`H5ZiCGvbJHx7!;YZgx7dkoyWBtxc&DEgWai6!c1g#QZ{?_5we)+aTkH7uZXMe>$ z^vT(`fAZ?5G*Wf@_-yO6Gamov-rL{&jJLn-)z5k7$Z&-t6Eg&!hcOnKbWL%qTT6|#2LlDkS!;T zky(q^H^4Rb?BKD1W<`IQR#ZlNnh$;8OFZj2mvY*P>sU-;s>Y+}3wDoBAhMLelcXkb zSh_L!s$em~qVg~$%?yF3i)7euBg|44 zkE?>0ui(9PsplQ@3G3c{Y=!oG{`{lYaQFU2-f+d~T(E5g<*GB5q&>?*t&K%cDMti_ z3J|kv*I~Z=lZSc7SMMXcb_c8+gBM-^k6jN%1=EX=WbT|zZt?~fMrjNE6_}6VOFx8L z?uEV(N+Hx1U||tfZ-dnvVdFCAb)_V?0IL>qf;OE{J-VK zFMp0F{MJ*h|Joy zEfiYh#vlap*e5T!aFL@rLWm_k##rleAuQIqurTZSF`F;c*3utIZa&!HAMc)^V=Y?b z5=fa+@g>W`fWeZ_P^1{`ix};T7%La)ul6&!u)q`F^m*>TW;>id#*5G0z;iCx$`jAo zOtDx68?(4DOS@wUG*qf(q*tcVnB^PSKge4?b2Ep&z5_O{ftNl8&RyZcs+l_UuC$j# z+x+)x$ua)d-2>nJF|^t+P=R&>)@*|QbucvtRS&kUhOrT7b)eM(;e+;|k;2XiSYx>6 zHD~kWtsD9Jw{GM)&wVc>b)NNmmwff>pZv=!_fJpIZg)XSOqw8sWX0G@Lf2^@NXUe+r^3sK`dZCZW8FFpvWcY zOCgH;|EWm{q!;u@(PtxHKM#|84dKEvfk0Y|QXVKtqtj-l-k{cMp#+qp5G|FfM^|bo zgi^wpqY^H(L)d&;%SVN3LI|WwBwUTSH4YuiTxg^OXr(CVfOZmNjUZAAoqH#@a%RYK zi5vD#@|EvA;#TA=0g*Z=&!!-A;?QI+rVk|ag{%)1FF9i!7eDb7?myUp@dZBj@f-Nu zr>=t&mhqa4w(zp2oy+Olwo(D=b5qPMG(a?{Mm{fj+GBai$VIM_G@^*Yp(eC1D_7m*Scn&8Foq_bh;a`<20g&q50SrrZ+1TTQi8{kL{uDu6V z7GcwR=pTYk8)jSJX*h8R_Am03H~olzeK$Ps8D|l#DA3+t;~W3{#pnLnn$Zuu_ie9w z{e03Q%UtA;BnhdB@$t!WwNv+?m_BFOh-!6b_dyLlB8{XC6U&JH5TjiNY)UB{+R<|^fh0KH73n+S&7KuR*O;^ zp%tx8huKDhMzaYB3Sod&5{1Y^C+UU|2w`)`)&2XUQoP(agi!gyYDjV~;Ymdl1`zo; zr*FU{#LX^Ki!Da`OL$=bnKPnHI0c6m0{ndGSlub*-V&NjQLT$4vjX{ zVpr}ba6h{-bh>V(ZpP5(yA@tamvoO$z`;jgu*j31bQ*8{y(e($%&ga2_23pekFXP*x*ITr#8GYv3HD}y@;lM_y(A?}? z;T^Ak?rHt~m6=wn+`g(^}qs5kG0|cSZo;ampj3aQYWk6Vp zNKWl9vbJ2JDIwH~69*NiFAKRs_u(U`XO;sUc(|Ez?Lre48?czdn&pnWICKxt5kklEKJTqr4L^599TUBpZSJ^dyRp+=D{n$$S|y1 z0|$;cGO@1)V=Hq+u?Zsq_`|&Htv}_}uRPs;?jwKla$x7DuDYT8PaplKY}LwPf-u4u zgAgz^HOu*DpT?6Oa|XwYyS0|g)?$*(#-l_1aUlxvv?NVZRE`k3;M|fK^GgVVf8X(I z8;CeA1Twi8(B|seU+`{{%&?*qVzl(`+Bf~|HM( z1X{1?FGgRDq(0A>%rWP^G9(LHImBtP)nu+#C(aC!@8fCbfqbD5Qeu_UaH*wa+Ifv0 z{-jW#B*q%jETt1WE~m#uc}h?W3*3EliZAWh#}D^UbMN7KXzCo+k`M(DM4*d2ba*je z;dRamI!m6jhB;$6bg%{{$(6Ilh5y=f9w}0s*B7z6SYWge^4zhAqpQpO^!qzp96wZs z(Lop(fazJ7J_6G-?mUWpaQK3}Vn_x4+K()j|twEfm&cmdJ#de!Ye~Fls zEc?$RUB4C&V$zIaA*9`Ci+dj0FGoj)Y^NL3O;bFrSvFR2qQW3z&(UedtmUK=RuclL?g5%?_N!AJ!L3sBWi3|wN{SAdcS z1rH((p$DGIRlm~x?t3mo@dNi-YM_>Icz=x@yN+_-{fF4O^B{-zPJ`Cqc~Eb`?5taz zHBcFVLK&J3n4fSz3n6S=4bv^Y^qFheJw3yzYeyJaxtbvKiA0LT(vG3ssWH%BJqd%!V6g@3*THEg!1nuLdKxMPCwQEibJw)@euz`# z%p7)M7FMrz*U`y9XE0i#`M~_TcV4~yWzRov)%Tuq?&&jQBSQqmA|peCR0k@Ijtx;L zL`==i68I4qNoq1YDbZT9urSNEEgSj5*RJOJpWem_fw3+3^xe|^*AFMG_%*G&T=?$DG987D-o7W1t|e5DvDl>fRC_;0l~ z!&sX`wibg$ON|h6$!i!Xi2MLw2)sNYmU$Ru^V$k2j<4_&DCLsoB+KZgY3_mrzS4MF zxiHKEw&$-LRO^)@_khU&Nu$~1&|(KqX$nf>=e4)hZQg7(53t;A9)gapMZ-@Nt~p7q=R1cPhf zZO?;Z0#l1QZZ60>SNX5Abw6OWog2fzV#zs z{G7{p-Jkv?AA0X!@yMYm7G~$k1dJ{l#E=oEy%^;`VIX@BAN-~NXstm>#oFa7`Ky2a z!CyaeXnfm;|Ki2Z>U2_)Bt=L;D@TLPmpV$)(}ae#hL^U#rTvMgpzrSi3c=RZ5k5-b*9X)>E)GlSNyPI|6Zr`3t^ zeIHLNJRzKVHdoO~;U>J0Jy_PETtX{^g*Z*=rWw{)q*R2SMk^=q=#|X6i+09w>{9^I zlZ1fpOYpUe!L!Uw9&0exxAC>U_S2|(j(b;2hb!u`0EW>;g z`kkDG_j~~M?1O=VJO5$@3PlL}T(~(m2h|E(cp;QVU~bXzErAac4VawcEAP1E1<$>7 z)4x7+XqIl4A{2R&Eyzs5K((Je4;|#QpZO}CxJ#U7eB*PU!=_Wb^B+FV+g|%S&OqiC z$bgY${aDO@iGip;dc*5~=|38iv0>#HfA^*9|LnG1M^60Q+h2NReX&8U*20*SrXQ1G z@Q_rakiWTS_pAT-ySLr2b)bCvJFmR-t$+H`r+lPYuce1)>Xa&Fj7iv9?PH?V9vS%9 z_uu!CT?c;WA1^umF9X!$I&p^}2vEfW^-fHRB=kI*-41gLizJyLiUMaGQsPUAlul6R zw0;sPT#_b*#FGxP?4}9rI3-P;u@{1X(DRT&R$6}a%sZ4|VGB*w-aMwhMRRlj`!*Z%lAj(+GWIP+9kx7^jnn9QBi#0=bhAFNsp z9ShY6&N&5f*WEBN4uukw`k^`iofaf5C`3@}z~ONi=!2m_=)^AM>I*;%zVglcp1XQk zVgD1)UU%heKQrPi<%HGCx#}kmKjYI^-|@SjdF%6EoOautsl^&L zO9=W3l!_$+tJ%<3GCWmZ-C7!Cuz z_R#((UGT1N-pYNCod5ZkKkcMbMk*s&tA$^!((ERD;=Ti1_>&!+R`xlm8gg`Q0iiTe z5V#mr%DkFYAcZ6Tgb*n5pGqNwq+?P#aYCAbCp`**kLT$nI`a7An76RGOvHt3HWy3w zB=}2&xp16{%yD5UoMz1BL@?D@gM_3 zB_5fnbHhLWD>qKfu=$*`@Jl6TCugx~mx1aKzyCW=<>5m+dEnijfE6oX+X)T|6ViFH zPksRo9D<9^avaJ)ADns;;T_vaXD6J88(Qu1q@)Y&CKL(~TUYm9F2KMrBppZ-w-6Kq zzV)^Hp1EoyJo1!t*WNu0uz4A8+A>U8N{o>TMU!BuSnMDfgYNHq1^# zvju(qdDwE)d6WL-&QK0M0JFQHGzw}6Rt`gL4z}M%`7!Hx#w&k|*3b~$+5+noyy)~z zoWFdC*Z;-e^1=6f244Ouc-o~f-GYLGi3uui|DP;gaw@#+MKC?@@{9uq@!#=o;>AT+ ze=;mv4t;$tk)Ju@;NUbTMo&8xR<3b1C>BuKUE|(yKK9m&fA{yVIP237j89^6^3V@F z#t%-iI8n!s{H3R*wdTPG_iTOaW6!diHm-f>!5zD>S}{8MUl(GDeKRwUo+uWIkp~$g zFMH=#zWCB-p8LTo9(%&KcORHQYvrm2rDS1XfK(}@F?6k^-}5+CNoM92I9zYytBko$ zjkU!h%LbS6v~S+}rSE^?C(mc&F!#RkS)4L92A!DLCVYJ7K3=!`C>X=PoV0@9SwBRp zlTlAHLamnyX`C#imxw!-TL=(Jk{Ls%>ymFzdj#5ZE50>YkqZHjOWKz%RzRq%0xHFTv2w%-gC#Z$78xjd04%n;+&Zzy4-U@r^#cp+Kd=bXb(eQo za_~Sn1DTyVRw~?_gW5Ed24Uq{&}qW{TixVbwi!m(LDqtM?*$M17d(yl^b;Hb+3ND= zTSj>O%|NB^fw@|Z6)T5n#ohlBm{%N}Z$0V-2`sNh zyy3&&dQLHtE8q37d6l#0OfU3Zb=wJW(H9P<;spvo*6hf^LKgtd4<1W|;; zR8wdzaAc;=9Y+_q>A(~>9bDwL`IeK1HxtN|s|`+#s|r&%GS5i@9Ly^cP= zZFjBnhg=vlb~5y>f}{7t^g~b?h1DlHWa;2;n45toUkvALgOPrit#jUBk;_(>dHbJ# z8m_$=EX>F3VfzyE>#=DY2ra(1YB+vXu2Gg_tI=#p3nv`1zwS(Z8tm3Asy$AyEEip)63))GJghHRcu)D0*Nshe&04*=uGYiStS>;ld<&TzepE^I|5E zmtS_>Yrce%{$FJ(+lx1c5{g8=9!guz}@s+#x!QKhzTF1Mf+~3d5J1Kg19%jd( zJPa$(gt!j-?r;@?Yq!C`Di}Wm8&^Qz0Hker${COr9CSO~fS=rsy#8j)`J3UzSGqhX zX}S(7CJ!fjiQ2N|aN~`LfBZOhbORL25c)7Q=JKY=M;yYHNjFY81vYGi#v;^Hw@6IQ zL#y4m@3SvCW83)W25YRXz%F{?>cb#gAYz_Ix?|f zKL3e7ecAN{i*Nq(km}wZGVx@&lrfzGV0YnTE$}qVk}gf zP5Nzum70Nq&qI@o{ipxq)ep0Iv@rkN3*~jY4@YAI5kVpP;a}hL$nVSxZ+xw<-|^ZF z18+ZLs7$RBBdiNSQj?KpnUkWcyeNsB4HtqqOG(lcCACxP3bDkxJDAob&^?KY$it>3 z6R{TUc`ms%8B^1A+sXn=KLM3;oAX3XyC7Q5!=Xf&GC zWI(qWyN4^B$PnL$P&=cjw;bbLsW{XrjB@p?a4V+ht#HPP7GvW)k+6AUoVi4|R(GM4 z21X0Ka&3hz{biIE%r18L>b(cqdCP8SbfB-~NV|5^CGwp%EFN&*xB46h#qQq@(|cjn zRv6s`I(Ei2-hj0Qc>3dD-oiuUu%Zusax?O$cVJH53@?5+t2T!kTSP0v`n~+&cUqvUU_x9D_K&^!4ju&3cz#Wtq#Rc1^&r|IqB`UwKxk6gCe| zETWYwOiQzbPN$v+2!bSuIdRi^uDkKJCqMb(cU^-~yyg8bdgr@d|AM#gpFH|Akj#J5 zKs>EzHro7;PkoQye%1wS94yoN83PfSp;;->DMU*lR)%1pQKzrnBoPXcWr%W_1D%*r zWiDHAOl;8#z-U|HYoPrvm1b9XP)8TAb>T)q5%o>U26wb+T#!bv#N%#fxDS}8o` zl5c^?J&2`~#%^)do--T^h+~G~M2!+*4LQr@8a7yiMxvq+JWX?<#(m=x+%rDIp4oXC znPsHd$Lf(PC-nERybx0M45I@TZaXl;CEvN3cB(i-2b?}KK*K2hb;m&`AFq@lEW?BQ z9Y?XO;*#j;S*M!Sz+eS2)X%cel_w$n<-3pBamaTA;ZDz`yM`O}S)<-wN*an!Mgu@R& zxdJ2Wp;~q!(BT;vl5o;Sm`b2Of}(*N?t_UA^kwjp%VBiLCE-Hm*hJ2_d%p8B|MUsC z_|)bxfqW53nO%<1DTaj>oBL@}xJjJ2a5T~{pp zLA?{Zd_r2%)R3ACt#mF;7fZ!W#u$<`LrCyFA7zjEi{8sbUiiDm6m_i{a6+%?00z%QK)p&Svj_J91&OBiw!-K2MibfXK(~fXTiCp&@|a}z-#R3M5{N3cG6pMF z!J4(OaSkyWWELa$3e8XY;%dr){fAY5r6jM?Qs2eC{|(RGRM3h%@L*9Nf;b~ zRVO-f@8JC|{M&E>tX>0M1BWM}O$TPr0(>s0wilF+YL5^h)R(b>Wu8iKg$^&F{SUybpc) zub=?UKah|7y9}gf9D&SG*8E&)tQ7()92%s3kNI}z?EMS1+pUsc(}w33qJTq*WwO@b zX{-9bxVcn#LA}#OON~{EIEl%!%)z=+<{B#D_RBLFNtWU%4^Me{$n=YyXRnKiFa}R) zhzgLz?3|cj`;iI87u)nH#ir2#)(rGfQsCt`Ir2TCav79nVX?-}@o647G{sD_#lXNY zr)}B7=_?0VoS);iJGXP=^|x`|k8a_r0X}s`i+jb^GmRMAMAaYE6!cZ*^k-4kM>V< z-BowN;R#s23aX^!c`EKB3RuIT?u;+!OSF- z$Kdc3RJ-u1=ey8tVcxAQS)PlS6b47#Ilb?_P#S{4K`=2Cs*rYI;vfV?h)SrVd&$N& zz+;~7xEzz@VOt8@ckzLDU-_~>e(@!r+BZIbjF1u%ZDM5JwI$08BZF0xlALq-oA20u z?G0yL{-URU`@5fe>oblnEYOM5f49Q-*EW!zarn};B=}`wVha>}pM`e&l-=`-w+g8v zPYR?kpO}kz?Nm(n$*aoW*jVzPS??sSyj3dNN#Y<{^d^d{RfV+duk5h z`<%9AGh0`$B&#p51_FZtpJY7VG)Y;tb{Tv29$LTg ziGTSZ`*!;O@K1mKs@J~a*&loG@MNyZ`}L1s%Rm~r2MI-n{5Ool7(*2JbTU(ZXlnY- zOsUO*R46PhYdO?1-@ABuWp-;dd`YtvBbAe;cVg!`JgwctMxcbk2J3(v3v3=9q7-;Y;r72!1}c>@L?M&YbKJLYoE=A|S!{IJ zvS~dRp1u{)ZgJP$5AegA?%)SE-Nl1D_dBF%-8w4m8jYQ|qogaa>UmTlEKA7F($u%P z8RS)p?w!Y}T#na*^|VMm;xYdxn}JO+HpYYBXDFdtX>XVPjb1;{5&l02S0?Jldw1r z-6jmKgNJv*#V5iGp9bU8xt!k~L$#Fea-0wU19JO~_$yAJ>lLv|Iqxxd1ksqoj&6dm z3iVky^YMAY-pRuj340Du9P}PK^u0enMQD{w&Mcyo#0ZcXc-H3I-$9zDY+SpVFMa1H z&;PBbzwJwm48#xK^8O7gS1&(2J~8+2LbcTE|NJeFenCo-IOWFeyE%31YN~~hEVBeM z%e%$=a?cTJO`ImtuIbquVku4zm0K$_;7BXs++}^6P97+q+G@v0B@t55i4!ME^qjmy zO4kO*Sc9<^C3T*dS%m%hJvfe=J!sEI_yObdbKGg@e=cTzmIJTyxJu9IdsveT5TegFr@B)E*Ei?Cg=G1jJF6BjPo)P+!_Sd#qrB!5rdUEQW2GF&$l zSFYP*?8fy3G!S+kXIBO<1|yP4byVsQ9jnLR~>?%BhOS*1&BKxP0Z5tuVjH zlJeJB$WkZqU%3|Vt{rqu=42>6lCw3B>@K)61L-d~=2Ked^7CczQNtSW@{@aHcJ4b(V=j!eV}mmxINEG;%1|F?j8y4# zVyB~0icUAinhfoEXrZ0pP)e+EjD?>YhZCy*V&kyZ;z^ARBNp2&b{;;+q3LM~5;hGD zu&Pp~5QGS&Q5O2jeFOvj9GaNoTX*c>ntONCXlGpcgvau%^Uq}F$RYmrlV9Q`fB#vo z`R*+ov>t3&hg>mAP%fYXjUNWk*N5L~(Au{hnJ3=ICb0{%q)hO2PT4p)5T6#k35AfTmTVcel_eO0*r zX4tq6)~$5>Nxo=-fo2EBM#;iH*m1yhPVpkQ-GufK^+mk2jaZn)q)p_^e(c~X$K7=E z2*+eF(#L&QZ$D%0y1qS^oWJe9nR*MYHBw2ekoZOt;pUPw5>F^DyY!5!zjDK&=l%F= zUwy*)7oUCo87FQyFte}#Hp7}<`3Ys)cmpJHOG=y-?3-->> zy?;7MUR?D39LAT7H&QlK3!J}Vh`1BGW(QK!>c(Wopgj*I^TujlE!UyRgMSvBaD=C!%A&<$y z5j}6@p+$rc8bpRcV@(@9vkUIK9~-Ao8Krw@7H+Z3VIN{>zWpHE? zCZ^qcl!E?c*m9L@{wUZOQS3*=4eabBtlsL1%97Y6|3W~)=c?~-fAUK%KjZT&)(kC9 zH|kggnY1L@qNPO$gH@J#r$wbw=G-&Z-~Ne@efgCKW;z>Q_`;`rYO&cS%`BPaR}S_1 zKYuGdEE!y3Q9Dlg$sG@J{eSyb7;86-aDv`%<7|}5}+@X#TWT|aol=J^P(_@}lz{_hWmt`MBI93LTfT^AX3cYw51JSOpbg=MN#Q z!IX!Qvxl**h9feQuG4Q>!?MT${GwOBx=34zx5fsf4~hyrA(#KXJx@uk~$aG=rU zF`L)(qKnQXQijib^&0-@liy_V$UJP>z{*PGDiJ+Jq$T}Q5_lTnOFShBwW1_6(vuVh z2bs9^}K&Kfu_F+b*Ael}!u`>P8TAi@ghz$pQQ^O(KopsdLo zIlDdBg)!JHp|=AU*5x|FN-~pSNRTo_m<(m1v-S))@pRZbPIcQFDw{TQ^h;kQ+jpmf zTF<@$9@+t|IXL%;POE;-NTF}=g& z;d!Y7-~AEXd%w$XeCbrmF)+RzIe!4E8{A^JFiv*bWw7>CS2c-nk)?0o=KFcy-@fE^ zZ~DWhfB2Ef3A8-Mad`P+Yx6nUS1RC#0b}c*UhC8B{o2>xd)mxQ&28E7D-QMA*Z*Pv+$0C_-$zZJ7dOw(ANv3@xv zt?8zT%Q@nhEXmNm=V07kQw3adu7o;n(ma~{BNr&@Frd-tu=nUu+MO=T2Kp%nJ~GP) zg8&_PC?g1ms$eaj|IuxH?B< zDF&Ky?2vIanFrEbgYKZ9BVn(>zpF< zns~?lN-2(e?a?D|0EGlH)54}P9ukqp$RtIiUC6qScCkqeHbGd2Dmo%nBa}v}Ud>A$ zj!FlO8`E}1s*C9Papb-SFkXb2?=Yuh?6!5#D4>o`!Q?LN$>+I_iAQ$9=966`xt-fx zi_*#d7;T@6VdvJ(1S`_p?ae9<$`{Or2rE9$jc7l}exNooYvQ^>p* zKqE;QDfDs1wsp4@D&f8pPuO^uk`mwZ2z>wF9AeLrpRWh`bq*l}fv34=V)Elho84Dd zd><_ZZ5(3t%=M#esubyTx^COCIH4PNb0}BlVU?Rmie5;SXFB-`;J(L6-L+B(PctxrrCcKD2?FV=8HEy5h6dPs-y^*5Ew9UA z=^(!bH`!goInleAV~O|Az|=h-Y4d!<@h5f(l*nv`z>$Z&Ms_`) zp#;|CO~8#q(u|3*wu4j=N|hn&U{r;;ZzY_(4Gv8r7TTbikae-AJ^>zi1iTE+cpU70 z2=?C#XI_~1zG^rbyDmAtV(KVVSHO;2;hgi~b+3Yhdvno1e(jlsfkC+8r|_+B!-fqm z%&N^mYZ?ZYBj=7HlQu#qY-^Tu#i?-elbjKn97gs8+_Q_PUwQTyzxDAqyzr5!NlacK zWi5D~#P>AT9+P_%q@)l=M85wkbqu{jz3xqK{5SH8Upy?9YT&bPVevUT=WFjR`2h$? zDkZz?9WGct$Qi>G;!YQ>6rCg{P7*w=ky0U5&p2F{!{d0g-h9e8%5h zcYX>R*RgTcC|)2b6+H%{kkK$;q!3aqh73g^!(l)<3Mdr<3ZX|7coYLo(esFWMWs@v zF<0mM?|;YLNZ~nOxYWZp=K@6U%9g;|V|i8XU91oY>7j&U-IWj+45`g4`J}sVJtLA* zJM>Dpy2V}>dxy-Vj#$iMtWDgv%G|>V7Y1ggfi)eZE}?9KZO>t6TF9V`NE=vOLvKBE z5VPqdCo!3ygso>et<<3%F6Ypp3+EPRo%TxMa*yE^h%L?2Qdltp z_islY-i$VWTj!KjzHXU_ce3S@2;QRck*ect+@R$ zr*3_u(QXlFjk@j}Qqs<1y4^U(l^w?+ag*TDiTI-oypc}^w5B5_EJw(3OpN@Ql&_n}+7?be#U;6;XP(4pF`z#TSXKxbE`*E}0+tmDREi;$Jd~=00fmB3G4O~2P081UzDMY5 z0#DIbDX}=Sz_s7|R{k&^-M=Z{`@liH!j%C_k;~hq%IoR13yq}8!z{4Ia7>i1&^pBP zBc#evGU;KZ2SOo*#0rP{*(C#2OF)LTxsl}36=#%=L$S6?merj>DvjvQlSzx{8$>r2 z$+9l8y$FjF*bOJa#9@fru;p|(u*b<#MpuAP4&`chTt}5o%N3S&oAAgkxZn~XaT5IA zBuX8kM%IG+e?VY84910FaSY`FWP2W&Hql`jn{<$kIn3}n$H~~t?a&KQZ*k9|$qm1E zoNrci2 z4O9pN} zgmPh&0q-i zvOa-rH=M9>e6Ldnui65;?sfaV+7!#>Cotm#11hw)0N#c@UwLiJy7U{flA$HTJ=FL-FN^^!g>pkplc z%yOia^3=7X3>G|+)R19lbz;a-Eh(IF94lmkr5>O^dsxtcPovx9(Bu?>?=#e2CXy0q zB+`KL6p=Rj({-Vg?x8oi#tPvYA^ZZX5V_5GzE7>yU}9#LVzEGf zxqwg3v}%;p-*zrg=ex@KZ2qRIN|DO zwZzIwnH7~Ht4bw?$_4uSBKk`%_vnv8`T~z)=)0dmkQ;(e(f6Fv*K=W37QTZ5C(^fqr0PSNXBy}iG4|5HMtkU4y5NVAwUNl3dXX*VHD44G1{ zafS}uW~M?QWxoA@lFlGxZfLUixdBQ9`M2drqVN$YOtyf{TBxLnTsVl)K1pE&8I2;3 zZbvK}a0=ar?sTnvR&Ij*JDoQum)u69t*)#4NV~9NGt>{lkG|uS#7jCsuSJ!Rl(sR$?kDhcsXRI3Kb+3C1um9s$ z0197J5yDBvJ>O?jrH@sW$T98ZA_L_D{e_Tfo^zCgfO6nb3_QHRC-glcPZ0zP-&gpa zMtcfRyDCPdd-zi?P;`W0FINykx*D0xA~JH7K<`R(((qATP?p+!VQ?K?oG?_Oq*`h% zl%)wtw+YQUB1v3V`JjLi8Y5iTC8We?30fnyYdB$*mzVeotkikpo)- z^B5*K5NloEtjYkKcAjfcUL3+!mw}0qQx8Fsk%d)E$8|rkQ5n&ghN=B9ycSuTg1K?` zeWOEs^i$Wo;z(_gQdFQ5XT*t{_?9n8mom;zEN)qswBm!+gE< zsG+s~1%Bt}+>oU|4B0z7d+IF{vwzlKDATouT5OqWCH%!1t0)B?NwZCyrH@rS!(rn$`Vf6b9c{6ts`8Tuvc9*KI_re47O6 zase%lA+<}3U%qXE=jlfk;D}>uvb*BEaLbr9N2|1JmaROD$mJ-#zpxf-()?bzZ!*W< z8!17$id<_g!en3*NMqvGA}Wp%rHb2dGj2ob>iB!unX$&zyBmc~ow8Vxxt+PJW1@O{ zFDM{0HAq3pe(d5wOmz*qx{7SJh1hcw_Jk)omG8uU*m?%scMDXOISKpBxT~a$3hq{% za0cA46Mpnc>^J{KU8xplGGfK$%I{`AL)$wS!D)nr})Ieid0y9YeasdTg0!M<9c zr}mI;ScWN75Jw-xR+mG$$o`3=+a~uPS-o{@`QU-6IaGcNlPqJnzmHTVv{R^NKElQ{ z;siv7$_#>(>$(Ho=3aeqPDn5lw;LOI^)Q)1R(`MDFQFz-Cy_~Upc(M zA3y$Fe*bZ&v$9&?$(KKuuYU2<_}+T7^s%<}sN6pYd>N1du1u!nF&sD6Aim70aaM5=ptA zZmFNR)JIw@(=C?h7W;@xWhhoKL5bA!$vhvW1B4eK!U$1}Fi`wWpCqCKnRTz5hkl+;u;5BBPS2s=E%Y$2c6-q+z1cf3JVi1MCdL+5W4weGsM_d#EsX(k)y6rT)s5~ znQ;i%*lIZCbXeHq3_R+`tlowhUQ1RSCKDRcwp&1C;Lx>&qcFGzQJ;ePRNk7kBH#L< zZ~eoAJ0GcTST%?u!&8=mpO4C{m0)j#WVILp9>Zg3>{ztfIWr>$Q@jR+wz&RH^i6313HG;y4$gqGhHJo}b zM@(=b)uiNcU4Io(8_oHcKry#61G?-E0$baQ7^z+ z>7@SFLF5__p?%^o!t_;9gQHNWkaZU6)~2vJKtyF%g_0-rLb>D}?O}9~C+H3oFgXun zghYr8iH8y)nVBO?8b}PLc9hHyP?fca)(qDzV9BilF*pP zuGxxB>Tv6gu0%IGPNQY4Gm4YXL+BzwZPG!+sd2ik!3|`k)!3wswANjNl&HBw?pnes z%pGtY_G8P{|MKN4-uCyO|7>5OM0sGCu-uRELsHxG$N#>=ZL0Ba46PL{WBAF`{F{wb zC@bg~!TxreXRaHiuNV-=F^NeqsYQA^KMRX;n2l@2lj~kwSuRV{N-Iqgcc|5Bj`|N= z-&`f63nj1wVF3zN{`uNF`SQLAcHenF%d(iFgwxM^0(abaEn(j&sHl%DZsYbFcL9UK z;pM*8l%t3s@bLpp=xNs~NNbcd?_;%((mq1_ zDCr17iNqIqTrD-aG>95lgAtN+eiCIZDlF$5iA&aP{#`PMV};6h?2gB}l04u0^`PBH8Hh9~zifysVK9{JoSgZsyXG4g3 z!M<9HGX^R=Vbu_})giNnI5s&fX7k+Rm`m#wmpO}cRexAldy-};i#6BMB@DbA?$k?l z*`Du1v7awodn@1FxsOA)-ou$2Ht?*MyokqKd^z{t_9LQ!v+%uucyTW_K8lbbLWD?J zaBwG}Jw+7x&M-WM@?Dsuv}-FADMg?(p3L{@mIAGuDp~bjQz9R^7g1tKt=rBzbh3nQ zW=Y!#-Fkc?q7zCinJEzNRkd-Sj;<6IvCjs52PmZG$sg_ z_HMVrA(g@}gAA}_PI50pOgxFrTF5LW>o&24K9nlqAHEmLML4tzihbBE7bB*2L${GX zaOL(hY&S6LPsGd~gop2SUPLMf``&z$6FrtAIPnxR+rp1KkhZYVfZNJJ2$JG3S=vJ9 zTg7E$sM@^iW9)~}T67bv;6ZDVfBo9`UkX5X0UO7^v)wmp`73Rely6B-_qVF;J7VyV?@NGS;dCsz?t zVQq%U3_^Rz@*qFmzLRTq9wHk*iXRnu|GVG9Ykv2Y+;YR!ghOY6F|_6nV9WwaLA;jk3v$*wSK2Psb2v?IulB;dlz*a05ne#B_IwJZB9*}^ zBJ|K&<4X^z1U8pEC@V;f!KMjur%S8ZpG2?~fLam$&I z&hzTPb$!Q4Xb>4$GL5GLhy|I__)$Me{Xx9hy(E3B;lM+%`E2Z-`w{a;v3*01F%R=C zq|5zCHH^9ER@ipB<4Jtqt)7j#Gpa48;{A9GGkX}8tz)U7UYbCm53{I9vluBQnT-)~ zGvAOXbdC0e1na}HQ9g6^Z7=-eKb(H+sucsXX{$pe^y!+6M(bE3j9<$@ewle#YZ)p= z+;Vv8IXBPO&Ro&g2f*#M1}|7K${EY5B+VwN%}6tYR(Yd^9-c4qy*EsjBR0Lm#aB{5 zvsQyFBZ>-6awKyiE6uP<6I3fqPfzi&pWI8ezrv>BlX%-xp2P><`gVT!6hIlndLGc8R{HB%XFV?pgzJH>Oo@urM>v%+YBk4vsUm=Me3M z{RlM0{$&WIoqv)}qNt4_u^FP9ki}g@5`rHhgMdtc$ugw1q(a~oswl5Or&*`d>7ad$ zNkMr5X};T(+i6rd$xD(fRV-TVBW(m(8vrFktm%++CehJyRA$MnK*|z!a)+x;uPs29 zVoo|AasN+YY%S=jgMp=V`yZ~^jNJVo=FmQ<47)myv(9%R)$A-RTTXJqHpJam>Cu z(8A*MruECFx2#KKj<;RRfOGUHQB#vW(C|rV71(qz$kXjR#O3;b<;CFAqMm|qC zaRXPJwUzgL?DKr#f4&nPZ6b}^WUUDV9i)uFdhP)W;|xW(b0T&nzP@&XN|o;x+p9+r zLM{<$$I#UtcOx*kopmL>#sE12A`CL^5O=#|ahHGqUkW^H(JFHaWFwK8%U5)7Ls2E^ z^EJaOMmb^A8l>;hZnT*`c!Wbc9%k?T4{~tVLtsd+&6)+)O z(r%lW?mW7%oH%Ktq>o7#@h2Z49zDsm@mYH!cITZibqLm!-Rd6(u-L#1jUxALhx_k? z$34l_kdWY{C#h@ov*lE{`v<6YoupWG$v!FgJ~pai+LL4^L7}m>i_W@kCo0c(J(5ZC z{5j;ho9}*hJ zgDj)!DR$2+oPYCN{gUNTNG1f^YfYZDa)ix8CE`v@YEq0bD4mP#^322Stzq-l(Bjw{ zY~4oBuaTGrIA`9m2%re=Lxq`YDrz}YExVndAG-* zMTwk8P(9{ZEFp1;r-=fOLgW!dlAMR5$~V7>(=WM%PGg?d(Zkrx_3{-$kO@VmCDIR2o{vm3 zlG-9-v4#f-&%+2oMus#N5%nRXKEPl)O}BHH_A%1c!e>%pl+$-9sq;`$VPya)$KeQ# z7DY_9NSf5pQI$+6ln5|04})#ywI2#LZADBSbaMDKb6Z463nD;Omm&A;gnG>(Rn|BU zB5qa0+No+>k>z#|!xmX-R<$C1y@{V3E0!k^e%+P2w@p4wWC-;_F zic|tmYABTW-c5IL`y=B#>zr*o_4E@tJUz$Lp7uhZ1nahtWC?-m>1+6l|M6x{ zz34(3bBC$V%%ZfPH`Q|@NFx=>3(>xZiMyl=^Q37P69iZxvBJUK){oG|ic|Hr>ln`? zQ5u_TzpPSZLSmK9pQ%Cy9##s3O_vnQN@z^y2*Sh=j$woc#Dr7(U6=Wpqpr68-v5CuHAXE!_d??Z1r7y4JTxS;$`fAnwO@Q%Oy&_9e8imVtNq`w@J zBtKszB6|%4jvtXHIMQeh-aNVZoN5%1O2I>o7Ec=+WPN{$xYZ`j61Unaa@6X$@T6Dl z)KeE(7b03|X*3%MA@P0R-9!n(C5=vIKmg-InH3kO_fN3K<2i>|(KwYPIlu zpIG_W+_>>0y#8Tq+{MHlr0-Bk2Lmgl))*_uluzb6utS7>h^UO!5k@GaEFy&_tL??} zHF!e^;gQYkLUa~#qOXiuz78>U*uk$Rb%}J=Aujb{5(DG=^Ux|s7ferbV_mmL6kgIn zXQ>OntlL(?`Xy4Exbk5ku*SGt#$?zmbHG4mAdXpc@(TPl%jk9t-@W=~SIs!G5>_mS z!AtqXXMXhReMhE7ZoBQiWw+gV|CXVFD!uB@r7BR7lXty=g?`WD?y31Juc^2CD?vaj zwG1l36ITy|Nr|m>=PMOLSjTPT#?UKYf0Wa*pp~ZG?vSR5L#)W9+7`z;D@70$>DHTk z70j=gq|YMlE9OAzQXsEWAe44q!dN1sV~m4xG>F=o_Hbu_z!=>#bJ!<$I>O3-sdJ!V-eP!jO@nHTyzsw=V6w1 zgfB-Ay2-4q4@i=o(pMDJGw=I_V;aT{&5mGyWZEwgd zGaNt2GMcI3_UXmv%}IeT6%V#LTs+#(wxKd{vyCw(pFqb%hCL;c&GE*b+}m1xe&9Tg072e#K)D`91R>wJ`8K-30pos0UcJf@xMc?MT!ew>luR^j7o z%!SC4e@|2NG?DLejH2%m1d7mC1imKl^f48%?@{nv(N*Z@!_$PGBJwl^?Gb5Bs1*gB z|D9A!j32=ZByV}k>v{d#{uGndsU4m~`ynVnrX@y6QYA>0#E*)YPKRW1k&@D`bG&j3 zQe5nF_2Owl=6iYJmBdJqOX-WC6ebL?D#F^Fh4*}f?}PRU`bH617aK1iqCtcRFtdA{ z3b)gS;pLcWzpF&Vfg)%hGD|i%idvY31AF1FTcFW&=a=R(_~B7lzKSHCL1eDui_Q$# z6cZGXGQ!x@siLJom>8ACD3hU+E+`8reD!wmJxm~U6IbR|a=plY^<2^53 zvu^F?eUB_|o?56O$jFj-X-K0D$B$w`@<^+(?MJo7CFk@xxx(Hg;o`A=(2}lo2#E_r zIIhkE$aiS69Mf9K9D8rJTTXMYR6hA!w-hB@2qr64?tS1P?%6xe#iwuM#MR4bHe3AO z%l-%`z_!z1{T9Og_mG(;LY9`Wdm%MQ52T0CAvRq^b{kmjVWj7h!+fQ*x$a*$q1TeS zmg9^;3YmMBJgkz!LBVitLtI-#X`hyoSnXspHYz$)Oe)%*!t@ltQVIy- z+|g|EtYXAj!+lPrY0a_TrlRLRj!D;hD{)VErIe%-cSzF|-}4c|Z4z&+aS6SYc!d(J z+5+Fd<33K>u%69hRe~Vo&))ji?B8=YoO~&qaXytJ57L>u7cHweVY!D?K0^3N<&zdd zOw!6F<_QK!JXfA-^P(-6WXmO4xyVIpDzB55c~|*fnAq#FVDlWR7g~w@ZzE3;YDws8 z3Z94WDFR;+c+MDt+&j2%%DoD5=c#`Jrt6I z`}cFk=_m2|&%B?~Xqm+W2M~Vb6%Z2BsUeC3NEzbIA9m z!dMs^hDyo7$5;^BDIc=LwIM2c9NBSz49mI=E5x05?>?vK3#?3*hEz#RZ$w!_r`6&u zJNG@;D|lcGhY!wj(QqHtazrahk0sNP_k((LXeDwaOJH4>Mw8L$bn!iJsc@>N)e_{f za}aRN?e}6mkFz$eWaY?mzJKiveB^`ggVBrNf=db8H5xa54u$WuL*NvuB5>hW5FxS* zn=GPK0kRm~ZIDGp5P?I=q|8GqiP5h#h)679L4N=ba}^k43$N1z?CsI@?ACo|GYI<1)Jaq!?_ z)~{W~XFl;>!fHVM&{4;ONJ*9p`lOO1+M^H^$l6V4HBi1sDunYkp3`Ubwt_Rt@kCzg z*yc<)#e-4;58*s@zRk7}66^aYzkun?p}YW5SV7i4im2t~wZ$^FRK_mMu(Xf1%#oSE zBl9CD_T!E3b6wua;of!&Mpq!ce(2OmO_tXUxg_4`k`qVT#4XB7=SjOEYb}tr7cgGl zVL#VyVKQgr#-Osqd8;ggPMe9@MaEVvVtON`OPhq>TR!;Rzga)_ zuHRYNf76*`{XBN{0K~D2`5d0R#C!CN!+DQ>{O-uDv*u8f@ z_a7MN%#%-G?XpqYoi4BW<2M6C@PwzbY-k8^)yK$ejP`~=BsudQktq#9h(RLT4W!g$ z(#O~iqA}-gP*lWtF8Ar>9#V=&)rI#i=UCq1761juu4}1kP@r=A(ONGxatO4`M*{66 z^MUr%fld zA)xXc1*OqKp@kq562DME2p?LDh@gl;64j2n;uYyZbr>28$G8ryK^TmV03TDXAk&U( zoZZfQcXeA(8A4QsF-Z-`Fv4}?$EIXn0ZUE_XZdqUGqP5PQlCfB6VORr7;TfhY$MNC zgm9>-HMzjiWB0+MEE}tGV9)+j60Ga(Yo!~72m)1 zveP)B9MW!e={enFDvW$jz@LGv1bGweEXmvb_&G-+bF*+go&xPd+~LOUJ6Sn4#`?iQ z3WX9M_|QjqWaq8$xaZQhQ>TtP>DPg;g4%JYucM!jPa;B$-=4$1yq#Nf4Mkw=hy3W9fwelektxLJ~_s zCbcVnl{zOZo%ff;2or=PtwlT?po+_2YH!|jT0nWgg*cr~&d@6tMuicOU{#3l0;i>F zb@J{N2^0$0p&>HcKpTrPHt&UBU5iWfZxW~TEkTC6ix z3|N5?l0->lmZIVg6eD)+IKV*FV`?rw<>2AjP|NdQP zUB7eR%lpd_(i)5eD_l*l6umIYb)@Yv<^q;hSdetPNxnmr$dlllL=@6B^eUIQb;mA_ zF19)2gmtW6Hp2Mi6z}@mPeb)FaNY$Jj_hUprZ1pHNG37|9}6F=JwRiFBDULdTMp#9 zAD5Q|n6!zVJp^_ScKQg?^N)E8daU^Iy{uRY4oR@ub_i*V6)DId!qGoWXfo5J-WiH3ttI zlM{$IBa3`|6tR?~IS+)87+<@F zd`XOvF5fUxAcR6FpV(8m*AW;bK~zLYjZIqEpbyhsfX1wIv3>mz1P(p2IU$yrTw0&= z7sY;eJ3Fz1X^jQXBkLc*lIBFE%URu?>K;~y7@Ojn>H*53agW@yoyuUD^2+6T>nZ2; zkk*A%(s|a*8k9*P^m*XEU6lI*+KF7Y^MM0rjtvYE6$?bg0@7Nyzd~;~f&?jjlyY4RZRiu*1let&RpcII9u^3jfNeRn z0}yeC9&y<#_wA)aVrjx3^BzmrisNa7$c?9mbme~$s9rdwDdl3wQ03l1E4M3FZ`IE| z!12a_%DEWVyTOIbZl319J5{jtTuHFbJxVAgk#xTsW0;$r<}H8na)wurFu!L%B9lZ) z;wzs_x>^=v1-e?nL>}q*0gBxO6NU)oc&XG1QUU)z!rnXHvb3u6{yxvMSo^4pWXM?z2}~@&)&~^)>^;yTfY_faSX24NB6p zCKO3iBQZr(Yd{oZw>Ai(2oqEY7SG9=NgL2?qSh9KGC}|<$C?nMB6QZFh^zA9$wjBv zD!BlyCJIT~ol|J*z&Wqj65+^XW3~mx2t0l1!ecC+IKrVryJdu&&v#J)WsYMC2YF84 zK@=4%pFYd+V;7igI@{;xr%iu}LABOFj z;rx)2LWMs4QCX&2~awchOK@VT!vBMV4R%^E0X^x ze3&)xd5FHP&&e6~CYMHWnJe*!_5TbaL7}2h6KF+f9D$KB5}}qHLlW(waRh<%m4sRm zYw5s@v?0`DP=PVhj}j=)855zoNC$)i%8VJHZ;>&qF0Zp=Zi-*}$?t*nv#j;{*lv!| z3PM4wbP$kRjUFF?)(FK`2iI=n!f+tOW_&2@f>6#kFUY!GoXcdAmNDc}L}4Ncag`zu zLg_(ArU;X?h*vJigs3op#w4oWhJGfyxmO zi*DI>&-wEg*<4;_b9t3uI4y7Za5y6P7FXosN<-nSherJ;+g>N%=2@IKh|JG0*M5r7 zpwGfd_!8BsESy{5fhUji+E={5PE_$x#Z+iFyl465T=4caF_i6_(&yA5Go5DErt zqt0x*MuqHJdw0)sXx|>@C+jq;36(e|3ZntFz1!`uxwb}UYfIi+m+HFGX(`;b%Xb%r zFM@i|N8_KQ%iqd5Ij1qc${gsJQ>Rbz?cesz{QW=O!&e`8f~|dfiF+2Eh+qWP(bXDd zY6M|HH;5%-$O}<9apL3qO1N|cLeDAk9z}OcdQg%oxiRzu=__|uQvgw|LDBD0bhmI} zO3?4X)+#h6pgD$%BG}r1)|jjro1x+awZ4kZy8dTndxC;T?h|jK>X7#_){+~G@}0Ua zjAYSMBB%~UNSIbwIJ-nvGX%8;t|+iLoC#&sW)`-({MCE^6CeD?PjSzEPb_u%JID6j zeBJN8;Z=A1(Z!1kbh^EP>}{a0ATl~9jZ%@~%|#(p=TeQNtZxkNr{l8rStSY#bTb+H z8Ei|26(wYvLsa8Sk9>`^UZq~G@x`Z4@|l1B6sJyn4Q}{;X2$9)e*CZL2h%WrEi@+K z#Mfct2qv0>ph6agsLeGmX%pcBPkQWieDD_o9icMh5n&JykSVLK932Jw6wbqo(D6FJ{RE>;-QG{8J4N{>i<(cPVInTKYQTN`18z05g z$FSW(vbJ{?_yl0G+v(L>W0j?&XU+>^r%SXF0tUg6JgjrXfw?x)>ZL;(aG7yf$|1(e`A~7N zh=j&ri-J7Q2s|}gDJS%E98N33N{!`n=eY048NT&R-@<*5KF(6R!=qn)3fKcvyEy;l zufXCa%TNGK3DDUFpx)iz0 z4klY<7))NE;{P3r2o6u5wQpp;`$k?-zM)lUZmr5@ z;M{OHjtPCMsT{#~v7?iaT zRrtb_$H?Of?X51+*a#a7>pbvKIK}Sto)F9UilW)nm zG96%*k$fZX`&>e(?>26cS%-)?DD<|~Z#>BlJuGfswo#NAaGAmnpszCTv%YC6MUheo zcTkOXQH$rPR`yb_9-vV>L>kX=Zut^_|Ka!X*6;l;zWv*Ngr}Z7#kDtFPh@d6p^ZN@J{J)xzS0V`qu0%tnV#VFueeQmE~1D)8Tti~7g!@|kllI% zr3>`(qBI)?hRs?9?UWy%C;Y3Dr`0zK9i=#{NcuS@jC`FXATu#82yt;D>}lsHf*7;4 zDyWp!P;H>{zT_c3A5ba~;K9lD9E2y`u8g7HeKmP*r7_An@uXNF_Y$v>L}e~VdtEPu z!a6A;8dne$1-Sy9=e|&>A&GhX*y4*8I~kir&Q`BWyVI41IN$PtKXgeEan6YvqJqfq zEzVkofTlljKh;Qv1u4J732cj!$+ShD`@|fS^829@Ly4k@t)*4&J$a7XZ@QL?=Pwf2 zfV)3&H_(KUDTzC^mRa6O6WS}Vc@$&D2y{Y`)KIpI&U&~|;6-gB3a6mIDLaY-XGSSZ zg6pp!ocis(l|B*o;f$bhS0Y+v#eER&qzOeO#kHy=Gjoj0 z-9c^aWx$;vYG9_4OoiDCeQK_oZkrUZJjF6%UD%!`h4<&}Ed-=rCk; zZG$^+yB@V;QUsfw!-gTaN0--63ii_qt)7L;L@m@;hZHa!XMZC}Vsa zuYEOI<7^JK1`ixLPN$bKQEMgp5zN3|0vAgAVsBe_+u#YH+0s)IRaBBGlpD; zn5<8rLlhB(*4Q9MWqn-HmpL6KfTV)!tzsyEWT=`~!hYNQs8!NpEaxgh4N_$(fA6*# z&o&z=jYkE71cf0gim_2lKTgS$6cxwlD57UlXttO5 zDh--}QKA{pVRT8L)Wgn4C3I-45Xw+W6Dm!t6)S7&Tzl;vUUuyqEUaMT06YY4^BiRY zs$S!ZUJtXjCgKN9(QCEnX+z+gXD@@(0oDY#z+mGDtR*ZgE(pj2F^)V`WaVJaqA%T? zdG6Il98{{}UAi4vFCLk2S_#3>`lcy-oSXH)TLmKK3NEyZhiM+v%%`gGu1Q4P9CTMQCy`~AE8oh5m%a*9oiy2*X3>)1^O+@xo|k_ zn2I54Pm$<%1cnSMdDrY#3urf(lb~R3Qv2=paFd72%1+1|1nXMlrT$57CYn z@gINbKk&g1{woI$9>m(h!?tEy1D8j=j2cX~l@Z#WRB1GYuakK6od8WyI9d}eZo7UT zES&K$s`fmpfUMUhJNXpq(s@j+O1!!(MKlmEQmInph16nD6k5|WQbi9;NM9?UAT$~s z#1KYU6OttrY!p-AP=Oci=&Z{p3W<#M0Q;!!(;3Iae=N`4m# zl=nW?_}o?kt8&{C=)yXJ{uY7F(Mn!lQsIn5DcI=o)Ui`n16bQ7&pQ-F2Ybb#t(DL! zFDwQ{tks>?Nm96tkl~XySAuYqwE&N^$b3Uv41}snBXKBl=b%>S@W~6DSXyJ>{0!Cc z7Ka~yoPYVpe}?fJ#XziZ{>z(59HRPdxd0}m2otoQnq-3zs{@QlytZwDid*y(DHzi4 zqKGhI0+@jjKQ~+|E9=9bTfE0F!x;wTUo6h>Ie6NlFn_ zD56v-F>yi>7#h=4Fm*jY|4Z-UnZu`1SPuR4gd#+bNW#&eh0vRRfP9bLC9mL7HHI} z1h$U~E8O?x2Ov8I?rchC#RFs4GL6Ks$Q)x}8vN3KJ%sER5?1FMK? zSd*apTPWK`nL33|2IoC2Dlu39hdyw_?{2bYPP`H0gzBq@k`#Agt2Jt%tckowH>tBZxrd&P2#TJN zG8GU;96Paa6+jTwQL6g9hjv<;h@4hLM&GHSB@0G^ET@Je3bw11Sbu`nqbzw|V68=O z%V|8`MQlR)s~bFhX@wj19UydyXC8f+Pk;Cm5MLv+?vw)CmqL!w5GSb9Ph$Ivgh>+@ z3KXfF!s>{^1QbC8VT86h)+A(6jMECTj6he>fy~YjAi-NkNi=*;QgKCIg0D3wk^Fg% z1(nb9pVMk339AlJVL}l`0#U~aHc80?X?^WS5rsFJo^o*00Y3W0M|teA{YA;D7eJ!$6J*1)C369&G8px5m(J~hGaT|1!Dqp$^Jg{Xs6>-3sqWLl9$DJo1+ zo9hDk1_t7YyxO3!5PM@Rj7X>2P*X&j!e}Dj!mLdIUhvySBEF3R7bd<1wg-VmMG>y( zNpaU-PlHBT&#l2pju1w0;XHiuJ(zqQ6;woQ!iQ$c6+S{ym)|ibLhA}gZgWAdf&|89 zpwUA0+n$1?pkCqB#pS&VZTx(Y?0F9zI7i?d0S;r_onHK%l>n$Nb$Xe{Pk+IMH?qJmta_j#?vS$M^_zI%LXG>xoJT4qUTI z7AB}5AxrBN8L0M_Nb~^ID_AY^LDG3B?Zt%Bx#=XxDz;BhZC-h)8QC5+uqTgR+F){Il+NlB7dImw`pRR#l&D>V5rYPnNVIA>^EBG^ z2;yC2CX{(}M$`N@E-Y&+eQf4MS>;D&%dFqQKE3a)!+0 z*h)nb@=lu|jB!Cq(CLvWBp0lt*d!)!j>2fP7U(xrh7iqG5Mmv)z0R0ZntmYS+yuV( z97N&~`hBUHgaOu%q;^2=5nE?`eq+6-c8+d$14W27H4&b61pPt- z25+a7xF5w(*HzxWtn$k9a#sl5!DIQjlAI_7E;>CK8!dEo0$@P&Ks6A=az z`pu;9xkm-stLUvu1ko5urMSqC0sx$K6j@&ib*meM){ z9xm!)C#u9&7M=68BbrUlEU&V<*(D4#Jv+n?2dR?t|I=#V(aUoeqMj3pe<$o?t3Vk= zqZ0VfM-Cd{>kKspe$(GZ3h3$fAU zM$^~E*F?NNfKoEjq41hj%88;(;lu!a_-ZWlbs$Ei1|k+H=?p1y-e&C0!+k_VPS2P6YE`GI-{Acw|L~C$0;^X z!Onw_*5u-J`+nz2;LY>wnhqRy+#8|Na>o5btq*hY)9xY zCP9SbJdmptIJ*t~@F<-xSZRhGAEglqN;gdK2luF4yjl_D87|MH_ThI_r3|^&L}(II zz~(01MuqlBRg6w4$wNkR7W+N6+Ffd4K$hiKyn%dM0-7Ek^w-}TwG!_k0Gq~95E?_h zD$+=W7VRvfMadEhs3h1Z7Oxejf;Q&4pjm;2N20;d2Y8wr+4Q;)XozW=Z<5P#@Ry1l-7DL z>F<(qSe2uQC{fCSoIq=e-lixn)+fmWMc@jwGxR|Wv7Yj0fAz7szxv>(AIeIY`$_|G zew9aB-%*kcE!r2@6hRp9=Z~CVt84hNn`Vfu6#)vr#1snrZ0~`OQDB{t0!o<a~^mzXB3^pi@ zQ`XNjk=9hS%q;9_AJRArQB1#XK?!?=uI6iF!D?}_0m1!+whsaiR#oe<`e2a3uZ zQUlhN)m7P~1tE?w&XV`qn4~7_#foP0aKlk>c7Z#-_2uJl`Hoj}?)<{@8wePTCN^5# z>;|q~_NurV^2O6@{K&_ifL6-)Uq8)gs09t#h95XPtTB`*ms55S#~)u@XRZ-*aDI}H z-2G+FJ@pjv*aSV5_~gv*Fxo(`z-+9bf{GC0Od#UNp|n_P6_AA~+UB?4> zq?+x2S!e};6dpsR$c)CNF>LlQ+R$@>-wm|Z3MhNbY?LrhNLa%@xsmqRy~T(dC&E|(BEOhjcqs380u+d?D&#vf>oA+>b&#vb;5M>L(z#KG6Pg8aW zGF16~XmJxpY8=?pq8=E?b5H*sE;j4RSbj)}FS`zlZqDNR8vDoVL}`Psf9-2v7tqNr zLL3>s)2s9pYnO2SHB3^cuVgnSNCqrTXJU#VlAR+;Jv``9knR?y=%H1F3KF{M2%!~n zo7@dU{fC|G>;^hR!ZE@B1EZO!^s_^~WiU>_tDbblyq6UICkoD>Ao`yyR zI(?z0l*mC|uzS2ov!1ZN(kIYrkbVxXvi8KrZ6{Sy_Z_8&lk+RK-HT4AC&t0>ltTqQ z<2Q;VLPY_YN5AC7e8L5U){G+AKC`c4cZ^dVE=l|_uSN>;Nj zYtrx_Qx{xLVI9gOxOUgq(7gA``VgcTwSo1u8fWD`6bdwjB=7#-SS3Y0Wpim2E^;8C zmsG_WZY&H8L$EH3jY7^`ywq6jc3D`s@caf+;E0X7J^%_zPOqqW(9hYj7AjTVG}0uD zH2t<468w~SoWhm!dn5_g{}8{B)}gTNS_>KM3;0f|r>F@((v zDA%KiX9OD50T>S*f*4W|;}~|q32Wb?``rN(wTL3Jkr|4ub)b#Q`p;SHC2Z{m1gPi2 zy{b%()iC@Tgp$r-i(HaIPe%!qB*B@I3bWM)-R`n6R)bwLU<-oCNFx^oJ^x}a*gr)x zj#x$rhw%xjI28;EPl8{7|SaMc(sb*}~wnh4hoF zEEARc(U#xvgv-T_1GvT0o;^`|aOW`{Kk1he?)6`-MY~*z)=`4#b+871J!Kx4+&kfa z^|EZTl_ZRfH80;p%atVqDb>|Z$>J8|Mmp8V3;MZS{Of8mq{KOm=<#~2xnxUvBxtbD zT8GjB>sxE&4yMOPc-3 zz=erie51U9_za=9fyuiNB{*y8g*B+uMP-l}op;I;V^FajB;Srw>q@)rc}N*<4$ykM zJ_g$pXy^P`IUrM(P$}t&&>G$AORvhoc}N2$`aX_F(L+PKOLJy|SKhFf_027ro`l1= zA(B->S)$G5?Nxc}bzUENaJrTNkqc_K+nhh|X9kuY!jF0=$_Dtr&oj^a=bcl61xe8F#1~$*p zMg$@R|IHyyq29#yHZeH1vj8oEgjt*W4Z@W-0`2Q`P6g;FCG2%5@(gPNncIk0*Yek| z-_H_xO@n)$4N@B%0qWM_JP&yo4gJEh(#;^!?5u?XTq_Z(*3NqwPnAb0lxF~F>z7ra zS?S2gtkG)B)5p$2e+8E`Vb6Y;n-P&PBXjfUc2Qd^kc^^DDq)ZgA+E}ejABWGbtDz? zFp-@mFg{TCUI-|Xs-GI0LOO+xQpzPKWJO^ESXG8ru>JZ7S<#0$N(~HS*az)!+6=xo zC<4!3&XnBbU}J+K(Dbf80Anq1jv~~Im;fDWk~o5m4sU<)4cxeEiq*|7M!A8c>yyo4Sun#X5CsS{ID5U*YFUg7#L$O+u1wQu( zyax$GQl+qgW+l|v&=6ZED=!gw8i)2^j4W6$yp6>bISwlX%0R|9XwQr;Jdk9zZEjg%L&u=~h<2i4DQu73N{Q>DX`cz&zfqw49_kDoj#f7cj; zks7MIDS@Sy3OSuMoQcRr=E>tk60aZ>xG>Oyc=^FN1U|&l!hs?Pa4MuQG6|NXCPoy6 zP;JtWs+6J9bN31VX|{O}*@e_)Nb6-H0o(pkJ49r;*GFyj2sXCR&e7g6OLomwFxEtC zM;HcVq3C0c#~~XlZLZln%a4BZO{}i3$$~`@y6qwuL*QfRCKx91zIWYfhtn42srijY zouenubM(YH7;A!(S?QUP1FeWl=@j2EC3!r}8|qQuLn+GTKfr@v7?)G#CRTy9bh17p)hc=6ID7gOkigV56U*nxo_Ltm`nVW} z($HQOXm4^JI$PLY-`_l6cuMQA+4e5m-$bQ6VxqBHa*}Rjh5U$?#6O6IV!`l)ohs2D zzzyAN`8i6ymc6poa64+`JnJ*-Q)HGR49J@eiKS;IMe?S|Feyy(P2qD}JUD}XQT=wF)?H>gEri5oVfN}+4t;xfWK7;FAL|5mqM)D!0 z0*sQdsB5%e^raW^*HU>8()P5B$p1bx5Q+D|I#et@BItm{-Ug(xpMfqipR`&MI;W{5 z-C7IFy0Bt6p@FrIfH{G0hb`}d4&eMLtlcCCCn#|$aD-@V(Wg)br7KvM5xNYm8|Y|+ z+;+U6xf0~g3ScDOU3vbTcu(gF*4vwGE-wrT^PU+Jx5fx!qo)OMEYre5uCmcQy|T$8 z%Udv6p%O}bSo(|d2HNsoZQpD?f|cieihj;Wy+XH_vv_F@q89Af&x=#bSJoF{Vy{@N z?ZDbud7(;^vigg!RO74+mX9~U^~Ez7iJkE~uQd_YD6p|N3ZnZ}S?`hlWmLY}&? z47G@_{=n`Z;&s`!FbB zK}-A<;B=|S>+?827C}5oD27mmVR_+mJ#R!>LmCrjqNI`shFY4ivDuTl&R8ARw}diO zNug1fe4-LVHv<4!S1e;G87e$~p3)j|xU9 zs^h4w6`X5>QrIwsEFXlRf!9=)jmMi<85t;zQOf3>Eqd+s!Ns?EPEf62;sC9j+wb=$ z`D5oh>2j%fe*a8^>$M@)%9FsP^RNUVw@U#@p=Kz6(9bPBTQE{f>9xD8E^k0}7WVDu zmJ!XRHn9Hybk7TWQ-x3+kqa3_4EjO>Bdaq>p<0*4Y0U6Y8g%T#uL=ZJxiR$-*j$Is z(}ITivoG@y?GS_6W*oLOdbZ^p1Fy5~t8-{Hw%UZ&2-GXG*m@RL+R*92be)k#N@QU< zG%RoRIdyh{*X^I-$^Yl|+`em^6PMPAjE{;3Ir)&qJK((ei>e2ql{{aUd4(GOYn(l zgCi^nhYD(SdA+i}l@(weQM-+ae7C3t+MBq@_;JoMfRe{`{2Q<8vvRs~_uCl+#pdbiMCfCp(D3l4&^(LSR3`|Q!(nFa_ zQQ*jXeO@{@%3XUyj<5I77W&$Hqqrgu*%oz_R;HAqTNEf~8LQM--RN?-pTpD)ho;6k ze&Si^B^mqPlX(mHYsM$_68HQZt+(lrZWmUq9R{6ZlI@sz01x&SIejL(RVAhQe z;J9)3BtQ1*oA}<_4q=?-#HCdtqkX8RyvSYYLR{%O{ChfvUfzKduw8ist~@_$3*yEM zpZwI{(_T3t8<5X0w6CU%RJ@i&Z+uZN_B(p$y+tRJBs+Z0cS6+ z$lo>mv@a!vPpS=HNiSfdE#4^Tq1s#Isb^8-1!`*(TdPW9Z~XgL>V5>Z1468F1lnN3 z7*}-Au0Sctw@lW>QD6B#-3fwHl93Yy##ydy#cXDFP=Fdfh5^@QKXstAqF)p^6)@7M^Yn=$(9^JU z7e|^^CLdXc@d@^37x?^{!_Y_}3!u>wC^2_9qvZzWIW)&$hXe|a* zlH>-vqJmS0i8j~hQ>W*9QU@K9gRsgm`21IVE-`*J917%rto?^}O2Og^EEcfQm01*1 z;}A7rbpzUMc8=EgrUUcbwSO0{xO$FV%?b<4>-4e$6DTQ zHqQ8)kn2H(UC^!o`DHHWRvo3VUD#a11~H}A8wdvQ{56g8Ujt845}~#|hdjqbF*m;a zR^pKf8oPEeJu}YuSc_H?GgGUuW4z9ui5Bx?O`<@vvbMqLODhCQ7O6od>xwv^Wgwgl zgZxNBi=lCp=B);yprTAl@}gj5avy*3KmRw6KKym!TV6pgEEDlySm+CU7!^jIV(xQ- zFc(i^orH)O!(^TF!^ygG`G0PYHwYFf}WC!*@r-Qq+h)r@_-X*JzLt}(+?I^i5 zQWH@Q709S;tEB_eAR9#Pbp@0bwlBueh~u@a+o9WO3*4N@Mr*WmY6jN2rqX5dDX7gh zM-HCVtKCo{hw{c;wwUBM+TmHTioCEQY=Gs+*=2$C;)vNoaj~-nLBM0rTtbkqMCuyA zK&v&b$-u{?0#AMxPCtoGS~A!rl+g(TNLHy^ccY~(Jt#5+6er* zzUfgcBZa{z7NjuCpjPo5EaPh%T|i?q2b&!l^$M;UQw;;^Q9!06*-R3jphpJQH{`YB z7%nY%0LXi;&X!NklVPrt)?jOm{=zv76&smDg9Oa*Pi zK_4qxyhh&`VTCw{(FUgsS;^f4h$5=hWP^TRu0a}8F^Wd5%CF!1#P1|QAVT|Pzx;5g z9QL7$2VtNgTkcq;&k)+59z4HfaX27%j_F2)2acWR@6N2j_$W+{@}-9#=FzWy5n>+- zp-~&>qy%qLPb*O#5K(eLr>};_4qqJV539Y(iMPNf>L`JBTN9uo@d7%-(S$Kmy z{W*`Jag>I!y}mT~zH~!IvKs4!+(N&etad162@riIkLtQ3!nzEU!uGpht+X+Vv?op} zoH1B~j99=CC}{^T{CyJbt^(qixt-(3C#U9^+J6-)2&sf2fA++w|MbUSdg>Q0WkpTn z6_JK^b@>8;A?-qL)%F!4iAOo^xvVKT=O~GmQ_UKmeC!0AT7gCt8cjCNoMNMMn)vL~ z&~2kIsP3{P=jHAl`1#r8w%Qtl`lxVuvOeH%p0C7$Pc)Snukef{Nh7QZdNYXHuT09f zf6n2*rJDYnr}=9khw;*%WiPmVDJNEeQ?ij54?Px2Q=lp3MFrdU4zo0o(o=8CL;M>L z?~O;itSy)r9RU~dwjcc!TwyVDJ7htZY=f{2t$Z;si6wkmU58r3$9mptZ*&<9Mf9|G zfqz2@y^LYJUcsaRCW(pTh(;1Kom5%f=s+65L_?C?OUuw|$Z_rzY;9mGH5oy@u_0c( zS{uTuZf{n zD*imR)WDv65H{8bcD{scbVPV~09(rx`6X~;5LF}(EpmTPj1=EB*hpr=6gnU`0b#Ez zUNW#aZ3owsAcP41;Zu)(51)PPdzhQxH{NpBe{W2VROwr8njAm*!kN}5g26)Y*WI%1 z27p&JDS%*G>b^aZ^akSNWaY%0795wGwe@B?ErI7w!hove(&CZWt>&2EW_>Zsp%y{@BJP@*qMK8Jg2` z{QiG`4}bJ0??TPriK|v5o{r)nQ@4b3!vIEF@&=byU~~*91Zu5S1ezY8U3%zwOaPrO z)T@Nm7?*@p+3|xEtEw1RV1rQNZ8CXIQit=8 z!`eBam=xW?`w*wNu#WC*`RPR^4aJFqU&#v2b6J8ql+dSECu_FAQ{LHFU_Kl-k-7cLC$n_u>B+fF*{mB0;m z)Ikx8ZArNgpv#|ucPJ)fw3aY8+7zqBw{-d)qi-#QRZLJp6`PRz>Z$8PeM0)9dp-GE z4Jd$>tV>0pn>* zD-3By5sfHdGzyq#*7@v-3v!=!PVvU6aaPYS3rsvSE}!@CQz)C!tyX-M-lJEwRN!N6 zq@nb7@*dK<@3;9mJ44L^0#4vjYJs22GaCj40`2<5$?WEXW`5@kkpfS$Zj`SlH-s{N!2KGszu0CYjTUM~W0C21WUBjb`IwzmY~8XxsMuTVxsA3FjU&S7gUiqR2)nS%(FqIc|Z zB)p6mKt)lAwFRozL&Y&R48duXEwFJsgtJWq{Q}qRio%Q1^8Q*uJz-Z8GZh=Yc{Jvy z_SSjHbd_^kz3=()fBf>pr`Fr4w$7a`SBmUx_R95ec^91zxymrXVJRg+)%GbtE@NR~ zlRFOXgnCO*lT45Y(irM3@?u?{-s77*KT3!(R^a8APCB$lYRH*H1Nu7@Dq^UO!=RD+^2t~bc z%-0fXp{5yzRANIK8Fq{{c;vz|%d6|$a$ufYM;eS+%ZDF331j1elr62JAH5%~tF$Yr zc&+ueL;~X@vJQ+9k9Xu5a28i@!0fC<2dRN$Pmrx&z^DWnC5iwkoxV)d4-<1=Qq2-35x=^HzsSbeT$*Zi)ItpI}PnzcCD37un|-zarESk zw3ZMmX`(}4X-#UfvlInvZOV?2^`z)Dy%W815!RMrViamIT=ycfnSIn&7W`Br**#ns zK;9)7c)+rC^Z6gjUC!nmghQqGD1Q?t6fWl%fA%MM=CLE}*}s<}F9z4I^udF*?>$F3 z@mG~U-1fR9tzA24R$?Y+CP*qN|MImbc>DYQnLqsa-9Q`m-9$0c5+g`UaW*T9dZZ2` zOOX$b$r*ZsgAQz z(E?0VBU-H*V@bqB95R~5#8F5R7|yM2LcMZ&*I4tcHYQqi4y-D0Rte?74fj3msSTy- zV7cwNa@9DI)->9W)pCx)6?6({H43Rj7{0s|Z68q6RdkWT-q= z_&k+{+{ld$K~(w@o-sa+b8E18R^nv(FuxBD-b8Xm3R{_{j94KOmW!#>v5G=5I_sqF(MYJpAtNTF5(JD^Qfif$`%hh> zo}|2Fc7mxmpr$qdbmW4-$gKu+dzkw_4`@2Iy3{kaw&eVFPRrL)c;ksj(47o7F$p_& z!HH+k_xuAvZJZ(yl}M#x!4R|}ODb}HSr?a7WyCZV@|+@(xt3@%VC>Rvi&{&i>cjF5 zVY9)$dX3R2WGsoPCz@&)5C$O^S2x)mMi*MGF#-c;l(WMa*$wZhMh!Htte%#-qTdU%UdG&Llcfiaa@P$9VAm)-eqL_M#S2gy1(ib9fpiqYLU=fpap@KORbTD(}(n!|pj48$smg zBRF#w_4JdN>0K0~qo5S#YhOUcHELs1tUvm%XadlYln!)A5mhiA@pMrFQGzNgp(|)t zMm?{~lQFXl)83+}B(e!?Y(bK+f2zq$9MFnG#?p{h91$5q5Cj}s-ePYpInhKBbZoXM zlSb8`*RG7XWmxA>he%*ev&~R6xI(JTMpG(bDhIi>?A`kf8f*ur)Sh`O{Z;b4)Ap5k!fm*LZu$dy#xhu&k5UJYPghj?5(AY zrG|e#enA#|t0n8=p3mW2k6>hsezhX!SZTn%z2dcs9HIpM(EZp9`a7q^3&tVEtuLhZ z#9_8hJx$&|OC0T>)W9sF1hhtV`{+0&^Kf?5>7s%JmsBJt4z#Sd#Y;l5pB$66;Po|_ zn`O_;1S4889Y>^bKqCmaYIc;ViBV2&w5e7q#~!`3LXf#))u9SdM(7X)Wk0yEd;ukb zT(~jLc`V?r4=@p;V1>ye?OEGgD6h>}h zwIRRXT!uY|z-7=HhlPtUy(kHJZIoW1Gj=mQ$2#eLDhHZWTt2@$Y~9r?~3cy}b0LFXPhj!=k29 zdJz0IDJ(6mxNWM=jZ<}2uiC+8Clf?QgHetM$yJP&DY9CN&X!e*jm=G}m56`%pYP-a z3#&YG@6&MWH9pBshGGa&D7_q7W6~N$*Bi!G+w-wJy`zM7(-_(vSXc(&&}55=YQ%UH z(hLG7S{2p{$1^K!ChJuu(~y1j3j3y;eEjG|9zL@QJ11d$3Lbb6_0ShEwcWHU6~B>M zIIs^!M&$eY2pl_#ec}n&yB|jDV%V)F!M}VB+f}%Ywm{v=P`C_ZVhWR>vm9e{8EFs( z@_U<8q@K2-l!Vt&2%B42v_Q;T9q9L&-#g3vc#9FEnHZ@s9tZ539p&C<&+_=aPjLU4 z1%BqWcOK1jgbp$1tyO2lXL!wBg-2QRU^lhSiTZcR+bZ!+|5YZi8c}*j1iEau%XRyv z;NY})1YHQ^s0)cnC--7|XXX9E1Z*a>^rRvDAg?gaNK4|%=gZr50iv5p5m{tK|me` zD4RpzRczuEikz@;|C>?AI zLtv}jLYJUS7~I3Y1esK}Z-HA{`JI zLtqTXD3lR3L#-4#2w6IJiApo!3xD!ULM=bHB8$xU-tIJHZnVXEKgT|GM2sVihelM= z;nP&&_2o^dRoOp1Lai224?`xSn9)X+)x6-?>LzI=VI~cjP9i3&75?hfI)C~2MX~#d zCR3Bs@U{EkvCm^_dqsa(DFFxe?iFLe!RQ$J$TJjAKMDH}$~~^sL4V74 zqfJS&xpZD)>PjlSozZEy|8Dq~zk#%l8{di25j^vVz{G`<8-L3yQSDWk(X9=rcp%DF zdXHfa;d~8b7&;AEyJg*D`2FzzLnBdWqM$_}spwUniHhqLRO)GH}F(ukH;Ts_g? z-qj9wA72)DxDrzv8D(j+3xEGNKrV@T90DOs&kD!_kR-&~5q{-selr}E-M*U0`OWTO z_MtD)KL0prFiqjQWcen_1O#!-D=HS)FcxI4C?HPBDpj9*#IlB>7*>~MLRJzAKoCY^ zj6nb{1QIY6?mn~1xwETMBih>F*6G$0H}2Z8I?|}4l~Fk7o>0zEAls@&T~6&*jx-He zSlHmq#T9}yMli0jTI?a-K$J6bzvC8#rB#o(bN4uO+fv79v|wQY9=i|H5%Sh9^x_eD zpI%oMP^BiY@1={97$lLz)9dGD?ss8jmuOAVpWI1w{4kt8Dm0YM1vq%UbfH~3BYh=G zQKzd2YVpAfNfoABDA5IZmI4FFgWJ9F@NtsrlN5 zKjKJY_4Q3ScM)7ceX`C}C1fg&m}pc{QNXFS4OY5+Ch8R?(~!D>>nB>A>h^i}lNTh> z4qJN|Wt=FDmvZ7C?QPgsUG-y)SvyI|px zAYN%z=n=~oB-u(*=?Q7HV02dMBTql8dbv=Pr?aTb!Q_GW zR@><+-kZo5-PqR-CV)e8;~|pPLZ2X_-80Daq6XJ>rH|qhfyo56t(JbL#-s9iPWPa!RVQOXooc^lYiUeYppCp8DX>6x24O z*cMd{G_p{j#0M0)rEmz6rW0EA20Gfo+y31T@a22&V|3R6Y?d(~E|#7_mp*v-|DlC$ z`^~nHYkR>{DMyB8bKx9s`}S|*mw(}{aA8e$(KHcn5r$$Uk*BW*o}pVws5eq7wUmig zgD?oV)b6m->Cs3-rs_3DO+*qK_KYsaT$iR;|J667lil=te`lMd75NocLrND%do%gN+Mu%vb|K?8T);=Zcz)BM71QfMOM7r`KXYY@(MEkjQnbbA@|6HVSYHv;R+(C-KpVtfQnJ_Ge}h-TsZ6EHFkiT@L>W3hWQ(dWu?yX4!{JBdf)!50fjg)NsLH zX(R&@p&J^?fQmEt8(FxLq^boj=Z7e)#op_=IOW`x-|hk!0B^X_m@!TBCL5 z$3_^hRoTolE^T&kPBGD_Fjh%u8ch^At{QDIKhfgHKKnRNt_3hzffMH-58%3eR6g}D zxF;Ux`uF`K2fy`BKKscpgA3rsL-L+WYp`R6y-R18&i0#h&XG z30I3Gk@}1AVP@G@-04cRv9ZOWxv2-Po|`$b)$0*RzAAY~Vfm8Pm)`*GsoGkLrjSk( z42nmNUw~?3z=R#7vCd)wg9$>Mbt1W>6_J5g9-I&)!FPx;wF4GU2+Fkc2GqGnq=A2{ z4;M~|3!d8#3rn!NENIKFS*Z%gO+mfdo4y$+A)nd>C!U3imxQ3!*?{qB5yG~Mgn{%C zxIt)CuJbDw-OyvWK_WlMt;Mje$U~$*-iVg(;wm<`Fg`|4Yo_*HOMCSq-}bF<#ab90 zn~*78+d@>w&=WrgRvy^K_J2IbPEcC2+U{c8EBxJW{{%0*>l!$ALUvYzu%egN1uQWE zb33M(9jQ}TSlsN8cMDo+$XGR|8Amk3h%nY17;iE^*5Ic;_8>sBLY<(J7M+oCs3f>f2UFyvNh0+!6-fA0^@Y(e0sjRzt9ekK}m57Py}%Fu+ z@PRM$wzs_K(bFIxvdyBEaFkej=i465Pq>%(vBE@wRBg{1Gy!B)E^YGa%xOxvf zb`(}Ruy2O{=axO(f8trV>xKNA?|U=u$QeHLXCH(;``|^l2(;YkaQzFfXZpeWS^WG1 zaMMk`Kcx=!3Y93}*72rS@`|F zZ}3XRi;RrG*)#MGKaCn4^@bBl=vDD`G7zt3jPJwmi3f>ONHs6LcGqVB0xSVmv}KgC zSnKY0&TOL}$(6UtktUi?K7D~t-glgrzwibKBc45WhBPvi@JktjSmy|$SX3vRv^aLU z83$%symH^DAXD9S7~csK(?U;g?gTRdNA82&`{C?qc=}PvLv|m43m0K)Rbtkw4uI0a z_6>buXy+`n#?iGFoH`0y>+&J`ZE@v0uA!I92P?r;J=m;EwCD=6c%IhVL9Vq8&C<%C zkQw1N)VL6$AfSEb6l}CHqf=Nk%^fe|umAeb`2FAgebTXMl<16JR!4fSWxB!nJ-_vM z3)v1X1crr+i;Og@JoS4&2~!^aoZBG_vzsx#w9e-G22GXq9u~6czG;5wj%(RiSmp5fMP7T`%^ZpX-u(yv3odow6)%#; zsO2?y!L?kQZSa*3`~&Qmg~@R#YBs8bNy5(QSsF{1IQ)UXKoOE>-K(r2`GY)A_&nk6-q(Dac6mOn@=C`kxUx$&(ECWb$@<0|L;9d@<_knk&7GX zdS!s&OB;#9T1xpZY+cC$p_!)!t^}rlRHJT zsYHghExI9l`MC>@(jW#4*ljO?i1$zi&=OiY3noU#5nE@D(c9QUjf|sf3A%L+@A$=E z=A$3^IHR+Ba78ZXds%hGT>&Rw@fe)Tw)4EKP1j*e$kMq5uG%}x=l}gb(sD(^~0j($^O(McLU?PgRab}WdH#@xbQ;&jf!F31VzK7xT66~MgJ8!>= zX{CAp zvxAX64Z+?!ZP;^-xW=R5Nh=Pt6*VGa*jp?4(;+WMI>@G z7te`1otlGPhoqj8=OS9x?}4&JNdV6I;VFJOUgj{aSdgk*qi*|;wyi}!x=krip}z(J z)+FRMBZ^YOIHrH*2*t7t25L^tfSKVtzw>Pze(EX4=JsOqKAxC!S*kvK!^_rwd3Pu~ zH?Mq@YV{by%Eb%3;<|%;;3wV;tDB%Sv!gY#dca5+G8V?9aYQo+s4LCBR*n6mbv|_R z0{`LOqcAZCyQko@pM~QW;o7~t^p5MesUGoPAASfXC-}Wr-oaNs`Y|5(+fTvE?i7^} zq$vC5J8q!!#d~<{<9EYN*GWyJS%+H6Opvf|?>yc6@8#&feh8zQ^z#kT7&9?Nqe*Xc z1UA=E{ah0IK#QblIsK{P8TS=qRETgYH?!j#|RE-ndt|Q=1!59fjS8P$wRQ<_uI0VV=Aj zf(Uo>8_`cc3CACY>#vrbMS&$pM^(ZkCcAV3E}ch3HCl(RCo9^# z_H}P%Yki&ZT~jjueOo2{@>6k_f3M_SpNnV7QHG8lUSad%1>X9a7x4!_`EOvoLsV$u zFr<=%)S|$*iv(OVHAXkLeAg2v`QOKu;Oc9k8N)|D1}B%`<^#O=&TDzY#3=84>N>0oqQ`+8sW+f*a3ndK=e&N@t>iQ+`QR|=z`==XT-tp{K{ zh0|xHvFXt5xZW15oPn!eM$tJA7oUaeUWy&v3t#(^xXT^8;KHg*ZK~Jc(7Ys_xr50` z%*kiq>`@VLXcu0TIrNP+eTuvoEF`z>cy2mAT%2WSrMLZ1kSzut#+8N?Bv=c^1gNw| zk>!9xg(*R$PQH4H-o;aBs~NfaE{>mgoHxDcdqCG19UBog?>9J)fwxdYmr@$<uz$xWuH9v>S>u~` zP4dCteHW|&*B%gKt5-;>6`E<4+h%ug;{ESs_0qEhdK~Ba1STPl8+00VdTA>438m51 zs#L)14X}TbF~%s-4km=94SwX7 zL;tj=)y!8nJLHzZF}U{%C>&dvwO^tXPWDB^UIC3X;;Kf4p3Sj1t{ZQ0dMo2|&zu*X zSG#S|DKU~bN-)Orvy@_^)8XZFBYfXW4oI@Tx&kAUFnbW5{sPP#0@cJk^*O0y9J&dP zoP%c`gF}0T0etGb#Boz2aA4Xa5OsfP_=*q0xI+i!u<3XIiA8+G=F5jXGK&FU9F!xJC> z07j)0u7g$;0v$>2TB!+duh)lK6`T6j+%$%MpS+R?lph#qjS?*`(0$@j^!SLVN`|qA ze2wH6Tax4x_`dsko%PjKICD|h7>$VUxcRET2S}YGWq4@u6>=1=po{y^Fx`|<-YUx8R`{)FFM@8s?!8Qm*WvL;;nVjB7w(Q*c=Ic7 z;f=F(-t*|QJiA`-E3bVyhg6$4|Ht2!!9lOMi9>Tu=AwiXYpcBTwi|frBOl{)@B0wk za=YmCHtX!HHh4i4arNF^j4oZ^?%(+}xrRDMYa`lXQB0PkgnjEbK2OE3RA6LOYB}BR zP~$5IF||7NuRnlYxrB@AVmL|Q`$8f?sW!JH)G`5sxs@l+N|e#-F@M9}`|rH*(7k6m z1x_2R8Xj6JO?d=b}^#zP*3SP=o-aKrbhYtsdGGWW`$&Y zl%f=D8z3>7C{D3jfGVvNTUo}A(Hj5$mDj@Rc{pT62c z`*Z3a+rS|`_$uC_@L`Tq3{)r`o^5L_F3>WtezQZ7IZUlZ5T@i=1}4M>Av&%RMNRtM zC0xFYiT3f!zx0cI=pX)(#?&sf725lDb8uJKhOB(`ja}dstLXf!yyyu$ar;6{26-N*a>%kRRqH^bZvjMkWIHJGne7@wNt>Slv4 zz3VsV^;ZeB*lhaJKqw62^))zlQdA!|+Wf>FH~vjM3g~2QoX#&hbYP)8LMuCF z?$jUSq!4JG16R<^3r4FE2b&eTMNZ9VCX$$bn((gAe2v~#n?|jIv-mM$qC6f%Ats8j z&WQ@4%dwq;Z+^jkxM>F*IU+Kn^$~HYy*3&?tXx6nBoO&E8b?H_lBpbj|jS8U7Ti*OuzI^Xj zY3|%jk!R0c^V{s>8_AQ!z$u%8G1>ODO5&{xp9iB%mDVF(iXyRXFsRkDpO zTul(GI7un$DRHX_E9;OIm}XNvL=v&v7&vka_8y}D(pO2LaAifBzj{5G8pRyk&F({c zMft`co9eL>u(Bp~v|SUMufP534{ni@*qo@yE<3bSmhFd91sU$Ia@;WyY;%KzRa0=) zXiC!|`;V&3+e7eYhB zeV;^s>Af&A1?%Tv>KYUg=Fv|G5_Q#$aB2N* z(pq;Bk`~Y+D~Xlf0}NtxN@&z)CxZxPdvq_)GTUko!_k7$%dd)@cBR3}=ngifr(nlC z+1NO1)hdBbNtA{nlTD{6P(eyq8vzwitBn8|ue|GZ96Nr3(Yd|Y?72n)!+ibu%Os_F zZ>zhjA#+&+Xv50l8aEx>#l3HNF+B7L{PX8T+5PsLx#ndr@K5|QT-bt_-onn^SYN~NFk-8bxG@6siH`rrRNR9bM&L8>Dau9+C& zV6#OdiFv^_hgkmnC;7zt--E&rwowt78u{ElsKl6arwB(TQ9(?AqFb%P_&B{hgSB;N zv?$UTBE#(X7$;7jg-%AbIs)e}z%$Rl#FQXhqoc5M7j+kKLOWq*M3JyCPMnra^wK)t zbH|~-y?WQgrBi3mvDMyu&Y`VxJog~a(eD>luitg8VzZO8yO}cIh}p6Z9Rx%ayy3=! zeDdf8KK{_N#3LgpYX_nT&RU`CGJ2 zH^m*3Q%q)#@49gxbDed5;75K2@&sSke+{;vJv^-rC|rYZfA5!poQ4H#=tk1BBX5`+su9^vm{CrIn6|(ncdOk1NS`(nIkfO$8rq%MB*ftiE26!rF0Fw zZjL#06{rPh?GpBGJOW$GFgk}a(*zHESXy!S?uT@W=+E8?Ef)E%-D)6e8 z34LIz?T2+J**QRMe9sgTi9TF=>hl>eL&%Ieqd;Ui+GF1r;(jF+q{%m(@eI6DYSE z&d?Ko^z-XGns_+9i&=OwTe4- z&+>s2=lI8m4ik)zQCRUI7Dr(%Q5a#O5UUK6X~bizU4HPfvlOj5-1$;C_MqSDa^YER zF5<5K7V_1n;fXH_?ck;x=s$d(o&V<_;i@@lxXChDya+)dv&mokO6X|lJHfoW7HO^Y zjbGcEkV^LKfNnl0!VQBIg~TY5f4Jdu@d9x!r-&1>)~tj9S_`y@;O!_Ss^YCNI@5Es z8>95Cf$Avz+Bkh3Qn(DXCJYmr^}T%ME1%=dZ+LFEZXTaxEUmC}W{elk&+^`1{UweZdxk() zaVka!b&A#`BnjsHd6HgEcJ-G)*_vmfc15_w2T@b#nxjOsk3*y#gXGDa0QI) zoPk=x-Z13)SH6fByx?jG92d7Xm>Hd5X>A!EeH!wRvGqRg%U=}lF**)KpC5VU3*J+W zDimRWN}}gIbgpHZ@|8zsAk)FSivcPnH=~;sjHa6X^@z+#5xEj->QTV1$tHjLm8Zx$ zIrUHv>Ice7(i=xHTFV5{ZkEA@Ps+dJ-^!qsC-0ZUdu%6k*FeS4x)puj-@*C?7@L7x zU(A{JeVSK2^e|j^P^b&5>(V4e0k>QaZ+bPX0C^L}#{ox{6;RFxRAL~Jp7@L!3{UTjo0u^2X}Gv)F>0$^6j@@jlFoDSAW~TXYEp( z^u;g0Of|WF*ErWsPB2wT*frkZ_19m^2Y%-_xcl>;!l(+U2p1&e^%f)v;qn?$x1hUs zuM~neRxxP|ljFp>g`;Po(ZG$=pjM?>sdD<#0!3z-nVLe4*SM`!W3DyIowr`Yx9p$h zomcH-rqN&}%Q*MsGvfYd$5|pHJAO*Exfa&hb<3W|-teMp{{GzR3W+U<^89&^%TTP# z5Us+Yoc;nze*tXCjUNaEryNrGp-4(p3BvlrUuVdw2wyNSC0Bf{m`F$=T%`P{F) zi=W(Dq5Zxfg**=4raaweywqsYr#MUET zmk|R?FH;6Bo&{`DRGy(0Pl4;h#+rnFrXq#5I3CVUjAHM}49-Djb3&VurA>Mxb98H? zWMM=SCPd*Z?|Rqo@F#!tzo<^^KxvV6EjKEucMKQ)09+V6XJAF!{;@4BQH9JDSBk}U zhel#p_})9=(Z~4L?|+BH_b+=nT)T(Y@1NzSkx^#rDc|+78#w#eW4z`KKTK}}sxN&Z z;p_-6*fq_~lhaJrDol@6dBZK&@wdPKJAB{+??;oMbp$%ZrFC2q5o~M-(|dLn4jh7o z3v%zqC)pd-=stBq>Kv1!P)$hdb=J;aBI|Tvq(P-sVPCVx)%6}c`7 zUUxIsZC-?DPQh3c#u}*eXK*GKCUJX%pLyNwf8463EH16k>2$u~q1`~_|G7t@Ak+GT z%9c=}B&)r`F_i}FOGUmXG>W=0B%$Vo^P~Lr7ar&A#SLopRM@SQP^lvf12U`FJKp5y z##5Lv#XW{sxl2G1w4R$5sQ}6iCymKPt zWj9_U)zgdTp|0TM8NTH;xA3cf`2D=<342 zm6VnL!S<|%bsR6Tu+5VgluB^b1{7N)7mmTDvrr8|rQ(h&H3_}2LVc)8Z*-np`kU(% zn~Ugvn<%V7eFj?{rBWY7sRlpsQ$NNhKmI93W_Eiz$~Is3{9;~|YM7V%k}IF92`qB2 zH7smwGM;Gu^_F=Vu5-g7X7)_;9lLk3w^8M)nI>U4zx(Y_8DaD#w=*&= zVbwKL<4jdkW?EI=_`+NGyLbN)fBe7x5Je=y=2%D?A`RPaqfJb2dKPZDUcBy!lQ1<) zaor(~_1bi|IxsUy(W){vK2GQSCD?3J8yf>0qvLhv>lG%_h|xG=EL2QZ4I|Zv@gnEx zXU_=LXkvnBs|_bkz{mtFuVZfBd*s_*a_gU;T;4(@DK?D1;i0Y4e4|4k(fBVM`k1y> z3TsGZ-5A(gF;ta7D~&OZh6%WKa+ELkJN(IK9}bD?}uVb3Op};3C})of=~YZJ9)?cNq+Li ztAu#eUx%5LKfU`2(y0-??^QRGTj)-Xaj|Wwq!m6|&kYj0EC17g)4EWu zsHnXEs=_ZA=b*rn0E@kxja=vqI7^l*>aCRbKYo(eOpmd5ZjAm$PYP}h@b!o+x6Cvv zEHAC|;9?KRU~Ue#)kQt^2&%h=o82Qfb@@DQ$8{7J?uK>-H@;Z9$!2zO{No=d+&s_Q zf8e_q>sl_HJjMEsacrUa>?6myeRhO{jRp@bukqN$OWZuxVyuyn^)eZm=IaE*EM#Z_ zmyJ#XsiOPLs#2|R_WS~W@&1qCiU8fHw_fmgnWn|F*dpN8BxHOgouAq_NRNyJEM z7;8k#O-=B~iA(&_2R{#Ud!ZJP-}_nA$_8%N0il*$z4y%fe(J41*hl5L^@^bXm&3f{ z7k+jSWIdO7ys;>5?MUc6SybOp%>4{h_?MV2`@`&RCNy={TMXoV9&MQo9y6` zzyBa3spa2&|C`7zY|>r0Kt4ai=9cBNPo3bKc8_!8#28O5w|U^)CFW}hJH~6+en#PW zRLbwjmkB7SGTvuURwx%*rCQ<4g+<=?{tu(5;L;FR9|aZTqDa`$OJ~uGXK^Fb5^B*C zBIWiZ+B%fAXsw9+Y;OXEE^=(vq2FI6FbNx-OZ>~nKE)6J@DEaN)o8D+i5T9Mw&A&0 z&-b|Or-RJir}YXnjy?|E?{oY7PE=9wna_Wo|M;K&kRuNshCA<|wR4gkQ!TF9Im4lu zNm@zFzL{}e{el~L_iz0Uzx}S?1r$a#2!aae1UEWOHZ_UMG)e1Z2cQVziaqDP>yFdMD8n@icH{E=Y`2cp-Vs4n51ec=<&3H9nvXL;J zCe)iPe(}8@;ncYOx+0%WjI5 zbx@+JZhc9ims_S9byinadFaBX>}aEt;4J2;Bk1-bHf>14e&T-A#6I-uCLI1e9D1RI zEjdu1=8OOPeN0ZY_}=e+J=wVxwl-EdzHbIr`h4oKW8Ai*!OLc6cxEf(?h~gOb#P#2 zoXF;M3+D&h_%6zAMUX+etMX+*(MBLA6n!5CaxL9H#| zE}ewNxEM)$&8rT^(j%=vDTB5$S0%(#>Xk52!yrH_M_^*kUO3MEUwMS@d)vPy3ZU2S z4xNYEmW!)W_^hkisf9P_vV)*$LiadifF&x}8&CmSw?{Ma+qg21?<SQR=EDaE?#`~9{$tM{UU$zmwyTv0$rDJXM|8|kwBuW@+Rw9(Jj~IxO}_1QuViQ77>NRQHmXD#RyI4-(}0W{p_F3%i=#Z8!)fRzoBTMr)pi=X_S?^|p4Y_H$P6~+JW zVSeG~fBqYWScCs$HuWR)dp`(y)d5%~Dkj=tbIZjZ6h@&6>*I2$rZIo|&|%)ZZznrv zTXZ)%UI$9hBkRP_=IbecdEz4UjV$<`JFyoQFc;5Z*Dk=$>wyA&{z;1QG|EoDUyEDT|-D>e;9TB&gM;sWpgz$YkFl_>Bun#Q<;s%Ry}-A<`8(0B zOV-N<=eHeP*#3Y0JfC6sSp>$;6<+C4@|RAxPpw|zjjw(MAAI~AtAXRz1M?i37-#p? zDC4z+m)(3Vds+>C{NMc$fB*4+24a~6r8G)MxO$Up=ME9kXlEkzbZ{5Z+4B@nJuC7* zGgCy7BI@*w<^gXHTQI7IEvB;J}M<3rEr2HNx@h$zw`Ui%NO4LckJ1-ndd$8>9l88@sf}u6aCOixbyy_B%a~Lr)^=d zP~=M|mU(cc!j@8j;ZhEhB*a=Gr8yPSdi2d7O za{XPc;0Pl;GFgk}>BYnL22mKYaPZoaJyzxm;Q6*V$3o{R0wKjo-*sca6As2?xa;n( zkS3C+|J<`kf~I9rpKO3Wo-E((_I&UBcyu?hdPd!nMj(ZtU2ieIWfM<7Zx3HOaD<7` zeoh-3WW2wJt1sI}Yhi|$zVI5pdH+2~db(dLQ6h)z86ujPw4N{R*s3dTn_^<1pX%`$ zaI&y>H{?8=dYy^EenxupZ0*T2(O=}-$EP`C+ZN8)G=^^yN?D&xJq3~^Wxi5psF-D_ z$EV=A3=IwPyYIP)14ou&$7aYBc+#dKyZi*VufW>3XV`c3#kc+OYo7atnWYud?!5UA za;fXyeC_uEdLO-vG-xRgh$#L=Wpr+1fopA0!XS0RYNSbp#7!+1ODBfWfdZditq2~(v!hZf7sRJ(V?;SpPipIAh+7wiJvvX6A;UWC^J?jUC7 zX2?&$uCrim4JL*`=lIOuzLRs#*~W9OdJ?VKRR)db!Qp=J3Osc0QEpjY=DE8^xL|4% zcdaz|+RO?$47-MU$+(IrPAp<4x>R?wktnAmNG{95N`?2{@CkIN@ufgXkU^WcIMB6o zNI`UI@}7t9dFaJyWV4JJ7_~Qp%9`!_q=}tb$n~Ne-#)LlFtq9(n~(z1h$N1=_3JmY zX>uDEU48{ovtora#76ybYPHn;X9E=YXF`bX^K`)(VGKe#1dS?Nw@%04iHUDShj4G!sap9xs{2bL5iLtU+ke? z?8WOXkTx5P_4P8|Ut}=vF*eY{BTLH^3wd^p58;tAko6hOXXx`JVY@>rV5lccR!L6V zG{GlsyN5sd_}45QDHdG+{EN8pRnKC_{sDOqUUd7bND(rJ>k%j7b7q9ou`pZ*y8FSv-a_MJ~$TgRNVeLDqcI5h+QrPufr8qTBWmLP7o zICJY1rCg3$y~@Ym^=|&>@BR*{PLSk)2v_%=!#wTx1f`*HgstB+6*E zjgGL=?C|`ze;D#ZmXo)2Gtb<+l_^KjY&LnvJ3q>EfAQJxddth6^!8&jizsQ3=pXn} z*T3m{zCVoh=p_Il-ER^{&k;Hq*T`&MErsLSOnc76OfDhV*vMHt@$u1~sffc*F#;(8r% z;1<|6 zeUnf=fhsRy3jH9!(J6@mBV7{ZbS(fpba@_GT}3;dCGB|*y0VNsaS&ZuLbNLoG?8gc zsuN6o9bdXgH$%qB6UGhRf8#&#v}at+&NKHCRLk9lBy1p5C*S^;^W)@KZ8S|&0a6%@ z7U(45^j*7o-@D(<+yBpBbk~E65Gjt#BV?9zV2osB(!$!=jHLoij4_eP(ASg0%ewf$ z^z1TAjXK*(S;k8xHWxf52Mf%s*APOocVv)07Yd$j0uE;#yd+_u)Qg|ZP_EVKEf&}^ zImVm*@}D?#aMjL&1cuWtKZg(BagdLH?wefutn=UT%%|@Aou{6)>)kFoN_B#(gtcw_ z0WbCI*S;BP#1C`{EfCTp6|Cy8`7&v`UVp|~9XLu5hau$%r0Wrt?Pwy3UKv<4B!7V$pZG685&L2lbpZz zEI#t7&rqq9aYPpI5l)V1bPUrs1hIhO0mPo|&{twIS7e|s&sw9x?7}KXkIay?Tbwb} zPpP+uZTT!)2a6nDuc8yf-myVSz9i==ik`zj&LIoreV_iJ0cMw%(I(-N{paxefBrsh z__G_~^qt^&{QQfb$#s{U&W#`V3a`BE^uN6NoT)cV4vZch%y`k-dJ|81s2}#vo8Eja zr`Rn009T4|-Ge5HFC`AQTjNfFFor}YWTd3o3R#yDUpYuEK^kgd#HLaYpL^&SL8Z;p zFFzYvO`;Tw+Gq(n!Eqh76kI+uy#S$O>EnZ=n0gIaUPrapF+!q7x0BYEN$SfKruGr8 zA3{Cy5az6lZSfm7;JizrCAsa!zo5Lh!XN$awTu*pXwELMB_r5e%5vY}Fw_&i^5DaK zb8Us68z1J`JGW7ZQa&-g%$MgYEH*+0vKc1&3uKifNi}}P<6U`m4 z4p^ycpexe)8+wJ2SWXzBfGg#X*<^tuQ4CBTLiySKKJnwbxyHlF#;o zT?Qj0IPN_rY`;z!EmG_;E7jT_MAC$EGs5^8Fe%bV90{Q@WQzqp`nCJmmdmpL{9S~V zdiTZGR+S@3=q=>gB_qCYWF9D3+IXoSQ?4Kyb!4lI_H#JBL+I)PY0#iJzK>w#0XTjb z3ggJTzKSU*xag^n4DrCHKF+PT-omS&|76bIwVn0(WqK3Aj%=37P!H?bEXzlh_~5a5 zQX*cod5ouRnW7v-ylZ-y&&{rJbiIRvU@V^@(_3J9sm|Yj?Aw+L-`fYc)&kA%vCB)O zmLrCSu#IdrFr6As)POW16`+)d_B^!TV-X)OOX@nLN}!~Jk&@K$kkUg62ccY)a*)EY z&j&clrPW^NrcZvJ*Z$ILDE9ZzuGYF%)TipW*gnTb&HwmB`F_hs07^PEYU_+`+QKuQ z`gGp)?!PBhz0fyhSpwT8VQ>(y*o#-pP+nMM<*xf-woGw&kkfZgv2}caqEJi}vTPeE zaj?>2IY>EYtdGH*L&j0`cn-auqStkC1q_dlu-LdK=tDgMZSDrR`%j`;pjPK!SLy{){x&(RC_1FD?704-< z(J0qn1!5hxo{CTw=`xx~YkZ{$J0UA^ikq{N^AgBvLBVlYE4cjA7w_gtn&2bpauZxwG6LN;T~`=EGl|hSk*4?u#XGa&|dt&B>8vDZ%kE zu8XHrOdKG>CbCmOgbifaLd7A{glG|?jKN5O5(Xs{(kP4sp)B#9ji#PSFldAZDLq!o zC%E;td-%ow`f75npxta+K?xq+4cJJI$80U%TSw*8<~RDvF+mCk=xG{WPPPBPUL9`9%G!9<#L8B-)Tsl1jZrpgc0J18IsvptNB)m*pX$`Pk?0=0#`kV$-%^I_0`$eF`u- zK?}h|DNnh)!jYBGD(fMjrx&d}WO>f^8ib2m7$C0CVWI?Q>RghghcH1KZ+tK5Js-l2 zH2U1jVQ`G#&?4{uhrcIDV_x~fXRv)}kjBCa%JJFRS0bn2L?HvMlsg_;;JqtlMjXvE zrv`ZCPX;mQFxxGy^>nk_b9U7j%ehtrROeM(jFvCPXMw$3=Ttw3o$I z8H7+^G;SI}93YbjnM62IfJh=#5}}f~YtNn7*%~z3=%9+BaSI=#0P*acMM2iE#XTl1}TFrAaLV*g%q6 z&u>ShtXA5PDekeRrg=%LS`8f%!nm)rh| z$a34qKEfBj_+`#NXB*GGaz83eIDBHBT_ZzmaZ`FTirKz?sD|8rWS$#V>*Qq2MWcD1 zJvGRc*$f3E*}8LxfiuUL`OX5&w;``!_a-=FvmN_QBFk3pL{_RMR{*~VUXIkwkUANp zn?<-8jF&^YS+wJJjffnC^1$>hCrNkbu#4Yp%#wVsBS$xZzWX@xXd}`f(5Xi14o`W?mCTh_x$nK7z>6bTYG9;G zZgQB>ZNm%?7U}ac^f(?<{e2XR1@2o|rk!Z^j`T5-b;$IHCv7ycJ-y6#24`sQn%^63YTFX54{{pS8X^CjgMU-u868F>3kFMatX zdpF-Yv%Cz7Qr&T%^e{<`wDBj&^yk; zT?cs2yWhjo+A`0)az8nz$RkH)IDgv|dmK1ZBy26_SnDs*tVew7=nC(fuhR)4_LeeS zH8sL^KZ8E8&i!pgBweej5`{2SfUP4iIbfxUvMzL5wkg=GSTDvTfW(?_CrW~JNR@+8 zE=UipJW{1>e9AiM&eS0YUnx8MFbdJ98b{`9Akwi+0% zPukm`x=DO@BAnV#eB6H0J)3QG+k+?}=NO)Q^^>^e=36*?|J@YEw^11Cp?}*bxnho@ zdp`Su3pe$B;|8g_*~R-AohZ^MSCC4eg(OZ=db1hM$fbN~dI_9SSU!Nf_p6xUO)xNt zQ68~Lag!9s&y&_?Nx}xXi3^A;M~Pdj_}iaOJo{~&1Gk}fo@>v|{e9r|anI+zzzsKi zm@R_^MkXfdL-=f(b##m^j2F0XhzlaY&qYNOcG) zL=s}h$EYlcmnX^epmY7G!VofBM7SB0;~@n2$|H(eeBq0q;`BY|vhU&x32N&g)OWQ< zzW1R@eAnm9<3EobAZxbUcT3+mz+t)%TRE9272;LjSNsvQtq8w zA?tgbHr&fVH#v%)L*8@Ax(+?A!)R}T5W}IlCC=Ehi+kqEy!^$#%lR+3>;oVA{r~o> zM~=-?YsFOB5!Fu2w;nmMwOVZsUi-XDf1}=LCt(T*Wp~pb!6jx+9R03e&kq|yNn|P} zqUFU0(=;fhD55x~*}W?D4tLE}@y4de=X`4QI$@&e#0l*nVJVLI(!B?nY^U7*N3UVi zWG^#^XNZI(h$3382u#G+9yr2p-deTpQF8}j_6Te}+s+xc*P&5IRaeM$>NFe2kX|2| z$+KzAKZr;S&h|@5j@*lhj>46%f?b!x>O8Ek!r_Ckco%1$yN}%$Uc&Z^F5{w$b}}_K zz@hmy4$Z9+D@omGj>ekLt%tC*+Lg7GTt1NDMVp4$GuX#!n&E599X>a^%KUm2;u!9%2(QyME zwV=}`B_;}&0l59^-{O+XE?{lu2-0=zf&k^TIqPfgb(5;qjm79=YR~flZs3 zom=MgS6)X8!P$FuF*eZ0$UuR$X2A4%i}C(EI|qvl`3|L=Pu^D)T$f_Tr|7v1m5PLE z!r{e5PT#YK2bLOK^U~j7@}jf8cJDv@-jinM7g;FRDfSc@9~;9}ip8ZBC*!-6dfa5a z)<7xsZ(h%jS=-Y;f&?+fpgbSi;funR`Hvtpk%mqj(~crKk>U7igL}#~#<%T42}7mc zB-APGD5lwtX+$YsxbGoK%{JeB+ka>G)_#sXe3Z~w-QQX)@Jaa8w+`}_uP%Vw3kydq zth?dn!yBJoMVEtCq)+Y>ultDc79%9S-xpxlO+sLaezo#cu@pV8y$t9 zA(L_GG@Iz4gO`M$EqyyRmK$e`L1}1KA131!dHCSB*|K#LYtx63u6uHgZzRh1!?7NP zg-`vMc+6&`)0n}@?cDR=!|Z>TuUp7NA>r(I( zMNd)6W*90JXoeB9%QbeNwwt?8tn!Qh^|xWm$k7uY`>S(gqMJufEMSbGP$;l@^JZMv zVST;Edaa4C65=@eS3$YoFU0Kp%#Y|hf$R7L<<&orD$CcVN)aU~QIyaMV;XVFLyHv- zHe$B!+68e)t<#|sr!>QupcPS0629`_5m;?<$M5|D7oD?-)x$G() zdzcU0{t$2d{1Na=usjV%AA((HL#Akb)!Q9dJWj4sCFo3JOhnJv`Lt9-x-tVpdu+er z_-FCd5YaPU3S0JBIsD23)GBcBUTB>FQ)OcFKCXT9YkBT9FQ9L*k3)+M?w_8e(vE3J z#kx+ZM+wtm!kz0)SX{Ho=F&y6a*%bbMweq1PQa>(@cvKW9Jmjiak^wd3lFO_B@uSP zgN0YM^el{A=)oNrA`T-|qVa`92!UfPttC!FWD;AzLdUEv-pSPXP7WNngPz_TE3-2= zuFGS+4%d*mJQEs+5Yxf>L_sAUAU;n4j4UNr`}?Ey-NS+sCAu9HAsPx5Sf(HU_T4Zgm-`P8=N&-V*e#)bJL+!zBF6o zj&hpogI^%S5VZfJe zzniH&o0ur%$-6Fvj7P648OXTw=RNuhSsbNUC|7BvaK^4Z-1v?A`R%v<4fJ^2_K$z~ zZ+zoP@v(pFMQWj6 z1mUZtjx$CIFx?ptol?waSud}1qSj_`bQGf#RBG68?JdMGI@rUstNG9;@4>6r zdHOjMe96yHjk4T+Y>7c9;pO{wkZ}|4ICLCxBUUZ%*n`#{xB&qr{@T=U&+UaGai@cm zg77lLwIi6Ii8FB~sZ@yN8AR_mWQH;Gx8pv1pQU+h+6KJ?;N>uVgD|-n`ghTsS>U!? zKFE9C{Sof{=G}-^gVQF5xbTc!?A<)UK-Qy_7zTR^JTkXV=jMY@)7BNMzi4-@L_o(t zD}i=oVc+pBFuoI}cEQAM>+e3g6$Um#-xw77?f=Wq+s?c?#h5Z%gnS<1yX19(Xs-}2 z9wMB5fS~*cakz#t0a8e2=jS+h;0P~y#Vb(6L_u_NfbF|Oe2;$~TYFfEg2!r#r3OUQ z;7OP4BkII_=;5QBH#$VG@6o3Oqs0sv-=`8p)SFGp^$tCy5}T*C@)tMV%3uE7jnI?f zWB>cw=U=dW>z5xmJdMEW8S6A9j$)Kl^z;@{>c^2F*Zmqld>7<@lTaH)A>n1NhcPj%~kJ}zO0ZyOwqC51U)$Q#Y2I0X#KRRtgn4sJern5j=J`5vg zz^1Lxs({EqW)j+~nE7wv9i1aB^upA3%P~q)OGF%>fPp<=vK;@;U3~VlALWMkex8T! z{ub?(Wk#|d=kA!`S*J~Mey+%8|LTJTcYMbR+l}@?PsTEoOTBh?DHXeJy`EJ%@qBw_ zGPb5F6uNfFJrNf2@3#PJ{LH{1y2dPKF_Z*dnOu=_k%nB|woeSZF| zDZcQXr?}P?`fa!;=e%974ma{$kFdUx-sZpIOqE#b}W-UcQPK`I9=t)y(HzCxNV@z;Ox*Zk@mf0gxxX|x8= z2+>tJ`5vA2Q=5#Be(cl-(EYqE_|sG~*qcX7#q0j=!yH^(^P_mskHbs;<5k1VNG##4+t4WIc>HQ49FyBMamwx3Y83W}4LsolZn6 zN$CU;ogkzfhSbB9g?2)FzD#3w+M3j#aWTxE;DtjeuRC`OUpp|*TR-{@SX+m_ahRKi zf@h2OgZIMhF({5hXBFZKs#d{-i|C8~3taJhSU&;fRcp5S$o;6bZF2~30CnrDp znDdkYAvtCJK3dQl?A?B_zev3k^7_B}5HsaEJI98(Z08n+2lCu-&%=E1mV05P4q2DK z`5&)*)hnKQ+53*nEfY5DklLxn|HuSUZ@l)}AJGBpu@?pmnoO?erZkGrOT*4?rPN8) zkklk7M^eh>ICgZJwIF3|Y7(tu+|(+23DCF`KvPG%~AGTfS-DmLd+&Z0F zndh9bnZYgN+;`VkNpJfkoPV_~id*Xt#?YvAt%0*3B+|`b;si1Cb*Qvq_qq01mg|9u zZRo-nD$s~dnWQz1IC=;E@i|fij853yska}5+qL$}SSO^xaTwSN`7tQ=WAcNzP9MT8 zBAjA3X);LRB5{yHA%#K+2O*r3Z!6&;rH_(X9GOF@9vr8Ht4cWjAg(`z%nc!O{ZQzG zTt7*nk2K$p%ob7EJSLMR@p7b|PwHk6xgwcDAGzLMI^{J!@WFrJna{YI-DmEly}pV% zb=XRrnhTF>zjs6aGao-`2`&V!R!Co8k&AY3;_LSwroX=joss;~r@z60TknCj2KbV{ z|E*WQ{3TDmkzSj(y%H4YHlou)G*-~@279KddkbI;Q74&&#!&%RhOLo=|hWE zD;QxQ>9jD3{tw?l#O&PkkMtWpJ`BcU?om2go1eKe4O>&GkOWamCyZDR1FBKNYB}JR zhh{Ou!(4vRd89#$O0_}KZ6-#Yn06Sl9EUU_L#G~aNCdxB3i$n(Uctvc zaucur<<}s{VV?SWdxuRUYd5xd%ogTJ3OYeXE$CFxjblhrKwtP0IR8mj5_Pp~HCDsW zHYJaJ8*lX}I#?qyF@hYbw25S5mqnh2H`(VxKv;)n9fF1(no43T%%Brn&zKanZRSa1 zNV^pIjf0pl0wZ*HF)zqVqW)u+y36;=x z-j{=b#ly!SFmT?*Q0V7%eKFTwy`R5)+n@1{cl;TWJo?ICu_%z=19))aFsv`(w3?(O zm?T1kb*Rl^Oa)Oo3v<~EtXfLkv5iAN3(|w~56JUS9KQ9Q zyK#IUVaQ4p5r+xu$JS`9t-%>*S|_B{Wp<=>j^6YMs6T|j zL+^hn8Icfd-e+rtx#O_92E?Xh*t%yH-~!k+Wt z&^>VYUZ|~;?bMM$i=?(n60BlO2Z4w3`cS12bl)&x-!K%1tX*}s$HKU-0$kgR*mz-$ zuqYPVjv-rZB`^`hiA~r})9y^I3bIi}RF{#JWoWIE#x*p$+rD*B(!&U+drx-|Qk|49 zGRA;#Fi7HP-ahA#A$Fa1=C?og`OiOX`=*JtnUxh*Pt4-@KH_INL8P(wEyvH&>9i-y zCyso}gu%ELl1`*)#{rd~L#3fv4^nPFGQ)S)8a($&7jxRyO)M_2(C&nEl7wcI&CbTiwzoc$F0m{b_`Y;&?@5{zMEv`>llPRLS=?9Pq_w$HbG?t%8RhHfb6s| zq!5OPuz~5UVv=P*!sKOe-ji+ded{Nn^*Aa~> zX;3BAO(3zwx6C8FA|{t3^)e`xNB9|p=MgJ~6jtRBtr2O0iETY2!wx!ZAcK~zcXSJ5 z0yeA;6bdPOFv79S1Z{u>0~XB^t}S@Gg4HP35)hLZ;ux(13^Cv!MYj$yVb{LmyOs?W zrQ6t1PCZ&Z_HQsK9D=lsA>irHdf|sY_<{GmOiRu2^$MkIhNbD_Pb7#0l(8GT@-sx8 z*2LQEu{)BWHKnDb6WhYQ7No2+W7guB@64`n*WpzzJ^yqrzu+v&)jDgH8i9sZ91#XF zwIHS%N32FMi`5R{@n!q_92~F+Q>6xTM^MKPkY;-jjWVKHNAG(k>^{rdN0%32Wd&8M zqD_QJ6J)yuNdpruW6~-TiQay>y`vwy6|waxm>2vvD^})ZU}+H+7A#a;U$gdMod&Yg zzz;)I5~JggG;U*}7CH(b4bi%TF_BG9x+A)56u|brZ{>9)LUqiKutRvVUPU~wA8cEa8ZY++ql zwTrt_Ms^x#ks_i1f*LYvVd5&rG;NncWH4l4bU*Ao2X^hSzThLnfPnQXtdya$4)vk_5bP77^QYjKd8h2$&QzQc8C@={ojnGJoF3B$UO-NE@5IRD% zsulrrGDzuzaFMP8-$x0T)FdQPfC<~S0lA^_;ab%ctrJ8XbZPbp8;wJO1R)V}L&DTS zZ0tgCQqT8fUEUq269Sa*skba!{LlaPA76a!YhV4*V+*Tvnr&jKp^$qbK{n#l1knPd z(P+mbj-%o7%(2_!W^IR-f>sdIiDMd_l+{i|IZ9Y*M11AJ!)WR9yr*Br)Z_^Bi>uVy z0a23Dh!YxNL_3I>4+EClDV<6K=9g?uGB#>ata1f{j=gH@h`A#eBy2kurgm6lsk{nn zE4Z~~Oq!4w0Z|LtsUy-lm>7e>qyaen;Ekf39`vTo=<*SKLTsSXwtbjp z1!`@p@ZN4&J*_CRD37&SwGeQeLejyjEWyeNbbB65^Q4uvJ>a-jqF)G51)O|8CYwjO z8I)rYDiNn7VVfkZVS)gYTDc&{&0#Vbz@XCr83xFxgE0vb+k4Q`>Duc_M0Ws52#M)p zyf#T>SMJ+ZJP2#$pp`$qWV3~CH(_PL7VG^Ju=Ok$ z9EMii&Wu#%P|YT(^Z*S(9TBw;eWxxfo%Kdep{_(e2AfMS8MFW0OR5 zS=+A4&@5L7!y{buwC8@|#(#Rx^T$T|f(IWyiYpV8^ij%vB0(M>-zz~PZAjA;-_Md7 zlRx;tgI|zw^U`82OSRFU9c$`AN+nL{v}5W~#QiH3?s#~b@u6WZzw830Mu%8gS)<+x zXvPt>Fr*QNG&?D^cEpKJgkGsYrD>&khOPMu)tViyYBr%!g{4^t5*XVKQ#&n5uv)P~ zaFr#TRFk+lNCOBPh$ukC9ZcH7r1fqcVGy_=dl1-TnDmQ?Vi8j)lIDxho3%ejsboF# z3VrsuyqsnGD%bWjgacmQ8k{DnRY-3&p=r@8s#T~i!TPNAW~?kBTW!#x9hQtZlWBq>iu)R49y6I7p08R&7QKkP=5&NWgSwPymcb0E4f5 zR%ea?4SxOi|L`||=PlR2EeSO2%<2=$+)fUr=4-x zz4zSrzRGIj6$z)(|&5A^iPTEAt=d8D&>sisZ+A3=PC~0{PMz+A{X4t(KR)A5kjGai*bB}a4<^Q0$!=^vB?3YtnIZFNV!p@3$&8N!qR_acOhytp2|?KMby{s zV|35Cvw!{eKY8H`o_^Uq$JUy(8+9DVAxxA1m<9H4jQ{Wi0h=U25a9cH&O3kKE5C8) zy$A0(IQRQ!?Vdok1B?j~(#1%_1d@!F_u znZZ%bdVJ(d5VeVe4o=uWX9Ql)Ajb8r`eYIjcN&;Z6&+T=qzLIEly94T(!((l*$E&8 z9ca)2LW9v(cm|!?`UGvo;)Jj;u965pgV743JD5%r5{tm4CPrfgw{3G!A(V#@R-a4b zAe2No0wWYgdY}}J;N*^>PVO9ClCiMebt#ms!O&LR4#nD$V;~Y94=#&A&cT zDspIUolvJZN}|&rPk-g#BmTpWSXyfYl2mAt$lzyu^7$NJyW`$xOdmP&$uqZ)W^x&i zm2!h9F$8f!HHum81T=z}Mi6o5krT|WH9707U7UOFUZm@?xV%c(j_4#Q>rqTS3|Q=h zbXqY~Td=TZk1j(4&_7^t)!Mp6n>sCMHj(8OOnCv6kId#sCwAI-jpGkms(Zc{{2oMW z4YBeN#)#YoN`f#V)iF=G^1?4ZI6ScBj@!QV$+5oth1;h_SXx_0p+I#929fwuaLF0l z*t}Zd-iIDx=Ew<7Klco_p0zAP@o%Yx-biu%#9B)As%HQkn7b-um@_ z^6S_Cmsh>$-Hz5gbZ8o-9DK*&d$N3g0^>hbfApwaylwLs2?1G0;^5&6i4#gPod|cRM4_1H@bLw{dCMKl9GYh5S$i1VJW0OUp}bn| zK7&H4lzFdX6%LCzE7q{EU=4Ws`|RQ!Em^W*-2u->XZkFMs#b>5IE?Rxo*}5rSqM2d z2orl?X&70aMx@=wavYXT#E zgmNK`5J|`G63Pl&TR%W)Ia*R;l)`k`zj9+YkOHKGmX7V7D`AgN!jcfvZXzlv@PI@J zJHI8nJVPTvUkY5{ChN-7%7CdFb8r=Pz2=1$Q0)b^b_Kak00;%cRO zo=Fn?tc#-*u9Ub!;0hQl78x4qV|j6vqYpm9+R7T4T%Nv76DY?ejv}NGI7(7fimX($ zav3|W8z;8O*Q(i~IOjuOzn%JXT+2wUS8SurQ&1YR{3-*qRv}&kmB;TNCC+8h#I@C^V{~fk7b9>S2W_m5urY=- zO_0KZ1X3yzQiSK?DM_=0X0`?I5RyBNfe7i!@2O{KE6^6P_X& zAG@X0-~Tt$$BzE}`r7(FUr5}Xk5Yo1lGJ5PMhS9fZ|6jqa{|VH2XfV&^_qnowP{16IW% z^cJj9eSMW6Y+KPcN20Ysnl1~M3{v-^;|Qq(Na_e<`x+Y#wuxIrvD>wYRt`vybVCbS3c`(fav?#QQ+QH#gVQJs_DBT=EiIMN>ym~6 zB5bV|N19}M9;D%_=Rft!uYLLRuDkexJr6{klw*hIc-9qr|NgQI&g1aX8mUQfghDAr z`V$J}e!uunpCFG7n=GAn3k^(e+H^zTz~D#b=T7|A+}zxoqM(s2cpjc|$tp!bNm{}o zH=^h*<*An|OwY`5X!0!x6?l|f$npWwF*hCPF)Bx${}za+obEsK&u7o4Olw? zRTpx-Hi3G!TMV?_vPZA54Z3B2R98W^(ADX}iWLVTY1klLJcL}YSe2EYQA|&XjLM@) zh}LEiMxYA?@U8hyC@m@>l|`Bm3N1w!dhM=mxp6$*nD&6QOGP<34c{f+rW@~I2o$(N z;7CcQ9m4zqWIV2V&Qreh>X$t0x1RRY^X?{0IdXIwqwP~JSKBO9+6V;dM|`CD&lW#n z31Y+2v{RbRCK=CfpMK^UZy6mO|NrJrocPVf`S~}*omMvIIAl}?*Nri;qS#i{9YwLP zmqxYDiMeHJw|#?7{{ZR4INr!OL&IbABq2eyifk*|aSTycjwheDiBfGqbs5%;o#4x4 zt#r@uxNYtw9eea@H7zY2V-v)c_OZn&d#6A0Ez-DZWq0cu?YGf!fFMD*Ib>>8-^paH zzld}#v(^!I{C0y~`{-{40C$6*)y20Ih6HSlXAJt}*O`<5#FaX6xeO!yyy&IR{P6Ri z{gk&}b?Lqba*pEYYK@@Yv7!&su~qxeQ2azD$VLbu2;+#Pky7aCsh+X-?B5)locg2r z`Ppli78YN(Uabv#LNJ)|36x`f?}CVi<5DUXDYx3Jt*%i%_yDy-hiMND(-v#J>58o}GCmZFcGnmf2ZXI>S`Dj!(r7@s3P{q? z(=BsWfQ~y5RS-rYbG?X69^qurM%ZaX--E1Ag0rzjJpLC!mr9E;mda&?Gt4PPt3}mL zCRQi3jAN0qb=Q`~tFC$WyPo;fi{Ej<-d!sq4Ov{PK`PPG;7D7;{4B;#bb_1=0YMM| zA%#MrcG?-I|G(o?lW$#KT)Jjqarw8`R@YBYlZ0O1C2<`bKf;S0N*Ra2LXOH?BHLJO)$9yNdCfND{EVHK&*tnd z&^rub+;+u-uCZr*9o=a{PcNV`(joP%_yWqe$eEG|*T-ZtcFY!OE9+CI5W!|dk`SZ` zu93vT-s$bsAhzq+3GL4q1&Gn1&Q;I8?2}hsc`Fh8$-8YU-jNiDrTE2Q^Y4yJ^t*l;MZ8T(7LMES~ zH{(+bV)`>4LphI5Pan0QL#0}$xiU|4?s#{h_d&4_iamDZR!E2vXhzU(Ly+wvS&^OP zNFpmY)GWj5tX;@n)+S3XZ;6S8Jjldyt2XKhS6MYh$FYedyA3|seZDApbR75OD6Y2j zc&#lYoy`#I?lCCQ&n`UKgsb^lOP)*y3=oQ923SdsZN<3oBZ6SiA|rY)GK{U%WE%NT3UYf+WPv1oj61~ zg1nbuAoU3|3FX*hu#~3}57B5x#MKtH`Z}wXdBCHSE7+YO*JDwuTn6$5OGWo1Xs)A< z93qWETbGPZSy8#TOJ@(7cK49NGIs|DIjIIBU_(trNNn|xMqN>dZc=Q}O*V!PZG*`& zaxqp`sMWHcD`eTaW$4(MlV^N&_vZ1>p1yVJi<>7$n_i)hw9{f~X%($p@~%8dCE;g$ z$9R1F)FsGcL!h;$)oLLfheDyJva48n=jJV&-&w6xwk|HOzNoxjeNMGjy|mr#DAyO{ z{45cImeE9!Cg?BGh==HO63U$>QLRN>D^shkpoL4-&)6MdcG`C2RKd_CLB4;b^1DYe-31mf#!PM*B?~%;|Tkn|~?Z0jF*vQwn4wfDn8Yq!@0$`izf zvBPeJVMv^$D5WU%^&QU^ihnw_DgDzBKQ{*PuR(0-8z4>egX8!@Wd-Cc(8vH}wW|Qik*W^cFoLDaZ>&27BTO zF_^A#pfz~z&ZBpsSqJcutw<7%K)=-E_N8$S(zT;s`LT@I6(T4R(9oMy#4&P{$BfB)8 zC*p^VpSc8iObCGnq9`IwBRZW3Ar<+2Vb04J=6XxfXW}p>Nn&bYyElmAEfTXQ4dT8u zPDEQLTBmxA)O@F{^^q`1P1bQpRYIwlqgcv)Nl5Fo)r?}2)L0=zA<){8VDP*0Z4yDw z_c6lIi6WE`IIbdzQ_>_w3c2blNim zQ|0WRh$rHScx=dj`5{ll6Y;YfPbA0_@kD%|`2PXE>3T#KIQOmq0000 +/// Service that provides pages for navigation. +/// +public sealed class DependencyInjectionNavigationViewPageProvider(IServiceProvider serviceProvider) : INavigationViewPageProvider +{ + public object? GetPage(Type pageType) + { + return serviceProvider.GetService(pageType); + } +} diff --git a/source/RevitLookup.UI.Playground/Client/Services/ViewModelsRegistrationExtensions.cs b/source/RevitLookup.UI.Playground/Client/Services/ViewModelsRegistrationExtensions.cs new file mode 100644 index 00000000..bdce6e3d --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/Services/ViewModelsRegistrationExtensions.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace RevitLookup.UI.Playground.Client.Services; + +public static class ViewModelsRegistrationExtensions +{ + public static void RegisterViewModels(this IServiceCollection services) + { + services.Scan(selector => selector.FromCallingAssembly() + .AddClasses(filter => filter.InNamespaces("RevitLookup.UI.Playground.Client.ViewModels")).AsSelf().WithTransientLifetime() + .AddClasses(filter => filter.NotInNamespaces("RevitLookup.UI.Playground.Client.ViewModels").Where(type => type.Name.EndsWith("ViewModel"))) + .AsImplementedInterfaces(type => type.Name.EndsWith("ViewModel")) + .WithScopedLifetime()); + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/Services/ViewsRegistrationExtensions.cs b/source/RevitLookup.UI.Playground/Client/Services/ViewsRegistrationExtensions.cs new file mode 100644 index 00000000..758ce89c --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/Services/ViewsRegistrationExtensions.cs @@ -0,0 +1,20 @@ +using System.Windows.Controls; +using Microsoft.Extensions.DependencyInjection; +using Wpf.Ui.Controls; + +namespace RevitLookup.UI.Playground.Client.Services; + +public static class ViewsRegistrationExtensions +{ + public static void RegisterViews(this IServiceCollection services) + { + services.Scan(selector => selector.FromAssemblyOf() + .AddClasses(filter => filter.AssignableTo()).AsSelf().WithScopedLifetime() + .AddClasses(filter => filter.AssignableTo()).AsSelf().WithTransientLifetime() + .AddClasses(filter => filter.AssignableTo()).AsSelf().WithTransientLifetime()); + + services.Scan(selector => selector.FromCallingAssembly() + .AddClasses(filter => filter.AssignableTo()).AsSelf().WithScopedLifetime() + .AddClasses(filter => filter.AssignableTo()).AsSelf().WithTransientLifetime()); + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/DashboardViewModel.cs b/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/DashboardViewModel.cs new file mode 100644 index 00000000..0ac7c638 --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/DashboardViewModel.cs @@ -0,0 +1,29 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using JetBrains.Annotations; +using RevitLookup.UI.Playground.Client.Views.Pages; +using Wpf.Ui; + +namespace RevitLookup.UI.Playground.Client.ViewModels.Pages; + +[UsedImplicitly] +public sealed partial class DashboardViewModel(INavigationService navigationService) : ObservableObject +{ + [RelayCommand] + private void NavigateToWindowsPage() + { + navigationService.NavigateWithHierarchy(typeof(WindowsPage)); + } + + [RelayCommand] + private void NavigateToPagesPage() + { + navigationService.NavigateWithHierarchy(typeof(PagesPage)); + } + + [RelayCommand] + private void NavigateToDialogsPage() + { + navigationService.NavigateWithHierarchy(typeof(DialogsPage)); + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/DesignGuidance/FontIconsPageViewModel.cs b/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/DesignGuidance/FontIconsPageViewModel.cs new file mode 100644 index 00000000..a4041e22 --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/DesignGuidance/FontIconsPageViewModel.cs @@ -0,0 +1,67 @@ +using System.IO; +using System.Reflection; +using System.Text.Json; +using CommunityToolkit.Mvvm.ComponentModel; +using JetBrains.Annotations; +using RevitLookup.UI.Playground.Client.Models; + +namespace RevitLookup.UI.Playground.Client.ViewModels.Pages.DesignGuidance; + +[UsedImplicitly] +public partial class FontIconsPageViewModel : ObservableObject +{ + [ObservableProperty] private List _icons; + [ObservableProperty] private List _filteredIcons = []; + [ObservableProperty] private FontIconData? _selectedIcon; + [ObservableProperty] private string _searchText = string.Empty; + + public FontIconsPageViewModel() + { + var jsonText = ReadIconData(); + Icons = JsonSerializer.Deserialize>(jsonText)! + .OrderBy(data => data.Name) + .ToList(); + + SelectedIcon = _icons.FirstOrDefault(); + } + + private static string ReadIconData() + { + const string resourceName = "RevitLookup.UI.Playground.Client.Models.FontIcons.json"; + + var assembly = Assembly.GetExecutingAssembly(); + using var stream = assembly.GetManifestResourceStream(resourceName)!; + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); + } + + partial void OnIconsChanged(List value) + { + FilteredIcons = value; + } + + async partial void OnSearchTextChanged(string value) + { + FilteredIcons = await Task.Run(() => + { + if (string.IsNullOrWhiteSpace(value)) + { + return Icons; + } + + var formattedText = value.Trim(); + var results = new List(); + + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var setData in Icons) + { + if (setData.Name.Contains(formattedText, StringComparison.OrdinalIgnoreCase)) + { + results.Add(setData); + } + } + + return results; + }); + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/DesignGuidance/SymbolIconsPageViewModel.cs b/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/DesignGuidance/SymbolIconsPageViewModel.cs new file mode 100644 index 00000000..8c09e1e8 --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/DesignGuidance/SymbolIconsPageViewModel.cs @@ -0,0 +1,62 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using JetBrains.Annotations; +using RevitLookup.UI.Playground.Client.Models; +using Wpf.Ui.Controls; + +namespace RevitLookup.UI.Playground.Client.ViewModels.Pages.DesignGuidance; + +[UsedImplicitly] +public partial class SymbolIconsPageViewModel : ObservableObject +{ + [ObservableProperty] private List _icons; + [ObservableProperty] private List _filteredIcons = []; + [ObservableProperty] private SymbolIconData? _selectedIcon; + [ObservableProperty] private string _searchText = string.Empty; + [ObservableProperty] private bool _useFilledIcons; + + public SymbolIconsPageViewModel() + { + var symbols = Enum.GetNames(typeof(SymbolRegular)); + Icons = symbols.Select(SymbolGlyph.Parse) + .Select(symbol => new SymbolIconData + { + Name = symbol.ToString(), + Icon = symbol, + Code = ((int)symbol).ToString("X4") + }) + .OrderBy(data => data.Name) + .ToList(); + + SelectedIcon = _icons.FirstOrDefault(); + } + + partial void OnIconsChanged(List value) + { + FilteredIcons = value; + } + + async partial void OnSearchTextChanged(string value) + { + FilteredIcons = await Task.Run(() => + { + if (string.IsNullOrWhiteSpace(value)) + { + return Icons; + } + + var formattedText = value.Trim(); + var results = new List(); + + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var setData in Icons) + { + if (setData.Name.Contains(formattedText, StringComparison.OrdinalIgnoreCase)) + { + results.Add(setData); + } + } + + return results; + }); + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/DialogsViewModel.cs b/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/DialogsViewModel.cs new file mode 100644 index 00000000..82f25fdf --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/DialogsViewModel.cs @@ -0,0 +1,18 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; +using RevitLookup.UI.Framework.Views.AboutProgram; + +namespace RevitLookup.UI.Playground.Client.ViewModels.Pages; + +[UsedImplicitly] +public sealed partial class DialogsViewModel(IServiceProvider serviceProvider) : ObservableObject +{ + [RelayCommand] + private async Task ShowOpenSourceDialog() + { + var dialog = serviceProvider.GetRequiredService(); + await dialog.ShowAsync(); + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/PagesViewModel.cs b/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/PagesViewModel.cs new file mode 100644 index 00000000..faba895d --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/PagesViewModel.cs @@ -0,0 +1,26 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using JetBrains.Annotations; +using RevitLookup.UI.Framework.Views.AboutProgram; +using RevitLookup.UI.Framework.Views.Settings; +using RevitLookup.UI.Playground.Client.Controls; + +namespace RevitLookup.UI.Playground.Client.ViewModels.Pages; + +[UsedImplicitly] +public sealed partial class PagesViewModel : ObservableObject +{ + [RelayCommand] + private void ShowSettingsPage() + { + var viewer = Host.CreateScope(); + viewer.ShowPage(); + } + + [RelayCommand] + private void ShowAboutPage() + { + var viewer = Host.CreateScope(); + viewer.ShowPage(); + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/WindowsViewModel.cs b/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/WindowsViewModel.cs new file mode 100644 index 00000000..a36ef2be --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/WindowsViewModel.cs @@ -0,0 +1,16 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using JetBrains.Annotations; + +namespace RevitLookup.UI.Playground.Client.ViewModels.Pages; + +[UsedImplicitly] +public sealed partial class WindowsViewModel : ObservableObject +{ + [RelayCommand] + private void ShowUpdater() + { + // var view = Host.CreateScope(); + // view.ShowDialog(); + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/ViewModels/PlaygroundViewModel.cs b/source/RevitLookup.UI.Playground/Client/ViewModels/PlaygroundViewModel.cs new file mode 100644 index 00000000..723a8be0 --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/ViewModels/PlaygroundViewModel.cs @@ -0,0 +1,29 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using JetBrains.Annotations; +using RevitLookup.UI.Playground.Client.Views.Pages; +using RevitLookup.UI.Playground.Client.Views.Pages.DesignGuidance; +using Wpf.Ui.Controls; + +namespace RevitLookup.UI.Playground.Client.ViewModels; + +[UsedImplicitly] +public sealed class PlaygroundViewModel : ObservableObject +{ + public List MenuItems { get; } = + [ + new NavigationViewItem("Home", SymbolRegular.Home24, typeof(DashboardPage)), + new NavigationViewItem + { + Content = "Design guidance", + Icon = new SymbolIcon(SymbolRegular.DesignIdeas24), + MenuItemsSource = new object[] + { + new NavigationViewItem("Typography", SymbolRegular.TextFont24, typeof(TypographyPage)), + // new NavigationViewItem("Colors", SymbolRegular.Color24, typeof(DashboardPage)), + new NavigationViewItem("Segoe icons", SymbolRegular.Diversity24, typeof(FontIconsPage)), + new NavigationViewItem("Fluent icons", SymbolRegular.Diversity24, typeof(SymbolIconsPage)), + } + }, + // new NavigationViewItemSeparator(), + ]; +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/Views/Pages/DashboardPage.xaml b/source/RevitLookup.UI.Playground/Client/Views/Pages/DashboardPage.xaml new file mode 100644 index 00000000..d353f142 --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/Views/Pages/DashboardPage.xaml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/Views/Pages/DashboardPage.xaml.cs b/source/RevitLookup.UI.Playground/Client/Views/Pages/DashboardPage.xaml.cs new file mode 100644 index 00000000..38a8c40e --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/Views/Pages/DashboardPage.xaml.cs @@ -0,0 +1,12 @@ +using RevitLookup.UI.Playground.Client.ViewModels.Pages; + +namespace RevitLookup.UI.Playground.Client.Views.Pages; + +public sealed partial class DashboardPage +{ + public DashboardPage(DashboardViewModel viewModel) + { + DataContext = viewModel; + InitializeComponent(); + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/Views/Pages/DesignGuidance/FontIconsPage.xaml b/source/RevitLookup.UI.Playground/Client/Views/Pages/DesignGuidance/FontIconsPage.xaml new file mode 100644 index 00000000..2f58c507 --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/Views/Pages/DesignGuidance/FontIconsPage.xaml @@ -0,0 +1,326 @@ + + + + + + + + + + + + + + How to get the font + + + + On Windows 11: There's nothing you need to do, the font comes with Windows. + + On Windows 10: Segoe Fluent Icons is not included by default on Windows 10. You can download it + + here + + . + + + + + How to use the font + + + + An icon with a 16-epx font size is the equivalent of a 16x16-epx icon, to make sizing and positioning more predictable. + For optimal appearance, use these specific sizes: 16, 20, 24, 32, 40, 48, and 64. Deviating from these font sizes could lead to less crisp or blurry outcomes. + + + + All glyphs in Segoe Fluent Icons have the same fixed width with a consistent height and left origin point, so + + layering + + and colorization effects can be achieved by drawing glyphs directly on top of each other. + + + + + XAML + + + + <ui:FontIcon Glyph="&#xEB51;" /> + + + + <ui:FontIcon Glyph="&#xEB51;" Foreground="#C72335" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/Views/Pages/DesignGuidance/FontIconsPage.xaml.cs b/source/RevitLookup.UI.Playground/Client/Views/Pages/DesignGuidance/FontIconsPage.xaml.cs new file mode 100644 index 00000000..a8b30287 --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/Views/Pages/DesignGuidance/FontIconsPage.xaml.cs @@ -0,0 +1,37 @@ +using System.Windows; +using System.Windows.Input; +using RevitLookup.UI.Playground.Client.ViewModels.Pages.DesignGuidance; + +namespace RevitLookup.UI.Playground.Client.Views.Pages.DesignGuidance; + +public sealed partial class FontIconsPage +{ + static FontIconsPage() + { + CommandManager.RegisterClassCommandBinding(typeof(FontIconsPage), new CommandBinding(ApplicationCommands.Copy, OnCopyContentClicked)); + } + + public FontIconsPage(FontIconsPageViewModel viewModel) + { + InitializeComponent(); + DataContext = viewModel; + } + + private static void OnCopyContentClicked(object sender, RoutedEventArgs args) + { + var routedArgs = (ExecutedRoutedEventArgs)args; + var parameter = routedArgs.Parameter.ToString(); + + if (!string.IsNullOrEmpty(parameter)) + { + try + { + Clipboard.SetText(parameter); + } + catch + { + // ignored + } + } + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/Views/Pages/DesignGuidance/SymbolIconsPage.xaml b/source/RevitLookup.UI.Playground/Client/Views/Pages/DesignGuidance/SymbolIconsPage.xaml new file mode 100644 index 00000000..a1434d04 --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/Views/Pages/DesignGuidance/SymbolIconsPage.xaml @@ -0,0 +1,339 @@ + + + + + + + + + + + + + + How to get the font + + + + + Fluent UI System Icons is not included by default on Windows. You can download it + + here + + . + + + + + How to use the font + + + + An icon with a 16-epx font size is the equivalent of a 16x16-epx icon, to make sizing and positioning more predictable. + For optimal appearance, use these specific sizes: 16, 20, 24, 32, 40, 48, and 64. Deviating from these font sizes could lead to less crisp or blurry outcomes. + + + + All icons in Fluent Icons font have the same fixed width with a consistent height and left origin point, + so layering and colorization effects can be achieved by drawing glyphs directly on top of each other. + + + + + XAML + + + + <ui:SymbolIcon Symbol="Savings24" /> + + + + <ui:SymbolIcon Symbol="Savings24" Filled="True" /> + + + + <ui:SymbolIcon Symbol="Savings24" Foreground="#C72335" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/Views/Pages/DesignGuidance/SymbolIconsPage.xaml.cs b/source/RevitLookup.UI.Playground/Client/Views/Pages/DesignGuidance/SymbolIconsPage.xaml.cs new file mode 100644 index 00000000..39a85ef2 --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/Views/Pages/DesignGuidance/SymbolIconsPage.xaml.cs @@ -0,0 +1,37 @@ +using System.Windows; +using System.Windows.Input; +using RevitLookup.UI.Playground.Client.ViewModels.Pages.DesignGuidance; + +namespace RevitLookup.UI.Playground.Client.Views.Pages.DesignGuidance; + +public sealed partial class SymbolIconsPage +{ + static SymbolIconsPage() + { + CommandManager.RegisterClassCommandBinding(typeof(SymbolIconsPage), new CommandBinding(ApplicationCommands.Copy, OnCopyContentClicked)); + } + + public SymbolIconsPage(SymbolIconsPageViewModel viewModel) + { + InitializeComponent(); + DataContext = viewModel; + } + + private static void OnCopyContentClicked(object sender, RoutedEventArgs args) + { + var routedArgs = (ExecutedRoutedEventArgs)args; + var parameter = routedArgs.Parameter.ToString(); + + if (!string.IsNullOrEmpty(parameter)) + { + try + { + Clipboard.SetText(parameter); + } + catch + { + // ignored + } + } + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/Views/Pages/DesignGuidance/TypographyPage.xaml b/source/RevitLookup.UI.Playground/Client/Views/Pages/DesignGuidance/TypographyPage.xaml new file mode 100644 index 00000000..b05e086b --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/Views/Pages/DesignGuidance/TypographyPage.xaml @@ -0,0 +1,379 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/Views/Pages/DesignGuidance/TypographyPage.xaml.cs b/source/RevitLookup.UI.Playground/Client/Views/Pages/DesignGuidance/TypographyPage.xaml.cs new file mode 100644 index 00000000..32993e12 --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/Views/Pages/DesignGuidance/TypographyPage.xaml.cs @@ -0,0 +1,10 @@ +namespace RevitLookup.UI.Playground.Client.Views.Pages.DesignGuidance; + +public sealed partial class TypographyPage +{ + public TypographyPage() + { + DataContext = this; + InitializeComponent(); + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/Views/Pages/DialogsPage.xaml b/source/RevitLookup.UI.Playground/Client/Views/Pages/DialogsPage.xaml new file mode 100644 index 00000000..965e6189 --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/Views/Pages/DialogsPage.xaml @@ -0,0 +1,27 @@ + + + + + + + + + \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/Views/Pages/DialogsPage.xaml.cs b/source/RevitLookup.UI.Playground/Client/Views/Pages/DialogsPage.xaml.cs new file mode 100644 index 00000000..6f4f5e0b --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/Views/Pages/DialogsPage.xaml.cs @@ -0,0 +1,16 @@ +using RevitLookup.UI.Playground.Client.ViewModels.Pages; +using Wpf.Ui.Abstractions.Controls; + +namespace RevitLookup.UI.Playground.Client.Views.Pages; + +public sealed partial class DialogsPage : INavigableView +{ + public DialogsPage(DialogsViewModel viewModel) + { + ViewModel = viewModel; + DataContext = this; + InitializeComponent(); + } + + public DialogsViewModel ViewModel { get; } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/Views/Pages/PagesPage.xaml b/source/RevitLookup.UI.Playground/Client/Views/Pages/PagesPage.xaml new file mode 100644 index 00000000..936f9dea --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/Views/Pages/PagesPage.xaml @@ -0,0 +1,31 @@ + + + + + + + + + \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/Views/Pages/PagesPage.xaml.cs b/source/RevitLookup.UI.Playground/Client/Views/Pages/PagesPage.xaml.cs new file mode 100644 index 00000000..40e5375e --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/Views/Pages/PagesPage.xaml.cs @@ -0,0 +1,16 @@ +using RevitLookup.UI.Playground.Client.ViewModels.Pages; +using Wpf.Ui.Abstractions.Controls; + +namespace RevitLookup.UI.Playground.Client.Views.Pages; + +public sealed partial class PagesPage: INavigableView +{ + public PagesPage(PagesViewModel viewModel) + { + ViewModel = viewModel; + DataContext = this; + InitializeComponent(); + } + + public PagesViewModel ViewModel { get; } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/Views/Pages/WindowsPage.xaml b/source/RevitLookup.UI.Playground/Client/Views/Pages/WindowsPage.xaml new file mode 100644 index 00000000..daf5bfab --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/Views/Pages/WindowsPage.xaml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/Views/Pages/WindowsPage.xaml.cs b/source/RevitLookup.UI.Playground/Client/Views/Pages/WindowsPage.xaml.cs new file mode 100644 index 00000000..880646a3 --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/Views/Pages/WindowsPage.xaml.cs @@ -0,0 +1,16 @@ +using RevitLookup.UI.Playground.Client.ViewModels.Pages; +using Wpf.Ui.Abstractions.Controls; + +namespace RevitLookup.UI.Playground.Client.Views.Pages; + +public sealed partial class WindowsPage : INavigableView +{ + public WindowsPage(WindowsViewModel viewModel) + { + ViewModel = viewModel; + DataContext = this; + InitializeComponent(); + } + + public WindowsViewModel ViewModel { get; } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/Views/PlaygroundView.xaml b/source/RevitLookup.UI.Playground/Client/Views/PlaygroundView.xaml new file mode 100644 index 00000000..88ec950c --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/Views/PlaygroundView.xaml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/Views/PlaygroundView.xaml.cs b/source/RevitLookup.UI.Playground/Client/Views/PlaygroundView.xaml.cs new file mode 100644 index 00000000..4f4e2c35 --- /dev/null +++ b/source/RevitLookup.UI.Playground/Client/Views/PlaygroundView.xaml.cs @@ -0,0 +1,44 @@ +using System.Windows; +using RevitLookup.UI.Playground.Client.ViewModels; +using RevitLookup.UI.Playground.Client.Views.Pages; +using Wpf.Ui; +using Wpf.Ui.Appearance; +using Wpf.Ui.Controls; + +namespace RevitLookup.UI.Playground.Client.Views; + +public sealed partial class PlaygroundView +{ + private readonly INavigationService _navigationService; + + public PlaygroundView(PlaygroundViewModel viewModel, + INavigationService navigationService, + IContentDialogService dialogService, + ISnackbarService snackbarService) + { + _navigationService = navigationService; + DataContext = viewModel; + InitializeComponent(); + + ApplicationThemeManager.Apply(this); + navigationService.SetNavigationControl(NavigationView); + dialogService.SetDialogHost(RootContentDialog); + snackbarService.SetSnackbarPresenter(SnackbarPresenter); + + Loaded += (sender, _) => + { + var self = (PlaygroundView)sender; + self._navigationService.Navigate(typeof(DashboardPage)); + }; + } + + private void OnNavigationSelectionChanged(object sender, RoutedEventArgs e) + { + if (sender is not NavigationView navigationView) return; + + var onControlsPage = navigationView.SelectedItem?.TargetPageType != typeof(DashboardPage); + var showHeader = onControlsPage ? Visibility.Visible : Visibility.Collapsed; + + NavigationView.SetCurrentValue(NavigationView.HeaderVisibilityProperty, showHeader); + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Config/OptionsConfiguration.cs b/source/RevitLookup.UI.Playground/Config/OptionsConfiguration.cs new file mode 100644 index 00000000..5b1bace9 --- /dev/null +++ b/source/RevitLookup.UI.Playground/Config/OptionsConfiguration.cs @@ -0,0 +1,22 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.Extensions.DependencyInjection; + +namespace RevitLookup.UI.Playground.Config; + +public static class OptionsConfiguration +{ + /// + /// Add global JsonSerialization configuration/> + /// + public static void AddSerializerOptions(this IServiceCollection services) + { + services.Configure(options => + { +#if DEBUG + options.WriteIndented = true; +#endif + options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; + }); + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Host.cs b/source/RevitLookup.UI.Playground/Host.cs index 5c6383a8..2406faf4 100644 --- a/source/RevitLookup.UI.Playground/Host.cs +++ b/source/RevitLookup.UI.Playground/Host.cs @@ -1,5 +1,11 @@ +using System.Windows; using Microsoft.Extensions.DependencyInjection; +using RevitLookup.Abstractions.Services; +using RevitLookup.UI.Framework.Services; +using RevitLookup.UI.Playground.Client.Services; +using RevitLookup.UI.Playground.Config; using Wpf.Ui; +using Wpf.Ui.Abstractions; namespace RevitLookup.UI.Playground; @@ -10,25 +16,50 @@ public static class Host { private static readonly IServiceProvider ServiceProvider = RegisterServices(); + private static ServiceProvider RegisterServices() + { + var services = new ServiceCollection(); + + services.AddSerializerOptions(); + + //Frontend services + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + //MVVM services + services.RegisterViews(); + services.RegisterViewModels(); + + return services.BuildServiceProvider(); + } + /// /// Gets a service of the specified type. /// /// The type of service object to get. - /// A service object of type T or null if there is no such service. + /// A service object of type T. public static T GetService() where T : class { return ServiceProvider.GetRequiredService(); } - private static ServiceProvider RegisterServices() + /// + /// Creates a window with the scope lifetime. + /// + /// The type of window to get. + /// A window of type T. + public static T CreateScope() where T : Window { - var services = new ServiceCollection(); + var scopeFactory = ServiceProvider.GetRequiredService(); + var scope = scopeFactory.CreateScope(); - //Frontend services - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + var window = scope.ServiceProvider.GetRequiredService(); + window.Closed += (_, _) => scope.Dispose(); - return services.BuildServiceProvider(); + return window; } } \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/RevitLookup.UI.Playground.csproj b/source/RevitLookup.UI.Playground/RevitLookup.UI.Playground.csproj index be9eef59..9a59f65a 100644 --- a/source/RevitLookup.UI.Playground/RevitLookup.UI.Playground.csproj +++ b/source/RevitLookup.UI.Playground/RevitLookup.UI.Playground.csproj @@ -6,16 +6,24 @@ WinExe latest enable - net48;net8.0-windows + net8.0-windows + + + + + + + + diff --git a/source/RevitLookup.UI.Playground/ViewModels/AboutProgram/MockAboutViewModel.cs b/source/RevitLookup.UI.Playground/ViewModels/AboutProgram/MockAboutViewModel.cs new file mode 100644 index 00000000..3bcf7bc6 --- /dev/null +++ b/source/RevitLookup.UI.Playground/ViewModels/AboutProgram/MockAboutViewModel.cs @@ -0,0 +1,93 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +using System.Runtime; +using System.Text; +using Bogus; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; +using RevitLookup.Abstractions.States; +using RevitLookup.Abstractions.ViewModels.AboutProgram; +using RevitLookup.UI.Framework.Views.AboutProgram; + +namespace RevitLookup.UI.Playground.ViewModels.AboutProgram; + +[UsedImplicitly] +public sealed partial class MockAboutViewModel : ObservableObject, IAboutViewModel +{ + private readonly IServiceProvider _serviceProvider; + + [ObservableProperty] private bool _isUpdateChecked; + [ObservableProperty] private SoftwareUpdateState _state; + [ObservableProperty] private Version _currentVersion; + [ObservableProperty] private string? _newVersion; + [ObservableProperty] private string? _errorMessage; + [ObservableProperty] private string? _releaseNotesUrl; + [ObservableProperty] private string? _latestCheckDate; + [ObservableProperty] private string _runtime; + + public MockAboutViewModel(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + + var faker = new Faker(); + CurrentVersion = faker.System.Version(); + Runtime = new StringBuilder() + .Append(".NET") + .Append(faker.Random.Int(1, 10)) + .Append(' ') + .Append(Environment.Is64BitProcess ? "x64" : "x86") + .Append(" (") + .Append(GCSettings.IsServerGC ? "Server" : "Workstation") + .Append(" GC)") + .ToString(); + } + + [RelayCommand] + private async Task CheckUpdatesAsync() + { + await Task.Delay(1000); + IsUpdateChecked = true; + + var faker = new Faker(); + + State = faker.PickRandom(); + NewVersion = faker.System.Version().ToString(3); + ReleaseNotesUrl = "https://github.com/"; + ErrorMessage = faker.Lorem.Sentence(); + LatestCheckDate = DateTime.Now.ToString("yyyy.MM.dd HH:mm:ss"); + } + + [RelayCommand] + private async Task DownloadUpdateAsync() + { + await Task.Delay(2000); + State = SoftwareUpdateState.ReadyToInstall; + } + + [RelayCommand] + private async Task ShowSoftwareDialogAsync() + { + var dialog = _serviceProvider.GetRequiredService(); + await dialog.ShowAsync(); + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/ViewModels/AboutProgram/MockOpenSourceViewModel.cs b/source/RevitLookup.UI.Playground/ViewModels/AboutProgram/MockOpenSourceViewModel.cs new file mode 100644 index 00000000..9c9f1391 --- /dev/null +++ b/source/RevitLookup.UI.Playground/ViewModels/AboutProgram/MockOpenSourceViewModel.cs @@ -0,0 +1,97 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +using CommunityToolkit.Mvvm.ComponentModel; +using JetBrains.Annotations; +using RevitLookup.Abstractions.Models; +using RevitLookup.Abstractions.ViewModels.AboutProgram; + +namespace RevitLookup.UI.Playground.ViewModels.AboutProgram; + +[UsedImplicitly] +public sealed class MockOpenSourceViewModel : ObservableObject, IOpenSourceViewModel +{ + public List Software { get; } = + [ + new() + { + SoftwareName = "CommunityToolkit.Mvvm", + SoftwareUri = "https://github.com/CommunityToolkit/dotnet", + LicenseName = "MIT License", + LicenseUri = "https://github.com/CommunityToolkit/dotnet/blob/main/License.md" + }, + new() + { + SoftwareName = "Microsoft.Extensions.Hosting", + SoftwareUri = "https://github.com/dotnet/runtime", + LicenseName = "MIT License", + LicenseUri = "https://github.com/dotnet/runtime/blob/main/LICENSE.TXT" + }, + new() + { + SoftwareName = "Nice3point.Revit.Api", + SoftwareUri = "https://github.com/Nice3point/RevitApi", + LicenseName = "MIT License", + LicenseUri = "https://github.com/Nice3point/RevitApi/blob/main/License.md" + }, + new() + { + SoftwareName = "Nice3point.Revit.Extensions", + SoftwareUri = "https://github.com/Nice3point/RevitExtensions", + LicenseName = "MIT License", + LicenseUri = "https://github.com/Nice3point/RevitExtensions/blob/main/License.md" + }, + new() + { + SoftwareName = "Nice3point.Revit.Templates", + SoftwareUri = "https://github.com/Nice3point/RevitTemplates", + LicenseName = "MIT License", + LicenseUri = "https://github.com/Nice3point/RevitTemplates/blob/main/License.md" + }, + new() + { + SoftwareName = "Nice3point.Revit.Toolkit", + SoftwareUri = "https://github.com/Nice3point/RevitToolkit", + LicenseName = "MIT License", + LicenseUri = "https://github.com/Nice3point/RevitToolkit/blob/main/License.md" + }, + new() + { + SoftwareName = "PolySharp", + SoftwareUri = "https://github.com/Sergio0694/PolySharp", + LicenseName = "MIT License", + LicenseUri = "https://github.com/Sergio0694/PolySharp/blob/main/LICENSE" + }, + new() + { + SoftwareName = "Serilog", + SoftwareUri = "https://github.com/serilog/serilog", + LicenseName = "Apache License 2.0", + LicenseUri = "https://github.com/serilog/serilog/blob/dev/LICENSE" + }, + new() + { + SoftwareName = "WPF-UI", + SoftwareUri = "https://github.com/lepoco/wpfui", + LicenseName = "MIT License", + LicenseUri = "https://github.com/lepoco/wpfui/blob/main/LICENSE" + } + ]; +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/ViewModels/Settings/MockSettingsViewModel.cs b/source/RevitLookup.UI.Playground/ViewModels/Settings/MockSettingsViewModel.cs new file mode 100644 index 00000000..ce311507 --- /dev/null +++ b/source/RevitLookup.UI.Playground/ViewModels/Settings/MockSettingsViewModel.cs @@ -0,0 +1,71 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; +using RevitLookup.Abstractions.Services; +using RevitLookup.Abstractions.ViewModels.Settings; +using RevitLookup.UI.Framework.Views.Settings; +using Wpf.Ui; +using Wpf.Ui.Appearance; +using Wpf.Ui.Controls; + +namespace RevitLookup.UI.Playground.ViewModels.Settings; + +[UsedImplicitly] +public sealed partial class MockSettingsViewModel(IServiceProvider serviceProvider, INotificationService notificationService) : ObservableObject, ISettingsViewModel +{ + [ObservableProperty] private ApplicationTheme _theme; + [ObservableProperty] private WindowBackdropType _background; + + [ObservableProperty] private bool _useTransition; + [ObservableProperty] private bool _useHardwareRendering; + [ObservableProperty] private bool _useSizeRestoring; + [ObservableProperty] private bool _useModifyTab; + + public List Themes { get; } = + [ + ApplicationTheme.Auto, + ApplicationTheme.Light, + ApplicationTheme.Dark, + ApplicationTheme.HighContrast + ]; + + public List BackgroundEffects { get; } = + [ + WindowBackdropType.None, + WindowBackdropType.Acrylic, + WindowBackdropType.Tabbed, + WindowBackdropType.Mica + ]; + + [RelayCommand] + private async Task ResetSettings() + { + var dialog = serviceProvider.GetRequiredService(); + var result = await dialog.ShowAsync(); + if (result != ContentDialogResult.Primary) return; + + notificationService.ShowSuccess("Reset was successful", "Some changes will be applied after closing the window"); + await Task.CompletedTask; + } +} \ No newline at end of file From 4087136a988c6476e692e22b501d2ae376227927 Mon Sep 17 00:00:00 2001 From: Nice3point Date: Wed, 6 Nov 2024 01:01:55 +0300 Subject: [PATCH 010/121] Add ModulesDialog --- .../Models/ModuleInfo.cs | 2 +- .../RevitLookup.Abstractions.csproj | 4 - .../ViewModels/Tools/IModulesViewModel.cs | 10 ++ .../Tools/ISearchElementsViewModel.cs | 7 + .../RevitLookup.UI.Framework.csproj | 5 + .../Views/AboutProgram/OpenSourceDialog.xaml | 4 +- .../Views/Settings/ResetSettingsDialog.xaml | 9 +- .../Views/Tools/ModulesDialog.xaml | 138 ++++++++++++++++++ .../Views/Tools/ModulesDialog.xaml.cs | 37 +++++ .../Views/Tools/SearchElementsDialog.xaml | 41 ++++++ .../Views/Tools/SearchElementsDialog.xaml.cs | 72 +++++++++ .../ViewModels/Pages/DialogsViewModel.cs | 15 ++ .../Client/Views/Pages/DialogsPage.xaml | 15 ++ .../Client/Views/PlaygroundView.xaml.cs | 5 +- .../ViewModels/Tools/MockModulesViewModel.cs | 92 ++++++++++++ .../Tools/MockSearchElementsViewModel.cs | 22 +++ 16 files changed, 463 insertions(+), 15 deletions(-) rename source/{RevitLookup => RevitLookup.Abstractions}/Models/ModuleInfo.cs (96%) create mode 100644 source/RevitLookup.Abstractions/ViewModels/Tools/IModulesViewModel.cs create mode 100644 source/RevitLookup.Abstractions/ViewModels/Tools/ISearchElementsViewModel.cs create mode 100644 source/RevitLookup.UI.Framework/Views/Tools/ModulesDialog.xaml create mode 100644 source/RevitLookup.UI.Framework/Views/Tools/ModulesDialog.xaml.cs create mode 100644 source/RevitLookup.UI.Framework/Views/Tools/SearchElementsDialog.xaml create mode 100644 source/RevitLookup.UI.Framework/Views/Tools/SearchElementsDialog.xaml.cs create mode 100644 source/RevitLookup.UI.Playground/ViewModels/Tools/MockModulesViewModel.cs create mode 100644 source/RevitLookup.UI.Playground/ViewModels/Tools/MockSearchElementsViewModel.cs diff --git a/source/RevitLookup/Models/ModuleInfo.cs b/source/RevitLookup.Abstractions/Models/ModuleInfo.cs similarity index 96% rename from source/RevitLookup/Models/ModuleInfo.cs rename to source/RevitLookup.Abstractions/Models/ModuleInfo.cs index 2abae7f6..866d140f 100644 --- a/source/RevitLookup/Models/ModuleInfo.cs +++ b/source/RevitLookup.Abstractions/Models/ModuleInfo.cs @@ -18,7 +18,7 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. -namespace RevitLookup.Models; +namespace RevitLookup.Abstractions.Models; public sealed class ModuleInfo { diff --git a/source/RevitLookup.Abstractions/RevitLookup.Abstractions.csproj b/source/RevitLookup.Abstractions/RevitLookup.Abstractions.csproj index 43c6df0e..834f3b32 100644 --- a/source/RevitLookup.Abstractions/RevitLookup.Abstractions.csproj +++ b/source/RevitLookup.Abstractions/RevitLookup.Abstractions.csproj @@ -16,10 +16,6 @@ - - - - diff --git a/source/RevitLookup.Abstractions/ViewModels/Tools/IModulesViewModel.cs b/source/RevitLookup.Abstractions/ViewModels/Tools/IModulesViewModel.cs new file mode 100644 index 00000000..75e11c97 --- /dev/null +++ b/source/RevitLookup.Abstractions/ViewModels/Tools/IModulesViewModel.cs @@ -0,0 +1,10 @@ +using RevitLookup.Abstractions.Models; + +namespace RevitLookup.Abstractions.ViewModels.Tools; + +public interface IModulesViewModel +{ + string SearchText { get; set; } + List FilteredModules { get; set; } + List Modules { get; set; } +} \ No newline at end of file diff --git a/source/RevitLookup.Abstractions/ViewModels/Tools/ISearchElementsViewModel.cs b/source/RevitLookup.Abstractions/ViewModels/Tools/ISearchElementsViewModel.cs new file mode 100644 index 00000000..1f0d676a --- /dev/null +++ b/source/RevitLookup.Abstractions/ViewModels/Tools/ISearchElementsViewModel.cs @@ -0,0 +1,7 @@ +namespace RevitLookup.Abstractions.ViewModels.Tools; + +public interface ISearchElementsViewModel +{ + string SearchText { get; set; } + bool SearchElements(); +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/RevitLookup.UI.Framework.csproj b/source/RevitLookup.UI.Framework/RevitLookup.UI.Framework.csproj index c6a45da7..667cf38b 100644 --- a/source/RevitLookup.UI.Framework/RevitLookup.UI.Framework.csproj +++ b/source/RevitLookup.UI.Framework/RevitLookup.UI.Framework.csproj @@ -11,6 +11,11 @@ + + MSBuild:Compile + Wpf + Designer + diff --git a/source/RevitLookup.UI.Framework/Views/AboutProgram/OpenSourceDialog.xaml b/source/RevitLookup.UI.Framework/Views/AboutProgram/OpenSourceDialog.xaml index 3e36804d..6672b431 100644 --- a/source/RevitLookup.UI.Framework/Views/AboutProgram/OpenSourceDialog.xaml +++ b/source/RevitLookup.UI.Framework/Views/AboutProgram/OpenSourceDialog.xaml @@ -17,9 +17,6 @@ - - - + + + + + + + + + \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/Tools/UnitsDialog.xaml.cs b/source/RevitLookup.UI.Framework/Views/Tools/UnitsDialog.xaml.cs new file mode 100644 index 00000000..0aec8c70 --- /dev/null +++ b/source/RevitLookup.UI.Framework/Views/Tools/UnitsDialog.xaml.cs @@ -0,0 +1,125 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using RevitLookup.Abstractions.Models.Tools; +using RevitLookup.Abstractions.ViewModels.Tools; +using RevitLookup.UI.Framework.Extensions; +using Wpf.Ui; +using Visibility = System.Windows.Visibility; + +namespace RevitLookup.UI.Framework.Views.Tools; + +public sealed partial class UnitsDialog +{ + private readonly IUnitsViewModel _viewModel; + + public UnitsDialog(IContentDialogService dialogService, IUnitsViewModel viewModel) : base(dialogService.GetDialogHost()) + { + _viewModel = viewModel; + DataContext = _viewModel; + InitializeComponent(); + } + + public async Task ShowParametersDialogAsync() + { + _viewModel.InitializeParameters(); + + Title = "BuiltIn Parameters"; + DialogMaxWidth = 1000; + + await ShowAsync(); + } + + public async Task ShowCategoriesDialogAsync() + { + _viewModel.InitializeCategories(); + + Title = "BuiltIn Categories"; + DialogMaxWidth = 600; + + await ShowAsync(); + } + + public async Task ShowForgeSchemaDialogAsync() + { + _viewModel.InitializeForgeSchema(); + + ClassColumn.Visibility = Visibility.Visible; + Title = "Forge Schema"; + DialogMaxWidth = 1100; + + await ShowAsync(); + } + + private void OnMouseEnter(object sender, RoutedEventArgs routedEventArgs) + { + var element = (FrameworkElement)sender; + var unitInfo = (UnitInfo)element.DataContext; + CreateTreeContextMenu(unitInfo, element); + } + + private void CreateTreeContextMenu(UnitInfo info, FrameworkElement row) + { + var contextMenu = new ContextMenu + { + Resources = Window.GetWindow(row)!.Resources, + PlacementTarget = row + }; + + contextMenu.AddMenuItem("CopyMenuItem") + .SetHeader("Copy unit") + .SetCommand(info, unitInfo => Clipboard.SetDataObject(unitInfo.Unit)) + .SetShortcut(ModifierKeys.Control, Key.C); + + contextMenu.AddMenuItem("CopyMenuItem") + .SetHeader("Copy label") + .SetCommand(info, unitInfo => Clipboard.SetDataObject(unitInfo.Label)); + + if (info.Class is not null) + { + contextMenu.AddMenuItem("CopyMenuItem") + .SetHeader("Copy class") + .SetCommand(info, unitInfo => Clipboard.SetDataObject(unitInfo.Class!)) + .SetShortcut(ModifierKeys.Control | ModifierKeys.Shift, Key.C); + } + + contextMenu.AddMenuItem("SnoopMenuItem") + .SetHeader("Snoop") + .SetCommand(info, unitInfo => + { + // var obj = unitInfo.UnitObject switch + // { + // BuiltInParameter parameter => RevitShell.GetBuiltinParameter(parameter), + // BuiltInCategory category => RevitShell.GetBuiltinCategory(category), + // _ => unitInfo.UnitObject + // }; + + Hide(); + // _serviceProvider.GetRequiredService().Snoop(new SnoopableObject(obj)); + // _serviceProvider.GetRequiredService().Navigate(typeof(SnoopPage)); + }); + + + row.ContextMenu = contextMenu; + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/App.xaml b/source/RevitLookup.UI.Playground/App.xaml index 30b4c90b..98484cc3 100644 --- a/source/RevitLookup.UI.Playground/App.xaml +++ b/source/RevitLookup.UI.Playground/App.xaml @@ -3,6 +3,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ui="http://revitlookup.com/xaml" + xmlns:menu="clr-namespace:RevitLookup.UI.Framework.Markup.Menu;assembly=RevitLookup.UI.Framework" Startup="OnStartup"> @@ -12,6 +13,7 @@ Source="pack://application:,,,/RevitLookup.UI.Playground;component/Client/Controls/ControlExample.xaml" /> + diff --git a/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/DialogsViewModel.cs b/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/DialogsViewModel.cs index 35b8686f..950d344c 100644 --- a/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/DialogsViewModel.cs +++ b/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/DialogsViewModel.cs @@ -11,23 +11,44 @@ namespace RevitLookup.UI.Playground.Client.ViewModels.Pages; public sealed partial class DialogsViewModel(IServiceProvider serviceProvider) : ObservableObject { [RelayCommand] - private async Task ShowOpenSourceDialog() + private async Task ShowOpenSourceDialogAsync() { var dialog = serviceProvider.GetRequiredService(); await dialog.ShowAsync(); } [RelayCommand] - private async Task ShowSearchElementsDialog() + private async Task ShowSearchElementsDialogAsync() { var dialog = serviceProvider.GetRequiredService(); await dialog.ShowAsync(); } [RelayCommand] - private async Task ShowModulesDialog() + private async Task ShowModulesDialogAsync() { var dialog = serviceProvider.GetRequiredService(); await dialog.ShowAsync(); } + + [RelayCommand] + private async Task ShowParametersDialogAsync() + { + var dialog = serviceProvider.GetRequiredService(); + await dialog.ShowParametersDialogAsync(); + } + + [RelayCommand] + private async Task ShowCategoriesDialogAsync() + { + var dialog = serviceProvider.GetRequiredService(); + await dialog.ShowCategoriesDialogAsync(); + } + + [RelayCommand] + private async Task ShowForgeSchemaDialogAsync() + { + var dialog = serviceProvider.GetRequiredService(); + await dialog.ShowForgeSchemaDialogAsync(); + } } \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/Views/Pages/DialogsPage.xaml b/source/RevitLookup.UI.Playground/Client/Views/Pages/DialogsPage.xaml index f1f2485d..b47c97ff 100644 --- a/source/RevitLookup.UI.Playground/Client/Views/Pages/DialogsPage.xaml +++ b/source/RevitLookup.UI.Playground/Client/Views/Pages/DialogsPage.xaml @@ -37,6 +37,21 @@ Icon="{ui:SymbolIcon BroadActivityFeed24}" Content="Modules dialog" Command="{Binding ViewModel.ShowModulesDialogCommand}" /> + + + \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/ViewModels/AboutProgram/MockOpenSourceViewModel.cs b/source/RevitLookup.UI.Playground/ViewModels/AboutProgram/MockOpenSourceViewModel.cs index 9c9f1391..44d032db 100644 --- a/source/RevitLookup.UI.Playground/ViewModels/AboutProgram/MockOpenSourceViewModel.cs +++ b/source/RevitLookup.UI.Playground/ViewModels/AboutProgram/MockOpenSourceViewModel.cs @@ -20,7 +20,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using JetBrains.Annotations; -using RevitLookup.Abstractions.Models; +using RevitLookup.Abstractions.Models.AboutProgram; using RevitLookup.Abstractions.ViewModels.AboutProgram; namespace RevitLookup.UI.Playground.ViewModels.AboutProgram; diff --git a/source/RevitLookup.UI.Playground/ViewModels/Tools/MockModulesViewModel.cs b/source/RevitLookup.UI.Playground/ViewModels/Tools/MockModulesViewModel.cs index 71850c0c..1e6dd97d 100644 --- a/source/RevitLookup.UI.Playground/ViewModels/Tools/MockModulesViewModel.cs +++ b/source/RevitLookup.UI.Playground/ViewModels/Tools/MockModulesViewModel.cs @@ -21,7 +21,7 @@ using System.Runtime.Loader; using CommunityToolkit.Mvvm.ComponentModel; -using RevitLookup.Abstractions.Models; +using RevitLookup.Abstractions.Models.Tools; using RevitLookup.Abstractions.ViewModels.Tools; namespace RevitLookup.UI.Playground.ViewModels.Tools; diff --git a/source/RevitLookup.UI.Playground/ViewModels/Tools/MockUnitsViewModel.cs b/source/RevitLookup.UI.Playground/ViewModels/Tools/MockUnitsViewModel.cs new file mode 100644 index 00000000..d047d49a --- /dev/null +++ b/source/RevitLookup.UI.Playground/ViewModels/Tools/MockUnitsViewModel.cs @@ -0,0 +1,89 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +using Bogus; +using CommunityToolkit.Mvvm.ComponentModel; +using JetBrains.Annotations; +using RevitLookup.Abstractions.Models.Tools; +using RevitLookup.Abstractions.ViewModels.Tools; + +namespace RevitLookup.UI.Playground.ViewModels.Tools; + +[UsedImplicitly] +public sealed partial class MockUnitsViewModel : ObservableObject, IUnitsViewModel +{ + [ObservableProperty] private List _units = []; + [ObservableProperty] private List _filteredUnits = []; + [ObservableProperty] private string _searchText = string.Empty; + + public void InitializeParameters() + { + Units = new Faker() + .RuleFor(info => info.Unit, faker => faker.Lorem.Sentence()) + .RuleFor(info => info.Label, faker => faker.Lorem.Word()) + .RuleFor(info => info.Value, faker => faker.Lorem.Word()) + .Generate(200); + } + + public void InitializeCategories() + { + Units = new Faker() + .RuleFor(info => info.Unit, faker => faker.Lorem.Sentence()) + .RuleFor(info => info.Label, faker => faker.Lorem.Word()) + .RuleFor(info => info.Value, faker => faker.Lorem.Word()) + .Generate(200); + } + + public void InitializeForgeSchema() + { + Units = new Faker() + .RuleFor(info => info.Unit, faker => faker.Lorem.Sentence()) + .RuleFor(info => info.Label, faker => faker.Lorem.Word()) + .RuleFor(info => info.Value, faker => faker.Lorem.Word()) + .RuleFor(info => info.Class, faker => faker.Lorem.Sentence()) + .Generate(200); + } + + async partial void OnSearchTextChanged(string value) + { + if (string.IsNullOrEmpty(SearchText)) + { + FilteredUnits = Units; + return; + } + + FilteredUnits = await Task.Run(() => + { + var formattedText = value.ToLower().Trim(); + var searchResults = new List(); + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var family in Units) + if (family.Label.ToLower().Contains(formattedText) || family.Unit.ToLower().Contains(formattedText)) + searchResults.Add(family); + + return searchResults; + }); + } + + partial void OnUnitsChanged(List value) + { + FilteredUnits = value; + } +} \ No newline at end of file From 006770f994a78e5434b5a08fc55cf087e72a6ebd Mon Sep 17 00:00:00 2001 From: Nice3point Date: Sat, 9 Nov 2024 15:58:43 +0300 Subject: [PATCH 012/121] Add EditValue dialogs --- .../Entries/ObservableIniEntry.cs | 66 +++++++++++++++ ...onverter.cs => InverseBooleanConverter.cs} | 5 +- .../RevitLookup.UI.Framework.csproj | 15 ---- .../Views/AboutProgram/AboutPage.xaml | 2 +- .../Views/AboutProgram/AboutPage.xaml.cs | 2 +- .../Views/AboutProgram/OpenSourceDialog.xaml | 3 - .../AboutProgram/OpenSourceDialog.xaml.cs | 4 +- .../Views/EditDialogs/EditIniEntryDialog.xaml | 59 +++++++++++++ .../EditDialogs/EditIniEntryDialog.xaml.cs | 84 +++++++++++++++++++ .../Views/EditDialogs/EditValueDialog.xaml | 37 ++++++++ .../Views/EditDialogs/EditValueDialog.xaml.cs | 54 ++++++++++++ .../Views/Settings/SettingsPage.xaml | 31 +++---- .../Views/Settings/SettingsPage.xaml.cs | 2 +- .../Views/Tools/UnitsDialog.xaml.cs | 2 +- source/RevitLookup.UI.Playground/App.xaml | 3 +- .../ViewModels/Pages/DialogsViewModel.cs | 32 ++++++- .../Client/ViewModels/PlaygroundViewModel.cs | 13 +++ .../Client/Views/Pages/DialogsPage.xaml | 26 +++++- .../Client/Views/Pages/PagesPage.xaml | 2 +- .../Client/Views/Pages/WindowsPage.xaml | 3 +- .../Client/Views/PlaygroundView.xaml | 1 + .../Client/Views/PlaygroundView.xaml.cs | 2 - 22 files changed, 393 insertions(+), 55 deletions(-) create mode 100644 source/RevitLookup.Abstractions/ObservableModels/Entries/ObservableIniEntry.cs rename source/RevitLookup.UI.Framework/Converters/{InverseBoolConverter.cs => InverseBooleanConverter.cs} (80%) create mode 100644 source/RevitLookup.UI.Framework/Views/EditDialogs/EditIniEntryDialog.xaml create mode 100644 source/RevitLookup.UI.Framework/Views/EditDialogs/EditIniEntryDialog.xaml.cs create mode 100644 source/RevitLookup.UI.Framework/Views/EditDialogs/EditValueDialog.xaml create mode 100644 source/RevitLookup.UI.Framework/Views/EditDialogs/EditValueDialog.xaml.cs diff --git a/source/RevitLookup.Abstractions/ObservableModels/Entries/ObservableIniEntry.cs b/source/RevitLookup.Abstractions/ObservableModels/Entries/ObservableIniEntry.cs new file mode 100644 index 00000000..c05bd86c --- /dev/null +++ b/source/RevitLookup.Abstractions/ObservableModels/Entries/ObservableIniEntry.cs @@ -0,0 +1,66 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +using System.ComponentModel.DataAnnotations; +using CommunityToolkit.Mvvm.ComponentModel; + +namespace RevitLookup.Abstractions.ObservableModels.Entries; + +public sealed partial class ObservableIniEntry : ObservableValidator +{ + [ObservableProperty] [Required] [NotifyDataErrorInfo] private string _category = string.Empty; + [ObservableProperty] [Required] [NotifyDataErrorInfo] private string _property = string.Empty; + [ObservableProperty] private string _value = string.Empty; + [ObservableProperty] private string? _defaultValue; + [ObservableProperty] private bool _isActive; + [ObservableProperty] private bool _isModified; + + public bool UserDefined { get; set; } + + public void Validate() + { + ValidateAllProperties(); + } + + partial void OnIsActiveChanged(bool value) + { + UserDefined = true; + } + + partial void OnValueChanged(string value) + { + IsModified = DefaultValue is not null && value != DefaultValue; + } + + partial void OnDefaultValueChanged(string? value) + { + IsModified = value != Value; + } + + public ObservableIniEntry Clone() + { + return new ObservableIniEntry + { + Category = Category, + Property = Property, + Value = Value + }; + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Converters/InverseBoolConverter.cs b/source/RevitLookup.UI.Framework/Converters/InverseBooleanConverter.cs similarity index 80% rename from source/RevitLookup.UI.Framework/Converters/InverseBoolConverter.cs rename to source/RevitLookup.UI.Framework/Converters/InverseBooleanConverter.cs index d713ed0c..4bdc4741 100644 --- a/source/RevitLookup.UI.Framework/Converters/InverseBoolConverter.cs +++ b/source/RevitLookup.UI.Framework/Converters/InverseBooleanConverter.cs @@ -1,11 +1,10 @@ -using System; -using System.Globalization; +using System.Globalization; using System.Windows.Data; using System.Windows.Markup; namespace RevitLookup.UI.Framework.Converters; -public sealed class InverseBoolConverter : MarkupExtension, IValueConverter +public sealed class InverseBooleanConverter : MarkupExtension, IValueConverter { public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { diff --git a/source/RevitLookup.UI.Framework/RevitLookup.UI.Framework.csproj b/source/RevitLookup.UI.Framework/RevitLookup.UI.Framework.csproj index 527d495b..c6a45da7 100644 --- a/source/RevitLookup.UI.Framework/RevitLookup.UI.Framework.csproj +++ b/source/RevitLookup.UI.Framework/RevitLookup.UI.Framework.csproj @@ -11,21 +11,6 @@ - - MSBuild:Compile - Wpf - Designer - - - MSBuild:Compile - Wpf - Designer - - - MSBuild:Compile - Wpf - Designer - diff --git a/source/RevitLookup.UI.Framework/Views/AboutProgram/AboutPage.xaml b/source/RevitLookup.UI.Framework/Views/AboutProgram/AboutPage.xaml index eef8cdb3..7e07f0e9 100644 --- a/source/RevitLookup.UI.Framework/Views/AboutProgram/AboutPage.xaml +++ b/source/RevitLookup.UI.Framework/Views/AboutProgram/AboutPage.xaml @@ -38,7 +38,7 @@ FontTypography="BodyStrong" Text="Version" /> diff --git a/source/RevitLookup.UI.Framework/Views/AboutProgram/AboutPage.xaml.cs b/source/RevitLookup.UI.Framework/Views/AboutProgram/AboutPage.xaml.cs index edc40804..525b17bf 100644 --- a/source/RevitLookup.UI.Framework/Views/AboutProgram/AboutPage.xaml.cs +++ b/source/RevitLookup.UI.Framework/Views/AboutProgram/AboutPage.xaml.cs @@ -28,8 +28,8 @@ public sealed partial class AboutPage : INavigableView public AboutPage(IAboutViewModel viewModel) { ViewModel = viewModel; - InitializeComponent(); DataContext = this; + InitializeComponent(); } public IAboutViewModel ViewModel { get; } diff --git a/source/RevitLookup.UI.Framework/Views/AboutProgram/OpenSourceDialog.xaml b/source/RevitLookup.UI.Framework/Views/AboutProgram/OpenSourceDialog.xaml index 6672b431..284ec1eb 100644 --- a/source/RevitLookup.UI.Framework/Views/AboutProgram/OpenSourceDialog.xaml +++ b/source/RevitLookup.UI.Framework/Views/AboutProgram/OpenSourceDialog.xaml @@ -64,9 +64,6 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/Dashboard/DashboardPage.xaml.cs b/source/RevitLookup.UI.Framework/Views/Dashboard/DashboardPage.xaml.cs new file mode 100644 index 00000000..568833a5 --- /dev/null +++ b/source/RevitLookup.UI.Framework/Views/Dashboard/DashboardPage.xaml.cs @@ -0,0 +1,36 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +using RevitLookup.Abstractions.ViewModels.Dashboard; +using Wpf.Ui.Abstractions.Controls; + +namespace RevitLookup.UI.Framework.Views.Dashboard; + +public sealed partial class DashboardPage : INavigableView +{ + public DashboardPage(IDashboardViewModel viewModel) + { + ViewModel = viewModel; + DataContext = this; + InitializeComponent(); + } + + public IDashboardViewModel ViewModel { get; } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/Windows/RevitLookupView.xaml b/source/RevitLookup.UI.Framework/Views/Windows/RevitLookupView.xaml index 200ad6eb..f98609c3 100644 --- a/source/RevitLookup.UI.Framework/Views/Windows/RevitLookupView.xaml +++ b/source/RevitLookup.UI.Framework/Views/Windows/RevitLookupView.xaml @@ -7,6 +7,7 @@ xmlns:rl="http://revitlookup.com/xaml" xmlns:settings="clr-namespace:RevitLookup.UI.Framework.Views.Settings" xmlns:aboutProgram="clr-namespace:RevitLookup.UI.Framework.Views.AboutProgram" + xmlns:dashboard="clr-namespace:RevitLookup.UI.Framework.Views.Dashboard" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="200" @@ -31,10 +32,10 @@ BreadcrumbBar="{Binding ElementName=BreadcrumbBar}" TitleBar="{Binding ElementName=TitleBar, Mode=OneWay}"> - - - - + diff --git a/source/RevitLookup.UI.Playground/Client/Controls/PageViewer.xaml b/source/RevitLookup.UI.Playground/Client/Controls/PageViewer.xaml index 9a1599cd..a23cad96 100644 --- a/source/RevitLookup.UI.Playground/Client/Controls/PageViewer.xaml +++ b/source/RevitLookup.UI.Playground/Client/Controls/PageViewer.xaml @@ -16,15 +16,13 @@ + Icon="{rl:ImageIcon '/Client/Resources/Images/ProductIcon.png'}" /> - + (); viewer.ShowPage(); } - + + [RelayCommand] + private void ShowDashboardPage() + { + var viewer = Host.CreateScope(); + viewer.SizeToContent = SizeToContent.Width; + viewer.Height = 850; + viewer.ShowPage(); + } + [RelayCommand] private void ShowAboutPage() { diff --git a/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/WindowsViewModel.cs b/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/WindowsViewModel.cs index e969b1f9..38863138 100644 --- a/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/WindowsViewModel.cs +++ b/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/WindowsViewModel.cs @@ -2,7 +2,10 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; +using RevitLookup.UI.Framework.Views.Dashboard; using RevitLookup.UI.Framework.Views.Windows; +using Wpf.Ui; namespace RevitLookup.UI.Playground.Client.ViewModels.Pages; @@ -12,8 +15,15 @@ public sealed partial class WindowsViewModel : ObservableObject [RelayCommand] private void ShowRevitLookupWindow() { - var view = Host.CreateScope(); + var scopeFactory = Host.GetService(); + var scope = scopeFactory.CreateScope(); + + var view = scope.ServiceProvider.GetRequiredService(); + var navigationService = scope.ServiceProvider.GetRequiredService(); + + view.Closed += (_, _) => scope.Dispose(); view.WindowStartupLocation = WindowStartupLocation.CenterScreen; view.Show(); + navigationService.Navigate(typeof(DashboardPage)); } } \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/Views/Pages/PagesPage.xaml b/source/RevitLookup.UI.Playground/Client/Views/Pages/PagesPage.xaml index dbbf49b9..d3025231 100644 --- a/source/RevitLookup.UI.Playground/Client/Views/Pages/PagesPage.xaml +++ b/source/RevitLookup.UI.Playground/Client/Views/Pages/PagesPage.xaml @@ -17,6 +17,11 @@ + NavigationGroups { get; } + + [RelayCommand] + private async Task NavigatePage(string? parameter) + { + await Task.CompletedTask; + } + + [RelayCommand] + private async Task OpenDialog(string parameter) + { + await Task.CompletedTask; + } +} \ No newline at end of file From c2959e512949eed30960f5e30928ae51b0656a5f Mon Sep 17 00:00:00 2001 From: Nice3point Date: Tue, 19 Nov 2024 19:50:16 +0300 Subject: [PATCH 019/121] Sync card margin --- .../Client/Views/Pages/DashboardPage.xaml | 4 +-- .../Client/Views/Pages/DialogsPage.xaml | 30 +++++++++---------- .../Client/Views/Pages/PagesPage.xaml | 11 +++++-- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/source/RevitLookup.UI.Playground/Client/Views/Pages/DashboardPage.xaml b/source/RevitLookup.UI.Playground/Client/Views/Pages/DashboardPage.xaml index fd8a9529..059e09e5 100644 --- a/source/RevitLookup.UI.Playground/Client/Views/Pages/DashboardPage.xaml +++ b/source/RevitLookup.UI.Playground/Client/Views/Pages/DashboardPage.xaml @@ -58,7 +58,7 @@ @@ -72,7 +72,7 @@ diff --git a/source/RevitLookup.UI.Playground/Client/Views/Pages/DialogsPage.xaml b/source/RevitLookup.UI.Playground/Client/Views/Pages/DialogsPage.xaml index 016c9674..39d5e782 100644 --- a/source/RevitLookup.UI.Playground/Client/Views/Pages/DialogsPage.xaml +++ b/source/RevitLookup.UI.Playground/Client/Views/Pages/DialogsPage.xaml @@ -18,7 +18,7 @@ Text="About program" FontTypography="BodyStrong" /> @@ -28,27 +28,27 @@ Text="Tools" FontTypography="BodyStrong" /> @@ -58,32 +58,32 @@ Text="Visualization" FontTypography="BodyStrong" /> @@ -93,17 +93,17 @@ Text="Edit dialogs" FontTypography="BodyStrong" /> diff --git a/source/RevitLookup.UI.Playground/Client/Views/Pages/PagesPage.xaml b/source/RevitLookup.UI.Playground/Client/Views/Pages/PagesPage.xaml index d3025231..b0013920 100644 --- a/source/RevitLookup.UI.Playground/Client/Views/Pages/PagesPage.xaml +++ b/source/RevitLookup.UI.Playground/Client/Views/Pages/PagesPage.xaml @@ -18,17 +18,22 @@ Text="Navigation elements" FontTypography="BodyStrong" /> + From d322d7099b480f6ad1c3bd8e8c17345c19a2e77e Mon Sep 17 00:00:00 2001 From: Nice3point Date: Sat, 30 Nov 2024 17:48:56 +0300 Subject: [PATCH 020/121] Decomposition object support --- ...itionMemberData.cs => DecomposedMember.cs} | 6 +- .../Metadata/DecomposedObject.cs | 16 +++++ .../Engine/LookupComposer.Decompose.cs | 16 +++-- .../Engine/LookupComposer.Diagnostic.cs | 6 +- .../Engine/LookupComposer.Enumeration.cs | 4 +- .../Engine/LookupComposer.Events.cs | 2 +- .../Engine/LookupComposer.Extensions.cs | 4 +- .../Engine/LookupComposer.Fields.cs | 2 +- .../Engine/LookupComposer.Methods.cs | 2 +- .../Engine/LookupComposer.Properties.cs | 2 +- .../Engine/LookupComposer.WriteHelpers.cs | 63 +++++++++++++++---- source/LookupEngine/Engine/LookupComposer.cs | 43 +++++++------ .../Formaters/ReflexionFormater.cs | 7 +-- tests/LookupEngine.Tests/OptionsTests.cs | 8 +-- 14 files changed, 118 insertions(+), 63 deletions(-) rename source/LookupEngine.Abstractions/Metadata/{DecompositionMemberData.cs => DecomposedMember.cs} (86%) create mode 100644 source/LookupEngine.Abstractions/Metadata/DecomposedObject.cs diff --git a/source/LookupEngine.Abstractions/Metadata/DecompositionMemberData.cs b/source/LookupEngine.Abstractions/Metadata/DecomposedMember.cs similarity index 86% rename from source/LookupEngine.Abstractions/Metadata/DecompositionMemberData.cs rename to source/LookupEngine.Abstractions/Metadata/DecomposedMember.cs index 2d464892..d2b3bff4 100644 --- a/source/LookupEngine.Abstractions/Metadata/DecompositionMemberData.cs +++ b/source/LookupEngine.Abstractions/Metadata/DecomposedMember.cs @@ -7,13 +7,13 @@ namespace LookupEngine.Abstractions; [PublicAPI] [DebuggerDisplay("Name = {Name} Value = {Value}")] -public sealed class DecompositionMemberData +public sealed class DecomposedMember { public int Depth { get; set; } - public required string Name { get; set; } public required object? Value { get; set; } + public required string Name { get; set; } public required string Type { get; set; } - public required string? TypeFullName { get; set; } + public required string TypeFullName { get; set; } public string? Description { get; set; } public double ComputationTime { get; set; } public long AllocatedBytes { get; set; } diff --git a/source/LookupEngine.Abstractions/Metadata/DecomposedObject.cs b/source/LookupEngine.Abstractions/Metadata/DecomposedObject.cs new file mode 100644 index 00000000..dd8310b2 --- /dev/null +++ b/source/LookupEngine.Abstractions/Metadata/DecomposedObject.cs @@ -0,0 +1,16 @@ +using System.Diagnostics; +using JetBrains.Annotations; + +// ReSharper disable once CheckNamespace +namespace LookupEngine.Abstractions; + +[PublicAPI] +[DebuggerDisplay("Name = {Name} Value = {Value}")] +public sealed class DecomposedObject +{ + public required object? Value { get; init; } + public required string Name { get; init; } + public required string Type { get; set; } + public required string TypeFullName { get; set; } + public required List Members { get; init; } +} \ No newline at end of file diff --git a/source/LookupEngine/Engine/LookupComposer.Decompose.cs b/source/LookupEngine/Engine/LookupComposer.Decompose.cs index 19c25461..1ee99dac 100644 --- a/source/LookupEngine/Engine/LookupComposer.Decompose.cs +++ b/source/LookupEngine/Engine/LookupComposer.Decompose.cs @@ -26,11 +26,12 @@ namespace LookupEngine; public sealed partial class LookupComposer { - private List DecomposeInstanceObject(object instance) + private DecomposedObject DecomposeInstanceObject(object instance) { - InputObject = instance; var objectType = instance.GetType(); var objectTypeHierarchy = GetTypeHierarchy(objectType); + var instanceDescriptor = _options.TypeResolver.Invoke(instance, null); + _decomposedObject = CreateInstanceDecomposition(instance, objectType, instanceDescriptor); for (var i = objectTypeHierarchy.Count - 1; i >= 0; i--) { @@ -53,22 +54,25 @@ private List DecomposeInstanceObject(object instance) Subtype = objectType; AddEnumerableItems(); - return _descriptors; + return _decomposedObject; } - private List DecomposeStaticObject(Type objectType) + private DecomposedObject DecomposeStaticObject(Type objectType) { var flags = BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly; if (!_options.IgnorePrivateMembers) flags |= BindingFlags.NonPublic; + var staticDescriptor = _options.TypeResolver.Invoke(null, objectType); + _decomposedObject = CreateStaticDecomposition(objectType, staticDescriptor); + Subtype = objectType; - SubtypeDescriptor = _options.TypeResolver.Invoke(null, Subtype); + SubtypeDescriptor = staticDescriptor; DecomposeFields(flags); DecomposeProperties(flags); DecomposeMethods(flags); - return _descriptors; + return _decomposedObject; } private List GetTypeHierarchy(Type inputType) diff --git a/source/LookupEngine/Engine/LookupComposer.Diagnostic.cs b/source/LookupEngine/Engine/LookupComposer.Diagnostic.cs index efd5cca5..4ec206a0 100644 --- a/source/LookupEngine/Engine/LookupComposer.Diagnostic.cs +++ b/source/LookupEngine/Engine/LookupComposer.Diagnostic.cs @@ -35,7 +35,7 @@ public sealed partial class LookupComposer _timeDiagnoser.StartMonitoring(); _memoryDiagnoser.StartMonitoring(); - var value = member.GetValue(InputObject); + var value = member.GetValue(DecomposedObject.Value); _memoryDiagnoser.StopMonitoring(); _timeDiagnoser.StopMonitoring(); @@ -50,7 +50,7 @@ public sealed partial class LookupComposer _timeDiagnoser.StartMonitoring(); _memoryDiagnoser.StartMonitoring(); - return member.GetValue(InputObject); + return member.GetValue(DecomposedObject.Value); } finally { @@ -66,7 +66,7 @@ public sealed partial class LookupComposer _timeDiagnoser.StartMonitoring(); _memoryDiagnoser.StartMonitoring(); - return member.Invoke(InputObject, null); + return member.Invoke(DecomposedObject.Value, null); } finally { diff --git a/source/LookupEngine/Engine/LookupComposer.Enumeration.cs b/source/LookupEngine/Engine/LookupComposer.Enumeration.cs index c78032a0..2b05a1d3 100644 --- a/source/LookupEngine/Engine/LookupComposer.Enumeration.cs +++ b/source/LookupEngine/Engine/LookupComposer.Enumeration.cs @@ -27,14 +27,14 @@ public sealed partial class LookupComposer { private void AddEnumerableItems() { - if (InputObject is not IEnumerable enumerable) return; + if (DecomposedObject.Value is not IEnumerable enumerable) return; var enumerator = enumerable.GetEnumerator(); var index = 0; while (enumerator.MoveNext()) { - WriteEnumerableResult(enumerator.Current, index); + WriteEnumerableMember(enumerator.Current, index); index++; } diff --git a/source/LookupEngine/Engine/LookupComposer.Events.cs b/source/LookupEngine/Engine/LookupComposer.Events.cs index bb5c3290..6713c4da 100644 --- a/source/LookupEngine/Engine/LookupComposer.Events.cs +++ b/source/LookupEngine/Engine/LookupComposer.Events.cs @@ -13,7 +13,7 @@ private void DecomposeEvents(BindingFlags bindingFlags) var members = Subtype.GetEvents(bindingFlags); foreach (var member in members) { - WriteDecompositionResult(ReflexionFormater.FormatTypeName(member.EventHandlerType), member); + WriteDecompositionMember(ReflexionFormater.FormatTypeName(member.EventHandlerType ?? typeof(object)), member); } } } \ No newline at end of file diff --git a/source/LookupEngine/Engine/LookupComposer.Extensions.cs b/source/LookupEngine/Engine/LookupComposer.Extensions.cs index 2f1916de..3557e3de 100644 --- a/source/LookupEngine/Engine/LookupComposer.Extensions.cs +++ b/source/LookupEngine/Engine/LookupComposer.Extensions.cs @@ -39,11 +39,11 @@ public void Register(string methodName, Func handler) try { var result = EvaluateValue(handler); - WriteExtensionResult(result, methodName); + WriteExtensionMember(result, methodName); } catch (Exception exception) { - WriteExtensionResult(exception, methodName); + WriteExtensionMember(exception, methodName); } } } \ No newline at end of file diff --git a/source/LookupEngine/Engine/LookupComposer.Fields.cs b/source/LookupEngine/Engine/LookupComposer.Fields.cs index 70332d89..7576fc11 100644 --- a/source/LookupEngine/Engine/LookupComposer.Fields.cs +++ b/source/LookupEngine/Engine/LookupComposer.Fields.cs @@ -35,7 +35,7 @@ private void DecomposeFields(BindingFlags bindingFlags) if (member.IsSpecialName) continue; var value = EvaluateValue(member); - WriteDecompositionResult(value, member); + WriteDecompositionMember(value, member); } } } \ No newline at end of file diff --git a/source/LookupEngine/Engine/LookupComposer.Methods.cs b/source/LookupEngine/Engine/LookupComposer.Methods.cs index ffcf182b..95fa89e6 100644 --- a/source/LookupEngine/Engine/LookupComposer.Methods.cs +++ b/source/LookupEngine/Engine/LookupComposer.Methods.cs @@ -53,7 +53,7 @@ private void DecomposeMethods(BindingFlags bindingFlags) value = exception; } - WriteDecompositionResult(value, member, parameters); + WriteDecompositionMember(value, member, parameters); } } diff --git a/source/LookupEngine/Engine/LookupComposer.Properties.cs b/source/LookupEngine/Engine/LookupComposer.Properties.cs index bb361ab3..9fb46b58 100644 --- a/source/LookupEngine/Engine/LookupComposer.Properties.cs +++ b/source/LookupEngine/Engine/LookupComposer.Properties.cs @@ -55,7 +55,7 @@ private void DecomposeProperties(BindingFlags bindingFlags) value = exception; } - WriteDecompositionResult(value, member, parameters); + WriteDecompositionMember(value, member, parameters); } } diff --git a/source/LookupEngine/Engine/LookupComposer.WriteHelpers.cs b/source/LookupEngine/Engine/LookupComposer.WriteHelpers.cs index 464572d9..8d769497 100644 --- a/source/LookupEngine/Engine/LookupComposer.WriteHelpers.cs +++ b/source/LookupEngine/Engine/LookupComposer.WriteHelpers.cs @@ -21,6 +21,7 @@ using System.Collections; using System.Reflection; using LookupEngine.Abstractions; +using LookupEngine.Abstractions.ComponentModel; using LookupEngine.Abstractions.Enums; using LookupEngine.Formaters; @@ -29,25 +30,61 @@ namespace LookupEngine; public sealed partial class LookupComposer { - private void WriteEnumerableResult(object? value, int index) + private static DecomposedObject CreateNullableDecomposition() { - var descriptor = new DecompositionMemberData + return new DecomposedObject + { + Name = $"{nameof(System)}.{nameof(Object)}", + Value = null, + Members = [], + Type = nameof(Object), + TypeFullName = $"{nameof(System)}.{nameof(Object)}" + }; + } + + private static DecomposedObject CreateInstanceDecomposition(object instance, Type type, Descriptor descriptor) + { + return new DecomposedObject + { + Name = descriptor.Name ?? type.Name, + Value = instance, + Type = ReflexionFormater.FormatTypeName(type), + TypeFullName = ReflexionFormater.FormatTypeFullName(type), + Members = new List(32) + }; + } + + private static DecomposedObject CreateStaticDecomposition(Type type, Descriptor descriptor) + { + return new DecomposedObject + { + Name = descriptor.Name ?? type.Name, + Value = type, + Type = ReflexionFormater.FormatTypeName(type), + TypeFullName = ReflexionFormater.FormatTypeFullName(type), + Members = new List(32) + }; + } + + private void WriteEnumerableMember(object? value, int index) + { + var member = new DecomposedMember { Depth = _depth, Value = value, // Value = RedirectValue(value), Name = $"{Subtype.Name}[{index}]", - TypeFullName = "System.Runtime.IEnumerable", MemberAttributes = MemberAttributes.Property, - Type = nameof(IEnumerable) + Type = nameof(IEnumerable), + TypeFullName = $"{nameof(System)}.{nameof(System.Collections)}.{nameof(IEnumerable)}", }; - _descriptors.Add(descriptor); + DecomposedObject.Members.Add(member); } - private void WriteExtensionResult(object? value, string name) + private void WriteExtensionMember(object? value, string name) { - var descriptor = new DecompositionMemberData + var member = new DecomposedMember { Depth = _depth, Name = name, @@ -60,24 +97,24 @@ private void WriteExtensionResult(object? value, string name) AllocatedBytes = _memoryDiagnoser.GetAllocatedBytes() }; - _descriptors.Add(descriptor); + DecomposedObject.Members.Add(member); } - private void WriteDecompositionResult(object? value, MemberInfo member, ParameterInfo[]? parameters = null) + private void WriteDecompositionMember(object? value, MemberInfo memberInfo, ParameterInfo[]? parameters = null) { - var descriptor = new DecompositionMemberData + var member = new DecomposedMember { Depth = _depth, Value = value, // Value = RedirectValue(member, value), - Name = ReflexionFormater.FormatMemberName(member, parameters), + Name = ReflexionFormater.FormatMemberName(memberInfo, parameters), Type = ReflexionFormater.FormatTypeName(Subtype), TypeFullName = ReflexionFormater.FormatTypeFullName(Subtype), - MemberAttributes = ModifiersFormater.FormatAttributes(member), + MemberAttributes = ModifiersFormater.FormatAttributes(memberInfo), ComputationTime = _timeDiagnoser.GetElapsed().TotalMilliseconds, AllocatedBytes = _memoryDiagnoser.GetAllocatedBytes() }; - _descriptors.Add(descriptor); + DecomposedObject.Members.Add(member); } } \ No newline at end of file diff --git a/source/LookupEngine/Engine/LookupComposer.cs b/source/LookupEngine/Engine/LookupComposer.cs index 3ce7fcd2..fb7fb2e0 100644 --- a/source/LookupEngine/Engine/LookupComposer.cs +++ b/source/LookupEngine/Engine/LookupComposer.cs @@ -30,45 +30,29 @@ namespace LookupEngine; public sealed partial class LookupComposer { private readonly DecomposeOptions _options; - private readonly List _descriptors = new(32); private int _depth; - private object? _inputObject; private Type? _subtype; private Descriptor? _subtypeDescriptor; + private DecomposedObject? _decomposedObject; private LookupComposer(DecomposeOptions options) { _options = options; } - [Pure] - public static List Decompose(object? value, DecomposeOptions? options = null) - { - if (value is null) return []; - - options ??= DecomposeOptions.Default; - var composer = new LookupComposer(options); - - return value switch - { - Type staticObjectType => composer.DecomposeStaticObject(staticObjectType), - _ => composer.DecomposeInstanceObject(value) - }; - } - - internal object InputObject + internal DecomposedObject DecomposedObject { get { - if (_inputObject is null) + if (_decomposedObject is null) { - EngineException.ThrowIfEngineNotInitialized(nameof(InputObject)); + EngineException.ThrowIfEngineNotInitialized(nameof(DecomposedObject)); } - return _inputObject; + return _decomposedObject; } - set => _inputObject = value; + set => _decomposedObject = value; } internal Type Subtype @@ -98,4 +82,19 @@ internal Descriptor SubtypeDescriptor } set => _subtypeDescriptor = value; } + + [Pure] + public static DecomposedObject Decompose(object? value, DecomposeOptions? options = null) + { + if (value is null) return CreateNullableDecomposition(); + + options ??= DecomposeOptions.Default; + var composer = new LookupComposer(options); + + return value switch + { + Type staticObjectType => composer.DecomposeStaticObject(staticObjectType), + _ => composer.DecomposeInstanceObject(value) + }; + } } \ No newline at end of file diff --git a/source/LookupEngine/Formaters/ReflexionFormater.cs b/source/LookupEngine/Formaters/ReflexionFormater.cs index 5e7d2ee0..03682b47 100644 --- a/source/LookupEngine/Formaters/ReflexionFormater.cs +++ b/source/LookupEngine/Formaters/ReflexionFormater.cs @@ -4,9 +4,8 @@ namespace LookupEngine.Formaters; public static class ReflexionFormater { - public static string FormatTypeName(Type? type) + public static string FormatTypeName(Type type) { - if (type is null) return string.Empty; if (!type.IsGenericType) return type.Name; var typeName = type.Name; @@ -24,10 +23,10 @@ public static string FormatTypeName(Type? type) return typeName; } - public static string? FormatTypeFullName(Type type) + public static string FormatTypeFullName(Type type) { var fullName = type.FullName; - if (fullName is null) return null; + if (fullName is null) return string.Empty; return type.IsGenericType ? fullName[..fullName.IndexOf('[')] : fullName; } diff --git a/tests/LookupEngine.Tests/OptionsTests.cs b/tests/LookupEngine.Tests/OptionsTests.cs index ce7e28ba..3e1b0b7a 100644 --- a/tests/LookupEngine.Tests/OptionsTests.cs +++ b/tests/LookupEngine.Tests/OptionsTests.cs @@ -9,7 +9,7 @@ public async Task Decompose_DefaultOptions() var descriptors = LookupComposer.Decompose("Hello World"); //Assert - await Assert.That(descriptors).IsNotEmpty(); + await Assert.That(descriptors.Members).IsNotEmpty(); } [Test] @@ -26,7 +26,7 @@ public async Task Decompose_Include_Fields() var optionalDescriptors = LookupComposer.Decompose("Hello World", options); //Assert - await Assert.That(() => optionalDescriptors.Count > defaultDescriptors.Count).ThrowsNothing(); + await Assert.That(() => optionalDescriptors.Members.Count > defaultDescriptors.Members.Count).ThrowsNothing(); } [Test] @@ -46,7 +46,7 @@ public async Task Decompose_With_Redirection() var optionalDescriptors = LookupComposer.Decompose(69, options); //Assert - await Assert.That(optionalDescriptors.Count(data => data.Value is string)) - .IsGreaterThan(defaultDescriptors.Count(data => data.Value is string)); + await Assert.That(optionalDescriptors.Members.Count(data => data.Value is string)) + .IsGreaterThan(defaultDescriptors.Members.Count(data => data.Value is string)); } } \ No newline at end of file From f48603dd697506790311847146917e66cdab0991 Mon Sep 17 00:00:00 2001 From: Nice3point Date: Sat, 30 Nov 2024 17:57:45 +0300 Subject: [PATCH 021/121] Add solution global config --- Directory.Build.props | 10 ++++++++++ build/Build.csproj | 6 ++---- install/Installer.csproj | 5 +++-- .../LookupEngine.Abstractions.csproj | 3 --- .../LookupEngine.Benchmarks.csproj | 3 --- source/LookupEngine/LookupEngine.csproj | 3 --- .../RevitLookup.Abstractions.csproj | 4 +--- source/RevitLookup.Common/RevitLookup.Common.csproj | 3 --- .../RevitLookup.UI.Abstractions.csproj | 3 --- .../RevitLookup.UI.Framework.csproj | 13 +------------ .../RevitLookup.UI.Playground.csproj | 8 ++++---- source/RevitLookup.UI/RevitLookup.UI.csproj | 11 +++++------ tests/LookupEngine.Tests/LookupEngine.Tests.csproj | 3 --- 13 files changed, 26 insertions(+), 49 deletions(-) create mode 100644 Directory.Build.props diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 00000000..00b7a121 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,10 @@ + + + + enable + latest + x64 + true + + + \ No newline at end of file diff --git a/build/Build.csproj b/build/Build.csproj index 0cc0e8dd..3bd1180e 100644 --- a/build/Build.csproj +++ b/build/Build.csproj @@ -2,18 +2,16 @@ Exe + disable CS0649;CS0169 - latest - true net8.0 .. .. 1 - AnyCPU - + diff --git a/install/Installer.csproj b/install/Installer.csproj index 6a6de95f..bcd3949b 100644 --- a/install/Installer.csproj +++ b/install/Installer.csproj @@ -1,13 +1,14 @@  + Exe - latest - x64 net48 false + + diff --git a/source/LookupEngine.Abstractions/LookupEngine.Abstractions.csproj b/source/LookupEngine.Abstractions/LookupEngine.Abstractions.csproj index a34a37dc..0fa78f9b 100644 --- a/source/LookupEngine.Abstractions/LookupEngine.Abstractions.csproj +++ b/source/LookupEngine.Abstractions/LookupEngine.Abstractions.csproj @@ -1,9 +1,6 @@  - enable - latest - enable net48;net8.0-windows diff --git a/source/LookupEngine.Benchmarks/LookupEngine.Benchmarks.csproj b/source/LookupEngine.Benchmarks/LookupEngine.Benchmarks.csproj index c8fe66d4..0ebaccaf 100644 --- a/source/LookupEngine.Benchmarks/LookupEngine.Benchmarks.csproj +++ b/source/LookupEngine.Benchmarks/LookupEngine.Benchmarks.csproj @@ -1,10 +1,7 @@ - enable Exe - latest - enable net8.0-windows diff --git a/source/LookupEngine/LookupEngine.csproj b/source/LookupEngine/LookupEngine.csproj index 661fba31..6ef19a5c 100644 --- a/source/LookupEngine/LookupEngine.csproj +++ b/source/LookupEngine/LookupEngine.csproj @@ -1,9 +1,6 @@  - enable - latest - enable net48;net8.0-windows diff --git a/source/RevitLookup.Abstractions/RevitLookup.Abstractions.csproj b/source/RevitLookup.Abstractions/RevitLookup.Abstractions.csproj index e1afbac0..897fbfad 100644 --- a/source/RevitLookup.Abstractions/RevitLookup.Abstractions.csproj +++ b/source/RevitLookup.Abstractions/RevitLookup.Abstractions.csproj @@ -1,9 +1,6 @@  - enable - latest - enable net48;net8.0-windows @@ -14,6 +11,7 @@ + diff --git a/source/RevitLookup.Common/RevitLookup.Common.csproj b/source/RevitLookup.Common/RevitLookup.Common.csproj index ce28a998..3f6aa85c 100644 --- a/source/RevitLookup.Common/RevitLookup.Common.csproj +++ b/source/RevitLookup.Common/RevitLookup.Common.csproj @@ -2,9 +2,6 @@ true - enable - latest - enable net48;net8.0-windows diff --git a/source/RevitLookup.UI.Abstractions/RevitLookup.UI.Abstractions.csproj b/source/RevitLookup.UI.Abstractions/RevitLookup.UI.Abstractions.csproj index 1568afbe..cabc9223 100644 --- a/source/RevitLookup.UI.Abstractions/RevitLookup.UI.Abstractions.csproj +++ b/source/RevitLookup.UI.Abstractions/RevitLookup.UI.Abstractions.csproj @@ -2,9 +2,6 @@ true - enable - latest - enable net48;net8.0-windows Wpf.Ui.Abstractions diff --git a/source/RevitLookup.UI.Framework/RevitLookup.UI.Framework.csproj b/source/RevitLookup.UI.Framework/RevitLookup.UI.Framework.csproj index 615ce3af..f267ec61 100644 --- a/source/RevitLookup.UI.Framework/RevitLookup.UI.Framework.csproj +++ b/source/RevitLookup.UI.Framework/RevitLookup.UI.Framework.csproj @@ -2,9 +2,6 @@ true - enable - latest - enable net48;net8.0-windows @@ -12,11 +9,6 @@ - - MSBuild:Compile - Wpf - Designer - @@ -24,6 +16,7 @@ + @@ -32,10 +25,6 @@ - - - - diff --git a/source/RevitLookup.UI.Playground/RevitLookup.UI.Playground.csproj b/source/RevitLookup.UI.Playground/RevitLookup.UI.Playground.csproj index 83e038f3..f6760d33 100644 --- a/source/RevitLookup.UI.Playground/RevitLookup.UI.Playground.csproj +++ b/source/RevitLookup.UI.Playground/RevitLookup.UI.Playground.csproj @@ -2,20 +2,20 @@ true - enable WinExe - latest - enable net8.0-windows + + + @@ -30,5 +30,5 @@ - + diff --git a/source/RevitLookup.UI/RevitLookup.UI.csproj b/source/RevitLookup.UI/RevitLookup.UI.csproj index f7a6529e..dad96b42 100644 --- a/source/RevitLookup.UI/RevitLookup.UI.csproj +++ b/source/RevitLookup.UI/RevitLookup.UI.csproj @@ -1,14 +1,9 @@ - + true - enable - latest - x64 - true true net48;net8.0-windows - CS8603;CS8618;CS8629;CS8600;CS0414;CS4014;CS8604;CS8765;CS8602;CS0168 Wpf.Ui @@ -21,4 +16,8 @@ + + + + \ No newline at end of file diff --git a/tests/LookupEngine.Tests/LookupEngine.Tests.csproj b/tests/LookupEngine.Tests/LookupEngine.Tests.csproj index 7384f6bb..a7032ccc 100644 --- a/tests/LookupEngine.Tests/LookupEngine.Tests.csproj +++ b/tests/LookupEngine.Tests/LookupEngine.Tests.csproj @@ -2,9 +2,6 @@ true - enable - latest - enable net8.0-windows From 4039c5eae2535d12f527c54ab4229c23e03d8a89 Mon Sep 17 00:00:00 2001 From: Nice3point Date: Sun, 1 Dec 2024 03:34:01 +0300 Subject: [PATCH 022/121] Members grid virtualization --- .../Enums/MemberAttributes.cs | 1 + .../ObservableDecomposedMember.cs | 17 + .../ObservableDecomposedObject.cs | 12 + .../ObservableDecomposedObjectsGroup.cs | 9 + source/RevitLookup.UI.Framework/App.xaml | 3 + .../Automation/NoAutomationWindowPeer.cs | 18 + .../ContentPlaceholder.xaml | 5 +- .../ContentPlaceholder.xaml.cs | 3 +- .../EmptyCollectionVisibilityConverter.cs | 8 +- ...olConverter.cs => EnumBooleanConverter.cs} | 4 +- ...verseEmptyCollectionVisibilityConverter.cs | 26 ++ .../ValueConverters/BytesToStringConverter.cs | 50 ++ .../MemberAttributeIconConverter.cs | 73 +++ .../SoftwareUpdateStateBooleanConverter.cs | 2 +- .../ValueConverters/TimeToStringConverter.cs | 52 +++ .../Utils/VisualExtensions.cs | 41 +- .../Views/AboutProgram/AboutPage.xaml | 322 +++++++------ .../Views/Dashboard/DashboardPage.xaml | 432 +++--------------- .../Views/Settings/SettingsPage.xaml | 316 +++++++------ .../Views/Summary/SnoopSummaryPage.xaml | 177 +++++++ .../Views/Summary/SnoopSummaryPage.xaml.cs | 43 ++ .../Summary/SummaryViewBase.ContextMenu.cs | 206 +++++++++ .../Views/Summary/SummaryViewBase.Gestures.cs | 65 +++ .../Summary/SummaryViewBase.Navigation.cs | 131 ++++++ .../Views/Summary/SummaryViewBase.Styles.cs | 88 ++++ .../Views/Summary/SummaryViewBase.ToolTips.cs | 108 +++++ .../Views/Summary/SummaryViewBase.xaml.cs | 252 ++++++++++ .../Views/Windows/RevitLookupView.xaml | 20 +- .../Views/Windows/RevitLookupView.xaml.cs | 7 + source/RevitLookup.UI.Playground/App.xaml | 13 +- .../Client/Controls/ControlExample.xaml.cs | 2 + .../Client/Controls/PageViewer.xaml.cs | 7 + .../ViewModelsRegistrationExtensions.cs | 4 +- .../DesignGuidance/FontIconsPageViewModel.cs | 5 +- .../SymbolIconsPageViewModel.cs | 5 +- .../Client/ViewModels/Pages/PagesViewModel.cs | 23 +- .../Client/Views/PlaygroundView.xaml.cs | 10 +- .../Mappers/DecompositionResultMapper.cs | 11 + .../RevitLookup.UI.Playground.csproj | 3 + .../MembersGrid/SummaryGridGroupStyles.xaml | 43 ++ .../SummaryTreeGroupTemplates.xaml | 76 +++ .../Summary/MockSnoopSummaryViewModel.cs | 119 +++++ .../ViewModels/Tools/MockModulesViewModel.cs | 17 +- .../ViewModels/Tools/MockUnitsViewModel.cs | 7 +- 44 files changed, 2099 insertions(+), 737 deletions(-) create mode 100644 source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedMember.cs create mode 100644 source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedObject.cs create mode 100644 source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedObjectsGroup.cs create mode 100644 source/RevitLookup.UI.Framework/Controls/Automation/NoAutomationWindowPeer.cs rename source/RevitLookup.UI.Framework/Converters/{EnumBoolConverter.cs => EnumBooleanConverter.cs} (88%) create mode 100644 source/RevitLookup.UI.Framework/Converters/InverseEmptyCollectionVisibilityConverter.cs create mode 100644 source/RevitLookup.UI.Framework/Converters/ValueConverters/BytesToStringConverter.cs create mode 100644 source/RevitLookup.UI.Framework/Converters/ValueConverters/MemberAttributeIconConverter.cs create mode 100644 source/RevitLookup.UI.Framework/Converters/ValueConverters/TimeToStringConverter.cs create mode 100644 source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml create mode 100644 source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml.cs create mode 100644 source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ContextMenu.cs create mode 100644 source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Gestures.cs create mode 100644 source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Navigation.cs create mode 100644 source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Styles.cs create mode 100644 source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ToolTips.cs create mode 100644 source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs create mode 100644 source/RevitLookup.UI.Playground/Mappers/DecompositionResultMapper.cs create mode 100644 source/RevitLookup.UI.Playground/Styles/ComponentStyles/Summary/MembersGrid/SummaryGridGroupStyles.xaml create mode 100644 source/RevitLookup.UI.Playground/Styles/ComponentStyles/Summary/ObjectsTree/SummaryTreeGroupTemplates.xaml create mode 100644 source/RevitLookup.UI.Playground/ViewModels/Summary/MockSnoopSummaryViewModel.cs diff --git a/source/LookupEngine.Abstractions/Enums/MemberAttributes.cs b/source/LookupEngine.Abstractions/Enums/MemberAttributes.cs index 5c819a8a..65f0662b 100644 --- a/source/LookupEngine.Abstractions/Enums/MemberAttributes.cs +++ b/source/LookupEngine.Abstractions/Enums/MemberAttributes.cs @@ -25,6 +25,7 @@ public enum MemberAttributes { Private = 0b1, Static = 0b10, + Field = 0b100, Property = 0b1000, Method = 0b10000, diff --git a/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedMember.cs b/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedMember.cs new file mode 100644 index 00000000..a8757121 --- /dev/null +++ b/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedMember.cs @@ -0,0 +1,17 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using LookupEngine.Abstractions.Enums; + +namespace RevitLookup.Abstractions.ObservableModels.Decomposition; + +public sealed class ObservableDecomposedMember : ObservableObject +{ + public required int Depth { get; init; } + public required object? Value { get; init; } + public required string Name { get; init; } + public required string Type { get; init; } + public required string TypeFullName { get; init; } + public required string? Description { get; init; } + public required double ComputationTime { get; init; } + public required long AllocatedBytes { get; init; } + public required MemberAttributes MemberAttributes { get; init; } +} \ No newline at end of file diff --git a/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedObject.cs b/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedObject.cs new file mode 100644 index 00000000..0bb7ec47 --- /dev/null +++ b/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedObject.cs @@ -0,0 +1,12 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace RevitLookup.Abstractions.ObservableModels.Decomposition; + +public sealed class ObservableDecomposedObject : ObservableObject +{ + public required object? Value { get; set; } + public required string Name { get; set; } + public required string Type { get; set; } + public required string TypeFullName { get; set; } + public required List Members { get; set; } +} \ No newline at end of file diff --git a/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedObjectsGroup.cs b/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedObjectsGroup.cs new file mode 100644 index 00000000..72ce991a --- /dev/null +++ b/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedObjectsGroup.cs @@ -0,0 +1,9 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace RevitLookup.Abstractions.ObservableModels.Decomposition; + +public sealed class ObservableDecomposedObjectsGroup : ObservableObject +{ + public required string GroupName { get; set; } + public required List GroupItems { get; set; } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/App.xaml b/source/RevitLookup.UI.Framework/App.xaml index fa6ad624..e0914cac 100644 --- a/source/RevitLookup.UI.Framework/App.xaml +++ b/source/RevitLookup.UI.Framework/App.xaml @@ -12,6 +12,9 @@ Theme="Light" /> + + diff --git a/source/RevitLookup.UI.Framework/Controls/Automation/NoAutomationWindowPeer.cs b/source/RevitLookup.UI.Framework/Controls/Automation/NoAutomationWindowPeer.cs new file mode 100644 index 00000000..ba5a8a92 --- /dev/null +++ b/source/RevitLookup.UI.Framework/Controls/Automation/NoAutomationWindowPeer.cs @@ -0,0 +1,18 @@ +using System.Windows; +using System.Windows.Automation.Peers; + +namespace RevitLookup.UI.Framework.Controls.Automation; + +/// +/// Windows peer disabling automation. Removes freezes when using Tooltip, Popup +/// +/// +/// https://github.com/dotnet/wpf/issues/5807 +/// +public sealed class NoAutomationWindowPeer(Window owner) : WindowAutomationPeer(owner) +{ + protected override List GetChildrenCore() + { + return []; + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Controls/ContentPlaceholder/ContentPlaceholder.xaml b/source/RevitLookup.UI.Framework/Controls/ContentPlaceholder/ContentPlaceholder.xaml index 0071f76a..5441956e 100644 --- a/source/RevitLookup.UI.Framework/Controls/ContentPlaceholder/ContentPlaceholder.xaml +++ b/source/RevitLookup.UI.Framework/Controls/ContentPlaceholder/ContentPlaceholder.xaml @@ -3,7 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:controls="clr-namespace:RevitLookup.Views.Controls" + xmlns:controls="clr-namespace:RevitLookup.UI.Framework.Controls.ContentPlaceholder" mc:Ignorable="d"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/Settings/SettingsPage.xaml b/source/RevitLookup.UI.Framework/Views/Settings/SettingsPage.xaml index b6ac1d2e..7fa6db42 100644 --- a/source/RevitLookup.UI.Framework/Views/Settings/SettingsPage.xaml +++ b/source/RevitLookup.UI.Framework/Views/Settings/SettingsPage.xaml @@ -13,166 +13,164 @@ d:DataContext="{d:DesignInstance settings:SettingsPage}" Foreground="{DynamicResource TextFillColorPrimaryBrush}"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml b/source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml new file mode 100644 index 00000000..27ead1c0 --- /dev/null +++ b/source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml.cs b/source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml.cs new file mode 100644 index 00000000..359621c6 --- /dev/null +++ b/source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml.cs @@ -0,0 +1,43 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +using RevitLookup.Abstractions.Services; +using RevitLookup.Abstractions.ViewModels.Summary; + +namespace RevitLookup.UI.Framework.Views.Summary; + +public sealed partial class SnoopSummaryPage +{ + public SnoopSummaryPage( + ISnoopSummaryViewModel viewModel, + ISettingsService settingsService, + IWindowIntercomService intercomService) + : base(settingsService, intercomService) + { + DataContext = this; + ViewModel = viewModel; + InitializeComponent(); + + SearchBoxControl = SummarySearchBox; + TreeViewControl = SummaryTreeView; + DataGridControl = SummaryDataGrid; + InitializeControls(); + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ContextMenu.cs b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ContextMenu.cs new file mode 100644 index 00000000..0e8689cd --- /dev/null +++ b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ContextMenu.cs @@ -0,0 +1,206 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +namespace RevitLookup.UI.Framework.Views.Summary; + +public partial class SummaryViewBase +{ + // /// + // /// Tree view context menu + // /// + // private void CreateTreeContextMenu(Descriptor descriptor, FrameworkElement row) + // { + // var contextMenu = new ContextMenu + // { + // Resources = Resources, + // PlacementTarget = row, + // DataContext = ViewModel + // }; + // + // row.ContextMenu = contextMenu; + // + // contextMenu.AddMenuItem("CopyMenuItem") + // .SetCommand(descriptor, parameter => Clipboard.SetDataObject(parameter.Name)) + // .SetShortcut(ModifierKeys.Control, Key.C); + // contextMenu.AddMenuItem("HelpMenuItem") + // .SetCommand(descriptor, parameter => HelpUtils.ShowHelp(parameter.TypeFullName)) + // .SetShortcut(Key.F1); + // + // if (descriptor is not IDescriptorConnector connector) return; + // + // try + // { + // connector.RegisterMenu(contextMenu); + // } + // catch (Exception exception) + // { + // var logger = ViewModel.ServiceProvider.GetRequiredService>(); + // var notificationService = ViewModel.ServiceProvider.GetRequiredService(); + // + // logger.LogError(exception, "RegisterMenu error"); + // notificationService.ShowError("RegisterMenu error", exception); + // } + // } + + // /// + // /// Data grid context menu + // /// + // private void CreateGridContextMenu(DataGrid dataGrid) + // { + // var contextMenu = new ContextMenu + // { + // Resources = Resources, + // PlacementTarget = dataGrid, + // DataContext = ViewModel + // }; + // + // dataGrid.ContextMenu = contextMenu; + // + // contextMenu.AddMenuItem("RefreshMenuItem") + // .SetCommand(ViewModel.RefreshMembersCommand) + // .SetGestureText(Key.F5); + // + // contextMenu.AddSeparator(); + // contextMenu.AddLabel("Columns"); + // + // contextMenu.AddMenuItem() + // .SetHeader("Time") + // .SetChecked(dataGrid.Columns[2].Visibility == Visibility.Visible) + // .SetCommand(dataGrid.Columns[2], parameter => + // { + // _settings.ShowTimeColumn = parameter.Visibility != Visibility.Visible; + // parameter.Visibility = _settings.ShowTimeColumn ? Visibility.Visible : Visibility.Collapsed; + // }); + // + // contextMenu.AddMenuItem() + // .SetHeader("Memory") + // .SetChecked(dataGrid.Columns[3].Visibility == Visibility.Visible) + // .SetCommand(dataGrid.Columns[3], parameter => + // { + // _settings.ShowMemoryColumn = parameter.Visibility != Visibility.Visible; + // parameter.Visibility = _settings.ShowMemoryColumn ? Visibility.Visible : Visibility.Collapsed; + // }); + // + // contextMenu.AddSeparator(); + // contextMenu.AddLabel("Show"); + // + // contextMenu.AddMenuItem() + // .SetHeader("Events") + // .SetChecked(_settings.IncludeEvents) + // .SetCommand(_settings, parameter => + // { + // parameter.IncludeEvents = !parameter.IncludeEvents; + // return ViewModel.RefreshMembersCommand.ExecuteAsync(null); + // }); + // contextMenu.AddMenuItem() + // .SetHeader("Extensions") + // .SetChecked(_settings.IncludeExtensions) + // .SetCommand(_settings, parameter => + // { + // parameter.IncludeExtensions = !parameter.IncludeExtensions; + // return ViewModel.RefreshMembersCommand.ExecuteAsync(null); + // }); + // contextMenu.AddMenuItem() + // .SetHeader("Fields") + // .SetChecked(_settings.IncludeFields) + // .SetCommand(_settings, parameter => + // { + // parameter.IncludeFields = !parameter.IncludeFields; + // return ViewModel.RefreshMembersCommand.ExecuteAsync(null); + // }); + // contextMenu.AddMenuItem() + // .SetHeader("Non-public") + // .SetChecked(_settings.IncludePrivate) + // .SetCommand(_settings, parameter => + // { + // parameter.IncludePrivate = !parameter.IncludePrivate; + // return ViewModel.RefreshMembersCommand.ExecuteAsync(null); + // }); + // contextMenu.AddMenuItem() + // .SetHeader("Root") + // .SetChecked(_settings.IncludeRootHierarchy) + // .SetCommand(_settings, parameter => + // { + // parameter.IncludeRootHierarchy = !parameter.IncludeRootHierarchy; + // return ViewModel.RefreshMembersCommand.ExecuteAsync(null); + // }); + // contextMenu.AddMenuItem() + // .SetHeader("Static") + // .SetChecked(_settings.IncludeStatic) + // .SetCommand(_settings, parameter => + // { + // parameter.IncludeStatic = !parameter.IncludeStatic; + // return ViewModel.RefreshMembersCommand.ExecuteAsync(null); + // }); + // contextMenu.AddMenuItem() + // .SetHeader("Unsupported") + // .SetChecked(_settings.IncludeUnsupported) + // .SetCommand(_settings, parameter => + // { + // parameter.IncludeUnsupported = !parameter.IncludeUnsupported; + // return ViewModel.RefreshMembersCommand.ExecuteAsync(null); + // }); + // } + + // /// + // /// Data grid row context menu + // /// + // private void CreateGridRowContextMenu(Descriptor descriptor, FrameworkElement row) + // { + // var contextMenu = new ContextMenu + // { + // Resources = Resources, + // PlacementTarget = row, + // DataContext = ViewModel + // }; + // + // row.ContextMenu = contextMenu; + // + // contextMenu.AddMenuItem("CopyMenuItem") + // .SetCommand(descriptor, parameter => Clipboard.SetDataObject($"{parameter.Name}: {parameter.Value.Descriptor.Name}")) + // .SetShortcut(ModifierKeys.Control, Key.C) + // .SetAvailability(descriptor.Value.Descriptor.Name is not null); + // + // contextMenu.AddMenuItem("CopyMenuItem") + // .SetHeader("Copy value") + // .SetCommand(descriptor, parameter => Clipboard.SetDataObject(parameter.Value.Descriptor.Name)) + // .SetShortcut(ModifierKeys.Control | ModifierKeys.Shift, Key.C) + // .SetAvailability(descriptor.Value.Descriptor.Name is not null); + // + // contextMenu.AddMenuItem("HelpMenuItem") + // .SetCommand(descriptor, parameter => HelpUtils.ShowHelp(parameter.TypeFullName, parameter.Name)) + // .SetShortcut(Key.F1); + // + // if (descriptor.Value.Descriptor is not IDescriptorConnector connector) return; + // + // try + // { + // connector.RegisterMenu(contextMenu); + // } + // catch (Exception exception) + // { + // var logger = ViewModel.ServiceProvider.GetRequiredService>(); + // var notificationService = ViewModel.ServiceProvider.GetRequiredService(); + // + // logger.LogError(exception, "RegisterMenu error"); + // notificationService.ShowError("RegisterMenu error", exception); + // } + // } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Gestures.cs b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Gestures.cs new file mode 100644 index 00000000..58dab2eb --- /dev/null +++ b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Gestures.cs @@ -0,0 +1,65 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +using System.Windows.Input; +using RevitLookup.UI.Framework.Views.Windows; +using Wpf.Ui.Abstractions.Controls; + +namespace RevitLookup.UI.Framework.Views.Summary; + +public partial class SummaryViewBase : INavigationAware +{ + public Task OnNavigatedToAsync() + { + var host = _intercomService.GetHost(); + host.PreviewKeyDown += OnPageKeyPressed; + return Task.CompletedTask; + } + + public Task OnNavigatedFromAsync() + { + var host = _intercomService.GetHost(); + host.PreviewKeyDown -= OnPageKeyPressed; + return Task.CompletedTask; + } + + /// + /// Page shortcuts + /// + private void AddShortcuts() + { + // var command = new AsyncRelayCommand(() => ViewModel.RefreshMembersCommand.ExecuteAsync(null)); + // InputBindings.Add(new KeyBinding(command, new KeyGesture(Key.F5))); + } + + /// + /// Window shortcuts + /// + private void OnPageKeyPressed(object sender, KeyEventArgs args) + { + if (SearchBoxControl.IsKeyboardFocused) return; + if (args.KeyboardDevice.Modifiers != ModifierKeys.None) return; + + var rootWindow = (RevitLookupView)sender; + if (rootWindow.RootContentDialog.Content is not null) return; + + if (args.Key is >= Key.D0 and <= Key.Z or >= Key.NumPad0 and <= Key.NumPad9) SearchBoxControl.Focus(); + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Navigation.cs b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Navigation.cs new file mode 100644 index 00000000..2ecd2431 --- /dev/null +++ b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Navigation.cs @@ -0,0 +1,131 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using RevitLookup.Abstractions.ObservableModels.Decomposition; +using RevitLookup.UI.Framework.Utils; + +namespace RevitLookup.UI.Framework.Views.Summary; + +public partial class SummaryViewBase +{ + /// + /// Handle tree view select event + /// + /// + /// Collect data for selected item + /// + protected void OnTreeItemSelected(object sender, RoutedPropertyChangedEventArgs args) + { + switch (args.NewValue) + { + case ObservableDecomposedObject decomposedObject: + ViewModel.SelectedDecomposedObject = decomposedObject; + break; + case ObservableDecomposedObjectsGroup: + ViewModel.SelectedDecomposedObject = default; + break; + default: + return; + } + + // ViewModel.FetchMembersCommand.Execute(null); + } + + // /// + // /// Handle tree view click event + // /// + // /// + // /// Open new window for navigation on Ctrl pressed + // /// + // private void OnTreeItemClicked(object sender, RoutedEventArgs args) + // { + // if ((Keyboard.Modifiers & ModifierKeys.Control) == 0) return; + // args.Handled = true; + // + // var element = (FrameworkElement) args.OriginalSource; + // switch (element.DataContext) + // { + // case SnoopableObject item: + // ViewModel.Navigate(item); + // break; + // case CollectionViewGroup group: + // ViewModel.Navigate(group.Items.Cast().ToArray()); + // break; + // } + // } + + // /// + // /// Handle data grid click event + // /// + // /// + // /// Open new window for navigation + // /// + // private void OnGridRowClicked(object sender, RoutedEventArgs args) + // { + // var row = (DataGridRow) sender; + // var context = (Descriptor) row.DataContext; + // if ((Keyboard.Modifiers & ModifierKeys.Control) == 0) + // { + // if (context.Value.Descriptor is not IDescriptorCollector) return; + // if (context.Value.Descriptor is IDescriptorEnumerator {IsEmpty: true}) return; + // } + // + // ViewModel.Navigate(context.Value); + // } + + /// + /// Handle cursor interaction + /// + protected void OnPresenterCursorInteracted(object sender, MouseEventArgs args) + { + var presenter = (FrameworkElement)sender; + if ((Keyboard.Modifiers & ModifierKeys.Control) == 0) + { + presenter.Cursor = null; + return; + } + + FrameworkElement? item = sender switch + { + DataGrid => ((DependencyObject)args.OriginalSource).FindVisualParent(), + TreeView => ((DependencyObject)args.OriginalSource).FindVisualParent(), + _ => throw new NotSupportedException() + }; + + if (item is null) + { + presenter.Cursor = null; + return; + } + + presenter.Cursor = Cursors.Hand; + presenter.PreviewKeyUp += OnPresenterCursorRestored; + } + + private void OnPresenterCursorRestored(object sender, KeyEventArgs e) + { + var presenter = (FrameworkElement)sender; + presenter.PreviewKeyUp -= OnPresenterCursorRestored; + presenter.Cursor = null; + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Styles.cs b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Styles.cs new file mode 100644 index 00000000..2119b420 --- /dev/null +++ b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Styles.cs @@ -0,0 +1,88 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +namespace RevitLookup.UI.Framework.Views.Summary; + +// /// +// /// Data grid row style selector +// /// +// private void SelectDataGridRowStyle(DataGridRow row) +// { +// var rowDescriptor = (Descriptor) row.DataContext; +// var styleName = rowDescriptor.Value.Descriptor switch +// { +// ExceptionDescriptor => "ExceptionDataGridRowStyle", +// IDescriptorEnumerator {IsEmpty: false} => "HandleDataGridRowStyle", +// IDescriptorEnumerator => "DefaultLookupDataGridRowStyle", +// IDescriptorCollector => "HandleDataGridRowStyle", +// _ => "DefaultLookupDataGridRowStyle" +// }; +// +// row.Style = (Style) FindResource(styleName); +// } +// } + +// public sealed class DataGridCellStyleSelector : DataTemplateSelector +// { +// /// +// /// Data grid cell style selector +// /// +// public override DataTemplate SelectTemplate(object item, DependencyObject container) +// { +// if (item is null) return null; +// +// var descriptor = (Descriptor) item; +// var presenter = (FrameworkElement) container; +// var templateName = descriptor.Value.Descriptor switch +// { +// ColorDescriptor => "DataGridColorCellTemplate", +// ColorMediaDescriptor => "DataGridColorCellTemplate", +// _ => "DefaultLookupDataGridCellTemplate" +// }; +// +// return (DataTemplate) presenter.FindResource(templateName); +// } +// } + +// public sealed class TreeViewItemTemplateSelector : DataTemplateSelector +// { +// /// +// /// Tree view row style selector +// /// +// public override DataTemplate SelectTemplate(object item, DependencyObject container) +// { +// if (item is null) return null; +// +// var presenter = (FrameworkElement) container; +// var templateName = item switch +// { +// CollectionViewGroup => "DefaultLookupTreeViewGroupTemplate", +// SnoopableObject snoopableObject => snoopableObject.Descriptor switch +// { +// ColorDescriptor => "TreeViewColorItemTemplate", +// ColorMediaDescriptor => "TreeViewColorItemTemplate", +// _ => "DefaultLookupTreeViewItemTemplate" +// }, +// +// _ => "DefaultLookupTreeViewItemTemplate" +// }; +// +// return (DataTemplate) presenter.FindResource(templateName); +// } \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ToolTips.cs b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ToolTips.cs new file mode 100644 index 00000000..2b8114af --- /dev/null +++ b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ToolTips.cs @@ -0,0 +1,108 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +using System.Text; +using System.Windows; +using LookupEngine.Abstractions.Enums; +using RevitLookup.Abstractions.ObservableModels.Decomposition; + +namespace RevitLookup.UI.Framework.Views.Summary; + +public partial class SummaryViewBase +{ + /// + /// Create tree view tooltips + /// + private void CreateTreeTooltip(ObservableDecomposedObject decomposedObject, FrameworkElement row) + { + row.ToolTip = new StringBuilder() + .Append("Name: ") + .AppendLine(decomposedObject.Name) + .Append("Type: ") + .AppendLine(decomposedObject.Type) + .Append("Full type: ") + .AppendLine(decomposedObject.TypeFullName) + .Append("Members: ") + .Append(decomposedObject.Members.Count) + .ToString(); + } + + /// + /// Create tree view tooltips + /// + private void CreateTreeTooltip(ObservableDecomposedObjectsGroup decomposedGroup, FrameworkElement row) + { + row.ToolTip = new StringBuilder() + .Append("Type: ") + .AppendLine(decomposedGroup.GroupName) + .Append("Items: ") + .Append(decomposedGroup.GroupItems.Count) + .ToString(); + } + + /// + /// Create data grid tooltips + /// + private void CreateGridRowTooltip(ObservableDecomposedMember member, FrameworkElement row) + { + var builder = new StringBuilder(); + + if ((member.MemberAttributes & MemberAttributes.Private) != 0) builder.Append("Private "); + if ((member.MemberAttributes & MemberAttributes.Static) != 0) builder.Append("Static "); + if ((member.MemberAttributes & MemberAttributes.Property) != 0) builder.Append("Property: "); + if ((member.MemberAttributes & MemberAttributes.Extension) != 0) builder.Append("Extension: "); + if ((member.MemberAttributes & MemberAttributes.Method) != 0) builder.Append("Method: "); + if ((member.MemberAttributes & MemberAttributes.Event) != 0) builder.Append("Event: "); + if ((member.MemberAttributes & MemberAttributes.Field) != 0) builder.Append("Field: "); + + builder.AppendLine(member.Name) + .Append("Type: ") + .AppendLine(member.Type) + .Append("Full type: ") + .AppendLine(member.TypeFullName) + .Append("Value: ") + .Append(member.Value); + + if (member.Description is not null) + { + builder.AppendLine() + .Append("Description: ") + .Append(member.Description); + } + + if (member.ComputationTime > 0) + { + builder.AppendLine() + .Append("Time: ") + .Append(member.ComputationTime) + .Append(" ms"); + } + + if (member.AllocatedBytes > 0) + { + builder.AppendLine() + .Append("Allocated: ") + .Append(member.AllocatedBytes) + .Append(" bytes"); + } + + row.ToolTip = builder.ToString(); + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs new file mode 100644 index 00000000..fe05f65c --- /dev/null +++ b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs @@ -0,0 +1,252 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +using System.Collections; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Data; +using RevitLookup.Abstractions.ObservableModels.Decomposition; +using RevitLookup.Abstractions.Services; +using RevitLookup.Abstractions.ViewModels.Summary; +using RevitLookup.UI.Framework.Utils; +using Wpf.Ui.Abstractions.Controls; +using Wpf.Ui.Controls; +using DataGrid = System.Windows.Controls.DataGrid; +using TreeView = Wpf.Ui.Controls.TreeView; +using TreeViewItem = System.Windows.Controls.TreeViewItem; +using Visibility = System.Windows.Visibility; + +namespace RevitLookup.UI.Framework.Views.Summary; + +public partial class SummaryViewBase : Page, INavigableView +{ + private readonly ISettingsService _settingsService; + private readonly IWindowIntercomService _intercomService; + + protected SummaryViewBase(ISettingsService settingsService, IWindowIntercomService intercomService) + { + _settingsService = settingsService; + _intercomService = intercomService; + + AddShortcuts(); + } + + public required UIElement SearchBoxControl { get; init; } + public required TreeView TreeViewControl { get; init; } + public required DataGrid DataGridControl { get; init; } + public required ISnoopSummaryViewModel ViewModel { get; init; } + + protected void InitializeControls() + { + InitializeTreeView(TreeViewControl); + InitializeDataGrid(DataGridControl); + } + + /// + /// Tree view initialization + /// + private void InitializeTreeView(TreeView control) + { + control.SelectedItemChanged += OnTreeItemSelected; + control.ItemsSourceChanged += OnTreeSourceChanged; + control.MouseMove += OnPresenterCursorInteracted; + control.ItemContainerGenerator.StatusChanged += OnTreeViewItemGenerated; + + if (control.ItemsSource is not null) OnTreeSourceChanged(control, control.ItemsSource); + } + + /// + /// Tree view source changed handled. Setup action after the setting source + /// + private void OnTreeSourceChanged(object? sender, IEnumerable enumerable) + { + var self = (FrameworkElement)sender!; + + if (self.IsLoaded) + { + ExpandFirstTreeGroup(); + return; + } + + self.Loaded += OnLoaded; + return; + + void OnLoaded(object nestedSender, RoutedEventArgs args) + { + var nestedSelf = (FrameworkElement)nestedSender; + nestedSelf.Loaded -= OnLoaded; + ExpandFirstTreeGroup(); + } + } + + /// + /// Expand the first tree view group after setting source + /// + private async void ExpandFirstTreeGroup() + { + try + { + // Await Frame transition. GetMembers freezes the thread and breaks the animation + var transitionDuration = (int)NavigationView.TransitionDurationProperty.DefaultMetadata.DefaultValue; + await Task.Delay(transitionDuration); + + //3 is optimal groups count for expanding + if (TreeViewControl.Items.Count > 3) return; + + var rootItem = (TreeViewItem?)TreeViewControl.GetItemAtIndex(0); + if (rootItem is null) return; + + var nestedItem = (TreeViewItem?)rootItem.GetItemAtIndex(0); + if (nestedItem is null) return; + + nestedItem.IsSelected = true; + } + catch + { + // ignored + } + } + + /// + /// Handle tree view item loaded + /// + /// + /// TreeView item customization after loading + /// + private void OnTreeViewItemGenerated(object? sender, EventArgs _) + { + var generator = (ItemContainerGenerator)sender!; + if (generator.Status == GeneratorStatus.ContainersGenerated) + { + foreach (var item in generator.Items) + { + var treeItem = (ItemsControl)generator.ContainerFromItem(item); + if (treeItem is null) continue; + + treeItem.Loaded -= OnTreeItemLoaded; + // treeItem.PreviewMouseLeftButtonUp -= OnTreeItemClicked; + + treeItem.Loaded += OnTreeItemLoaded; + // treeItem.PreviewMouseLeftButtonUp += OnTreeItemClicked; + + if (treeItem.Items.Count > 0) + { + treeItem.ItemContainerGenerator.StatusChanged -= OnTreeViewItemGenerated; + treeItem.ItemContainerGenerator.StatusChanged += OnTreeViewItemGenerated; + } + } + } + } + + /// + /// Create tree view tooltips, menus + /// + private void OnTreeItemLoaded(object? sender, RoutedEventArgs args) + { + var element = (FrameworkElement)sender!; + switch (element.DataContext) + { + case ObservableDecomposedObjectsGroup decomposedGroup: + CreateTreeTooltip(decomposedGroup, element); + // CreateTreeContextMenu(context.Descriptor, element); + break; + case ObservableDecomposedObject decomposedObject: + CreateTreeTooltip(decomposedObject, element); + // CreateTreeContextMenu(context.Descriptor, element); + break; + } + } + + /// + /// Handle data grid reference changed event + /// + /// + /// Data grid initialization, validation + /// + private void InitializeDataGrid(DataGrid control) + { + ApplyGrouping(control); + ValidateTimeColumn(control); + ValidateAllocatedColumn(control); + // CreateGridContextMenu(dataGrid); + control.LoadingRow += OnGridRowLoading; + control.MouseMove += OnPresenterCursorInteracted; + } + + /// + /// Set DataGrid grouping rules + /// + /// + private void ApplyGrouping(DataGrid dataGrid) + { + // dataGrid.Items.SortDescriptions.Clear(); + // dataGrid.Items.SortDescriptions.Add(new SortDescription(nameof(ObservableDecomposedMember.Depth), ListSortDirection.Descending)); + // dataGrid.Items.SortDescriptions.Add(new SortDescription(nameof(ObservableDecomposedMember.MemberAttributes), ListSortDirection.Ascending)); + // dataGrid.Items.SortDescriptions.Add(new SortDescription(nameof(ObservableDecomposedMember.Name), ListSortDirection.Ascending)); + + dataGrid.Items.GroupDescriptions!.Clear(); + dataGrid.Items.GroupDescriptions.Add(new PropertyGroupDescription(nameof(ObservableDecomposedMember.Type))); + } + + // + // Handle data grid row loading event + // + // + // Select row style + // + protected void OnGridRowLoading(object? sender, DataGridRowEventArgs args) + { + var row = args.Row; + row.Loaded += OnGridRowLoaded; + // row.PreviewMouseLeftButtonUp += OnGridRowClicked; + // SelectDataGridRowStyle(row); + } + + /// + /// Handle data grid row loaded event + /// + /// + /// Create tooltips, context menu + /// + protected void OnGridRowLoaded(object sender, RoutedEventArgs args) + { + var element = (FrameworkElement)sender; + var member = (ObservableDecomposedMember)element.DataContext; + CreateGridRowTooltip(member, element); + // CreateGridRowContextMenu(member, element); + } + + /// + /// Show/hide time column + /// + private void ValidateTimeColumn(DataGrid control) + { + control.Columns[2].Visibility = _settingsService.GeneralSettings.ShowTimeColumn ? Visibility.Visible : Visibility.Collapsed; + } + + /// + /// Show/hide allocated column + /// + private void ValidateAllocatedColumn(DataGrid control) + { + control.Columns[3].Visibility = _settingsService.GeneralSettings.ShowMemoryColumn ? Visibility.Visible : Visibility.Collapsed; + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/Windows/RevitLookupView.xaml b/source/RevitLookup.UI.Framework/Views/Windows/RevitLookupView.xaml index f98609c3..a89367be 100644 --- a/source/RevitLookup.UI.Framework/Views/Windows/RevitLookupView.xaml +++ b/source/RevitLookup.UI.Framework/Views/Windows/RevitLookupView.xaml @@ -8,6 +8,7 @@ xmlns:settings="clr-namespace:RevitLookup.UI.Framework.Views.Settings" xmlns:aboutProgram="clr-namespace:RevitLookup.UI.Framework.Views.AboutProgram" xmlns:dashboard="clr-namespace:RevitLookup.UI.Framework.Views.Dashboard" + xmlns:pages="clr-namespace:RevitLookup.UI.Framework.Views.Summary" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="200" @@ -36,10 +37,10 @@ Content="Dashboard" TargetPageType="{x:Type dashboard:DashboardPage}" Icon="{rl:SymbolIcon AppGeneric24}" /> - - - - + @@ -69,7 +70,16 @@ + Margin="12 12 0 0"> + + + + + + - + - + + + + + diff --git a/source/RevitLookup.UI.Playground/Client/Controls/ControlExample.xaml.cs b/source/RevitLookup.UI.Playground/Client/Controls/ControlExample.xaml.cs index b62b2e96..afeffb60 100644 --- a/source/RevitLookup.UI.Playground/Client/Controls/ControlExample.xaml.cs +++ b/source/RevitLookup.UI.Playground/Client/Controls/ControlExample.xaml.cs @@ -128,12 +128,14 @@ private static void Copy_SourceCode(object sender, RoutedEventArgs e) Clipboard.SetText(controlExample.XamlCode); var peer = UIElementAutomationPeer.CreatePeerForElement((Button)e.OriginalSource); +#if NETCOREAPP peer.RaiseNotificationEvent( AutomationNotificationKind.Other, AutomationNotificationProcessing.ImportantMostRecent, "Source Code Copied", "ButtonClickedActivity" ); +#endif break; case "Copy_CsharpCode": diff --git a/source/RevitLookup.UI.Playground/Client/Controls/PageViewer.xaml.cs b/source/RevitLookup.UI.Playground/Client/Controls/PageViewer.xaml.cs index c5e24a16..a331192c 100644 --- a/source/RevitLookup.UI.Playground/Client/Controls/PageViewer.xaml.cs +++ b/source/RevitLookup.UI.Playground/Client/Controls/PageViewer.xaml.cs @@ -1,7 +1,9 @@ using System.Windows; +using System.Windows.Automation.Peers; using System.Windows.Controls; using Microsoft.Extensions.DependencyInjection; using RevitLookup.Abstractions.Services; +using RevitLookup.UI.Framework.Controls.Automation; using Wpf.Ui; namespace RevitLookup.UI.Playground.Client.Controls; @@ -51,4 +53,9 @@ private void OnViewerFrameResized(object sender, SizeChangedEventArgs args) Top -= (ActualHeight - MinHeight) / 2; } } + + protected override AutomationPeer OnCreateAutomationPeer() + { + return new NoAutomationWindowPeer(this); + } } \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/Services/ViewModelsRegistrationExtensions.cs b/source/RevitLookup.UI.Playground/Client/Services/ViewModelsRegistrationExtensions.cs index bdce6e3d..e4381d14 100644 --- a/source/RevitLookup.UI.Playground/Client/Services/ViewModelsRegistrationExtensions.cs +++ b/source/RevitLookup.UI.Playground/Client/Services/ViewModelsRegistrationExtensions.cs @@ -7,7 +7,9 @@ public static class ViewModelsRegistrationExtensions public static void RegisterViewModels(this IServiceCollection services) { services.Scan(selector => selector.FromCallingAssembly() - .AddClasses(filter => filter.InNamespaces("RevitLookup.UI.Playground.Client.ViewModels")).AsSelf().WithTransientLifetime() + .AddClasses(filter => filter.InNamespaces("RevitLookup.UI.Playground.Client.ViewModels")) + .AsSelf() + .WithScopedLifetime() .AddClasses(filter => filter.NotInNamespaces("RevitLookup.UI.Playground.Client.ViewModels").Where(type => type.Name.EndsWith("ViewModel"))) .AsImplementedInterfaces(type => type.Name.EndsWith("ViewModel")) .WithScopedLifetime()); diff --git a/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/DesignGuidance/FontIconsPageViewModel.cs b/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/DesignGuidance/FontIconsPageViewModel.cs index a4041e22..1673ad6c 100644 --- a/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/DesignGuidance/FontIconsPageViewModel.cs +++ b/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/DesignGuidance/FontIconsPageViewModel.cs @@ -4,13 +4,16 @@ using CommunityToolkit.Mvvm.ComponentModel; using JetBrains.Annotations; using RevitLookup.UI.Playground.Client.Models; +#if NETFRAMEWORK +using RevitLookup.UI.Framework.Extensions; +#endif namespace RevitLookup.UI.Playground.Client.ViewModels.Pages.DesignGuidance; [UsedImplicitly] public partial class FontIconsPageViewModel : ObservableObject { - [ObservableProperty] private List _icons; + [ObservableProperty] private List _icons = []; [ObservableProperty] private List _filteredIcons = []; [ObservableProperty] private FontIconData? _selectedIcon; [ObservableProperty] private string _searchText = string.Empty; diff --git a/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/DesignGuidance/SymbolIconsPageViewModel.cs b/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/DesignGuidance/SymbolIconsPageViewModel.cs index 8c09e1e8..571075cb 100644 --- a/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/DesignGuidance/SymbolIconsPageViewModel.cs +++ b/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/DesignGuidance/SymbolIconsPageViewModel.cs @@ -2,13 +2,16 @@ using JetBrains.Annotations; using RevitLookup.UI.Playground.Client.Models; using Wpf.Ui.Controls; +#if NETFRAMEWORK +using RevitLookup.UI.Framework.Extensions; +#endif namespace RevitLookup.UI.Playground.Client.ViewModels.Pages.DesignGuidance; [UsedImplicitly] public partial class SymbolIconsPageViewModel : ObservableObject { - [ObservableProperty] private List _icons; + [ObservableProperty] private List _icons = []; [ObservableProperty] private List _filteredIcons = []; [ObservableProperty] private SymbolIconData? _selectedIcon; [ObservableProperty] private string _searchText = string.Empty; diff --git a/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/PagesViewModel.cs b/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/PagesViewModel.cs index 451e3306..5a9e851c 100644 --- a/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/PagesViewModel.cs +++ b/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/PagesViewModel.cs @@ -5,6 +5,7 @@ using RevitLookup.UI.Framework.Views.AboutProgram; using RevitLookup.UI.Framework.Views.Dashboard; using RevitLookup.UI.Framework.Views.Settings; +using RevitLookup.UI.Framework.Views.Summary; using RevitLookup.UI.Playground.Client.Controls; namespace RevitLookup.UI.Playground.Client.ViewModels.Pages; @@ -13,19 +14,29 @@ namespace RevitLookup.UI.Playground.Client.ViewModels.Pages; public sealed partial class PagesViewModel : ObservableObject { [RelayCommand] - private void ShowSettingsPage() + private void ShowDashboardPage() { var viewer = Host.CreateScope(); - viewer.ShowPage(); + viewer.SizeToContent = SizeToContent.Width; + viewer.Height = 850; + viewer.ShowPage(); } [RelayCommand] - private void ShowDashboardPage() + private void ShowSnoopSummaryPage() { var viewer = Host.CreateScope(); - viewer.SizeToContent = SizeToContent.Width; - viewer.Height = 850; - viewer.ShowPage(); + viewer.SizeToContent = SizeToContent.Manual; + viewer.Height = 500; + viewer.Width = 900; + viewer.ShowPage(); + } + + [RelayCommand] + private void ShowSettingsPage() + { + var viewer = Host.CreateScope(); + viewer.ShowPage(); } [RelayCommand] diff --git a/source/RevitLookup.UI.Playground/Client/Views/PlaygroundView.xaml.cs b/source/RevitLookup.UI.Playground/Client/Views/PlaygroundView.xaml.cs index 977dea3e..ac54121e 100644 --- a/source/RevitLookup.UI.Playground/Client/Views/PlaygroundView.xaml.cs +++ b/source/RevitLookup.UI.Playground/Client/Views/PlaygroundView.xaml.cs @@ -1,5 +1,7 @@ using System.Windows; +using System.Windows.Automation.Peers; using RevitLookup.Abstractions.Services; +using RevitLookup.UI.Framework.Controls.Automation; using RevitLookup.UI.Playground.Client.ViewModels; using RevitLookup.UI.Playground.Client.Views.Pages; using Wpf.Ui; @@ -11,7 +13,8 @@ public sealed partial class PlaygroundView { private readonly INavigationService _navigationService; - public PlaygroundView(PlaygroundViewModel viewModel, + public PlaygroundView( + PlaygroundViewModel viewModel, INavigationService navigationService, IContentDialogService dialogService, ISnackbarService snackbarService, @@ -42,4 +45,9 @@ private void OnNavigationSelectionChanged(object sender, RoutedEventArgs e) NavigationView.SetCurrentValue(NavigationView.HeaderVisibilityProperty, showHeader); } + + protected override AutomationPeer OnCreateAutomationPeer() + { + return new NoAutomationWindowPeer(this); + } } \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Mappers/DecompositionResultMapper.cs b/source/RevitLookup.UI.Playground/Mappers/DecompositionResultMapper.cs new file mode 100644 index 00000000..6710b17e --- /dev/null +++ b/source/RevitLookup.UI.Playground/Mappers/DecompositionResultMapper.cs @@ -0,0 +1,11 @@ +using LookupEngine.Abstractions; +using RevitLookup.Abstractions.ObservableModels.Decomposition; +using Riok.Mapperly.Abstractions; + +namespace RevitLookup.UI.Playground.Mappers; + +[Mapper] +public static partial class DecompositionResultMapper +{ + public static partial ObservableDecomposedObject Convert(DecomposedObject decomposedObject); +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/RevitLookup.UI.Playground.csproj b/source/RevitLookup.UI.Playground/RevitLookup.UI.Playground.csproj index f6760d33..ec2d7f4f 100644 --- a/source/RevitLookup.UI.Playground/RevitLookup.UI.Playground.csproj +++ b/source/RevitLookup.UI.Playground/RevitLookup.UI.Playground.csproj @@ -14,6 +14,8 @@ + + @@ -23,6 +25,7 @@ + diff --git a/source/RevitLookup.UI.Playground/Styles/ComponentStyles/Summary/MembersGrid/SummaryGridGroupStyles.xaml b/source/RevitLookup.UI.Playground/Styles/ComponentStyles/Summary/MembersGrid/SummaryGridGroupStyles.xaml new file mode 100644 index 00000000..1fce7cd5 --- /dev/null +++ b/source/RevitLookup.UI.Playground/Styles/ComponentStyles/Summary/MembersGrid/SummaryGridGroupStyles.xaml @@ -0,0 +1,43 @@ + + + + + + + + + + \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Styles/ComponentStyles/Summary/ObjectsTree/SummaryTreeGroupTemplates.xaml b/source/RevitLookup.UI.Playground/Styles/ComponentStyles/Summary/ObjectsTree/SummaryTreeGroupTemplates.xaml new file mode 100644 index 00000000..7a50ff75 --- /dev/null +++ b/source/RevitLookup.UI.Playground/Styles/ComponentStyles/Summary/ObjectsTree/SummaryTreeGroupTemplates.xaml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/ViewModels/Summary/MockSnoopSummaryViewModel.cs b/source/RevitLookup.UI.Playground/ViewModels/Summary/MockSnoopSummaryViewModel.cs new file mode 100644 index 00000000..6df13447 --- /dev/null +++ b/source/RevitLookup.UI.Playground/ViewModels/Summary/MockSnoopSummaryViewModel.cs @@ -0,0 +1,119 @@ +using System.Collections; +using System.Windows.Media; +using Bogus; +using CommunityToolkit.Mvvm.ComponentModel; +using JetBrains.Annotations; +using LookupEngine; +using RevitLookup.Abstractions.ObservableModels.Decomposition; +using RevitLookup.Abstractions.ViewModels.Summary; +using RevitLookup.UI.Playground.Mappers; +#if NETFRAMEWORK +using RevitLookup.UI.Framework.Extensions; +#endif + +namespace RevitLookup.UI.Playground.ViewModels.Summary; + +[UsedImplicitly] +public sealed partial class MockSnoopSummaryViewModel : ObservableObject, ISnoopSummaryViewModel +{ + [ObservableProperty] private string _searchText = string.Empty; + [ObservableProperty] private ObservableDecomposedObject? _selectedDecomposedObject; + [ObservableProperty] private List _decomposedObjects = []; + [ObservableProperty] private List _filteredDecomposedObjects = []; + [ObservableProperty] private List _members = []; + [ObservableProperty] private List _filteredMembers = []; + + public MockSnoopSummaryViewModel() + { + var globalFaker = new Faker(); + var strings = new Faker() + .CustomInstantiator(faker => faker.Lorem.Sentence(400)) + .GenerateBetween(0, 100); + + var integers = Enumerable.Range(0, globalFaker.Random.Int(1, 100)) + .Select(_ => globalFaker.Random.Int()) + .ToList(); + + var colors = Enumerable.Range(0, globalFaker.Random.Int(1, 100)) + .Select(_ => Color.FromRgb(globalFaker.Random.Byte(), globalFaker.Random.Byte(), globalFaker.Random.Byte())) + .ToList(); + + var objects = new ArrayList(); + objects.AddRange(strings); + objects.AddRange(integers); + objects.AddRange(colors); + + DecomposedObjects = objects + .Cast() + .Select(value => LookupComposer.Decompose(value)) + .Select(DecompositionResultMapper.Convert) + .ToList(); + } + + partial void OnDecomposedObjectsChanged(List value) + { + OnSearchTextChanged(SearchText); + } + + partial void OnSelectedDecomposedObjectChanged(ObservableDecomposedObject? value) + { + if (value is null) + { + Members = []; + return; + } + + Members = value.Members; + } + + partial void OnMembersChanged(List value) + { + FilteredMembers = value; + } + + async partial void OnSearchTextChanged(string value) + { + try + { + if (string.IsNullOrEmpty(SearchText)) + { + FilteredDecomposedObjects = ApplyGrouping(DecomposedObjects); + return; + } + + FilteredDecomposedObjects = await Task.Run(() => + { + var formattedText = value.Trim(); + var searchResults = new List(); + // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator + foreach (var item in DecomposedObjects) + { + if (item.Name.Contains(formattedText, StringComparison.OrdinalIgnoreCase) || item.Name.Contains(formattedText, StringComparison.OrdinalIgnoreCase)) + { + searchResults.Add(item); + } + } + + return ApplyGrouping(searchResults); + }); + } + catch + { + // ignored + } + } + + private List ApplyGrouping(List objects) + { + return objects + .OrderBy(data => data.Type) + .ThenBy(data => data.Name) + .GroupBy(data => data.Type) + .Select(group => new ObservableDecomposedObjectsGroup + { + GroupName = group.Key, + GroupItems = group.ToList() + }) + .ToList(); + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/ViewModels/Tools/MockModulesViewModel.cs b/source/RevitLookup.UI.Playground/ViewModels/Tools/MockModulesViewModel.cs index 1e6dd97d..f5618e2b 100644 --- a/source/RevitLookup.UI.Playground/ViewModels/Tools/MockModulesViewModel.cs +++ b/source/RevitLookup.UI.Playground/ViewModels/Tools/MockModulesViewModel.cs @@ -19,18 +19,23 @@ // (Rights in Technical Data and Computer Software), as applicable. -using System.Runtime.Loader; using CommunityToolkit.Mvvm.ComponentModel; using RevitLookup.Abstractions.Models.Tools; using RevitLookup.Abstractions.ViewModels.Tools; +#if NETCOREAPP +using System.Runtime.Loader; +#endif +#if NETFRAMEWORK +using RevitLookup.UI.Framework.Extensions; +#endif namespace RevitLookup.UI.Playground.ViewModels.Tools; public sealed partial class MockModulesViewModel : ObservableObject, IModulesViewModel { [ObservableProperty] private string _searchText = string.Empty; + [ObservableProperty] private List _modules = []; [ObservableProperty] private List _filteredModules = []; - [ObservableProperty] private List _modules; public MockModulesViewModel() { @@ -68,14 +73,14 @@ async partial void OnSearchTextChanged(string value) FilteredModules = await Task.Run(() => { - var formattedText = value.ToLower().Trim(); + var formattedText = value.Trim(); var searchResults = new List(); // ReSharper disable once LoopCanBeConvertedToQuery foreach (var module in Modules) { - if (module.Name.ToLower().Contains(formattedText) || - module.Path.ToLower().Contains(formattedText) || - module.Version.ToLower().Contains(formattedText)) + if (module.Name.Contains(formattedText, StringComparison.OrdinalIgnoreCase) || + module.Path.Contains(formattedText, StringComparison.OrdinalIgnoreCase) || + module.Version.Contains(formattedText, StringComparison.OrdinalIgnoreCase)) { searchResults.Add(module); } diff --git a/source/RevitLookup.UI.Playground/ViewModels/Tools/MockUnitsViewModel.cs b/source/RevitLookup.UI.Playground/ViewModels/Tools/MockUnitsViewModel.cs index d047d49a..6209a5d7 100644 --- a/source/RevitLookup.UI.Playground/ViewModels/Tools/MockUnitsViewModel.cs +++ b/source/RevitLookup.UI.Playground/ViewModels/Tools/MockUnitsViewModel.cs @@ -23,6 +23,9 @@ using JetBrains.Annotations; using RevitLookup.Abstractions.Models.Tools; using RevitLookup.Abstractions.ViewModels.Tools; +#if NETFRAMEWORK +using RevitLookup.UI.Framework.Extensions; +#endif namespace RevitLookup.UI.Playground.ViewModels.Tools; @@ -71,11 +74,11 @@ async partial void OnSearchTextChanged(string value) FilteredUnits = await Task.Run(() => { - var formattedText = value.ToLower().Trim(); + var formattedText = value.Trim(); var searchResults = new List(); // ReSharper disable once LoopCanBeConvertedToQuery foreach (var family in Units) - if (family.Label.ToLower().Contains(formattedText) || family.Unit.ToLower().Contains(formattedText)) + if (family.Label.Contains(formattedText, StringComparison.OrdinalIgnoreCase) || family.Unit.Contains(formattedText, StringComparison.OrdinalIgnoreCase)) searchResults.Add(family); return searchResults; From 2db2d2352f1ed4080f9c87d307c4c805cee1760d Mon Sep 17 00:00:00 2001 From: Nice3point Date: Sun, 1 Dec 2024 03:39:28 +0300 Subject: [PATCH 023/121] Change pages lifetime --- .../Client/Services/ViewsRegistrationExtensions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/RevitLookup.UI.Playground/Client/Services/ViewsRegistrationExtensions.cs b/source/RevitLookup.UI.Playground/Client/Services/ViewsRegistrationExtensions.cs index 758ce89c..b1e5aad5 100644 --- a/source/RevitLookup.UI.Playground/Client/Services/ViewsRegistrationExtensions.cs +++ b/source/RevitLookup.UI.Playground/Client/Services/ViewsRegistrationExtensions.cs @@ -8,13 +8,13 @@ public static class ViewsRegistrationExtensions { public static void RegisterViews(this IServiceCollection services) { - services.Scan(selector => selector.FromAssemblyOf() + services.Scan(selector => selector.FromAssemblyOf() .AddClasses(filter => filter.AssignableTo()).AsSelf().WithScopedLifetime() - .AddClasses(filter => filter.AssignableTo()).AsSelf().WithTransientLifetime() + .AddClasses(filter => filter.AssignableTo()).AsSelf().WithScopedLifetime() .AddClasses(filter => filter.AssignableTo()).AsSelf().WithTransientLifetime()); services.Scan(selector => selector.FromCallingAssembly() .AddClasses(filter => filter.AssignableTo()).AsSelf().WithScopedLifetime() - .AddClasses(filter => filter.AssignableTo()).AsSelf().WithTransientLifetime()); + .AddClasses(filter => filter.AssignableTo()).AsSelf().WithScopedLifetime()); } } \ No newline at end of file From 76c2af90c1090f69fe0e086b4f61d958d2ca037e Mon Sep 17 00:00:00 2001 From: Nice3point Date: Sun, 1 Dec 2024 16:43:16 +0300 Subject: [PATCH 024/121] Rename projects --- .../LookupEngine.Tests.Performance}/Benchmark.cs | 2 +- .../LookupEngine.Tests.Performance}/ClosureBenchmark.cs | 2 +- .../LookupEngine.Tests.Performance.csproj | 0 .../LookupEngine.Tests.Performance}/ResolveTypeBench.cs | 2 +- .../LookupEngine.Tests.Performance}/SortBench.cs | 2 +- .../LookupEngine.Tests.Performance}/TypeEqualBench.cs | 2 +- .../LookupEngine.Tests.Unit.csproj} | 0 .../OptionsTests.cs | 7 ++----- 8 files changed, 7 insertions(+), 10 deletions(-) rename {source/LookupEngine.Benchmarks => tests/LookupEngine.Tests.Performance}/Benchmark.cs (66%) rename {source/LookupEngine.Benchmarks => tests/LookupEngine.Tests.Performance}/ClosureBenchmark.cs (99%) rename source/LookupEngine.Benchmarks/LookupEngine.Benchmarks.csproj => tests/LookupEngine.Tests.Performance/LookupEngine.Tests.Performance.csproj (100%) rename {source/LookupEngine.Benchmarks => tests/LookupEngine.Tests.Performance}/ResolveTypeBench.cs (97%) rename {source/LookupEngine.Benchmarks => tests/LookupEngine.Tests.Performance}/SortBench.cs (98%) rename {source/LookupEngine.Benchmarks => tests/LookupEngine.Tests.Performance}/TypeEqualBench.cs (98%) rename tests/{LookupEngine.Tests/LookupEngine.Tests.csproj => LookupEngine.Tests.Unit/LookupEngine.Tests.Unit.csproj} (100%) rename tests/{LookupEngine.Tests => LookupEngine.Tests.Unit}/OptionsTests.cs (90%) diff --git a/source/LookupEngine.Benchmarks/Benchmark.cs b/tests/LookupEngine.Tests.Performance/Benchmark.cs similarity index 66% rename from source/LookupEngine.Benchmarks/Benchmark.cs rename to tests/LookupEngine.Tests.Performance/Benchmark.cs index 4835a8bc..5ad7f5fa 100644 --- a/source/LookupEngine.Benchmarks/Benchmark.cs +++ b/tests/LookupEngine.Tests.Performance/Benchmark.cs @@ -1,4 +1,4 @@ using BenchmarkDotNet.Running; -using LookupEngine.Benchmarks; +using LookupEngine.Tests.Performance; BenchmarkRunner.Run(); \ No newline at end of file diff --git a/source/LookupEngine.Benchmarks/ClosureBenchmark.cs b/tests/LookupEngine.Tests.Performance/ClosureBenchmark.cs similarity index 99% rename from source/LookupEngine.Benchmarks/ClosureBenchmark.cs rename to tests/LookupEngine.Tests.Performance/ClosureBenchmark.cs index acb688b0..eb10cbc6 100644 --- a/source/LookupEngine.Benchmarks/ClosureBenchmark.cs +++ b/tests/LookupEngine.Tests.Performance/ClosureBenchmark.cs @@ -20,7 +20,7 @@ using BenchmarkDotNet.Attributes; -namespace LookupEngine.Benchmarks; +namespace LookupEngine.Tests.Performance; [ShortRunJob] [MemoryDiagnoser] diff --git a/source/LookupEngine.Benchmarks/LookupEngine.Benchmarks.csproj b/tests/LookupEngine.Tests.Performance/LookupEngine.Tests.Performance.csproj similarity index 100% rename from source/LookupEngine.Benchmarks/LookupEngine.Benchmarks.csproj rename to tests/LookupEngine.Tests.Performance/LookupEngine.Tests.Performance.csproj diff --git a/source/LookupEngine.Benchmarks/ResolveTypeBench.cs b/tests/LookupEngine.Tests.Performance/ResolveTypeBench.cs similarity index 97% rename from source/LookupEngine.Benchmarks/ResolveTypeBench.cs rename to tests/LookupEngine.Tests.Performance/ResolveTypeBench.cs index fdbc4b02..369e01a3 100644 --- a/source/LookupEngine.Benchmarks/ResolveTypeBench.cs +++ b/tests/LookupEngine.Tests.Performance/ResolveTypeBench.cs @@ -20,7 +20,7 @@ using BenchmarkDotNet.Attributes; -namespace LookupEngine.Benchmarks; +namespace LookupEngine.Tests.Performance; [MediumRunJob] [MemoryDiagnoser] diff --git a/source/LookupEngine.Benchmarks/SortBench.cs b/tests/LookupEngine.Tests.Performance/SortBench.cs similarity index 98% rename from source/LookupEngine.Benchmarks/SortBench.cs rename to tests/LookupEngine.Tests.Performance/SortBench.cs index 775ce641..52103334 100644 --- a/source/LookupEngine.Benchmarks/SortBench.cs +++ b/tests/LookupEngine.Tests.Performance/SortBench.cs @@ -21,7 +21,7 @@ using System.Reflection; using BenchmarkDotNet.Attributes; -namespace LookupEngine.Benchmarks; +namespace LookupEngine.Tests.Performance; [MediumRunJob] [MemoryDiagnoser(false)] diff --git a/source/LookupEngine.Benchmarks/TypeEqualBench.cs b/tests/LookupEngine.Tests.Performance/TypeEqualBench.cs similarity index 98% rename from source/LookupEngine.Benchmarks/TypeEqualBench.cs rename to tests/LookupEngine.Tests.Performance/TypeEqualBench.cs index 9e8bb80c..9eed528d 100644 --- a/source/LookupEngine.Benchmarks/TypeEqualBench.cs +++ b/tests/LookupEngine.Tests.Performance/TypeEqualBench.cs @@ -20,7 +20,7 @@ using BenchmarkDotNet.Attributes; -namespace LookupEngine.Benchmarks; +namespace LookupEngine.Tests.Performance; [MediumRunJob] [MemoryDiagnoser] diff --git a/tests/LookupEngine.Tests/LookupEngine.Tests.csproj b/tests/LookupEngine.Tests.Unit/LookupEngine.Tests.Unit.csproj similarity index 100% rename from tests/LookupEngine.Tests/LookupEngine.Tests.csproj rename to tests/LookupEngine.Tests.Unit/LookupEngine.Tests.Unit.csproj diff --git a/tests/LookupEngine.Tests/OptionsTests.cs b/tests/LookupEngine.Tests.Unit/OptionsTests.cs similarity index 90% rename from tests/LookupEngine.Tests/OptionsTests.cs rename to tests/LookupEngine.Tests.Unit/OptionsTests.cs index 3e1b0b7a..8cbe5923 100644 --- a/tests/LookupEngine.Tests/OptionsTests.cs +++ b/tests/LookupEngine.Tests.Unit/OptionsTests.cs @@ -1,4 +1,4 @@ -namespace LookupEngine.Tests; +namespace LookupEngine.Tests.Unit; public sealed class LookupEngineTests { @@ -35,10 +35,7 @@ public async Task Decompose_With_Redirection() //Arrange var options = new DecomposeOptions { - RedirectMap = - [ - new Redirect(i => "d") - ] + IncludeFields = true }; //Act From 9dab88eaf461517afe1050c938e144ceddb987c7 Mon Sep 17 00:00:00 2001 From: Nice3point Date: Sun, 1 Dec 2024 16:43:58 +0300 Subject: [PATCH 025/121] Add value decomposition --- .../Configuration/IDescriptorRedirection.cs | 2 +- .../Metadata/DecomposedMember.cs | 10 +-- .../Metadata/DecomposedObject.cs | 6 +- .../Metadata/DecomposedValue.cs | 15 ++++ .../Engine/LookupComposer.Decompose.cs | 10 +-- .../Engine/LookupComposer.Diagnostic.cs | 6 +- .../Engine/LookupComposer.Enumeration.cs | 2 +- .../Engine/LookupComposer.Events.cs | 2 +- .../Engine/LookupComposer.Extensions.cs | 2 +- .../Engine/LookupComposer.Fields.cs | 2 +- .../Engine/LookupComposer.Methods.cs | 4 +- .../Engine/LookupComposer.Properties.cs | 4 +- .../Engine/LookupComposer.Redirection.cs | 41 +++++++++++ .../Engine/LookupComposer.WriteHelpers.cs | 71 +++++++++++++------ source/LookupEngine/Engine/LookupComposer.cs | 24 +++---- source/LookupEngine/IRedirect.cs | 3 - .../LookupEngine/Options/DecomposeOptions.cs | 2 - source/LookupEngine/Redirect.cs | 6 -- source/LookupEngine/SnoopableObject.cs | 57 --------------- .../ObservableDecomposedMember.cs | 18 ++--- .../ObservableDecomposedObject.cs | 8 +-- .../ObservableDecomposedValue.cs | 11 +++ .../Summary/ISnoopSummaryViewModel.cs | 21 ++++++ .../Views/Summary/SnoopSummaryPage.xaml | 2 +- .../Views/Summary/SummaryViewBase.ToolTips.cs | 8 +-- .../Views/Summary/SummaryViewBase.xaml.cs | 2 +- .../Summary/MockSnoopSummaryViewModel.cs | 4 +- 27 files changed, 196 insertions(+), 147 deletions(-) create mode 100644 source/LookupEngine.Abstractions/Metadata/DecomposedValue.cs create mode 100644 source/LookupEngine/Engine/LookupComposer.Redirection.cs delete mode 100644 source/LookupEngine/IRedirect.cs delete mode 100644 source/LookupEngine/Redirect.cs delete mode 100644 source/LookupEngine/SnoopableObject.cs create mode 100644 source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedValue.cs create mode 100644 source/RevitLookup.Abstractions/ViewModels/Summary/ISnoopSummaryViewModel.cs diff --git a/source/LookupEngine.Abstractions/Configuration/IDescriptorRedirection.cs b/source/LookupEngine.Abstractions/Configuration/IDescriptorRedirection.cs index 776ccc16..d596a7d9 100644 --- a/source/LookupEngine.Abstractions/Configuration/IDescriptorRedirection.cs +++ b/source/LookupEngine.Abstractions/Configuration/IDescriptorRedirection.cs @@ -25,5 +25,5 @@ namespace LookupEngine.Abstractions.Configuration; /// public interface IDescriptorRedirection { - bool TryRedirect(string target, out object output); + bool TryRedirect(string targetMember, out object result); } \ No newline at end of file diff --git a/source/LookupEngine.Abstractions/Metadata/DecomposedMember.cs b/source/LookupEngine.Abstractions/Metadata/DecomposedMember.cs index d2b3bff4..b5f59f1c 100644 --- a/source/LookupEngine.Abstractions/Metadata/DecomposedMember.cs +++ b/source/LookupEngine.Abstractions/Metadata/DecomposedMember.cs @@ -6,14 +6,14 @@ namespace LookupEngine.Abstractions; [PublicAPI] -[DebuggerDisplay("Name = {Name} Value = {Value}")] +[DebuggerDisplay("Name = {Name} Value = {Value.Name}")] public sealed class DecomposedMember { - public int Depth { get; set; } - public required object? Value { get; set; } + public required DecomposedValue Value { get; set; } + public required int Depth { get; set; } public required string Name { get; set; } - public required string Type { get; set; } - public required string TypeFullName { get; set; } + public required string DeclaringTypeName { get; set; } + public required string DeclaringTypeFullName { get; set; } public string? Description { get; set; } public double ComputationTime { get; set; } public long AllocatedBytes { get; set; } diff --git a/source/LookupEngine.Abstractions/Metadata/DecomposedObject.cs b/source/LookupEngine.Abstractions/Metadata/DecomposedObject.cs index dd8310b2..2e2540be 100644 --- a/source/LookupEngine.Abstractions/Metadata/DecomposedObject.cs +++ b/source/LookupEngine.Abstractions/Metadata/DecomposedObject.cs @@ -5,12 +5,12 @@ namespace LookupEngine.Abstractions; [PublicAPI] -[DebuggerDisplay("Name = {Name} Value = {Value}")] +[DebuggerDisplay("Name = {Name} Value = {RawValue}")] public sealed class DecomposedObject { - public required object? Value { get; init; } + public required object? RawValue { get; init; } public required string Name { get; init; } - public required string Type { get; set; } + public required string TypeName { get; set; } public required string TypeFullName { get; set; } public required List Members { get; init; } } \ No newline at end of file diff --git a/source/LookupEngine.Abstractions/Metadata/DecomposedValue.cs b/source/LookupEngine.Abstractions/Metadata/DecomposedValue.cs new file mode 100644 index 00000000..114917e1 --- /dev/null +++ b/source/LookupEngine.Abstractions/Metadata/DecomposedValue.cs @@ -0,0 +1,15 @@ +using System.Diagnostics; +using JetBrains.Annotations; + +// ReSharper disable once CheckNamespace +namespace LookupEngine.Abstractions; + +[PublicAPI] +[DebuggerDisplay("Name = {Name} Value = {RawValue}")] +public sealed class DecomposedValue +{ + public required object? RawValue { get; set; } + public required string Name { get; set; } + public required string TypeName { get; set; } + public required string TypeFullName { get; set; } +} \ No newline at end of file diff --git a/source/LookupEngine/Engine/LookupComposer.Decompose.cs b/source/LookupEngine/Engine/LookupComposer.Decompose.cs index 1ee99dac..e1e333c2 100644 --- a/source/LookupEngine/Engine/LookupComposer.Decompose.cs +++ b/source/LookupEngine/Engine/LookupComposer.Decompose.cs @@ -35,8 +35,8 @@ private DecomposedObject DecomposeInstanceObject(object instance) for (var i = objectTypeHierarchy.Count - 1; i >= 0; i--) { - Subtype = objectTypeHierarchy[i]; - SubtypeDescriptor = _options.TypeResolver.Invoke(instance, Subtype); + DeclaringType = objectTypeHierarchy[i]; + DeclaringDescriptor = _options.TypeResolver.Invoke(instance, DeclaringType); var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly; if (!_options.IgnoreStaticMembers) flags |= BindingFlags.Static; @@ -51,7 +51,7 @@ private DecomposedObject DecomposeInstanceObject(object instance) _depth--; } - Subtype = objectType; + DeclaringType = objectType; AddEnumerableItems(); return _decomposedObject; @@ -65,8 +65,8 @@ private DecomposedObject DecomposeStaticObject(Type objectType) var staticDescriptor = _options.TypeResolver.Invoke(null, objectType); _decomposedObject = CreateStaticDecomposition(objectType, staticDescriptor); - Subtype = objectType; - SubtypeDescriptor = staticDescriptor; + DeclaringType = objectType; + DeclaringDescriptor = staticDescriptor; DecomposeFields(flags); DecomposeProperties(flags); diff --git a/source/LookupEngine/Engine/LookupComposer.Diagnostic.cs b/source/LookupEngine/Engine/LookupComposer.Diagnostic.cs index 4ec206a0..91918b28 100644 --- a/source/LookupEngine/Engine/LookupComposer.Diagnostic.cs +++ b/source/LookupEngine/Engine/LookupComposer.Diagnostic.cs @@ -35,7 +35,7 @@ public sealed partial class LookupComposer _timeDiagnoser.StartMonitoring(); _memoryDiagnoser.StartMonitoring(); - var value = member.GetValue(DecomposedObject.Value); + var value = member.GetValue(DecomposedObject.RawValue); _memoryDiagnoser.StopMonitoring(); _timeDiagnoser.StopMonitoring(); @@ -50,7 +50,7 @@ public sealed partial class LookupComposer _timeDiagnoser.StartMonitoring(); _memoryDiagnoser.StartMonitoring(); - return member.GetValue(DecomposedObject.Value); + return member.GetValue(DecomposedObject.RawValue); } finally { @@ -66,7 +66,7 @@ public sealed partial class LookupComposer _timeDiagnoser.StartMonitoring(); _memoryDiagnoser.StartMonitoring(); - return member.Invoke(DecomposedObject.Value, null); + return member.Invoke(DecomposedObject.RawValue, null); } finally { diff --git a/source/LookupEngine/Engine/LookupComposer.Enumeration.cs b/source/LookupEngine/Engine/LookupComposer.Enumeration.cs index 2b05a1d3..3b199d2b 100644 --- a/source/LookupEngine/Engine/LookupComposer.Enumeration.cs +++ b/source/LookupEngine/Engine/LookupComposer.Enumeration.cs @@ -27,7 +27,7 @@ public sealed partial class LookupComposer { private void AddEnumerableItems() { - if (DecomposedObject.Value is not IEnumerable enumerable) return; + if (DecomposedObject.RawValue is not IEnumerable enumerable) return; var enumerator = enumerable.GetEnumerator(); diff --git a/source/LookupEngine/Engine/LookupComposer.Events.cs b/source/LookupEngine/Engine/LookupComposer.Events.cs index 6713c4da..264c56f9 100644 --- a/source/LookupEngine/Engine/LookupComposer.Events.cs +++ b/source/LookupEngine/Engine/LookupComposer.Events.cs @@ -10,7 +10,7 @@ private void DecomposeEvents(BindingFlags bindingFlags) { if (_options.IncludeEvents) return; - var members = Subtype.GetEvents(bindingFlags); + var members = DeclaringType.GetEvents(bindingFlags); foreach (var member in members) { WriteDecompositionMember(ReflexionFormater.FormatTypeName(member.EventHandlerType ?? typeof(object)), member); diff --git a/source/LookupEngine/Engine/LookupComposer.Extensions.cs b/source/LookupEngine/Engine/LookupComposer.Extensions.cs index 3557e3de..e6c21d1c 100644 --- a/source/LookupEngine/Engine/LookupComposer.Extensions.cs +++ b/source/LookupEngine/Engine/LookupComposer.Extensions.cs @@ -29,7 +29,7 @@ public sealed partial class LookupComposer : IExtensionManager private void ExecuteExtensions() { if (!_options.EnableExtensions) return; - if (SubtypeDescriptor is not IDescriptorExtension extension) return; + if (DeclaringDescriptor is not IDescriptorExtension extension) return; extension.RegisterExtensions(this); } diff --git a/source/LookupEngine/Engine/LookupComposer.Fields.cs b/source/LookupEngine/Engine/LookupComposer.Fields.cs index 7576fc11..d6d9158e 100644 --- a/source/LookupEngine/Engine/LookupComposer.Fields.cs +++ b/source/LookupEngine/Engine/LookupComposer.Fields.cs @@ -29,7 +29,7 @@ private void DecomposeFields(BindingFlags bindingFlags) { if (!_options.IncludeFields) return; - var members = Subtype.GetFields(bindingFlags); + var members = DeclaringType.GetFields(bindingFlags); foreach (var member in members) { if (member.IsSpecialName) continue; diff --git a/source/LookupEngine/Engine/LookupComposer.Methods.cs b/source/LookupEngine/Engine/LookupComposer.Methods.cs index 95fa89e6..e54228d0 100644 --- a/source/LookupEngine/Engine/LookupComposer.Methods.cs +++ b/source/LookupEngine/Engine/LookupComposer.Methods.cs @@ -28,7 +28,7 @@ public sealed partial class LookupComposer { private void DecomposeMethods(BindingFlags bindingFlags) { - var members = Subtype.GetMethods(bindingFlags); + var members = DeclaringType.GetMethods(bindingFlags); foreach (var member in members) { if (member.IsSpecialName) continue; @@ -60,7 +60,7 @@ private void DecomposeMethods(BindingFlags bindingFlags) private bool TryResolve(MethodInfo member, ParameterInfo[] parameters, out object? value) { value = null; - if (SubtypeDescriptor is not IDescriptorResolver resolver) return false; + if (DeclaringDescriptor is not IDescriptorResolver resolver) return false; var handler = resolver.Resolve(member.Name, parameters); if (handler is null) return false; diff --git a/source/LookupEngine/Engine/LookupComposer.Properties.cs b/source/LookupEngine/Engine/LookupComposer.Properties.cs index 9fb46b58..8fe389d1 100644 --- a/source/LookupEngine/Engine/LookupComposer.Properties.cs +++ b/source/LookupEngine/Engine/LookupComposer.Properties.cs @@ -30,7 +30,7 @@ public sealed partial class LookupComposer { private void DecomposeProperties(BindingFlags bindingFlags) { - var members = Subtype.GetProperties(bindingFlags); + var members = DeclaringType.GetProperties(bindingFlags); foreach (var member in members) { if (member.IsSpecialName) continue; @@ -62,7 +62,7 @@ private void DecomposeProperties(BindingFlags bindingFlags) private bool TryResolve(PropertyInfo member, ParameterInfo[]? parameters, out object? value) { value = null; - if (SubtypeDescriptor is not IDescriptorResolver resolver) return false; + if (DeclaringDescriptor is not IDescriptorResolver resolver) return false; var handler = resolver.Resolve(member.Name, parameters); if (handler is null) return false; diff --git a/source/LookupEngine/Engine/LookupComposer.Redirection.cs b/source/LookupEngine/Engine/LookupComposer.Redirection.cs new file mode 100644 index 00000000..2c5b5522 --- /dev/null +++ b/source/LookupEngine/Engine/LookupComposer.Redirection.cs @@ -0,0 +1,41 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +using LookupEngine.Abstractions.ComponentModel; +using LookupEngine.Abstractions.Configuration; + +// ReSharper disable once CheckNamespace +namespace LookupEngine; + +public sealed partial class LookupComposer +{ + private Descriptor RedirectValue(string targetMember, ref object value) + { + var valueDescriptor = _options.TypeResolver.Invoke(value, null); + + while (valueDescriptor is IDescriptorRedirection redirection) + { + if (!redirection.TryRedirect(targetMember, out value)) break; + valueDescriptor = _options.TypeResolver.Invoke(value, null); + } + + return valueDescriptor; + } +} \ No newline at end of file diff --git a/source/LookupEngine/Engine/LookupComposer.WriteHelpers.cs b/source/LookupEngine/Engine/LookupComposer.WriteHelpers.cs index 8d769497..08a23c18 100644 --- a/source/LookupEngine/Engine/LookupComposer.WriteHelpers.cs +++ b/source/LookupEngine/Engine/LookupComposer.WriteHelpers.cs @@ -35,20 +35,22 @@ private static DecomposedObject CreateNullableDecomposition() return new DecomposedObject { Name = $"{nameof(System)}.{nameof(Object)}", - Value = null, + RawValue = null, Members = [], - Type = nameof(Object), + TypeName = nameof(Object), TypeFullName = $"{nameof(System)}.{nameof(Object)}" }; } private static DecomposedObject CreateInstanceDecomposition(object instance, Type type, Descriptor descriptor) { + var formatTypeName = ReflexionFormater.FormatTypeName(type); + return new DecomposedObject { - Name = descriptor.Name ?? type.Name, - Value = instance, - Type = ReflexionFormater.FormatTypeName(type), + Name = descriptor.Name ?? formatTypeName, + RawValue = instance, + TypeName = formatTypeName, TypeFullName = ReflexionFormater.FormatTypeFullName(type), Members = new List(32) }; @@ -56,11 +58,13 @@ private static DecomposedObject CreateInstanceDecomposition(object instance, Typ private static DecomposedObject CreateStaticDecomposition(Type type, Descriptor descriptor) { + var formatTypeName = ReflexionFormater.FormatTypeName(type); + return new DecomposedObject { - Name = descriptor.Name ?? type.Name, - Value = type, - Type = ReflexionFormater.FormatTypeName(type), + Name = descriptor.Name ?? formatTypeName, + RawValue = type, + TypeName = formatTypeName, TypeFullName = ReflexionFormater.FormatTypeFullName(type), Members = new List(32) }; @@ -71,27 +75,53 @@ private void WriteEnumerableMember(object? value, int index) var member = new DecomposedMember { Depth = _depth, - Value = value, - // Value = RedirectValue(value), - Name = $"{Subtype.Name}[{index}]", + Value = CreateValue(nameof(IEnumerable), value), + Name = $"{DeclaringType.Name}[{index}]", MemberAttributes = MemberAttributes.Property, - Type = nameof(IEnumerable), - TypeFullName = $"{nameof(System)}.{nameof(System.Collections)}.{nameof(IEnumerable)}", + DeclaringTypeName = nameof(IEnumerable), + DeclaringTypeFullName = $"{nameof(System)}.{nameof(System.Collections)}.{nameof(IEnumerable)}", }; DecomposedObject.Members.Add(member); } + private DecomposedValue CreateNullableValue() + { + return new DecomposedValue + { + RawValue = null, + Name = string.Empty, + TypeName = nameof(Object), + TypeFullName = $"{nameof(System)}.{nameof(Object)}" + }; + } + + private DecomposedValue CreateValue(string targetMember, object? value) + { + if (value is null) return CreateNullableValue(); + + var valueType = value.GetType(); + var formatTypeName = ReflexionFormater.FormatTypeName(valueType); + var valueDescriptor = RedirectValue(targetMember, ref value); + + return new DecomposedValue + { + RawValue = value, + Name = valueDescriptor.Name ?? formatTypeName, + TypeName = formatTypeName, + TypeFullName = ReflexionFormater.FormatTypeFullName(valueType) + }; + } + private void WriteExtensionMember(object? value, string name) { var member = new DecomposedMember { Depth = _depth, Name = name, - Value = value, - // Value = RedirectValue(value), - Type = ReflexionFormater.FormatTypeName(Subtype), - TypeFullName = ReflexionFormater.FormatTypeFullName(Subtype), + Value = CreateValue(name, value), + DeclaringTypeName = ReflexionFormater.FormatTypeName(DeclaringType), + DeclaringTypeFullName = ReflexionFormater.FormatTypeFullName(DeclaringType), MemberAttributes = MemberAttributes.Extension, ComputationTime = _timeDiagnoser.GetElapsed().TotalMilliseconds, AllocatedBytes = _memoryDiagnoser.GetAllocatedBytes() @@ -105,11 +135,10 @@ private void WriteDecompositionMember(object? value, MemberInfo memberInfo, Para var member = new DecomposedMember { Depth = _depth, - Value = value, - // Value = RedirectValue(member, value), + Value = CreateValue(memberInfo.Name, value), Name = ReflexionFormater.FormatMemberName(memberInfo, parameters), - Type = ReflexionFormater.FormatTypeName(Subtype), - TypeFullName = ReflexionFormater.FormatTypeFullName(Subtype), + DeclaringTypeName = ReflexionFormater.FormatTypeName(DeclaringType), + DeclaringTypeFullName = ReflexionFormater.FormatTypeFullName(DeclaringType), MemberAttributes = ModifiersFormater.FormatAttributes(memberInfo), ComputationTime = _timeDiagnoser.GetElapsed().TotalMilliseconds, AllocatedBytes = _memoryDiagnoser.GetAllocatedBytes() diff --git a/source/LookupEngine/Engine/LookupComposer.cs b/source/LookupEngine/Engine/LookupComposer.cs index fb7fb2e0..d20ae226 100644 --- a/source/LookupEngine/Engine/LookupComposer.cs +++ b/source/LookupEngine/Engine/LookupComposer.cs @@ -32,8 +32,8 @@ public sealed partial class LookupComposer private readonly DecomposeOptions _options; private int _depth; - private Type? _subtype; - private Descriptor? _subtypeDescriptor; + private Type? _declaringType; + private Descriptor? _declaringDescriptor; private DecomposedObject? _decomposedObject; private LookupComposer(DecomposeOptions options) @@ -55,32 +55,32 @@ internal DecomposedObject DecomposedObject set => _decomposedObject = value; } - internal Type Subtype + internal Type DeclaringType { get { - if (_subtype is null) + if (_declaringType is null) { - EngineException.ThrowIfEngineNotInitialized(nameof(Subtype)); + EngineException.ThrowIfEngineNotInitialized(nameof(DeclaringType)); } - return _subtype; + return _declaringType; } - set => _subtype = value; + set => _declaringType = value; } - internal Descriptor SubtypeDescriptor + internal Descriptor DeclaringDescriptor { get { - if (_subtypeDescriptor is null) + if (_declaringDescriptor is null) { - EngineException.ThrowIfEngineNotInitialized(nameof(SubtypeDescriptor)); + EngineException.ThrowIfEngineNotInitialized(nameof(DeclaringDescriptor)); } - return _subtypeDescriptor; + return _declaringDescriptor; } - set => _subtypeDescriptor = value; + set => _declaringDescriptor = value; } [Pure] diff --git a/source/LookupEngine/IRedirect.cs b/source/LookupEngine/IRedirect.cs deleted file mode 100644 index aee093c8..00000000 --- a/source/LookupEngine/IRedirect.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace LookupEngine; - -public interface IRedirect; \ No newline at end of file diff --git a/source/LookupEngine/Options/DecomposeOptions.cs b/source/LookupEngine/Options/DecomposeOptions.cs index 8aeb1784..ac94a116 100644 --- a/source/LookupEngine/Options/DecomposeOptions.cs +++ b/source/LookupEngine/Options/DecomposeOptions.cs @@ -15,8 +15,6 @@ public sealed class DecomposeOptions set => _typeResolver = value; } - public IRedirect[]? RedirectMap { get; set; } - public bool IncludeRoot { get; set; } public bool IncludeFields { get; set; } public bool IncludeEvents { get; set; } diff --git a/source/LookupEngine/Redirect.cs b/source/LookupEngine/Redirect.cs deleted file mode 100644 index ef75ea8c..00000000 --- a/source/LookupEngine/Redirect.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace LookupEngine; - -public sealed class Redirect(Func converter) : IRedirect -{ - public Func Converter { get; } = converter; -} \ No newline at end of file diff --git a/source/LookupEngine/SnoopableObject.cs b/source/LookupEngine/SnoopableObject.cs deleted file mode 100644 index 11244543..00000000 --- a/source/LookupEngine/SnoopableObject.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2003-2024 by Autodesk, Inc. -// Copyright 2003-2024 by Autodesk, Inc. -// -// Permission to use, copy, modify, and distribute this software in -// object code form for any purpose and without fee is hereby granted, -// provided that the above copyright notice appears in all copies and -// that both that copyright notice and the limited warranty and -// restricted rights notice below appear in all supporting -// documentation. -// -// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. -// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF -// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. -// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE -// UNINTERRUPTED OR ERROR FREE. -// -// Use, duplication, or disclosure by the U.S. Government is subject to -// restrictions set forth in FAR 52.227-19 (Commercial Computer -// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) -// (Rights in Technical Data and Computer Software), as applicable. - -namespace LookupEngine.Abstractions.Metadata; - -// public sealed class SnoopableObject -// { -// private IList _members; -// -// public SnoopableObject(Type type) -// { -// Object = type; -// Descriptor = DescriptorUtils.FindSuitableDescriptor(type); -// } -// -// public SnoopableObject(object obj) -// { -// Object = obj; -// Descriptor = DescriptorUtils.FindSuitableDescriptor(obj); -// } -// -// public object Object { get; set; } -// public Descriptor Descriptor { get; set; } -// -// public IList GetMembers() -// { -// return _members = LookupComposer.Decompose(Object); -// } -// -// public async Task> GetMembersAsync() -// { -// return _members = await RevitShell.ExternalDescriptorHandler.RaiseAsync(_ => LookupComposer.Decompose(Object)); -// } -// -// public async Task> GetCachedMembersAsync() -// { -// return _members ?? await GetMembersAsync(); -// } -// } \ No newline at end of file diff --git a/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedMember.cs b/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedMember.cs index a8757121..40d9730c 100644 --- a/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedMember.cs +++ b/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedMember.cs @@ -5,13 +5,13 @@ namespace RevitLookup.Abstractions.ObservableModels.Decomposition; public sealed class ObservableDecomposedMember : ObservableObject { - public required int Depth { get; init; } - public required object? Value { get; init; } - public required string Name { get; init; } - public required string Type { get; init; } - public required string TypeFullName { get; init; } - public required string? Description { get; init; } - public required double ComputationTime { get; init; } - public required long AllocatedBytes { get; init; } - public required MemberAttributes MemberAttributes { get; init; } + public required ObservableDecomposedValue Value { get; set; } + public required int Depth { get; set; } + public required string Name { get; set; } + public required string DeclaringTypeName { get; set; } + public required string DeclaringTypeFullName { get; set; } + public string? Description { get; set; } + public double ComputationTime { get; set; } + public long AllocatedBytes { get; set; } + public MemberAttributes MemberAttributes { get; set; } } \ No newline at end of file diff --git a/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedObject.cs b/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedObject.cs index 0bb7ec47..a5a84684 100644 --- a/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedObject.cs +++ b/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedObject.cs @@ -4,9 +4,9 @@ namespace RevitLookup.Abstractions.ObservableModels.Decomposition; public sealed class ObservableDecomposedObject : ObservableObject { - public required object? Value { get; set; } - public required string Name { get; set; } - public required string Type { get; set; } + public required object? RawValue { get; init; } + public required string Name { get; init; } + public required string TypeName { get; set; } public required string TypeFullName { get; set; } - public required List Members { get; set; } + public required List Members { get; init; } } \ No newline at end of file diff --git a/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedValue.cs b/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedValue.cs new file mode 100644 index 00000000..3f7389c0 --- /dev/null +++ b/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedValue.cs @@ -0,0 +1,11 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace RevitLookup.Abstractions.ObservableModels.Decomposition; + +public sealed class ObservableDecomposedValue : ObservableObject +{ + public required object? RawValue { get; set; } + public required string Name { get; set; } + public required string TypeName { get; set; } + public required string TypeFullName { get; set; } +} \ No newline at end of file diff --git a/source/RevitLookup.Abstractions/ViewModels/Summary/ISnoopSummaryViewModel.cs b/source/RevitLookup.Abstractions/ViewModels/Summary/ISnoopSummaryViewModel.cs new file mode 100644 index 00000000..2ccbe0ca --- /dev/null +++ b/source/RevitLookup.Abstractions/ViewModels/Summary/ISnoopSummaryViewModel.cs @@ -0,0 +1,21 @@ +using RevitLookup.Abstractions.ObservableModels.Decomposition; + +namespace RevitLookup.Abstractions.ViewModels.Summary; + +public interface ISnoopSummaryViewModel +{ + ObservableDecomposedObject? SelectedDecomposedObject { get; set; } + List DecomposedObjects { get; } + List FilteredDecomposedObjects { get; } + List Members { get; set; } + + List FilteredMembers { get; } + + // IAsyncRelayCommand FetchMembersCommand { get; } + // IAsyncRelayCommand RefreshMembersCommand { get; } + string SearchText { get; set; } + // public IServiceProvider ServiceProvider { get; } + // void Navigate(SnoopableObject selectedItem); + // void Navigate(IList selectedItems); + // void RemoveObject(object obj); +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml b/source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml index 27ead1c0..39ccc6a7 100644 --- a/source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml +++ b/source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml @@ -122,7 +122,7 @@ + Text="{Binding Value.Name, Mode=OneTime}" /> diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ToolTips.cs b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ToolTips.cs index 2b8114af..043627d7 100644 --- a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ToolTips.cs +++ b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ToolTips.cs @@ -36,7 +36,7 @@ private void CreateTreeTooltip(ObservableDecomposedObject decomposedObject, Fram .Append("Name: ") .AppendLine(decomposedObject.Name) .Append("Type: ") - .AppendLine(decomposedObject.Type) + .AppendLine(decomposedObject.TypeName) .Append("Full type: ") .AppendLine(decomposedObject.TypeFullName) .Append("Members: ") @@ -74,11 +74,11 @@ private void CreateGridRowTooltip(ObservableDecomposedMember member, FrameworkEl builder.AppendLine(member.Name) .Append("Type: ") - .AppendLine(member.Type) + .AppendLine(member.Value.TypeName) .Append("Full type: ") - .AppendLine(member.TypeFullName) + .AppendLine(member.Value.TypeFullName) .Append("Value: ") - .Append(member.Value); + .Append(member.Value.Name); if (member.Description is not null) { diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs index fe05f65c..59eee6bf 100644 --- a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs +++ b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs @@ -203,7 +203,7 @@ private void ApplyGrouping(DataGrid dataGrid) // dataGrid.Items.SortDescriptions.Add(new SortDescription(nameof(ObservableDecomposedMember.Name), ListSortDirection.Ascending)); dataGrid.Items.GroupDescriptions!.Clear(); - dataGrid.Items.GroupDescriptions.Add(new PropertyGroupDescription(nameof(ObservableDecomposedMember.Type))); + dataGrid.Items.GroupDescriptions.Add(new PropertyGroupDescription(nameof(ObservableDecomposedMember.DeclaringTypeName))); } // diff --git a/source/RevitLookup.UI.Playground/ViewModels/Summary/MockSnoopSummaryViewModel.cs b/source/RevitLookup.UI.Playground/ViewModels/Summary/MockSnoopSummaryViewModel.cs index 6df13447..8dd80096 100644 --- a/source/RevitLookup.UI.Playground/ViewModels/Summary/MockSnoopSummaryViewModel.cs +++ b/source/RevitLookup.UI.Playground/ViewModels/Summary/MockSnoopSummaryViewModel.cs @@ -106,9 +106,9 @@ async partial void OnSearchTextChanged(string value) private List ApplyGrouping(List objects) { return objects - .OrderBy(data => data.Type) + .OrderBy(data => data.TypeName) .ThenBy(data => data.Name) - .GroupBy(data => data.Type) + .GroupBy(data => data.TypeName) .Select(group => new ObservableDecomposedObjectsGroup { GroupName = group.Key, From 0dd72a0b7a7d2257f088ff8ec3803b04e23af157 Mon Sep 17 00:00:00 2001 From: Nice3point Date: Sun, 1 Dec 2024 16:51:34 +0300 Subject: [PATCH 026/121] Fix redirection type name --- source/LookupEngine/Engine/LookupComposer.WriteHelpers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/LookupEngine/Engine/LookupComposer.WriteHelpers.cs b/source/LookupEngine/Engine/LookupComposer.WriteHelpers.cs index 08a23c18..88daf554 100644 --- a/source/LookupEngine/Engine/LookupComposer.WriteHelpers.cs +++ b/source/LookupEngine/Engine/LookupComposer.WriteHelpers.cs @@ -100,9 +100,9 @@ private DecomposedValue CreateValue(string targetMember, object? value) { if (value is null) return CreateNullableValue(); + var valueDescriptor = RedirectValue(targetMember, ref value); var valueType = value.GetType(); var formatTypeName = ReflexionFormater.FormatTypeName(valueType); - var valueDescriptor = RedirectValue(targetMember, ref value); return new DecomposedValue { From 7bc1e43a6319d778e361a30cfbc76059a4fb9ff4 Mon Sep 17 00:00:00 2001 From: Nice3point Date: Sun, 1 Dec 2024 20:46:55 +0300 Subject: [PATCH 027/121] Contextmenu connector support --- .../Metadata/DecomposedMember.cs | 10 +- .../Metadata/DecomposedObject.cs | 2 + .../Metadata/DecomposedValue.cs | 2 + .../Engine/LookupComposer.Enumeration.cs | 1 + .../Engine/LookupComposer.WriteHelpers.cs | 5 +- .../ObservableDecomposedObject.cs | 2 + .../ObservableDecomposedValue.cs | 2 + .../Configuration/IDescriptorConnector.cs | 21 +- .../Extensions/ContextMenuExtensions.cs | 7 + .../RevitLookup.UI.Framework.csproj | 1 + .../Utils/HelpUtils.cs | 59 +++ .../Views/Summary/SnoopSummaryPage.xaml | 4 +- .../Views/Summary/SnoopSummaryPage.xaml.cs | 8 +- .../Summary/SummaryViewBase.ContextMenu.cs | 372 +++++++++--------- .../Views/Summary/SummaryViewBase.xaml.cs | 53 ++- .../Summary/MockSnoopSummaryViewModel.cs | 35 +- 16 files changed, 353 insertions(+), 231 deletions(-) rename source/{LookupEngine.Abstractions => RevitLookup.UI.Framework/Engine}/Configuration/IDescriptorConnector.cs (76%) create mode 100644 source/RevitLookup.UI.Framework/Utils/HelpUtils.cs diff --git a/source/LookupEngine.Abstractions/Metadata/DecomposedMember.cs b/source/LookupEngine.Abstractions/Metadata/DecomposedMember.cs index b5f59f1c..776fa196 100644 --- a/source/LookupEngine.Abstractions/Metadata/DecomposedMember.cs +++ b/source/LookupEngine.Abstractions/Metadata/DecomposedMember.cs @@ -9,11 +9,11 @@ namespace LookupEngine.Abstractions; [DebuggerDisplay("Name = {Name} Value = {Value.Name}")] public sealed class DecomposedMember { - public required DecomposedValue Value { get; set; } - public required int Depth { get; set; } - public required string Name { get; set; } - public required string DeclaringTypeName { get; set; } - public required string DeclaringTypeFullName { get; set; } + public required DecomposedValue Value { get; init; } + public required int Depth { get; init; } + public required string Name { get; init; } + public required string DeclaringTypeName { get; init; } + public required string DeclaringTypeFullName { get; init; } public string? Description { get; set; } public double ComputationTime { get; set; } public long AllocatedBytes { get; set; } diff --git a/source/LookupEngine.Abstractions/Metadata/DecomposedObject.cs b/source/LookupEngine.Abstractions/Metadata/DecomposedObject.cs index 2e2540be..1bd4c328 100644 --- a/source/LookupEngine.Abstractions/Metadata/DecomposedObject.cs +++ b/source/LookupEngine.Abstractions/Metadata/DecomposedObject.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using JetBrains.Annotations; +using LookupEngine.Abstractions.ComponentModel; // ReSharper disable once CheckNamespace namespace LookupEngine.Abstractions; @@ -13,4 +14,5 @@ public sealed class DecomposedObject public required string TypeName { get; set; } public required string TypeFullName { get; set; } public required List Members { get; init; } + public Descriptor? Descriptor { get; init; } } \ No newline at end of file diff --git a/source/LookupEngine.Abstractions/Metadata/DecomposedValue.cs b/source/LookupEngine.Abstractions/Metadata/DecomposedValue.cs index 114917e1..24fe7ca6 100644 --- a/source/LookupEngine.Abstractions/Metadata/DecomposedValue.cs +++ b/source/LookupEngine.Abstractions/Metadata/DecomposedValue.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using JetBrains.Annotations; +using LookupEngine.Abstractions.ComponentModel; // ReSharper disable once CheckNamespace namespace LookupEngine.Abstractions; @@ -12,4 +13,5 @@ public sealed class DecomposedValue public required string Name { get; set; } public required string TypeName { get; set; } public required string TypeFullName { get; set; } + public Descriptor? Descriptor { get; init; } } \ No newline at end of file diff --git a/source/LookupEngine/Engine/LookupComposer.Enumeration.cs b/source/LookupEngine/Engine/LookupComposer.Enumeration.cs index 3b199d2b..48becf28 100644 --- a/source/LookupEngine/Engine/LookupComposer.Enumeration.cs +++ b/source/LookupEngine/Engine/LookupComposer.Enumeration.cs @@ -36,6 +36,7 @@ private void AddEnumerableItems() { WriteEnumerableMember(enumerator.Current, index); index++; + _depth--; } if (enumerator is IDisposable disposable) diff --git a/source/LookupEngine/Engine/LookupComposer.WriteHelpers.cs b/source/LookupEngine/Engine/LookupComposer.WriteHelpers.cs index 88daf554..a7fbfe6d 100644 --- a/source/LookupEngine/Engine/LookupComposer.WriteHelpers.cs +++ b/source/LookupEngine/Engine/LookupComposer.WriteHelpers.cs @@ -52,6 +52,7 @@ private static DecomposedObject CreateInstanceDecomposition(object instance, Typ RawValue = instance, TypeName = formatTypeName, TypeFullName = ReflexionFormater.FormatTypeFullName(type), + Descriptor = descriptor, Members = new List(32) }; } @@ -66,6 +67,7 @@ private static DecomposedObject CreateStaticDecomposition(Type type, Descriptor RawValue = type, TypeName = formatTypeName, TypeFullName = ReflexionFormater.FormatTypeFullName(type), + Descriptor = descriptor, Members = new List(32) }; } @@ -109,7 +111,8 @@ private DecomposedValue CreateValue(string targetMember, object? value) RawValue = value, Name = valueDescriptor.Name ?? formatTypeName, TypeName = formatTypeName, - TypeFullName = ReflexionFormater.FormatTypeFullName(valueType) + TypeFullName = ReflexionFormater.FormatTypeFullName(valueType), + Descriptor = valueDescriptor }; } diff --git a/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedObject.cs b/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedObject.cs index a5a84684..f602560e 100644 --- a/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedObject.cs +++ b/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedObject.cs @@ -1,4 +1,5 @@ using CommunityToolkit.Mvvm.ComponentModel; +using LookupEngine.Abstractions.ComponentModel; namespace RevitLookup.Abstractions.ObservableModels.Decomposition; @@ -9,4 +10,5 @@ public sealed class ObservableDecomposedObject : ObservableObject public required string TypeName { get; set; } public required string TypeFullName { get; set; } public required List Members { get; init; } + public Descriptor? Descriptor { get; init; } } \ No newline at end of file diff --git a/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedValue.cs b/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedValue.cs index 3f7389c0..dbfc5000 100644 --- a/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedValue.cs +++ b/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedValue.cs @@ -1,4 +1,5 @@ using CommunityToolkit.Mvvm.ComponentModel; +using LookupEngine.Abstractions.ComponentModel; namespace RevitLookup.Abstractions.ObservableModels.Decomposition; @@ -8,4 +9,5 @@ public sealed class ObservableDecomposedValue : ObservableObject public required string Name { get; set; } public required string TypeName { get; set; } public required string TypeFullName { get; set; } + public Descriptor? Descriptor { get; init; } } \ No newline at end of file diff --git a/source/LookupEngine.Abstractions/Configuration/IDescriptorConnector.cs b/source/RevitLookup.UI.Framework/Engine/Configuration/IDescriptorConnector.cs similarity index 76% rename from source/LookupEngine.Abstractions/Configuration/IDescriptorConnector.cs rename to source/RevitLookup.UI.Framework/Engine/Configuration/IDescriptorConnector.cs index 0f6bfd93..df4db764 100644 --- a/source/LookupEngine.Abstractions/Configuration/IDescriptorConnector.cs +++ b/source/RevitLookup.UI.Framework/Engine/Configuration/IDescriptorConnector.cs @@ -18,15 +18,14 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. -// using System.Windows.Controls; -// -// namespace RevitLookup.Core.Contracts; -// -// /// -// /// Indicates that additional members can be added to the descriptor -// /// -// public interface IDescriptorConnector -// { -// void RegisterMenu(ContextMenu contextMenu); -// } +using System.Windows.Controls; +namespace RevitLookup.UI.Framework.Engine.Configuration; + +/// +/// Indicates that additional members can be added to the descriptor +/// +public interface IDescriptorConnector +{ + void RegisterMenu(ContextMenu contextMenu); +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Extensions/ContextMenuExtensions.cs b/source/RevitLookup.UI.Framework/Extensions/ContextMenuExtensions.cs index bc2d7257..b8cc9ce7 100644 --- a/source/RevitLookup.UI.Framework/Extensions/ContextMenuExtensions.cs +++ b/source/RevitLookup.UI.Framework/Extensions/ContextMenuExtensions.cs @@ -144,4 +144,11 @@ public static MenuItem SetAvailability(this MenuItem item, bool condition) return item; } + + public static MenuItem SetStaysOpenOnClick(this MenuItem item, bool condition) + { + item.SetCurrentValue(MenuItem.StaysOpenOnClickProperty, condition); + + return item; + } } \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/RevitLookup.UI.Framework.csproj b/source/RevitLookup.UI.Framework/RevitLookup.UI.Framework.csproj index f267ec61..44b99540 100644 --- a/source/RevitLookup.UI.Framework/RevitLookup.UI.Framework.csproj +++ b/source/RevitLookup.UI.Framework/RevitLookup.UI.Framework.csproj @@ -15,6 +15,7 @@ + diff --git a/source/RevitLookup.UI.Framework/Utils/HelpUtils.cs b/source/RevitLookup.UI.Framework/Utils/HelpUtils.cs new file mode 100644 index 00000000..b669a602 --- /dev/null +++ b/source/RevitLookup.UI.Framework/Utils/HelpUtils.cs @@ -0,0 +1,59 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +using RevitLookup.Common.Tools; + +namespace RevitLookup.UI.Framework.Utils; + +public static class HelpUtils +{ + public static void ShowHelp(string query) + { + string uri; + + if (query.Contains(' ')) + { + uri = $"https://duckduckgo.com/?q={query}"; + } + else if (query.StartsWith("System")) + { + query = query.Replace('`', '-'); + uri = $"https://docs.microsoft.com/en-us/dotnet/api/{query}"; + } + else + { + uri = $"https://duckduckgo.com/?q={query}"; + } + + ProcessTasks.StartShell(uri); + } + + public static void ShowHelp(string query, string parameter) + { + if (query.StartsWith("System")) + { + ShowHelp($"{query}.{parameter}"); + } + else + { + ShowHelp($"{query} {parameter}"); + } + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml b/source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml index 39ccc6a7..cff77311 100644 --- a/source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml +++ b/source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml @@ -57,7 +57,7 @@ VirtualizingPanel.VirtualizationMode="Recycling"> - - + diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml.cs b/source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml.cs index 359621c6..33781384 100644 --- a/source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml.cs +++ b/source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml.cs @@ -18,6 +18,7 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. +using Microsoft.Extensions.Logging; using RevitLookup.Abstractions.Services; using RevitLookup.Abstractions.ViewModels.Summary; @@ -28,8 +29,11 @@ public sealed partial class SnoopSummaryPage public SnoopSummaryPage( ISnoopSummaryViewModel viewModel, ISettingsService settingsService, - IWindowIntercomService intercomService) - : base(settingsService, intercomService) + IWindowIntercomService intercomService, + INotificationService notificationService, + ILoggerFactory loggerFactory) + : base(settingsService, intercomService, notificationService, loggerFactory) + { DataContext = this; ViewModel = viewModel; diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ContextMenu.cs b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ContextMenu.cs index 0e8689cd..2934efb7 100644 --- a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ContextMenu.cs +++ b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ContextMenu.cs @@ -18,189 +18,199 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using Microsoft.Extensions.Logging; +using RevitLookup.Abstractions.ObservableModels.Decomposition; +using RevitLookup.UI.Framework.Engine.Configuration; +using RevitLookup.UI.Framework.Extensions; +using RevitLookup.UI.Framework.Utils; +using Wpf.Ui; + namespace RevitLookup.UI.Framework.Views.Summary; public partial class SummaryViewBase { - // /// - // /// Tree view context menu - // /// - // private void CreateTreeContextMenu(Descriptor descriptor, FrameworkElement row) - // { - // var contextMenu = new ContextMenu - // { - // Resources = Resources, - // PlacementTarget = row, - // DataContext = ViewModel - // }; - // - // row.ContextMenu = contextMenu; - // - // contextMenu.AddMenuItem("CopyMenuItem") - // .SetCommand(descriptor, parameter => Clipboard.SetDataObject(parameter.Name)) - // .SetShortcut(ModifierKeys.Control, Key.C); - // contextMenu.AddMenuItem("HelpMenuItem") - // .SetCommand(descriptor, parameter => HelpUtils.ShowHelp(parameter.TypeFullName)) - // .SetShortcut(Key.F1); - // - // if (descriptor is not IDescriptorConnector connector) return; - // - // try - // { - // connector.RegisterMenu(contextMenu); - // } - // catch (Exception exception) - // { - // var logger = ViewModel.ServiceProvider.GetRequiredService>(); - // var notificationService = ViewModel.ServiceProvider.GetRequiredService(); - // - // logger.LogError(exception, "RegisterMenu error"); - // notificationService.ShowError("RegisterMenu error", exception); - // } - // } - - // /// - // /// Data grid context menu - // /// - // private void CreateGridContextMenu(DataGrid dataGrid) - // { - // var contextMenu = new ContextMenu - // { - // Resources = Resources, - // PlacementTarget = dataGrid, - // DataContext = ViewModel - // }; - // - // dataGrid.ContextMenu = contextMenu; - // - // contextMenu.AddMenuItem("RefreshMenuItem") - // .SetCommand(ViewModel.RefreshMembersCommand) - // .SetGestureText(Key.F5); - // - // contextMenu.AddSeparator(); - // contextMenu.AddLabel("Columns"); - // - // contextMenu.AddMenuItem() - // .SetHeader("Time") - // .SetChecked(dataGrid.Columns[2].Visibility == Visibility.Visible) - // .SetCommand(dataGrid.Columns[2], parameter => - // { - // _settings.ShowTimeColumn = parameter.Visibility != Visibility.Visible; - // parameter.Visibility = _settings.ShowTimeColumn ? Visibility.Visible : Visibility.Collapsed; - // }); - // - // contextMenu.AddMenuItem() - // .SetHeader("Memory") - // .SetChecked(dataGrid.Columns[3].Visibility == Visibility.Visible) - // .SetCommand(dataGrid.Columns[3], parameter => - // { - // _settings.ShowMemoryColumn = parameter.Visibility != Visibility.Visible; - // parameter.Visibility = _settings.ShowMemoryColumn ? Visibility.Visible : Visibility.Collapsed; - // }); - // - // contextMenu.AddSeparator(); - // contextMenu.AddLabel("Show"); - // - // contextMenu.AddMenuItem() - // .SetHeader("Events") - // .SetChecked(_settings.IncludeEvents) - // .SetCommand(_settings, parameter => - // { - // parameter.IncludeEvents = !parameter.IncludeEvents; - // return ViewModel.RefreshMembersCommand.ExecuteAsync(null); - // }); - // contextMenu.AddMenuItem() - // .SetHeader("Extensions") - // .SetChecked(_settings.IncludeExtensions) - // .SetCommand(_settings, parameter => - // { - // parameter.IncludeExtensions = !parameter.IncludeExtensions; - // return ViewModel.RefreshMembersCommand.ExecuteAsync(null); - // }); - // contextMenu.AddMenuItem() - // .SetHeader("Fields") - // .SetChecked(_settings.IncludeFields) - // .SetCommand(_settings, parameter => - // { - // parameter.IncludeFields = !parameter.IncludeFields; - // return ViewModel.RefreshMembersCommand.ExecuteAsync(null); - // }); - // contextMenu.AddMenuItem() - // .SetHeader("Non-public") - // .SetChecked(_settings.IncludePrivate) - // .SetCommand(_settings, parameter => - // { - // parameter.IncludePrivate = !parameter.IncludePrivate; - // return ViewModel.RefreshMembersCommand.ExecuteAsync(null); - // }); - // contextMenu.AddMenuItem() - // .SetHeader("Root") - // .SetChecked(_settings.IncludeRootHierarchy) - // .SetCommand(_settings, parameter => - // { - // parameter.IncludeRootHierarchy = !parameter.IncludeRootHierarchy; - // return ViewModel.RefreshMembersCommand.ExecuteAsync(null); - // }); - // contextMenu.AddMenuItem() - // .SetHeader("Static") - // .SetChecked(_settings.IncludeStatic) - // .SetCommand(_settings, parameter => - // { - // parameter.IncludeStatic = !parameter.IncludeStatic; - // return ViewModel.RefreshMembersCommand.ExecuteAsync(null); - // }); - // contextMenu.AddMenuItem() - // .SetHeader("Unsupported") - // .SetChecked(_settings.IncludeUnsupported) - // .SetCommand(_settings, parameter => - // { - // parameter.IncludeUnsupported = !parameter.IncludeUnsupported; - // return ViewModel.RefreshMembersCommand.ExecuteAsync(null); - // }); - // } - - // /// - // /// Data grid row context menu - // /// - // private void CreateGridRowContextMenu(Descriptor descriptor, FrameworkElement row) - // { - // var contextMenu = new ContextMenu - // { - // Resources = Resources, - // PlacementTarget = row, - // DataContext = ViewModel - // }; - // - // row.ContextMenu = contextMenu; - // - // contextMenu.AddMenuItem("CopyMenuItem") - // .SetCommand(descriptor, parameter => Clipboard.SetDataObject($"{parameter.Name}: {parameter.Value.Descriptor.Name}")) - // .SetShortcut(ModifierKeys.Control, Key.C) - // .SetAvailability(descriptor.Value.Descriptor.Name is not null); - // - // contextMenu.AddMenuItem("CopyMenuItem") - // .SetHeader("Copy value") - // .SetCommand(descriptor, parameter => Clipboard.SetDataObject(parameter.Value.Descriptor.Name)) - // .SetShortcut(ModifierKeys.Control | ModifierKeys.Shift, Key.C) - // .SetAvailability(descriptor.Value.Descriptor.Name is not null); - // - // contextMenu.AddMenuItem("HelpMenuItem") - // .SetCommand(descriptor, parameter => HelpUtils.ShowHelp(parameter.TypeFullName, parameter.Name)) - // .SetShortcut(Key.F1); - // - // if (descriptor.Value.Descriptor is not IDescriptorConnector connector) return; - // - // try - // { - // connector.RegisterMenu(contextMenu); - // } - // catch (Exception exception) - // { - // var logger = ViewModel.ServiceProvider.GetRequiredService>(); - // var notificationService = ViewModel.ServiceProvider.GetRequiredService(); - // - // logger.LogError(exception, "RegisterMenu error"); - // notificationService.ShowError("RegisterMenu error", exception); - // } - // } + /// + /// Tree view context menu + /// + private void CreateTreeContextMenu(ObservableDecomposedObject decomposedObject, FrameworkElement row) + { + var contextMenu = new ContextMenu + { + PlacementTarget = row, + Resources = UiApplication.Current.Resources + }; + + row.ContextMenu = contextMenu; + + contextMenu.AddMenuItem("CopyMenuItem") + .SetCommand(decomposedObject, parameter => Clipboard.SetDataObject(parameter.Name)) + .SetShortcut(ModifierKeys.Control, Key.C); + contextMenu.AddMenuItem("HelpMenuItem") + .SetCommand(decomposedObject, parameter => HelpUtils.ShowHelp(parameter.TypeFullName)) + .SetShortcut(Key.F1); + + if (decomposedObject.Descriptor is not IDescriptorConnector connector) return; + + try + { + connector.RegisterMenu(contextMenu); + } + catch (Exception exception) + { + _logger.LogError(exception, "Failed to register the context menu"); + _notificationService.ShowError("Failed to register the context menu", exception); + } + } + + /// + /// Data grid context menu + /// + private void CreateGridContextMenu(DataGrid dataGrid) + { + var contextMenu = new ContextMenu + { + PlacementTarget = dataGrid, + Resources = UiApplication.Current.Resources + }; + + dataGrid.ContextMenu = contextMenu; + + contextMenu.AddMenuItem("RefreshMenuItem") + // .SetCommand(ViewModel.RefreshMembersCommand) + .SetGestureText(Key.F5); + + contextMenu.AddSeparator(); + contextMenu.AddLabel("Columns"); + + contextMenu.AddMenuItem() + .SetHeader("Time") + .SetStaysOpenOnClick(true) + .SetChecked(dataGrid.Columns[2].Visibility == Visibility.Visible) + .SetCommand(dataGrid.Columns[2], parameter => + { + _settingsService.GeneralSettings.ShowTimeColumn = parameter.Visibility != Visibility.Visible; + parameter.Visibility = _settingsService.GeneralSettings.ShowTimeColumn ? Visibility.Visible : Visibility.Collapsed; + }); + + contextMenu.AddMenuItem() + .SetHeader("Memory") + .SetStaysOpenOnClick(true) + .SetChecked(dataGrid.Columns[3].Visibility == Visibility.Visible) + .SetCommand(dataGrid.Columns[3], parameter => + { + _settingsService.GeneralSettings.ShowMemoryColumn = parameter.Visibility != Visibility.Visible; + parameter.Visibility = _settingsService.GeneralSettings.ShowMemoryColumn ? Visibility.Visible : Visibility.Collapsed; + }); + + contextMenu.AddSeparator(); + contextMenu.AddLabel("Show"); + + contextMenu.AddMenuItem() + .SetHeader("Events") + .SetStaysOpenOnClick(true) + .SetChecked(_settingsService.GeneralSettings.IncludeEvents) + .SetCommand(_settingsService.GeneralSettings, parameter => + { + parameter.IncludeEvents = !parameter.IncludeEvents; + // return ViewModel.RefreshMembersCommand.ExecuteAsync(null); + }); + contextMenu.AddMenuItem() + .SetHeader("Extensions") + .SetStaysOpenOnClick(true) + .SetChecked(_settingsService.GeneralSettings.IncludeExtensions) + .SetCommand(_settingsService.GeneralSettings, parameter => + { + parameter.IncludeExtensions = !parameter.IncludeExtensions; + // return ViewModel.RefreshMembersCommand.ExecuteAsync(null); + }); + contextMenu.AddMenuItem() + .SetHeader("Fields") + .SetStaysOpenOnClick(true) + .SetChecked(_settingsService.GeneralSettings.IncludeFields) + .SetCommand(_settingsService.GeneralSettings, parameter => + { + parameter.IncludeFields = !parameter.IncludeFields; + // return ViewModel.RefreshMembersCommand.ExecuteAsync(null); + }); + contextMenu.AddMenuItem() + .SetHeader("Non-public") + .SetStaysOpenOnClick(true) + .SetChecked(_settingsService.GeneralSettings.IncludePrivate) + .SetCommand(_settingsService.GeneralSettings, parameter => + { + parameter.IncludePrivate = !parameter.IncludePrivate; + // return ViewModel.RefreshMembersCommand.ExecuteAsync(null); + }); + contextMenu.AddMenuItem() + .SetHeader("Root") + .SetStaysOpenOnClick(true) + .SetChecked(_settingsService.GeneralSettings.IncludeRootHierarchy) + .SetCommand(_settingsService.GeneralSettings, parameter => + { + parameter.IncludeRootHierarchy = !parameter.IncludeRootHierarchy; + // return ViewModel.RefreshMembersCommand.ExecuteAsync(null); + }); + contextMenu.AddMenuItem() + .SetHeader("Static") + .SetStaysOpenOnClick(true) + .SetChecked(_settingsService.GeneralSettings.IncludeStatic) + .SetCommand(_settingsService.GeneralSettings, parameter => + { + parameter.IncludeStatic = !parameter.IncludeStatic; + // return ViewModel.RefreshMembersCommand.ExecuteAsync(null); + }); + contextMenu.AddMenuItem() + .SetHeader("Unsupported") + .SetStaysOpenOnClick(true) + .SetChecked(_settingsService.GeneralSettings.IncludeUnsupported) + .SetCommand(_settingsService.GeneralSettings, parameter => + { + parameter.IncludeUnsupported = !parameter.IncludeUnsupported; + // return ViewModel.RefreshMembersCommand.ExecuteAsync(null); + }); + } + + /// + /// Data grid row context menu + /// + private void CreateGridRowContextMenu(ObservableDecomposedMember member, FrameworkElement row) + { + var contextMenu = new ContextMenu + { + PlacementTarget = row, + Resources = UiApplication.Current.Resources, + }; + + row.ContextMenu = contextMenu; + + contextMenu.AddMenuItem("CopyMenuItem") + .SetCommand(member, parameter => Clipboard.SetDataObject($"{parameter.Name}: {parameter.Value.Name}")) + .SetShortcut(ModifierKeys.Control, Key.C) + .SetAvailability(member.Value.Name != string.Empty); + + contextMenu.AddMenuItem("CopyMenuItem") + .SetHeader("Copy value") + .SetCommand(member, parameter => Clipboard.SetDataObject(parameter.Value.Name)) + .SetShortcut(ModifierKeys.Control | ModifierKeys.Shift, Key.C) + .SetAvailability(member.Value.Name != string.Empty); + + contextMenu.AddMenuItem("HelpMenuItem") + .SetCommand(member, parameter => HelpUtils.ShowHelp(parameter.DeclaringTypeFullName, parameter.Name)) + .SetShortcut(Key.F1); + + if (member.Value.Descriptor is not IDescriptorConnector connector) return; + + try + { + connector.RegisterMenu(contextMenu); + } + catch (Exception exception) + { + _logger.LogError(exception, "Failed to register the context menu"); + _notificationService.ShowError("Failed to register the context menu", exception); + } + } } \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs index 59eee6bf..da7d5dc6 100644 --- a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs +++ b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs @@ -19,17 +19,19 @@ // (Rights in Technical Data and Computer Software), as applicable. using System.Collections; +using System.ComponentModel; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Data; +using Microsoft.Extensions.Logging; using RevitLookup.Abstractions.ObservableModels.Decomposition; using RevitLookup.Abstractions.Services; using RevitLookup.Abstractions.ViewModels.Summary; using RevitLookup.UI.Framework.Utils; using Wpf.Ui.Abstractions.Controls; using Wpf.Ui.Controls; -using DataGrid = System.Windows.Controls.DataGrid; +using DataGrid = Wpf.Ui.Controls.DataGrid; using TreeView = Wpf.Ui.Controls.TreeView; using TreeViewItem = System.Windows.Controls.TreeViewItem; using Visibility = System.Windows.Visibility; @@ -40,11 +42,18 @@ public partial class SummaryViewBase : Page, INavigableView _logger; - protected SummaryViewBase(ISettingsService settingsService, IWindowIntercomService intercomService) + protected SummaryViewBase(ISettingsService settingsService, + IWindowIntercomService intercomService, + INotificationService notificationService, + ILoggerFactory loggerFactory) { _settingsService = settingsService; _intercomService = intercomService; + _notificationService = notificationService; + _logger = loggerFactory.CreateLogger(); AddShortcuts(); } @@ -166,11 +175,10 @@ private void OnTreeItemLoaded(object? sender, RoutedEventArgs args) { case ObservableDecomposedObjectsGroup decomposedGroup: CreateTreeTooltip(decomposedGroup, element); - // CreateTreeContextMenu(context.Descriptor, element); break; case ObservableDecomposedObject decomposedObject: CreateTreeTooltip(decomposedObject, element); - // CreateTreeContextMenu(context.Descriptor, element); + CreateTreeContextMenu(decomposedObject, element); break; } } @@ -181,38 +189,45 @@ private void OnTreeItemLoaded(object? sender, RoutedEventArgs args) /// /// Data grid initialization, validation /// - private void InitializeDataGrid(DataGrid control) + private void InitializeDataGrid(DataGrid dataGrid) { - ApplyGrouping(control); - ValidateTimeColumn(control); - ValidateAllocatedColumn(control); - // CreateGridContextMenu(dataGrid); - control.LoadingRow += OnGridRowLoading; - control.MouseMove += OnPresenterCursorInteracted; + ApplyGrouping(dataGrid); + ValidateTimeColumn(dataGrid); + ValidateAllocatedColumn(dataGrid); + CreateGridContextMenu(dataGrid); + dataGrid.LoadingRow += OnGridRowLoading; + dataGrid.MouseMove += OnPresenterCursorInteracted; + dataGrid.ItemsSourceChanged += ApplySorting; } /// /// Set DataGrid grouping rules /// - /// private void ApplyGrouping(DataGrid dataGrid) { - // dataGrid.Items.SortDescriptions.Clear(); - // dataGrid.Items.SortDescriptions.Add(new SortDescription(nameof(ObservableDecomposedMember.Depth), ListSortDirection.Descending)); - // dataGrid.Items.SortDescriptions.Add(new SortDescription(nameof(ObservableDecomposedMember.MemberAttributes), ListSortDirection.Ascending)); - // dataGrid.Items.SortDescriptions.Add(new SortDescription(nameof(ObservableDecomposedMember.Name), ListSortDirection.Ascending)); - dataGrid.Items.GroupDescriptions!.Clear(); dataGrid.Items.GroupDescriptions.Add(new PropertyGroupDescription(nameof(ObservableDecomposedMember.DeclaringTypeName))); } + /// + /// Set DataGrid sorting rules + /// + private void ApplySorting(object sender, EventArgs eventArgs) + { + var dataGrid = (DataGrid)sender; + + dataGrid.Items.SortDescriptions.Add(new SortDescription(nameof(ObservableDecomposedMember.Depth), ListSortDirection.Descending)); + dataGrid.Items.SortDescriptions.Add(new SortDescription(nameof(ObservableDecomposedMember.MemberAttributes), ListSortDirection.Ascending)); + dataGrid.Items.SortDescriptions.Add(new SortDescription(nameof(ObservableDecomposedMember.Name), ListSortDirection.Ascending)); + } + // // Handle data grid row loading event // // // Select row style // - protected void OnGridRowLoading(object? sender, DataGridRowEventArgs args) + private void OnGridRowLoading(object? sender, DataGridRowEventArgs args) { var row = args.Row; row.Loaded += OnGridRowLoaded; @@ -231,7 +246,7 @@ protected void OnGridRowLoaded(object sender, RoutedEventArgs args) var element = (FrameworkElement)sender; var member = (ObservableDecomposedMember)element.DataContext; CreateGridRowTooltip(member, element); - // CreateGridRowContextMenu(member, element); + CreateGridRowContextMenu(member, element); } /// diff --git a/source/RevitLookup.UI.Playground/ViewModels/Summary/MockSnoopSummaryViewModel.cs b/source/RevitLookup.UI.Playground/ViewModels/Summary/MockSnoopSummaryViewModel.cs index 8dd80096..bb0d4617 100644 --- a/source/RevitLookup.UI.Playground/ViewModels/Summary/MockSnoopSummaryViewModel.cs +++ b/source/RevitLookup.UI.Playground/ViewModels/Summary/MockSnoopSummaryViewModel.cs @@ -5,6 +5,7 @@ using JetBrains.Annotations; using LookupEngine; using RevitLookup.Abstractions.ObservableModels.Decomposition; +using RevitLookup.Abstractions.Services; using RevitLookup.Abstractions.ViewModels.Summary; using RevitLookup.UI.Playground.Mappers; #if NETFRAMEWORK @@ -23,18 +24,18 @@ public sealed partial class MockSnoopSummaryViewModel : ObservableObject, ISnoop [ObservableProperty] private List _members = []; [ObservableProperty] private List _filteredMembers = []; - public MockSnoopSummaryViewModel() + public MockSnoopSummaryViewModel(ISettingsService settingsService) { var globalFaker = new Faker(); var strings = new Faker() - .CustomInstantiator(faker => faker.Lorem.Sentence(400)) - .GenerateBetween(0, 100); + .CustomInstantiator(faker => faker.Lorem.Sentence(40)) + .GenerateBetween(1, 10); - var integers = Enumerable.Range(0, globalFaker.Random.Int(1, 100)) + var integers = Enumerable.Range(0, globalFaker.Random.Int(1, 10)) .Select(_ => globalFaker.Random.Int()) .ToList(); - var colors = Enumerable.Range(0, globalFaker.Random.Int(1, 100)) + var colors = Enumerable.Range(0, globalFaker.Random.Int(1, 10)) .Select(_ => Color.FromRgb(globalFaker.Random.Byte(), globalFaker.Random.Byte(), globalFaker.Random.Byte())) .ToList(); @@ -43,11 +44,25 @@ public MockSnoopSummaryViewModel() objects.AddRange(integers); objects.AddRange(colors); - DecomposedObjects = objects - .Cast() - .Select(value => LookupComposer.Decompose(value)) - .Select(DecompositionResultMapper.Convert) - .ToList(); + var options = new DecomposeOptions + { + IncludeRoot = settingsService.GeneralSettings.IncludeRootHierarchy, + IncludeFields = settingsService.GeneralSettings.IncludeFields, + IncludeEvents = settingsService.GeneralSettings.IncludeEvents, + IncludeUnsupported = settingsService.GeneralSettings.IncludeUnsupported, + IgnorePrivateMembers = !settingsService.GeneralSettings.IncludePrivate, + IgnoreStaticMembers = !settingsService.GeneralSettings.IncludeStatic, + EnableExtensions = settingsService.GeneralSettings.IncludeExtensions + }; + + var decomposedObjects = new List(); + foreach (var obj in objects) + { + var decomposedObject = LookupComposer.Decompose(obj, options); + decomposedObjects.Add(DecompositionResultMapper.Convert(decomposedObject)); + } + + DecomposedObjects = decomposedObjects; } partial void OnDecomposedObjectsChanged(List value) From 7118680c4475cb6b47d5917359184f0deddeaf17 Mon Sep 17 00:00:00 2001 From: Nice3point Date: Sun, 1 Dec 2024 20:47:13 +0300 Subject: [PATCH 028/121] Fix SecurityCritical CLR exceptions --- source/LookupEngine/Engine/LookupComposer.Methods.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/source/LookupEngine/Engine/LookupComposer.Methods.cs b/source/LookupEngine/Engine/LookupComposer.Methods.cs index e54228d0..d359aac1 100644 --- a/source/LookupEngine/Engine/LookupComposer.Methods.cs +++ b/source/LookupEngine/Engine/LookupComposer.Methods.cs @@ -32,6 +32,7 @@ private void DecomposeMethods(BindingFlags bindingFlags) foreach (var member in members) { if (member.IsSpecialName) continue; + if (member.IsFamily && member.IsSecurityCritical) continue; //Object-critical methods cause CLR exception object? value; var parameters = member.GetParameters(); From 89d4da808f791ca381446dd1b131c1b4506d818e Mon Sep 17 00:00:00 2001 From: Nice3point Date: Sun, 1 Dec 2024 21:08:03 +0300 Subject: [PATCH 029/121] Setup uniform grid --- .../ValueConverters/WidthToUniformColumnsConverter.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/source/RevitLookup.UI.Framework/Converters/ValueConverters/WidthToUniformColumnsConverter.cs b/source/RevitLookup.UI.Framework/Converters/ValueConverters/WidthToUniformColumnsConverter.cs index cbfb0cd7..15889421 100644 --- a/source/RevitLookup.UI.Framework/Converters/ValueConverters/WidthToUniformColumnsConverter.cs +++ b/source/RevitLookup.UI.Framework/Converters/ValueConverters/WidthToUniformColumnsConverter.cs @@ -8,12 +8,9 @@ public sealed class WidthToUniformColumnsConverter : MarkupExtension, IValueConv { public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { - var applicationTheme = (double)value!; - return applicationTheme switch - { - < 1200d => 1, - _ => 2 - }; + var width = (double)value!; + var columns = (int)Math.Floor(width / 600d); + return columns > 0 ? columns : 1; } public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) From e6dcf33313199bb3d3a3bc3042a710fba36a8d85 Mon Sep 17 00:00:00 2001 From: Nice3point Date: Sun, 1 Dec 2024 21:20:10 +0300 Subject: [PATCH 030/121] Tooltips, Menus lazy loading --- .../Views/Summary/SummaryViewBase.ContextMenu.cs | 2 ++ .../Views/Summary/SummaryViewBase.ToolTips.cs | 6 ++++++ .../Views/Summary/SummaryViewBase.xaml.cs | 14 +++++++------- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ContextMenu.cs b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ContextMenu.cs index 2934efb7..93faf4b7 100644 --- a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ContextMenu.cs +++ b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ContextMenu.cs @@ -178,6 +178,8 @@ private void CreateGridContextMenu(DataGrid dataGrid) /// private void CreateGridRowContextMenu(ObservableDecomposedMember member, FrameworkElement row) { + if (row.ContextMenu is not null) return; + var contextMenu = new ContextMenu { PlacementTarget = row, diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ToolTips.cs b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ToolTips.cs index 043627d7..0c6fc3f0 100644 --- a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ToolTips.cs +++ b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ToolTips.cs @@ -32,6 +32,8 @@ public partial class SummaryViewBase /// private void CreateTreeTooltip(ObservableDecomposedObject decomposedObject, FrameworkElement row) { + if (row.ToolTip is not null) return; + row.ToolTip = new StringBuilder() .Append("Name: ") .AppendLine(decomposedObject.Name) @@ -49,6 +51,8 @@ private void CreateTreeTooltip(ObservableDecomposedObject decomposedObject, Fram /// private void CreateTreeTooltip(ObservableDecomposedObjectsGroup decomposedGroup, FrameworkElement row) { + if (row.ToolTip is not null) return; + row.ToolTip = new StringBuilder() .Append("Type: ") .AppendLine(decomposedGroup.GroupName) @@ -62,6 +66,8 @@ private void CreateTreeTooltip(ObservableDecomposedObjectsGroup decomposedGroup, /// private void CreateGridRowTooltip(ObservableDecomposedMember member, FrameworkElement row) { + if (row.ToolTip is not null) return; + var builder = new StringBuilder(); if ((member.MemberAttributes & MemberAttributes.Private) != 0) builder.Append("Private "); diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs index da7d5dc6..25ea0fc0 100644 --- a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs +++ b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs @@ -150,10 +150,10 @@ private void OnTreeViewItemGenerated(object? sender, EventArgs _) var treeItem = (ItemsControl)generator.ContainerFromItem(item); if (treeItem is null) continue; - treeItem.Loaded -= OnTreeItemLoaded; + treeItem.MouseEnter -= OnTreeItemCaptured; // treeItem.PreviewMouseLeftButtonUp -= OnTreeItemClicked; - treeItem.Loaded += OnTreeItemLoaded; + treeItem.MouseEnter += OnTreeItemCaptured; // treeItem.PreviewMouseLeftButtonUp += OnTreeItemClicked; if (treeItem.Items.Count > 0) @@ -168,7 +168,7 @@ private void OnTreeViewItemGenerated(object? sender, EventArgs _) /// /// Create tree view tooltips, menus /// - private void OnTreeItemLoaded(object? sender, RoutedEventArgs args) + private void OnTreeItemCaptured(object? sender, RoutedEventArgs args) { var element = (FrameworkElement)sender!; switch (element.DataContext) @@ -212,9 +212,9 @@ private void ApplyGrouping(DataGrid dataGrid) /// /// Set DataGrid sorting rules /// - private void ApplySorting(object sender, EventArgs eventArgs) + private void ApplySorting(object? sender, EventArgs eventArgs) { - var dataGrid = (DataGrid)sender; + var dataGrid = (DataGrid)sender!; dataGrid.Items.SortDescriptions.Add(new SortDescription(nameof(ObservableDecomposedMember.Depth), ListSortDirection.Descending)); dataGrid.Items.SortDescriptions.Add(new SortDescription(nameof(ObservableDecomposedMember.MemberAttributes), ListSortDirection.Ascending)); @@ -230,7 +230,7 @@ private void ApplySorting(object sender, EventArgs eventArgs) private void OnGridRowLoading(object? sender, DataGridRowEventArgs args) { var row = args.Row; - row.Loaded += OnGridRowLoaded; + row.MouseEnter += OnGridRowCaptured; // row.PreviewMouseLeftButtonUp += OnGridRowClicked; // SelectDataGridRowStyle(row); } @@ -241,7 +241,7 @@ private void OnGridRowLoading(object? sender, DataGridRowEventArgs args) /// /// Create tooltips, context menu /// - protected void OnGridRowLoaded(object sender, RoutedEventArgs args) + private void OnGridRowCaptured(object sender, RoutedEventArgs args) { var element = (FrameworkElement)sender; var member = (ObservableDecomposedMember)element.DataContext; From bed7df8d2402933460550f41d92d8fa2632baac0 Mon Sep 17 00:00:00 2001 From: Nice3point Date: Sun, 1 Dec 2024 23:20:05 +0300 Subject: [PATCH 031/121] Custom styles support --- .../Metadata/DecomposedObject.cs | 1 + .../ObservableDecomposedObject.cs | 1 + .../CombinedDescriptorLabelConverter.cs | 74 ++++++++++++++++++ .../Views/Summary/SnoopSummaryPage.xaml | 15 +--- .../Summary/SummaryViewBase.Navigation.cs | 2 +- .../Views/Summary/SummaryViewBase.Styles.cs | 66 +++++++--------- .../Views/Summary/SummaryViewBase.xaml.cs | 1 - source/RevitLookup.UI.Playground/App.xaml | 14 +++- .../MembersGrid/DataGridCellTemplate.xaml | 61 +++++++++++++++ .../DataGridCellTemplateSelector.cs | 27 +++++++ .../DataGridGroupStyles.xaml} | 7 +- .../MembersGrid/DataGridRowStyle.xaml | 49 ++++++++++++ .../MembersGrid/DataGridRowStyleSelector.cs | 35 +++++++++ .../ObjectsTree/TreeGroupTemplates.xaml | 75 ++++++++++++++++++ .../TreeViewItemTemplateSelector.cs | 27 +++++++ .../SummaryTreeGroupTemplates.xaml | 76 ------------------- .../Styles/Converters/ObjectColorConverter.cs | 28 +++++++ 17 files changed, 427 insertions(+), 132 deletions(-) create mode 100644 source/RevitLookup.UI.Framework/Converters/ValueConverters/CombinedDescriptorLabelConverter.cs create mode 100644 source/RevitLookup.UI.Playground/Styles/ComponentStyles/MembersGrid/DataGridCellTemplate.xaml create mode 100644 source/RevitLookup.UI.Playground/Styles/ComponentStyles/MembersGrid/DataGridCellTemplateSelector.cs rename source/RevitLookup.UI.Playground/Styles/ComponentStyles/{Summary/MembersGrid/SummaryGridGroupStyles.xaml => MembersGrid/DataGridGroupStyles.xaml} (85%) create mode 100644 source/RevitLookup.UI.Playground/Styles/ComponentStyles/MembersGrid/DataGridRowStyle.xaml create mode 100644 source/RevitLookup.UI.Playground/Styles/ComponentStyles/MembersGrid/DataGridRowStyleSelector.cs create mode 100644 source/RevitLookup.UI.Playground/Styles/ComponentStyles/ObjectsTree/TreeGroupTemplates.xaml create mode 100644 source/RevitLookup.UI.Playground/Styles/ComponentStyles/ObjectsTree/TreeViewItemTemplateSelector.cs delete mode 100644 source/RevitLookup.UI.Playground/Styles/ComponentStyles/Summary/ObjectsTree/SummaryTreeGroupTemplates.xaml create mode 100644 source/RevitLookup.UI.Playground/Styles/Converters/ObjectColorConverter.cs diff --git a/source/LookupEngine.Abstractions/Metadata/DecomposedObject.cs b/source/LookupEngine.Abstractions/Metadata/DecomposedObject.cs index 1bd4c328..cb752dad 100644 --- a/source/LookupEngine.Abstractions/Metadata/DecomposedObject.cs +++ b/source/LookupEngine.Abstractions/Metadata/DecomposedObject.cs @@ -14,5 +14,6 @@ public sealed class DecomposedObject public required string TypeName { get; set; } public required string TypeFullName { get; set; } public required List Members { get; init; } + public string? Description { get; set; } public Descriptor? Descriptor { get; init; } } \ No newline at end of file diff --git a/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedObject.cs b/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedObject.cs index f602560e..4aefd2c0 100644 --- a/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedObject.cs +++ b/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedObject.cs @@ -10,5 +10,6 @@ public sealed class ObservableDecomposedObject : ObservableObject public required string TypeName { get; set; } public required string TypeFullName { get; set; } public required List Members { get; init; } + public string? Description { get; init; } public Descriptor? Descriptor { get; init; } } \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Converters/ValueConverters/CombinedDescriptorLabelConverter.cs b/source/RevitLookup.UI.Framework/Converters/ValueConverters/CombinedDescriptorLabelConverter.cs new file mode 100644 index 00000000..6740947e --- /dev/null +++ b/source/RevitLookup.UI.Framework/Converters/ValueConverters/CombinedDescriptorLabelConverter.cs @@ -0,0 +1,74 @@ +using System.Globalization; +using System.Windows.Data; +using System.Windows.Markup; +using RevitLookup.Abstractions.ObservableModels.Decomposition; + +namespace RevitLookup.UI.Framework.Converters.ValueConverters; + +public sealed class SingleDescriptorLabelConverter : DescriptorLabelConverter +{ + public override object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + var member = (ObservableDecomposedObject)value!; + if (!TryConvertInvalidNames(member.Name, out var name)) + { + name = CreateSingleName(member.Name, member.Description); + } + + return name; + } + + private static string CreateSingleName(string name, string? description) + { + return string.IsNullOrEmpty(description) ? name : description; + } +} + +public sealed class CombinedDescriptorLabelConverter : DescriptorLabelConverter +{ + public override object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + var member = (ObservableDecomposedMember)value!; + if (!TryConvertInvalidNames(member.Value.Name, out var name)) + { + name = CreateCombinedName(member.Value.Name, member.Description); + } + + return name; + } + + private static string CreateCombinedName(string name, string? description) + { + if (string.IsNullOrEmpty(description)) return name; + if (description.EndsWith(name, StringComparison.OrdinalIgnoreCase)) return description; + + return $"{description}: {name}"; + } +} + +public abstract class DescriptorLabelConverter : MarkupExtension, IValueConverter +{ + protected bool TryConvertInvalidNames(string text, out string result) + { + result = text switch + { + null => "", + "" => "", + _ => text + }; + + return text != result; + } + + public abstract object Convert(object? value, Type targetType, object? parameter, CultureInfo culture); + + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + return this; + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml b/source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml index cff77311..7fe87c7b 100644 --- a/source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml +++ b/source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml @@ -74,11 +74,12 @@ HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" ItemsSource="{Binding ViewModel.FilteredMembers}" + RowStyle="{x:Null}" + RowStyleSelector="{StaticResource DataGridRowStyleSelector}" ScrollViewer.CanContentScroll="True" VirtualizingPanel.IsVirtualizingWhenGrouping="True" VirtualizingPanel.VirtualizationMode="Recycling"> - @@ -110,23 +111,15 @@ + Width="2*" + CellTemplateSelector="{StaticResource DataGridCellTemplateSelector}"> - - - - - - /// Handle cursor interaction /// - protected void OnPresenterCursorInteracted(object sender, MouseEventArgs args) + private void OnPresenterCursorInteracted(object sender, MouseEventArgs args) { var presenter = (FrameworkElement)sender; if ((Keyboard.Modifiers & ModifierKeys.Control) == 0) diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Styles.cs b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Styles.cs index 2119b420..4b60d00f 100644 --- a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Styles.cs +++ b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Styles.cs @@ -20,46 +20,32 @@ namespace RevitLookup.UI.Framework.Views.Summary; -// /// -// /// Data grid row style selector -// /// -// private void SelectDataGridRowStyle(DataGridRow row) -// { -// var rowDescriptor = (Descriptor) row.DataContext; -// var styleName = rowDescriptor.Value.Descriptor switch -// { -// ExceptionDescriptor => "ExceptionDataGridRowStyle", -// IDescriptorEnumerator {IsEmpty: false} => "HandleDataGridRowStyle", -// IDescriptorEnumerator => "DefaultLookupDataGridRowStyle", -// IDescriptorCollector => "HandleDataGridRowStyle", -// _ => "DefaultLookupDataGridRowStyle" -// }; -// -// row.Style = (Style) FindResource(styleName); -// } -// } - -// public sealed class DataGridCellStyleSelector : DataTemplateSelector -// { -// /// -// /// Data grid cell style selector -// /// -// public override DataTemplate SelectTemplate(object item, DependencyObject container) -// { -// if (item is null) return null; -// -// var descriptor = (Descriptor) item; -// var presenter = (FrameworkElement) container; -// var templateName = descriptor.Value.Descriptor switch -// { -// ColorDescriptor => "DataGridColorCellTemplate", -// ColorMediaDescriptor => "DataGridColorCellTemplate", -// _ => "DefaultLookupDataGridCellTemplate" -// }; -// -// return (DataTemplate) presenter.FindResource(templateName); -// } -// } +public partial class SummaryViewBase +{ + // /// + // /// Data grid row style selector + // /// + // private void SelectDataGridRowStyle(DataGridRow row) + // { + // var member = (ObservableDecomposedMember)row.DataContext; + // + // var styleName = member.Value.RawValue switch + // { + // Exception => "ExceptionDataGridRowStyle", + // ICollection { Count: > 0 } => "EnumerableDataGridRowStyle", + // _ => null + // } + // ?? + // member.Value.Descriptor switch + // { + // IDescriptorEnumerator { IsEmpty: false } => "HandleDataGridRowStyle", + // IDescriptorCollector => "HandleDataGridRowStyle", + // _ => "DefaultLookupDataGridRowStyle" + // }; + // + // row.Style = (Style)FindResource(styleName); + // } +} // public sealed class TreeViewItemTemplateSelector : DataTemplateSelector // { diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs index 25ea0fc0..4578125e 100644 --- a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs +++ b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs @@ -232,7 +232,6 @@ private void OnGridRowLoading(object? sender, DataGridRowEventArgs args) var row = args.Row; row.MouseEnter += OnGridRowCaptured; // row.PreviewMouseLeftButtonUp += OnGridRowClicked; - // SelectDataGridRowStyle(row); } /// diff --git a/source/RevitLookup.UI.Playground/App.xaml b/source/RevitLookup.UI.Playground/App.xaml index 25b005bd..e0bd50b2 100644 --- a/source/RevitLookup.UI.Playground/App.xaml +++ b/source/RevitLookup.UI.Playground/App.xaml @@ -4,10 +4,15 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ui="http://revitlookup.com/xaml" xmlns:menu="clr-namespace:RevitLookup.UI.Framework.Markup.Menu;assembly=RevitLookup.UI.Framework" + xmlns:membersGrid="clr-namespace:RevitLookup.UI.Playground.Styles.ComponentStyles.MembersGrid" Startup="OnStartup"> + + + @@ -19,10 +24,15 @@ + Source="pack://application:,,,/RevitLookup.UI.Playground;component/Styles/ComponentStyles/ObjectsTree/TreeGroupTemplates.xaml" /> + + Source="pack://application:,,,/RevitLookup.UI.Playground;component/Styles/ComponentStyles/MembersGrid/DataGridRowStyle.xaml" /> + + diff --git a/source/RevitLookup.UI.Playground/Styles/ComponentStyles/MembersGrid/DataGridCellTemplate.xaml b/source/RevitLookup.UI.Playground/Styles/ComponentStyles/MembersGrid/DataGridCellTemplate.xaml new file mode 100644 index 00000000..816a8a9a --- /dev/null +++ b/source/RevitLookup.UI.Playground/Styles/ComponentStyles/MembersGrid/DataGridCellTemplate.xaml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Styles/ComponentStyles/MembersGrid/DataGridCellTemplateSelector.cs b/source/RevitLookup.UI.Playground/Styles/ComponentStyles/MembersGrid/DataGridCellTemplateSelector.cs new file mode 100644 index 00000000..7c55cad0 --- /dev/null +++ b/source/RevitLookup.UI.Playground/Styles/ComponentStyles/MembersGrid/DataGridCellTemplateSelector.cs @@ -0,0 +1,27 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using RevitLookup.Abstractions.ObservableModels.Decomposition; + +namespace RevitLookup.UI.Playground.Styles.ComponentStyles.MembersGrid; + +/// +/// Data grid cell template selector +/// +public sealed class DataGridCellTemplateSelector : DataTemplateSelector +{ + public override DataTemplate? SelectTemplate(object? item, DependencyObject container) + { + if (item is null) return null; + + var member = (ObservableDecomposedMember)item; + var presenter = (FrameworkElement)container; + var templateName = member.Value.RawValue switch + { + Color => "SummaryMediaColorCellTemplate", + _ => "DefaultSummaryCellTemplate" + }; + + return (DataTemplate)presenter.FindResource(templateName); + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Styles/ComponentStyles/Summary/MembersGrid/SummaryGridGroupStyles.xaml b/source/RevitLookup.UI.Playground/Styles/ComponentStyles/MembersGrid/DataGridGroupStyles.xaml similarity index 85% rename from source/RevitLookup.UI.Playground/Styles/ComponentStyles/Summary/MembersGrid/SummaryGridGroupStyles.xaml rename to source/RevitLookup.UI.Playground/Styles/ComponentStyles/MembersGrid/DataGridGroupStyles.xaml index 1fce7cd5..8227e0ce 100644 --- a/source/RevitLookup.UI.Playground/Styles/ComponentStyles/Summary/MembersGrid/SummaryGridGroupStyles.xaml +++ b/source/RevitLookup.UI.Playground/Styles/ComponentStyles/MembersGrid/DataGridGroupStyles.xaml @@ -4,10 +4,15 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:rl="http://revitlookup.com/xaml" + xmlns:membersGrid="clr-namespace:RevitLookup.UI.Playground.Styles.ComponentStyles.MembersGrid" mc:Ignorable="d"> + + + x:Key="DefaultSummaryGridGroupStyle" + ContainerStyleSelector="{StaticResource DataGridRowStyleSelector}"> + + + + + + \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Styles/ComponentStyles/MembersGrid/DataGridRowStyleSelector.cs b/source/RevitLookup.UI.Playground/Styles/ComponentStyles/MembersGrid/DataGridRowStyleSelector.cs new file mode 100644 index 00000000..ac8e879f --- /dev/null +++ b/source/RevitLookup.UI.Playground/Styles/ComponentStyles/MembersGrid/DataGridRowStyleSelector.cs @@ -0,0 +1,35 @@ +using System.Collections; +using System.Windows; +using System.Windows.Controls; +using LookupEngine.Abstractions.Configuration; +using RevitLookup.Abstractions.ObservableModels.Decomposition; + +namespace RevitLookup.UI.Playground.Styles.ComponentStyles.MembersGrid; + +/// +/// Data grid row style selector +/// +public sealed class DataGridRowStyleSelector : StyleSelector +{ + public override Style? SelectStyle(object item, DependencyObject container) + { + var member = (ObservableDecomposedMember)item; + var presenter = (FrameworkElement)container; + + var styleName = member.Value.RawValue switch + { + Exception => "ExceptionDataGridRowStyle", + ICollection { Count: > 0 } => "HandledDataGridRowStyle", + _ => null + } + ?? + member.Value.Descriptor switch + { + IDescriptorEnumerator { IsEmpty: false } => "HandleDataGridRowStyle", + IDescriptorCollector => "HandledDataGridRowStyle", + _ => "DefaultLookupDataGridRowStyle" + }; + + return (Style)presenter.FindResource(styleName); + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Styles/ComponentStyles/ObjectsTree/TreeGroupTemplates.xaml b/source/RevitLookup.UI.Playground/Styles/ComponentStyles/ObjectsTree/TreeGroupTemplates.xaml new file mode 100644 index 00000000..80cee151 --- /dev/null +++ b/source/RevitLookup.UI.Playground/Styles/ComponentStyles/ObjectsTree/TreeGroupTemplates.xaml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Styles/ComponentStyles/ObjectsTree/TreeViewItemTemplateSelector.cs b/source/RevitLookup.UI.Playground/Styles/ComponentStyles/ObjectsTree/TreeViewItemTemplateSelector.cs new file mode 100644 index 00000000..eb11572f --- /dev/null +++ b/source/RevitLookup.UI.Playground/Styles/ComponentStyles/ObjectsTree/TreeViewItemTemplateSelector.cs @@ -0,0 +1,27 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using RevitLookup.Abstractions.ObservableModels.Decomposition; + +namespace RevitLookup.UI.Playground.Styles.ComponentStyles.ObjectsTree; + +public sealed class TreeViewItemTemplateSelector : DataTemplateSelector +{ + /// + /// Tree view row style selector + /// + public override DataTemplate? SelectTemplate(object? item, DependencyObject container) + { + if (item is null) return null; + + var presenter = (FrameworkElement)container; + var decomposedObject = (ObservableDecomposedObject)item; + var templateName = decomposedObject.RawValue switch + { + Color => "SummaryMediaColorItemTemplate", + _ => "DefaultSummaryTreeItemTemplate" + }; + + return (DataTemplate)presenter.FindResource(templateName); + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Styles/ComponentStyles/Summary/ObjectsTree/SummaryTreeGroupTemplates.xaml b/source/RevitLookup.UI.Playground/Styles/ComponentStyles/Summary/ObjectsTree/SummaryTreeGroupTemplates.xaml deleted file mode 100644 index 7a50ff75..00000000 --- a/source/RevitLookup.UI.Playground/Styles/ComponentStyles/Summary/ObjectsTree/SummaryTreeGroupTemplates.xaml +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Styles/Converters/ObjectColorConverter.cs b/source/RevitLookup.UI.Playground/Styles/Converters/ObjectColorConverter.cs new file mode 100644 index 00000000..a41c875a --- /dev/null +++ b/source/RevitLookup.UI.Playground/Styles/Converters/ObjectColorConverter.cs @@ -0,0 +1,28 @@ +using System.Globalization; +using System.Windows.Data; +using System.Windows.Markup; +using System.Windows.Media; + +namespace RevitLookup.UI.Playground.Styles.Converters; + +public sealed class ObjectColorConverter : MarkupExtension, IValueConverter +{ + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + return value switch + { + Color color => color, + _ => throw new ArgumentOutOfRangeException(nameof(value), value, null) + }; + } + + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + return this; + } +} \ No newline at end of file From a4a15e2890743afeb840764d308390fb806998e9 Mon Sep 17 00:00:00 2001 From: Nice3point Date: Tue, 3 Dec 2024 03:57:42 +0300 Subject: [PATCH 032/121] Fix WPF issues --- .../Views/Summary/SnoopSummaryPage.xaml | 6 +- .../Views/Summary/SummaryViewBase.Internal.cs | 72 ++++++++++++++++++ .../Summary/SummaryViewBase.Navigation.cs | 6 +- .../Views/Summary/SummaryViewBase.Styles.cs | 74 ------------------- .../Views/Summary/SummaryViewBase.ToolTips.cs | 6 +- .../Views/Summary/SummaryViewBase.xaml.cs | 26 ++++--- .../Config/ApplicationOptions.cs | 2 +- .../MembersGrid/DataGridRowStyleSelector.cs | 36 +++++---- 8 files changed, 119 insertions(+), 109 deletions(-) create mode 100644 source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Internal.cs delete mode 100644 source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Styles.cs diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml b/source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml index 7fe87c7b..c01f0477 100644 --- a/source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml +++ b/source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml @@ -72,7 +72,7 @@ CanUserReorderColumns="False" AutoGenerateColumns="False" HorizontalScrollBarVisibility="Disabled" - VerticalScrollBarVisibility="Auto" + VerticalScrollBarVisibility="Visible" ItemsSource="{Binding ViewModel.FilteredMembers}" RowStyle="{x:Null}" RowStyleSelector="{StaticResource DataGridRowStyleSelector}" @@ -122,7 +122,7 @@ + /// By default, WPF calculates the column width after adding items to the ItemSource. This fix calculates it on loading + /// + /// + /// https://github.com/dotnet/wpf/blob/main/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/DataGrid.cs#L98 + /// + private static void FixInitialGridColumnSize(object sender, RoutedEventArgs args) + { + var dataGrid = (DataGrid)sender; + var passiveScrollViewer = dataGrid.FindVisualChild()!; + var gridColumns = InternalGridColumnsProperty.GetValue(dataGrid); + InternalGridScrollHostField.SetValue(dataGrid, passiveScrollViewer); + InternalGridInvalidateColumnWidthsComputationMethod.Invoke(gridColumns, null); + + passiveScrollViewer.SizeChanged += FixCanContentScrollResizing; + } + + /// + /// By default, WPF doesn't recalculate column widths if ScrollViewer.CanContentScroll is enabled + /// + /// + /// https://github.com/dotnet/wpf/blob/main/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/DataGrid.cs#L1961-L1968 + /// + private static void FixCanContentScrollResizing(object sender, SizeChangedEventArgs e) + { + var scrollViewer = (PassiveScrollViewer)sender; + var dataGrid = scrollViewer.FindVisualParent(); //find parent to avoid closure allocations + InternalGridOnViewportSizeChangedMethod.Invoke(dataGrid, [e.PreviousSize, e.NewSize]); + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Navigation.cs b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Navigation.cs index e75ca9fb..de5b902e 100644 --- a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Navigation.cs +++ b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Navigation.cs @@ -34,7 +34,7 @@ public partial class SummaryViewBase /// /// Collect data for selected item /// - protected void OnTreeItemSelected(object sender, RoutedPropertyChangedEventArgs args) + private void OnTreeItemSelected(object sender, RoutedPropertyChangedEventArgs args) { switch (args.NewValue) { @@ -96,7 +96,7 @@ protected void OnTreeItemSelected(object sender, RoutedPropertyChangedEventArgs< /// /// Handle cursor interaction /// - private void OnPresenterCursorInteracted(object sender, MouseEventArgs args) + private static void OnPresenterCursorInteracted(object sender, MouseEventArgs args) { var presenter = (FrameworkElement)sender; if ((Keyboard.Modifiers & ModifierKeys.Control) == 0) @@ -122,7 +122,7 @@ private void OnPresenterCursorInteracted(object sender, MouseEventArgs args) presenter.PreviewKeyUp += OnPresenterCursorRestored; } - private void OnPresenterCursorRestored(object sender, KeyEventArgs e) + private static void OnPresenterCursorRestored(object sender, KeyEventArgs e) { var presenter = (FrameworkElement)sender; presenter.PreviewKeyUp -= OnPresenterCursorRestored; diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Styles.cs b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Styles.cs deleted file mode 100644 index 4b60d00f..00000000 --- a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Styles.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2003-2024 by Autodesk, Inc. -// -// Permission to use, copy, modify, and distribute this software in -// object code form for any purpose and without fee is hereby granted, -// provided that the above copyright notice appears in all copies and -// that both that copyright notice and the limited warranty and -// restricted rights notice below appear in all supporting -// documentation. -// -// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. -// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF -// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. -// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE -// UNINTERRUPTED OR ERROR FREE. -// -// Use, duplication, or disclosure by the U.S. Government is subject to -// restrictions set forth in FAR 52.227-19 (Commercial Computer -// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) -// (Rights in Technical Data and Computer Software), as applicable. - -namespace RevitLookup.UI.Framework.Views.Summary; - -public partial class SummaryViewBase -{ - // /// - // /// Data grid row style selector - // /// - // private void SelectDataGridRowStyle(DataGridRow row) - // { - // var member = (ObservableDecomposedMember)row.DataContext; - // - // var styleName = member.Value.RawValue switch - // { - // Exception => "ExceptionDataGridRowStyle", - // ICollection { Count: > 0 } => "EnumerableDataGridRowStyle", - // _ => null - // } - // ?? - // member.Value.Descriptor switch - // { - // IDescriptorEnumerator { IsEmpty: false } => "HandleDataGridRowStyle", - // IDescriptorCollector => "HandleDataGridRowStyle", - // _ => "DefaultLookupDataGridRowStyle" - // }; - // - // row.Style = (Style)FindResource(styleName); - // } -} - -// public sealed class TreeViewItemTemplateSelector : DataTemplateSelector -// { -// /// -// /// Tree view row style selector -// /// -// public override DataTemplate SelectTemplate(object item, DependencyObject container) -// { -// if (item is null) return null; -// -// var presenter = (FrameworkElement) container; -// var templateName = item switch -// { -// CollectionViewGroup => "DefaultLookupTreeViewGroupTemplate", -// SnoopableObject snoopableObject => snoopableObject.Descriptor switch -// { -// ColorDescriptor => "TreeViewColorItemTemplate", -// ColorMediaDescriptor => "TreeViewColorItemTemplate", -// _ => "DefaultLookupTreeViewItemTemplate" -// }, -// -// _ => "DefaultLookupTreeViewItemTemplate" -// }; -// -// return (DataTemplate) presenter.FindResource(templateName); -// } \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ToolTips.cs b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ToolTips.cs index 0c6fc3f0..301a7ba1 100644 --- a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ToolTips.cs +++ b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ToolTips.cs @@ -30,7 +30,7 @@ public partial class SummaryViewBase /// /// Create tree view tooltips /// - private void CreateTreeTooltip(ObservableDecomposedObject decomposedObject, FrameworkElement row) + private static void CreateTreeTooltip(ObservableDecomposedObject decomposedObject, FrameworkElement row) { if (row.ToolTip is not null) return; @@ -49,7 +49,7 @@ private void CreateTreeTooltip(ObservableDecomposedObject decomposedObject, Fram /// /// Create tree view tooltips /// - private void CreateTreeTooltip(ObservableDecomposedObjectsGroup decomposedGroup, FrameworkElement row) + private static void CreateTreeTooltip(ObservableDecomposedObjectsGroup decomposedGroup, FrameworkElement row) { if (row.ToolTip is not null) return; @@ -64,7 +64,7 @@ private void CreateTreeTooltip(ObservableDecomposedObjectsGroup decomposedGroup, /// /// Create data grid tooltips /// - private void CreateGridRowTooltip(ObservableDecomposedMember member, FrameworkElement row) + private static void CreateGridRowTooltip(ObservableDecomposedMember member, FrameworkElement row) { if (row.ToolTip is not null) return; diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs index 4578125e..a141c839 100644 --- a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs +++ b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs @@ -85,31 +85,32 @@ private void InitializeTreeView(TreeView control) /// /// Tree view source changed handled. Setup action after the setting source /// - private void OnTreeSourceChanged(object? sender, IEnumerable enumerable) + private static void OnTreeSourceChanged(object? sender, IEnumerable enumerable) { - var self = (FrameworkElement)sender!; + var treeView = (TreeView)sender!; - if (self.IsLoaded) + if (treeView.IsLoaded) { - ExpandFirstTreeGroup(); + ExpandFirstTreeGroup(treeView); return; } - self.Loaded += OnLoaded; + treeView.Loaded += OnLoaded; return; void OnLoaded(object nestedSender, RoutedEventArgs args) { - var nestedSelf = (FrameworkElement)nestedSender; - nestedSelf.Loaded -= OnLoaded; - ExpandFirstTreeGroup(); + var self = (TreeView)nestedSender; + self.Loaded -= OnLoaded; + ExpandFirstTreeGroup(treeView); } } /// /// Expand the first tree view group after setting source /// - private async void ExpandFirstTreeGroup() + /// + private static async void ExpandFirstTreeGroup(TreeView treeView) { try { @@ -118,9 +119,9 @@ private async void ExpandFirstTreeGroup() await Task.Delay(transitionDuration); //3 is optimal groups count for expanding - if (TreeViewControl.Items.Count > 3) return; + if (treeView.Items.Count > 3) return; - var rootItem = (TreeViewItem?)TreeViewControl.GetItemAtIndex(0); + var rootItem = (TreeViewItem?)treeView.GetItemAtIndex(0); if (rootItem is null) return; var nestedItem = (TreeViewItem?)rootItem.GetItemAtIndex(0); @@ -198,6 +199,7 @@ private void InitializeDataGrid(DataGrid dataGrid) dataGrid.LoadingRow += OnGridRowLoading; dataGrid.MouseMove += OnPresenterCursorInteracted; dataGrid.ItemsSourceChanged += ApplySorting; + dataGrid.Loaded += FixInitialGridColumnSize; } /// @@ -212,7 +214,7 @@ private void ApplyGrouping(DataGrid dataGrid) /// /// Set DataGrid sorting rules /// - private void ApplySorting(object? sender, EventArgs eventArgs) + private static void ApplySorting(object? sender, EventArgs eventArgs) { var dataGrid = (DataGrid)sender!; diff --git a/source/RevitLookup.UI.Playground/Config/ApplicationOptions.cs b/source/RevitLookup.UI.Playground/Config/ApplicationOptions.cs index acf39bb3..6897b6c8 100644 --- a/source/RevitLookup.UI.Playground/Config/ApplicationOptions.cs +++ b/source/RevitLookup.UI.Playground/Config/ApplicationOptions.cs @@ -18,7 +18,7 @@ public static void AddApplicationOptions(this IServiceCollection services) public static void AddFolderOptions(this IServiceCollection services) { - var rootPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!; + var rootPath = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory!.FullName; services.Configure(options => { options.RootFolder = rootPath; diff --git a/source/RevitLookup.UI.Playground/Styles/ComponentStyles/MembersGrid/DataGridRowStyleSelector.cs b/source/RevitLookup.UI.Playground/Styles/ComponentStyles/MembersGrid/DataGridRowStyleSelector.cs index ac8e879f..815a6383 100644 --- a/source/RevitLookup.UI.Playground/Styles/ComponentStyles/MembersGrid/DataGridRowStyleSelector.cs +++ b/source/RevitLookup.UI.Playground/Styles/ComponentStyles/MembersGrid/DataGridRowStyleSelector.cs @@ -1,6 +1,7 @@ using System.Collections; using System.Windows; using System.Windows.Controls; +using LookupEngine.Abstractions.ComponentModel; using LookupEngine.Abstractions.Configuration; using RevitLookup.Abstractions.ObservableModels.Decomposition; @@ -16,20 +17,29 @@ public sealed class DataGridRowStyleSelector : StyleSelector var member = (ObservableDecomposedMember)item; var presenter = (FrameworkElement)container; - var styleName = member.Value.RawValue switch - { - Exception => "ExceptionDataGridRowStyle", - ICollection { Count: > 0 } => "HandledDataGridRowStyle", - _ => null - } - ?? - member.Value.Descriptor switch - { - IDescriptorEnumerator { IsEmpty: false } => "HandleDataGridRowStyle", - IDescriptorCollector => "HandledDataGridRowStyle", - _ => "DefaultLookupDataGridRowStyle" - }; + var styleName = SelectByType(member.Value.RawValue) ?? + SelectByDescriptor(member.Value.Descriptor); return (Style)presenter.FindResource(styleName); } + + private static string? SelectByType(object? value) + { + return value switch + { + Exception => "ExceptionDataGridRowStyle", + ICollection { Count: > 0 } => "HandledDataGridRowStyle", + _ => null + }; + } + + private static string SelectByDescriptor(Descriptor? descriptor) + { + return descriptor switch + { + IDescriptorEnumerator { IsEmpty: false } => "HandleDataGridRowStyle", + IDescriptorCollector => "HandledDataGridRowStyle", + _ => "DefaultLookupDataGridRowStyle" + }; + } } \ No newline at end of file From 37cbf876ad8a473a9315c5c2d8e3a65a6de11f3b Mon Sep 17 00:00:00 2001 From: Nice3point Date: Mon, 9 Dec 2024 21:47:29 +0300 Subject: [PATCH 033/121] Lookup api support, rewrite snoop api --- .../ComponentModel/EnumerableDescriptor.cs | 43 +++ .../ComponentModel/StringDescriptor.cs | 9 + .../Metadata/DecomposedObject.cs | 2 +- .../Engine/LookupComposer.Decompose.cs | 68 ++++- .../Engine/LookupComposer.Diagnostic.cs | 6 +- .../Engine/LookupComposer.Enumeration.cs | 2 +- .../Engine/LookupComposer.WriteHelpers.cs | 39 +-- source/LookupEngine/Engine/LookupComposer.cs | 52 +++- .../Formaters/ReflexionFormater.cs | 8 - .../LookupEngine/Options/DecomposeOptions.cs | 9 +- .../Summary/KnownDecompositionObject.cs | 22 ++ .../ObservableDecomposedObject.cs | 11 +- .../Services/IRevitLookupUiService.cs | 32 ++ .../Services/IVisualDecompositionService.cs | 14 + .../Summary/ISnoopSummaryViewModel.cs | 23 +- .../WidthToUniformColumnsConverter.cs | 2 +- .../Views/AboutProgram/OpenSourceDialog.xaml | 1 + .../Views/Summary/SnoopSummaryPage.xaml | 5 +- .../Summary/SummaryViewBase.ContextMenu.cs | 30 +- .../Views/Summary/SummaryViewBase.Gestures.cs | 11 +- .../Views/Summary/SummaryViewBase.Internal.cs | 20 +- .../Summary/SummaryViewBase.Navigation.cs | 85 +++--- .../Views/Summary/SummaryViewBase.ToolTips.cs | 4 +- .../Views/Summary/SummaryViewBase.xaml.cs | 6 +- .../Views/Tools/ModulesDialog.xaml | 1 + .../Views/Tools/SearchElementsDialog.xaml.cs | 25 +- .../Views/Tools/UnitsDialog.xaml | 1 + .../Client/Controls/PageViewer.xaml.cs | 6 + .../Client/ViewModels/Pages/PagesViewModel.cs | 15 + .../ViewModels/Pages/WindowsViewModel.cs | 63 +++- .../Client/Views/Pages/WindowsPage.xaml | 15 + source/RevitLookup.UI.Playground/Host.cs | 2 + .../Mappers/DecompositionResultMapper.cs | 3 +- .../Services/MockRevitLookupUiService.cs | 115 +++++++ .../MockVisualDecompositionService.cs | 105 +++++++ .../Services/RevitLookupUiService.cs | 284 ++++++++++++++++++ .../MembersGrid/DataGridRowStyleSelector.cs | 7 +- .../Dashboard/MockDashboardViewModel.cs | 133 +++++++- .../Summary/MockSnoopSummaryViewModel.cs | 146 +++++---- .../ViewModels/Tools/MockModulesViewModel.cs | 42 +-- .../Tools/MockSearchElementsViewModel.cs | 14 +- 41 files changed, 1228 insertions(+), 253 deletions(-) create mode 100644 source/LookupEngine.Abstractions/ComponentModel/EnumerableDescriptor.cs create mode 100644 source/LookupEngine.Abstractions/ComponentModel/StringDescriptor.cs create mode 100644 source/RevitLookup.Abstractions/Models/Summary/KnownDecompositionObject.cs create mode 100644 source/RevitLookup.Abstractions/Services/IRevitLookupUiService.cs create mode 100644 source/RevitLookup.Abstractions/Services/IVisualDecompositionService.cs create mode 100644 source/RevitLookup.UI.Playground/Services/MockRevitLookupUiService.cs create mode 100644 source/RevitLookup.UI.Playground/Services/MockVisualDecompositionService.cs create mode 100644 source/RevitLookup.UI.Playground/Services/RevitLookupUiService.cs diff --git a/source/LookupEngine.Abstractions/ComponentModel/EnumerableDescriptor.cs b/source/LookupEngine.Abstractions/ComponentModel/EnumerableDescriptor.cs new file mode 100644 index 00000000..586b5833 --- /dev/null +++ b/source/LookupEngine.Abstractions/ComponentModel/EnumerableDescriptor.cs @@ -0,0 +1,43 @@ +using System.Collections; +using System.Reflection; +using LookupEngine.Abstractions.Collections; +using LookupEngine.Abstractions.Configuration; + +namespace LookupEngine.Abstractions.ComponentModel; + +public sealed class EnumerableDescriptor : Descriptor, IDescriptorEnumerator, IDescriptorResolver +{ + public EnumerableDescriptor(IEnumerable value) + { + Enumerator = value.GetEnumerator(); + + //Checking types to reduce memory allocation when creating an iterator and increase performance + IsEmpty = value switch + { + ICollection enumerable => enumerable.Count == 0, + _ => !Enumerator.MoveNext() + }; + + if (Enumerator is IDisposable disposable) + { + disposable.Dispose(); + } + } + + public IEnumerator Enumerator { get; } + public bool IsEmpty { get; } + + public Func? Resolve(string target, ParameterInfo[]? parameters) + { + return target switch + { + nameof(IEnumerable.GetEnumerator) => ResolveGetEnumerator, + _ => null + }; + + IVariants ResolveGetEnumerator() + { + return Variants.Empty(); + } + } +} \ No newline at end of file diff --git a/source/LookupEngine.Abstractions/ComponentModel/StringDescriptor.cs b/source/LookupEngine.Abstractions/ComponentModel/StringDescriptor.cs new file mode 100644 index 00000000..f2dce8b7 --- /dev/null +++ b/source/LookupEngine.Abstractions/ComponentModel/StringDescriptor.cs @@ -0,0 +1,9 @@ +namespace LookupEngine.Abstractions.ComponentModel; + +public sealed class StringDescriptor : Descriptor +{ + public StringDescriptor(string text) + { + Name = text; + } +} \ No newline at end of file diff --git a/source/LookupEngine.Abstractions/Metadata/DecomposedObject.cs b/source/LookupEngine.Abstractions/Metadata/DecomposedObject.cs index cb752dad..72d7f37a 100644 --- a/source/LookupEngine.Abstractions/Metadata/DecomposedObject.cs +++ b/source/LookupEngine.Abstractions/Metadata/DecomposedObject.cs @@ -13,7 +13,7 @@ public sealed class DecomposedObject public required string Name { get; init; } public required string TypeName { get; set; } public required string TypeFullName { get; set; } - public required List Members { get; init; } + public List Members { get; } = []; public string? Description { get; set; } public Descriptor? Descriptor { get; init; } } \ No newline at end of file diff --git a/source/LookupEngine/Engine/LookupComposer.Decompose.cs b/source/LookupEngine/Engine/LookupComposer.Decompose.cs index e1e333c2..3fd51484 100644 --- a/source/LookupEngine/Engine/LookupComposer.Decompose.cs +++ b/source/LookupEngine/Engine/LookupComposer.Decompose.cs @@ -18,6 +18,7 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. +using System.Diagnostics.Contracts; using System.Reflection; using LookupEngine.Abstractions; @@ -26,21 +27,51 @@ namespace LookupEngine; public sealed partial class LookupComposer { - private DecomposedObject DecomposeInstanceObject(object instance) + [Pure] + private DecomposedObject DecomposeInstance(object instance, bool decomposeMembers) { var objectType = instance.GetType(); - var objectTypeHierarchy = GetTypeHierarchy(objectType); var instanceDescriptor = _options.TypeResolver.Invoke(instance, null); _decomposedObject = CreateInstanceDecomposition(instance, objectType, instanceDescriptor); + if (decomposeMembers) + { + var members = DecomposeInstanceMembers(instance, objectType); + _decomposedObject.Members.AddRange(members); + } + + return _decomposedObject; + } + + [Pure] + private DecomposedObject DecomposeType(Type type, bool decomposeMembers) + { + var staticDescriptor = _options.TypeResolver.Invoke(null, type); + _decomposedObject = CreateStaticDecomposition(type, staticDescriptor); + + if (decomposeMembers) + { + var members = DecomposeTypeMembers(type); + _decomposedObject.Members.AddRange(members); + } + + return _decomposedObject; + } + + [Pure] + private List DecomposeInstanceMembers(object instance, Type objectType) + { + _decomposedMembers = new List(32); + + var objectTypeHierarchy = GetTypeHierarchy(objectType); for (var i = objectTypeHierarchy.Count - 1; i >= 0; i--) { DeclaringType = objectTypeHierarchy[i]; DeclaringDescriptor = _options.TypeResolver.Invoke(instance, DeclaringType); var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly; - if (!_options.IgnoreStaticMembers) flags |= BindingFlags.Static; - if (!_options.IgnorePrivateMembers) flags |= BindingFlags.NonPublic; + if (_options.IncludeStaticMembers) flags |= BindingFlags.Static; + if (_options.IncludePrivateMembers) flags |= BindingFlags.NonPublic; DecomposeFields(flags); DecomposeProperties(flags); @@ -54,27 +85,34 @@ private DecomposedObject DecomposeInstanceObject(object instance) DeclaringType = objectType; AddEnumerableItems(); - return _decomposedObject; + return _decomposedMembers; } - private DecomposedObject DecomposeStaticObject(Type objectType) + [Pure] + private List DecomposeTypeMembers(Type type) { + _decomposedMembers = new List(32); + var flags = BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly; - if (!_options.IgnorePrivateMembers) flags |= BindingFlags.NonPublic; + if (_options.IncludePrivateMembers) flags |= BindingFlags.NonPublic; - var staticDescriptor = _options.TypeResolver.Invoke(null, objectType); - _decomposedObject = CreateStaticDecomposition(objectType, staticDescriptor); + var objectTypeHierarchy = GetTypeHierarchy(type); + for (var i = objectTypeHierarchy.Count - 1; i >= 0; i--) + { + DeclaringType = objectTypeHierarchy[i]; + DeclaringDescriptor = _options.TypeResolver.Invoke(null, DeclaringType); - DeclaringType = objectType; - DeclaringDescriptor = staticDescriptor; + DecomposeFields(flags); + DecomposeProperties(flags); + DecomposeMethods(flags); - DecomposeFields(flags); - DecomposeProperties(flags); - DecomposeMethods(flags); + _depth--; + } - return _decomposedObject; + return _decomposedMembers; } + [Pure] private List GetTypeHierarchy(Type inputType) { var types = new List(); diff --git a/source/LookupEngine/Engine/LookupComposer.Diagnostic.cs b/source/LookupEngine/Engine/LookupComposer.Diagnostic.cs index 91918b28..b2f92dd0 100644 --- a/source/LookupEngine/Engine/LookupComposer.Diagnostic.cs +++ b/source/LookupEngine/Engine/LookupComposer.Diagnostic.cs @@ -35,7 +35,7 @@ public sealed partial class LookupComposer _timeDiagnoser.StartMonitoring(); _memoryDiagnoser.StartMonitoring(); - var value = member.GetValue(DecomposedObject.RawValue); + var value = member.GetValue(_input); _memoryDiagnoser.StopMonitoring(); _timeDiagnoser.StopMonitoring(); @@ -50,7 +50,7 @@ public sealed partial class LookupComposer _timeDiagnoser.StartMonitoring(); _memoryDiagnoser.StartMonitoring(); - return member.GetValue(DecomposedObject.RawValue); + return member.GetValue(_input); } finally { @@ -66,7 +66,7 @@ public sealed partial class LookupComposer _timeDiagnoser.StartMonitoring(); _memoryDiagnoser.StartMonitoring(); - return member.Invoke(DecomposedObject.RawValue, null); + return member.Invoke(_input, null); } finally { diff --git a/source/LookupEngine/Engine/LookupComposer.Enumeration.cs b/source/LookupEngine/Engine/LookupComposer.Enumeration.cs index 48becf28..06078e49 100644 --- a/source/LookupEngine/Engine/LookupComposer.Enumeration.cs +++ b/source/LookupEngine/Engine/LookupComposer.Enumeration.cs @@ -27,7 +27,7 @@ public sealed partial class LookupComposer { private void AddEnumerableItems() { - if (DecomposedObject.RawValue is not IEnumerable enumerable) return; + if (_input is not IEnumerable enumerable) return; var enumerator = enumerable.GetEnumerator(); diff --git a/source/LookupEngine/Engine/LookupComposer.WriteHelpers.cs b/source/LookupEngine/Engine/LookupComposer.WriteHelpers.cs index a7fbfe6d..5f56ca38 100644 --- a/source/LookupEngine/Engine/LookupComposer.WriteHelpers.cs +++ b/source/LookupEngine/Engine/LookupComposer.WriteHelpers.cs @@ -36,7 +36,6 @@ private static DecomposedObject CreateNullableDecomposition() { Name = $"{nameof(System)}.{nameof(Object)}", RawValue = null, - Members = [], TypeName = nameof(Object), TypeFullName = $"{nameof(System)}.{nameof(Object)}" }; @@ -48,12 +47,11 @@ private static DecomposedObject CreateInstanceDecomposition(object instance, Typ return new DecomposedObject { - Name = descriptor.Name ?? formatTypeName, + Name = string.IsNullOrEmpty(descriptor.Name) ? formatTypeName : descriptor.Name!, RawValue = instance, TypeName = formatTypeName, - TypeFullName = ReflexionFormater.FormatTypeFullName(type), - Descriptor = descriptor, - Members = new List(32) + TypeFullName = $"{type.Namespace}.{formatTypeName}", + Descriptor = descriptor }; } @@ -63,12 +61,11 @@ private static DecomposedObject CreateStaticDecomposition(Type type, Descriptor return new DecomposedObject { - Name = descriptor.Name ?? formatTypeName, + Name = string.IsNullOrEmpty(descriptor.Name) ? formatTypeName : descriptor.Name!, RawValue = type, TypeName = formatTypeName, - TypeFullName = ReflexionFormater.FormatTypeFullName(type), - Descriptor = descriptor, - Members = new List(32) + TypeFullName = $"{type.Namespace}.{formatTypeName}", + Descriptor = descriptor }; } @@ -78,13 +75,13 @@ private void WriteEnumerableMember(object? value, int index) { Depth = _depth, Value = CreateValue(nameof(IEnumerable), value), - Name = $"{DeclaringType.Name}[{index}]", + Name = $"{DeclaringType.Name.Replace("[]", string.Empty)}[{index}]", MemberAttributes = MemberAttributes.Property, DeclaringTypeName = nameof(IEnumerable), DeclaringTypeFullName = $"{nameof(System)}.{nameof(System.Collections)}.{nameof(IEnumerable)}", }; - DecomposedObject.Members.Add(member); + DecomposedMembers.Add(member); } private DecomposedValue CreateNullableValue() @@ -109,44 +106,48 @@ private DecomposedValue CreateValue(string targetMember, object? value) return new DecomposedValue { RawValue = value, - Name = valueDescriptor.Name ?? formatTypeName, + Name = string.IsNullOrEmpty(valueDescriptor.Name) ? formatTypeName : valueDescriptor.Name!, TypeName = formatTypeName, - TypeFullName = ReflexionFormater.FormatTypeFullName(valueType), + TypeFullName = $"{valueType.Namespace}.{formatTypeName}", Descriptor = valueDescriptor }; } private void WriteExtensionMember(object? value, string name) { + var formatTypeName = ReflexionFormater.FormatTypeName(DeclaringType); + var member = new DecomposedMember { Depth = _depth, Name = name, Value = CreateValue(name, value), - DeclaringTypeName = ReflexionFormater.FormatTypeName(DeclaringType), - DeclaringTypeFullName = ReflexionFormater.FormatTypeFullName(DeclaringType), + DeclaringTypeName = formatTypeName, + DeclaringTypeFullName = $"{DeclaringType.Namespace}.{formatTypeName}", MemberAttributes = MemberAttributes.Extension, ComputationTime = _timeDiagnoser.GetElapsed().TotalMilliseconds, AllocatedBytes = _memoryDiagnoser.GetAllocatedBytes() }; - DecomposedObject.Members.Add(member); + DecomposedMembers.Add(member); } private void WriteDecompositionMember(object? value, MemberInfo memberInfo, ParameterInfo[]? parameters = null) { + var formatTypeName = ReflexionFormater.FormatTypeName(DeclaringType); + var member = new DecomposedMember { Depth = _depth, Value = CreateValue(memberInfo.Name, value), Name = ReflexionFormater.FormatMemberName(memberInfo, parameters), - DeclaringTypeName = ReflexionFormater.FormatTypeName(DeclaringType), - DeclaringTypeFullName = ReflexionFormater.FormatTypeFullName(DeclaringType), + DeclaringTypeName = formatTypeName, + DeclaringTypeFullName = $"{DeclaringType.Namespace}.{formatTypeName}", MemberAttributes = ModifiersFormater.FormatAttributes(memberInfo), ComputationTime = _timeDiagnoser.GetElapsed().TotalMilliseconds, AllocatedBytes = _memoryDiagnoser.GetAllocatedBytes() }; - DecomposedObject.Members.Add(member); + DecomposedMembers.Add(member); } } \ No newline at end of file diff --git a/source/LookupEngine/Engine/LookupComposer.cs b/source/LookupEngine/Engine/LookupComposer.cs index d20ae226..9b5439b3 100644 --- a/source/LookupEngine/Engine/LookupComposer.cs +++ b/source/LookupEngine/Engine/LookupComposer.cs @@ -29,30 +29,33 @@ namespace LookupEngine; [PublicAPI] public sealed partial class LookupComposer { + private readonly object? _input; private readonly DecomposeOptions _options; private int _depth; private Type? _declaringType; private Descriptor? _declaringDescriptor; private DecomposedObject? _decomposedObject; + private List? _decomposedMembers; - private LookupComposer(DecomposeOptions options) + private LookupComposer(object value, DecomposeOptions options) { + _input = value; _options = options; } - internal DecomposedObject DecomposedObject + internal List DecomposedMembers { get { - if (_decomposedObject is null) + if (_decomposedMembers is null) { - EngineException.ThrowIfEngineNotInitialized(nameof(DecomposedObject)); + EngineException.ThrowIfEngineNotInitialized(nameof(DecomposedMembers)); } - return _decomposedObject; + return _decomposedMembers; } - set => _decomposedObject = value; + set => _decomposedMembers = value; } internal Type DeclaringType @@ -89,12 +92,43 @@ public static DecomposedObject Decompose(object? value, DecomposeOptions? option if (value is null) return CreateNullableDecomposition(); options ??= DecomposeOptions.Default; - var composer = new LookupComposer(options); + var composer = new LookupComposer(value, options); return value switch { - Type staticObjectType => composer.DecomposeStaticObject(staticObjectType), - _ => composer.DecomposeInstanceObject(value) + Type type => composer.DecomposeType(type, true), + _ => composer.DecomposeInstance(value, true) + }; + } + + + [Pure] + public static DecomposedObject DecomposeObject(object? value, DecomposeOptions? options = null) + { + if (value is null) return CreateNullableDecomposition(); + + options ??= DecomposeOptions.Default; + var composer = new LookupComposer(value, options); + + return value switch + { + Type type => composer.DecomposeType(type, false), + _ => composer.DecomposeInstance(value, false) + }; + } + + [Pure] + public static List DecomposeMembers(object? value, DecomposeOptions? options = null) + { + if (value is null) return []; + + options ??= DecomposeOptions.Default; + var composer = new LookupComposer(value, options); + + return value switch + { + Type type => composer.DecomposeTypeMembers(type), + _ => composer.DecomposeInstanceMembers(value, value.GetType()) }; } } \ No newline at end of file diff --git a/source/LookupEngine/Formaters/ReflexionFormater.cs b/source/LookupEngine/Formaters/ReflexionFormater.cs index 03682b47..fba309c4 100644 --- a/source/LookupEngine/Formaters/ReflexionFormater.cs +++ b/source/LookupEngine/Formaters/ReflexionFormater.cs @@ -23,14 +23,6 @@ public static string FormatTypeName(Type type) return typeName; } - public static string FormatTypeFullName(Type type) - { - var fullName = type.FullName; - if (fullName is null) return string.Empty; - - return type.IsGenericType ? fullName[..fullName.IndexOf('[')] : fullName; - } - public static string FormatMemberName(MemberInfo member, ParameterInfo[]? parameters) { if (parameters is null) return member.Name; diff --git a/source/LookupEngine/Options/DecomposeOptions.cs b/source/LookupEngine/Options/DecomposeOptions.cs index ac94a116..4fb8ea9c 100644 --- a/source/LookupEngine/Options/DecomposeOptions.cs +++ b/source/LookupEngine/Options/DecomposeOptions.cs @@ -1,4 +1,5 @@ -using JetBrains.Annotations; +using System.Collections; +using JetBrains.Annotations; using LookupEngine.Abstractions.ComponentModel; // ReSharper disable once CheckNamespace @@ -19,8 +20,8 @@ public sealed class DecomposeOptions public bool IncludeFields { get; set; } public bool IncludeEvents { get; set; } public bool IncludeUnsupported { get; set; } - public bool IgnorePrivateMembers { get; set; } = true; - public bool IgnoreStaticMembers { get; set; } = true; + public bool IncludePrivateMembers { get; set; } + public bool IncludeStaticMembers { get; set; } public bool EnableExtensions { get; set; } public static DecomposeOptions Default => new(); @@ -30,6 +31,8 @@ private static Descriptor DefaultResolveMap(object? obj, Type? type) return obj switch { bool value when type is null || type == typeof(bool) => new BooleanDescriptor(value), + string value when type is null || type == typeof(string) => new StringDescriptor(value), + IEnumerable value => new EnumerableDescriptor(value), Exception value when type is null || type == typeof(Exception) => new ExceptionDescriptor(value), _ => new ObjectDescriptor(obj) }; diff --git a/source/RevitLookup.Abstractions/Models/Summary/KnownDecompositionObject.cs b/source/RevitLookup.Abstractions/Models/Summary/KnownDecompositionObject.cs new file mode 100644 index 00000000..d19864c4 --- /dev/null +++ b/source/RevitLookup.Abstractions/Models/Summary/KnownDecompositionObject.cs @@ -0,0 +1,22 @@ +namespace RevitLookup.Abstractions.Models.Summary; + +public enum KnownDecompositionObject +{ + View, + Document, + Application, + UiApplication, + Database, + DependentElements, + Selection, + Face, + Edge, + Point, + SubElement, + LinkedElement, + ComponentManager, + PerformanceAdviser, + UpdaterRegistry, + Services, + Schemas +} \ No newline at end of file diff --git a/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedObject.cs b/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedObject.cs index 4aefd2c0..7b59d77a 100644 --- a/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedObject.cs +++ b/source/RevitLookup.Abstractions/ObservableModels/Decomposition/ObservableDecomposedObject.cs @@ -3,13 +3,20 @@ namespace RevitLookup.Abstractions.ObservableModels.Decomposition; -public sealed class ObservableDecomposedObject : ObservableObject +public sealed partial class ObservableDecomposedObject : ObservableObject { + [ObservableProperty] private List _members = []; + [ObservableProperty] private List _filteredMembers = []; + public required object? RawValue { get; init; } public required string Name { get; init; } public required string TypeName { get; set; } public required string TypeFullName { get; set; } - public required List Members { get; init; } public string? Description { get; init; } public Descriptor? Descriptor { get; init; } + + partial void OnMembersChanged(List value) + { + FilteredMembers = value; + } } \ No newline at end of file diff --git a/source/RevitLookup.Abstractions/Services/IRevitLookupUiService.cs b/source/RevitLookup.Abstractions/Services/IRevitLookupUiService.cs new file mode 100644 index 00000000..e372166c --- /dev/null +++ b/source/RevitLookup.Abstractions/Services/IRevitLookupUiService.cs @@ -0,0 +1,32 @@ +using System.Collections; +using System.Windows; +using System.Windows.Controls; +using RevitLookup.Abstractions.Models.Summary; +using RevitLookup.Abstractions.ObservableModels.Decomposition; + +namespace RevitLookup.Abstractions.Services; + +public interface IRevitLookupUiService : ILookupServiceDependsStage, ILookupServiceRunStage +{ + ILookupServiceDependsStage Decompose(KnownDecompositionObject decompositionObject); + ILookupServiceDependsStage Decompose(object? obj); + ILookupServiceDependsStage Decompose(IEnumerable objects); + ILookupServiceDependsStage Decompose(ObservableDecomposedObject decomposedObject); + ILookupServiceDependsStage Decompose(List decomposedObjects); +} + +public interface ILookupServiceDependsStage : ILookupServiceShowStage +{ + ILookupServiceShowStage DependsOn(Window parent); +} + +public interface ILookupServiceShowStage +{ + ILookupServiceRunStage Show() where T : Page; + // ILookupServiceRunStage ShowDialog() where T : Page; +} + +public interface ILookupServiceRunStage +{ + void RunService(Action handler) where T : class; +} \ No newline at end of file diff --git a/source/RevitLookup.Abstractions/Services/IVisualDecompositionService.cs b/source/RevitLookup.Abstractions/Services/IVisualDecompositionService.cs new file mode 100644 index 00000000..7a2d14e1 --- /dev/null +++ b/source/RevitLookup.Abstractions/Services/IVisualDecompositionService.cs @@ -0,0 +1,14 @@ +using System.Collections; +using RevitLookup.Abstractions.Models.Summary; +using RevitLookup.Abstractions.ObservableModels.Decomposition; + +namespace RevitLookup.Abstractions.Services; + +public interface IVisualDecompositionService +{ + Task VisualizeDecompositionAsync(KnownDecompositionObject decompositionObject); + Task VisualizeDecompositionAsync(object? obj); + Task VisualizeDecompositionAsync(IEnumerable objects); + Task VisualizeDecompositionAsync(ObservableDecomposedObject decomposedObject); + Task VisualizeDecompositionAsync(List decomposedObjects); +} \ No newline at end of file diff --git a/source/RevitLookup.Abstractions/ViewModels/Summary/ISnoopSummaryViewModel.cs b/source/RevitLookup.Abstractions/ViewModels/Summary/ISnoopSummaryViewModel.cs index 2ccbe0ca..15f2b550 100644 --- a/source/RevitLookup.Abstractions/ViewModels/Summary/ISnoopSummaryViewModel.cs +++ b/source/RevitLookup.Abstractions/ViewModels/Summary/ISnoopSummaryViewModel.cs @@ -1,21 +1,24 @@ -using RevitLookup.Abstractions.ObservableModels.Decomposition; +using CommunityToolkit.Mvvm.Input; +using RevitLookup.Abstractions.ObservableModels.Decomposition; namespace RevitLookup.Abstractions.ViewModels.Summary; public interface ISnoopSummaryViewModel { + string SearchText { get; set; } + + //Objects ObservableDecomposedObject? SelectedDecomposedObject { get; set; } - List DecomposedObjects { get; } + List DecomposedObjects { get; set; } List FilteredDecomposedObjects { get; } - List Members { get; set; } - List FilteredMembers { get; } + //Commands + IAsyncRelayCommand RefreshMembersCommand { get; } + + //Navigation + void Navigate(object? value); + void Navigate(ObservableDecomposedObject value); + void Navigate(List values); - // IAsyncRelayCommand FetchMembersCommand { get; } - // IAsyncRelayCommand RefreshMembersCommand { get; } - string SearchText { get; set; } - // public IServiceProvider ServiceProvider { get; } - // void Navigate(SnoopableObject selectedItem); - // void Navigate(IList selectedItems); // void RemoveObject(object obj); } \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Converters/ValueConverters/WidthToUniformColumnsConverter.cs b/source/RevitLookup.UI.Framework/Converters/ValueConverters/WidthToUniformColumnsConverter.cs index 15889421..33c2e85b 100644 --- a/source/RevitLookup.UI.Framework/Converters/ValueConverters/WidthToUniformColumnsConverter.cs +++ b/source/RevitLookup.UI.Framework/Converters/ValueConverters/WidthToUniformColumnsConverter.cs @@ -9,7 +9,7 @@ public sealed class WidthToUniformColumnsConverter : MarkupExtension, IValueConv public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { var width = (double)value!; - var columns = (int)Math.Floor(width / 600d); + var columns = (int)Math.Floor(width / 470d); return columns > 0 ? columns : 1; } diff --git a/source/RevitLookup.UI.Framework/Views/AboutProgram/OpenSourceDialog.xaml b/source/RevitLookup.UI.Framework/Views/AboutProgram/OpenSourceDialog.xaml index 284ec1eb..ab419fbe 100644 --- a/source/RevitLookup.UI.Framework/Views/AboutProgram/OpenSourceDialog.xaml +++ b/source/RevitLookup.UI.Framework/Views/AboutProgram/OpenSourceDialog.xaml @@ -35,6 +35,7 @@ CanUserResizeColumns="False" CanUserSortColumns="False" VerticalScrollBarVisibility="Visible" + ScrollViewer.VerticalScrollBarVisibility="Visible" ItemsSource="{Binding Software}"> + VirtualizingPanel.IsVirtualizing="True"> + .SetCommand(_settingsService.GeneralSettings, async parameter => { parameter.IncludeEvents = !parameter.IncludeEvents; - // return ViewModel.RefreshMembersCommand.ExecuteAsync(null); + await ViewModel.RefreshMembersCommand.ExecuteAsync(null); }); contextMenu.AddMenuItem() .SetHeader("Extensions") .SetStaysOpenOnClick(true) .SetChecked(_settingsService.GeneralSettings.IncludeExtensions) - .SetCommand(_settingsService.GeneralSettings, parameter => + .SetCommand(_settingsService.GeneralSettings, async parameter => { parameter.IncludeExtensions = !parameter.IncludeExtensions; - // return ViewModel.RefreshMembersCommand.ExecuteAsync(null); + await ViewModel.RefreshMembersCommand.ExecuteAsync(null); }); contextMenu.AddMenuItem() .SetHeader("Fields") .SetStaysOpenOnClick(true) .SetChecked(_settingsService.GeneralSettings.IncludeFields) - .SetCommand(_settingsService.GeneralSettings, parameter => + .SetCommand(_settingsService.GeneralSettings, async parameter => { parameter.IncludeFields = !parameter.IncludeFields; - // return ViewModel.RefreshMembersCommand.ExecuteAsync(null); + await ViewModel.RefreshMembersCommand.ExecuteAsync(null); }); contextMenu.AddMenuItem() .SetHeader("Non-public") .SetStaysOpenOnClick(true) .SetChecked(_settingsService.GeneralSettings.IncludePrivate) - .SetCommand(_settingsService.GeneralSettings, parameter => + .SetCommand(_settingsService.GeneralSettings, async parameter => { parameter.IncludePrivate = !parameter.IncludePrivate; - // return ViewModel.RefreshMembersCommand.ExecuteAsync(null); + await ViewModel.RefreshMembersCommand.ExecuteAsync(null); }); contextMenu.AddMenuItem() .SetHeader("Root") .SetStaysOpenOnClick(true) .SetChecked(_settingsService.GeneralSettings.IncludeRootHierarchy) - .SetCommand(_settingsService.GeneralSettings, parameter => + .SetCommand(_settingsService.GeneralSettings, async parameter => { parameter.IncludeRootHierarchy = !parameter.IncludeRootHierarchy; - // return ViewModel.RefreshMembersCommand.ExecuteAsync(null); + await ViewModel.RefreshMembersCommand.ExecuteAsync(null); }); contextMenu.AddMenuItem() .SetHeader("Static") .SetStaysOpenOnClick(true) .SetChecked(_settingsService.GeneralSettings.IncludeStatic) - .SetCommand(_settingsService.GeneralSettings, parameter => + .SetCommand(_settingsService.GeneralSettings, async parameter => { parameter.IncludeStatic = !parameter.IncludeStatic; - // return ViewModel.RefreshMembersCommand.ExecuteAsync(null); + await ViewModel.RefreshMembersCommand.ExecuteAsync(null); }); contextMenu.AddMenuItem() .SetHeader("Unsupported") .SetStaysOpenOnClick(true) .SetChecked(_settingsService.GeneralSettings.IncludeUnsupported) - .SetCommand(_settingsService.GeneralSettings, parameter => + .SetCommand(_settingsService.GeneralSettings, async parameter => { parameter.IncludeUnsupported = !parameter.IncludeUnsupported; - // return ViewModel.RefreshMembersCommand.ExecuteAsync(null); + await ViewModel.RefreshMembersCommand.ExecuteAsync(null); }); } diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Gestures.cs b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Gestures.cs index 58dab2eb..89e57d9c 100644 --- a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Gestures.cs +++ b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Gestures.cs @@ -19,6 +19,7 @@ // (Rights in Technical Data and Computer Software), as applicable. using System.Windows.Input; +using CommunityToolkit.Mvvm.Input; using RevitLookup.UI.Framework.Views.Windows; using Wpf.Ui.Abstractions.Controls; @@ -26,6 +27,9 @@ namespace RevitLookup.UI.Framework.Views.Summary; public partial class SummaryViewBase : INavigationAware { + /// + /// Callback when navigating to the current page + /// public Task OnNavigatedToAsync() { var host = _intercomService.GetHost(); @@ -33,6 +37,9 @@ public Task OnNavigatedToAsync() return Task.CompletedTask; } + /// + /// Callback when navigating from this page + /// public Task OnNavigatedFromAsync() { var host = _intercomService.GetHost(); @@ -45,8 +52,8 @@ public Task OnNavigatedFromAsync() /// private void AddShortcuts() { - // var command = new AsyncRelayCommand(() => ViewModel.RefreshMembersCommand.ExecuteAsync(null)); - // InputBindings.Add(new KeyBinding(command, new KeyGesture(Key.F5))); + var command = new AsyncRelayCommand(() => ViewModel.RefreshMembersCommand.ExecuteAsync(null)); + InputBindings.Add(new KeyBinding(command, new KeyGesture(Key.F5))); } /// diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Internal.cs b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Internal.cs index a1e177f9..43898755 100644 --- a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Internal.cs +++ b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Internal.cs @@ -29,16 +29,20 @@ namespace RevitLookup.UI.Framework.Views.Summary; public partial class SummaryViewBase { private static readonly FieldInfo InternalGridScrollHostField = - typeof(System.Windows.Controls.DataGrid).GetField("_internalScrollHost", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly)!; + typeof(System.Windows.Controls.DataGrid).GetField("_internalScrollHost", + BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly)!; private static readonly PropertyInfo InternalGridColumnsProperty = - typeof(System.Windows.Controls.DataGrid).GetProperty("InternalColumns", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly)!; + typeof(System.Windows.Controls.DataGrid).GetProperty("InternalColumns", + BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly)!; private static readonly MethodInfo InternalGridInvalidateColumnWidthsComputationMethod = - InternalGridColumnsProperty.PropertyType.GetMethod("InvalidateColumnWidthsComputation", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)!; + InternalGridColumnsProperty.PropertyType.GetMethod("InvalidateColumnWidthsComputation", + BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)!; private static readonly MethodInfo InternalGridOnViewportSizeChangedMethod = - typeof(System.Windows.Controls.DataGrid).GetMethod("OnViewportSizeChanged", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)!; + typeof(System.Windows.Controls.DataGrid).GetMethod("OnViewportSizeChanged", + BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)!; /// /// By default, WPF calculates the column width after adding items to the ItemSource. This fix calculates it on loading @@ -49,7 +53,13 @@ public partial class SummaryViewBase private static void FixInitialGridColumnSize(object sender, RoutedEventArgs args) { var dataGrid = (DataGrid)sender; - var passiveScrollViewer = dataGrid.FindVisualChild()!; + var passiveScrollViewer = dataGrid.FindVisualChild(); + if (passiveScrollViewer is null) + { + dataGrid.ApplyTemplate(); + passiveScrollViewer = dataGrid.FindVisualChild()!; + } + var gridColumns = InternalGridColumnsProperty.GetValue(dataGrid); InternalGridScrollHostField.SetValue(dataGrid, passiveScrollViewer); InternalGridInvalidateColumnWidthsComputationMethod.Invoke(gridColumns, null); diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Navigation.cs b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Navigation.cs index de5b902e..e370e7f0 100644 --- a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Navigation.cs +++ b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Navigation.cs @@ -21,6 +21,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Input; +using LookupEngine.Abstractions.Configuration; using RevitLookup.Abstractions.ObservableModels.Decomposition; using RevitLookup.UI.Framework.Utils; @@ -47,51 +48,50 @@ private void OnTreeItemSelected(object sender, RoutedPropertyChangedEventArgs + /// Handle tree view click event + /// + /// + /// Navigate on Ctrl pressed + /// + private void OnTreeItemClicked(object sender, RoutedEventArgs args) + { + if ((Keyboard.Modifiers & ModifierKeys.Control) == 0) return; + args.Handled = true; - // ViewModel.FetchMembersCommand.Execute(null); + var element = (FrameworkElement)args.OriginalSource; + switch (element.DataContext) + { + case ObservableDecomposedObject item: + ViewModel.Navigate(item); + break; + case ObservableDecomposedObjectsGroup group: + ViewModel.Navigate(group.GroupItems); + break; + } } - // /// - // /// Handle tree view click event - // /// - // /// - // /// Open new window for navigation on Ctrl pressed - // /// - // private void OnTreeItemClicked(object sender, RoutedEventArgs args) - // { - // if ((Keyboard.Modifiers & ModifierKeys.Control) == 0) return; - // args.Handled = true; - // - // var element = (FrameworkElement) args.OriginalSource; - // switch (element.DataContext) - // { - // case SnoopableObject item: - // ViewModel.Navigate(item); - // break; - // case CollectionViewGroup group: - // ViewModel.Navigate(group.Items.Cast().ToArray()); - // break; - // } - // } + /// + /// Handle data grid click event + /// + /// + /// Navigate on row clicked + /// + private void OnGridRowClicked(object sender, RoutedEventArgs args) + { + var row = (DataGridRow)sender; + if (row.DataContext is not ObservableDecomposedMember context) return; - // /// - // /// Handle data grid click event - // /// - // /// - // /// Open new window for navigation - // /// - // private void OnGridRowClicked(object sender, RoutedEventArgs args) - // { - // var row = (DataGridRow) sender; - // var context = (Descriptor) row.DataContext; - // if ((Keyboard.Modifiers & ModifierKeys.Control) == 0) - // { - // if (context.Value.Descriptor is not IDescriptorCollector) return; - // if (context.Value.Descriptor is IDescriptorEnumerator {IsEmpty: true}) return; - // } - // - // ViewModel.Navigate(context.Value); - // } + if ((Keyboard.Modifiers & ModifierKeys.Control) == 0) + { + if (context.Value.Descriptor is not IDescriptorCollector) return; + if (context.Value.Descriptor is IDescriptorEnumerator { IsEmpty: true }) return; + } + + ViewModel.Navigate(context.Value.RawValue); + } /// /// Handle cursor interaction @@ -122,6 +122,9 @@ private static void OnPresenterCursorInteracted(object sender, MouseEventArgs ar presenter.PreviewKeyUp += OnPresenterCursorRestored; } + /// + /// Restore cursor + /// private static void OnPresenterCursorRestored(object sender, KeyEventArgs e) { var presenter = (FrameworkElement)sender; diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ToolTips.cs b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ToolTips.cs index 301a7ba1..ed5bdc8b 100644 --- a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ToolTips.cs +++ b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.ToolTips.cs @@ -40,9 +40,7 @@ private static void CreateTreeTooltip(ObservableDecomposedObject decomposedObjec .Append("Type: ") .AppendLine(decomposedObject.TypeName) .Append("Full type: ") - .AppendLine(decomposedObject.TypeFullName) - .Append("Members: ") - .Append(decomposedObject.Members.Count) + .Append(decomposedObject.TypeFullName) .ToString(); } diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs index a141c839..f3bb44c6 100644 --- a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs +++ b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs @@ -152,10 +152,10 @@ private void OnTreeViewItemGenerated(object? sender, EventArgs _) if (treeItem is null) continue; treeItem.MouseEnter -= OnTreeItemCaptured; - // treeItem.PreviewMouseLeftButtonUp -= OnTreeItemClicked; + treeItem.PreviewMouseLeftButtonUp -= OnTreeItemClicked; treeItem.MouseEnter += OnTreeItemCaptured; - // treeItem.PreviewMouseLeftButtonUp += OnTreeItemClicked; + treeItem.PreviewMouseLeftButtonUp += OnTreeItemClicked; if (treeItem.Items.Count > 0) { @@ -233,7 +233,7 @@ private void OnGridRowLoading(object? sender, DataGridRowEventArgs args) { var row = args.Row; row.MouseEnter += OnGridRowCaptured; - // row.PreviewMouseLeftButtonUp += OnGridRowClicked; + row.PreviewMouseLeftButtonUp += OnGridRowClicked; } /// diff --git a/source/RevitLookup.UI.Framework/Views/Tools/ModulesDialog.xaml b/source/RevitLookup.UI.Framework/Views/Tools/ModulesDialog.xaml index d2a6ec9f..1a2f90cf 100644 --- a/source/RevitLookup.UI.Framework/Views/Tools/ModulesDialog.xaml +++ b/source/RevitLookup.UI.Framework/Views/Tools/ModulesDialog.xaml @@ -43,6 +43,7 @@ CanUserResizeColumns="True" CanUserSortColumns="True" MinColumnWidth="100" + ScrollViewer.VerticalScrollBarVisibility="Visible" ItemsSource="{Binding FilteredModules}"> (); - // notificationService.ShowWarning("Search elements", "There are no elements found for your request"); - // return; - // } - // - // _serviceProvider.GetRequiredService().Snoop(new SnoopableObject(elements)); - // _serviceProvider.GetRequiredService().Navigate(typeof(SnoopPage)); - // } } \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/Tools/UnitsDialog.xaml b/source/RevitLookup.UI.Framework/Views/Tools/UnitsDialog.xaml index 69353fd1..ac4ccde9 100644 --- a/source/RevitLookup.UI.Framework/Views/Tools/UnitsDialog.xaml +++ b/source/RevitLookup.UI.Framework/Views/Tools/UnitsDialog.xaml @@ -42,6 +42,7 @@ CanUserResizeColumns="True" CanUserSortColumns="True" MinColumnWidth="100" + ScrollViewer.VerticalScrollBarVisibility="Visible" ItemsSource="{Binding FilteredUnits}"> (Action handler) where T : class + { + var service = _serviceProvider.GetRequiredService(); + handler.Invoke(service); + } + private void OnViewerFrameResized(object sender, SizeChangedEventArgs args) { if (args.PreviousSize.Height == 0 || args.PreviousSize.Width == 0) return; diff --git a/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/PagesViewModel.cs b/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/PagesViewModel.cs index 5a9e851c..82edb969 100644 --- a/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/PagesViewModel.cs +++ b/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/PagesViewModel.cs @@ -1,7 +1,9 @@ using System.Windows; +using Bogus; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using JetBrains.Annotations; +using RevitLookup.Abstractions.Services; using RevitLookup.UI.Framework.Views.AboutProgram; using RevitLookup.UI.Framework.Views.Dashboard; using RevitLookup.UI.Framework.Views.Settings; @@ -29,6 +31,19 @@ private void ShowSnoopSummaryPage() viewer.SizeToContent = SizeToContent.Manual; viewer.Height = 500; viewer.Width = 900; + viewer.RunService(service => + { + var faker = new Faker(); + + var strings = new List(); + for (var i = 0; i < 1000; i++) + { + strings.Add(faker.Lorem.Sentence(300)); + } + + service.VisualizeDecompositionAsync(strings); + }); + viewer.ShowPage(); } diff --git a/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/WindowsViewModel.cs b/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/WindowsViewModel.cs index 38863138..c9aa2b02 100644 --- a/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/WindowsViewModel.cs +++ b/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/WindowsViewModel.cs @@ -1,11 +1,12 @@ -using System.Windows; +using System.Reflection; +using System.Windows.Media; +using Bogus; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using JetBrains.Annotations; -using Microsoft.Extensions.DependencyInjection; +using RevitLookup.Abstractions.Services; using RevitLookup.UI.Framework.Views.Dashboard; -using RevitLookup.UI.Framework.Views.Windows; -using Wpf.Ui; +using RevitLookup.UI.Framework.Views.Summary; namespace RevitLookup.UI.Playground.Client.ViewModels.Pages; @@ -15,15 +16,53 @@ public sealed partial class WindowsViewModel : ObservableObject [RelayCommand] private void ShowRevitLookupWindow() { - var scopeFactory = Host.GetService(); - var scope = scopeFactory.CreateScope(); + Host.GetService() + .Show(); + } + + [RelayCommand] + private void DecomposeColors() + { + var faker = new Faker(); + + var colors = new List(); + for (var i = 0; i < 200; i++) + { + colors.Add(Color.FromArgb( + faker.Random.Byte(), + faker.Random.Byte(), + faker.Random.Byte(), + faker.Random.Byte() + )); + } + + Host.GetService() + .Decompose(colors) + .Show(); + } - var view = scope.ServiceProvider.GetRequiredService(); - var navigationService = scope.ServiceProvider.GetRequiredService(); + [RelayCommand] + private void DecomposeText() + { + var faker = new Faker(); - view.Closed += (_, _) => scope.Dispose(); - view.WindowStartupLocation = WindowStartupLocation.CenterScreen; - view.Show(); - navigationService.Navigate(typeof(DashboardPage)); + var strings = new List(); + for (var i = 0; i < 1000; i++) + { + strings.Add(faker.Lorem.Sentence(69)); + } + + Host.GetService() + .Decompose(strings) + .Show(); + } + + [RelayCommand] + private void DecomposeAssembly() + { + var assembly = Assembly.GetExecutingAssembly(); + Host.GetService() + .Decompose(assembly.GetTypes()) + .Show(); } } \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/Views/Pages/WindowsPage.xaml b/source/RevitLookup.UI.Playground/Client/Views/Pages/WindowsPage.xaml index 62049b5c..dd016fce 100644 --- a/source/RevitLookup.UI.Playground/Client/Views/Pages/WindowsPage.xaml +++ b/source/RevitLookup.UI.Playground/Client/Views/Pages/WindowsPage.xaml @@ -22,6 +22,21 @@ Icon="{ui:SymbolIcon AirplaneTakeOff24}" Content="RevitLookup window" Command="{Binding ViewModel.ShowRevitLookupWindowCommand}" /> + + + \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Host.cs b/source/RevitLookup.UI.Playground/Host.cs index aff75fbb..febd4416 100644 --- a/source/RevitLookup.UI.Playground/Host.cs +++ b/source/RevitLookup.UI.Playground/Host.cs @@ -43,6 +43,8 @@ private static ServiceProvider RegisterServices() //Services services.AddSingleton(); services.AddSingleton(); + services.AddScoped(); + services.AddTransient(); return services.BuildServiceProvider(); } diff --git a/source/RevitLookup.UI.Playground/Mappers/DecompositionResultMapper.cs b/source/RevitLookup.UI.Playground/Mappers/DecompositionResultMapper.cs index 6710b17e..dcf19439 100644 --- a/source/RevitLookup.UI.Playground/Mappers/DecompositionResultMapper.cs +++ b/source/RevitLookup.UI.Playground/Mappers/DecompositionResultMapper.cs @@ -4,8 +4,9 @@ namespace RevitLookup.UI.Playground.Mappers; -[Mapper] +[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Source)] public static partial class DecompositionResultMapper { public static partial ObservableDecomposedObject Convert(DecomposedObject decomposedObject); + public static partial ObservableDecomposedMember Convert(DecomposedMember decomposedMember); } \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Services/MockRevitLookupUiService.cs b/source/RevitLookup.UI.Playground/Services/MockRevitLookupUiService.cs new file mode 100644 index 00000000..5f2a19d7 --- /dev/null +++ b/source/RevitLookup.UI.Playground/Services/MockRevitLookupUiService.cs @@ -0,0 +1,115 @@ +using System.Collections; +using System.Windows; +using System.Windows.Controls; +using Microsoft.Extensions.DependencyInjection; +using RevitLookup.Abstractions.Models.Summary; +using RevitLookup.Abstractions.ObservableModels.Decomposition; +using RevitLookup.Abstractions.Services; +using RevitLookup.UI.Framework.Views.Windows; +using Wpf.Ui; + +namespace RevitLookup.UI.Playground.Services; + +public sealed class MockRevitLookupUiService : IRevitLookupUiService +{ + private Window? _parent; + private readonly Task _activeTask = Task.CompletedTask; + private readonly IServiceScope _scope; + private readonly IVisualDecompositionService _decompositionService; + private readonly INavigationService _navigationService; + private readonly Window _host; + + public MockRevitLookupUiService(IServiceScopeFactory scopeFactory) + { + _scope = scopeFactory.CreateScope(); + + _host = _scope.ServiceProvider.GetRequiredService(); + _decompositionService = _scope.ServiceProvider.GetRequiredService(); + _navigationService = _scope.ServiceProvider.GetRequiredService(); + + _host.Closed += (_, _) => _scope.Dispose(); + } + + public ILookupServiceDependsStage Decompose(KnownDecompositionObject decompositionObject) + { + _activeTask.ContinueWith(_ => _decompositionService.VisualizeDecompositionAsync(decompositionObject), TaskScheduler.FromCurrentSynchronizationContext()); + + return this; + } + + public ILookupServiceDependsStage Decompose(object? obj) + { + _activeTask.ContinueWith(_ => _decompositionService.VisualizeDecompositionAsync(obj), TaskScheduler.FromCurrentSynchronizationContext()); + return this; + } + + public ILookupServiceDependsStage Decompose(IEnumerable objects) + { + _activeTask.ContinueWith(_ => _decompositionService.VisualizeDecompositionAsync(objects), TaskScheduler.FromCurrentSynchronizationContext()); + return this; + } + + public ILookupServiceDependsStage Decompose(ObservableDecomposedObject decomposedObject) + { + _activeTask.ContinueWith(_ => _decompositionService.VisualizeDecompositionAsync(decomposedObject), TaskScheduler.FromCurrentSynchronizationContext()); + return this; + } + + public ILookupServiceDependsStage Decompose(List decomposedObjects) + { + _activeTask.ContinueWith(_ => _decompositionService.VisualizeDecompositionAsync(decomposedObjects), TaskScheduler.FromCurrentSynchronizationContext()); + return this; + } + + public ILookupServiceShowStage DependsOn(Window parent) + { + _parent = parent; + return this; + } + + public ILookupServiceRunStage Show() where T : Page + { + _activeTask.ContinueWith(_ => + { + ShowHost(false); + _navigationService.Navigate(typeof(T)); + }, TaskScheduler.FromCurrentSynchronizationContext()); + + return this; + } + + public void RunService(Action handler) where T : class + { + _activeTask.ContinueWith(_ => InvokeService(handler), TaskScheduler.FromCurrentSynchronizationContext()); + } + + private void InvokeService(Action handler) where T : class + { + var service = _scope.ServiceProvider.GetRequiredService(); + handler.Invoke(service); + } + + private void ShowHost(bool modal) + { + if (_parent is null) + { + _host.WindowStartupLocation = WindowStartupLocation.CenterScreen; + } + else + { + _host.WindowStartupLocation = WindowStartupLocation.Manual; + _host.Left = _parent.Left + 47; + _host.Top = _parent.Top + 49; + } + + if (modal) + { + _host.ShowDialog(); + } + else + { + _host.Show(); + _host.Focus(); + } + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Services/MockVisualDecompositionService.cs b/source/RevitLookup.UI.Playground/Services/MockVisualDecompositionService.cs new file mode 100644 index 00000000..544513e0 --- /dev/null +++ b/source/RevitLookup.UI.Playground/Services/MockVisualDecompositionService.cs @@ -0,0 +1,105 @@ +using System.Collections; +using System.Diagnostics.CodeAnalysis; +using System.Windows; +using LookupEngine; +using RevitLookup.Abstractions.Models.Summary; +using RevitLookup.Abstractions.ObservableModels.Decomposition; +using RevitLookup.Abstractions.Services; +using RevitLookup.Abstractions.ViewModels.Summary; +using RevitLookup.UI.Playground.Mappers; + +namespace RevitLookup.UI.Playground.Services; + +public sealed class MockVisualDecompositionService( + IWindowIntercomService intercomService, + INotificationService notificationService, + ISnoopSummaryViewModel summaryViewModel) + : IVisualDecompositionService +{ + public async Task VisualizeDecompositionAsync(KnownDecompositionObject decompositionObject) + { + try + { + switch (decompositionObject) + { + case KnownDecompositionObject.Face: + case KnownDecompositionObject.Edge: + case KnownDecompositionObject.LinkedElement: + case KnownDecompositionObject.Point: + case KnownDecompositionObject.SubElement: + HideHost(); + await Task.Delay(1000); + break; + } + + summaryViewModel.DecomposedObjects = await DecomposeAsync(new object[] { decompositionObject }); + } + catch (OperationCanceledException) + { + notificationService.ShowWarning("Operation cancelled", "Operation cancelled by user"); + } + catch (Exception exception) + { + notificationService.ShowError("Operation cancelled", exception); + } + finally + { + ShowHost(); + } + } + + public async Task VisualizeDecompositionAsync(object? obj) + { + summaryViewModel.DecomposedObjects = await DecomposeAsync(new[] { obj }); + } + + public async Task VisualizeDecompositionAsync(IEnumerable objects) + { + summaryViewModel.DecomposedObjects = await DecomposeAsync(objects); + } + + public async Task VisualizeDecompositionAsync(ObservableDecomposedObject decomposedObject) + { + summaryViewModel.DecomposedObjects = [decomposedObject]; + await Task.CompletedTask; + } + + public async Task VisualizeDecompositionAsync(List decomposedObjects) + { + summaryViewModel.DecomposedObjects = decomposedObjects; + await Task.CompletedTask; + } + + [SuppressMessage("ReSharper", "LoopCanBeConvertedToQuery")] + private async Task> DecomposeAsync(IEnumerable objects) + { + return await Task.Run(() => + { + var count = objects is ICollection collection ? collection.Count : 4; + var decomposedObjects = new List(count); + foreach (var obj in objects) + { + var decomposedObject = LookupComposer.DecomposeObject(obj); + decomposedObjects.Add(DecompositionResultMapper.Convert(decomposedObject)); + } + + return decomposedObjects; + }); + } + + private void ShowHost() + { + var host = intercomService.GetHost(); + if (!host.IsLoaded) return; + + host.Visibility = Visibility.Visible; + } + + private void HideHost() + { + var host = intercomService.GetHost(); + if (!host.IsLoaded) return; + + host.Visibility = Visibility.Hidden; + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Services/RevitLookupUiService.cs b/source/RevitLookup.UI.Playground/Services/RevitLookupUiService.cs new file mode 100644 index 00000000..b6cf4454 --- /dev/null +++ b/source/RevitLookup.UI.Playground/Services/RevitLookupUiService.cs @@ -0,0 +1,284 @@ +namespace RevitLookup.UI.Playground.Services; + +// public sealed class RevitLookupUiService : IRevitLookupUiService +// { +// private static readonly Dispatcher Dispatcher; +// private UiServiceImpl _uiService = default!; +// +// static RevitLookupUiService() +// { +// var uiThread = new Thread(Dispatcher.Run); +// uiThread.SetApartmentState(ApartmentState.STA); +// uiThread.Start(); +// +// Dispatcher = EnsureDispatcherStart(uiThread); +// } +// +// public RevitLookupUiService(IServiceScopeFactory scopeFactory) +// { +// if (Dispatcher.CheckAccess()) +// { +// _uiService = new UiServiceImpl(scopeFactory); +// } +// else +// { +// Dispatcher.Invoke(() => _uiService = new UiServiceImpl(scopeFactory)); +// } +// } +// +// public ILookupServiceDependsStage Decompose(KnownDecompositionObject decompositionObject) +// { +// if (Dispatcher.CheckAccess()) +// { +// _uiService.Decompose(decompositionObject); +// } +// else +// { +// Dispatcher.Invoke(() => _uiService.Decompose(decompositionObject)); +// } +// +// return this; +// } +// public ILookupServiceDependsStage Decompose(object obj) +// { +// if (Dispatcher.CheckAccess()) +// { +// _uiService.Decompose(obj); +// } +// else +// { +// Dispatcher.Invoke(() => _uiService.Decompose(obj)); +// } +// +// return this; +// } +// +// public ILookupServiceDependsStage Decompose(IEnumerable objects) +// { +// if (Dispatcher.CheckAccess()) +// { +// _uiService.Decompose(objects); +// } +// else +// { +// Dispatcher.Invoke(() => _uiService.Decompose(objects)); +// } +// +// return this; +// } +// +// public ILookupServiceDependsStage Decompose(ObservableDecomposedObject decomposedObject) +// { +// if (Dispatcher.CheckAccess()) +// { +// _uiService.Decompose(decomposedObject); +// } +// else +// { +// Dispatcher.Invoke(() => _uiService.Decompose(decomposedObject)); +// } +// +// return this; +// } +// +// public ILookupServiceDependsStage Decompose(List decomposedObjects) +// { +// if (Dispatcher.CheckAccess()) +// { +// _uiService.Decompose(decomposedObjects); +// } +// else +// { +// Dispatcher.Invoke(() => _uiService.Decompose(decomposedObjects)); +// } +// +// return this; +// } +// +// public ILookupServiceShowStage DependsOn(Window parent) +// { +// if (Dispatcher.CheckAccess()) +// { +// _uiService.DependsOn(parent); +// } +// else +// { +// Dispatcher.Invoke(() => _uiService.DependsOn(parent)); +// } +// +// return this; +// } +// +// public ILookupServiceRunStage Show() where T : Page +// { +// if (Dispatcher.CheckAccess()) +// { +// _uiService.Show(); +// } +// else +// { +// Dispatcher.Invoke(() => _uiService.Show()); +// } +// +// return this; +// } +// +// public void RunService(Action handler) where T : class +// { +// if (Dispatcher.CheckAccess()) +// { +// _uiService.RunService(handler); +// } +// else +// { +// Dispatcher.Invoke(() => _uiService.RunService(handler)); +// } +// } +// +// private static Dispatcher EnsureDispatcherStart(Thread thread) +// { +// Dispatcher? dispatcher = null; +// SpinWait spinWait = new(); +// while (dispatcher is null) +// { +// spinWait.SpinOnce(); +// dispatcher = Dispatcher.FromThread(thread); +// } +// +// // We must yield +// // Sometimes the Dispatcher is unavailable for current thread +// Thread.Sleep(1); +// +// return dispatcher; +// } +// +// private sealed class UiServiceImpl +// { +// private Window? _parent; +// private Task? _activeTask; +// private readonly IServiceScope _scope; +// private readonly IVisualDecompositionService _decompositionService; +// private readonly INavigationService _navigationService; +// private readonly Window _host; +// +// public UiServiceImpl(IServiceScopeFactory scopeFactory) +// { +// _scope = scopeFactory.CreateScope(); +// +// _host = _scope.ServiceProvider.GetRequiredService(); +// _decompositionService = _scope.ServiceProvider.GetRequiredService(); +// _navigationService = _scope.ServiceProvider.GetRequiredService(); +// +// _host.Closed += (_, _) => _scope.Dispose(); +// } +// +// public void Decompose(KnownDecompositionObject decompositionObject) +// { +// if (_activeTask is null || _activeTask.IsCompleted) +// { +// _activeTask = _decompositionService.VisualizeDecompositionAsync(decompositionObject); +// } +// else +// { +// _activeTask = _activeTask.ContinueWith(_ => _decompositionService.VisualizeDecompositionAsync(decompositionObject), TaskScheduler.FromCurrentSynchronizationContext()); +// } +// } +// +// public void Decompose(object obj) +// { +// if (_activeTask is null || _activeTask.IsCompleted) +// { +// _activeTask = _decompositionService.VisualizeDecompositionAsync(obj); +// } +// else +// { +// _activeTask = _activeTask.ContinueWith(_ => _decompositionService.VisualizeDecompositionAsync(obj), TaskScheduler.FromCurrentSynchronizationContext()); +// } +// } +// +// public void Decompose(IEnumerable enumerable) +// { +// if (_activeTask is null || _activeTask.IsCompleted) +// { +// _activeTask = _decompositionService.VisualizeDecompositionAsync(enumerable); +// } +// else +// { +// _activeTask = _activeTask.ContinueWith(_ => _decompositionService.VisualizeDecompositionAsync(enumerable), TaskScheduler.FromCurrentSynchronizationContext()); +// } +// } +// +// public void Decompose(ObservableDecomposedObject decomposedObject) +// { +// _decompositionService.VisualizeDecompositionAsync(decomposedObject); +// } +// +// public void Decompose(List decomposedObjects) +// { +// _decompositionService.VisualizeDecompositionAsync(decomposedObjects); +// } +// +// public void DependsOn(Window parent) +// { +// _parent = parent; +// } +// +// public void Show() where T : Page +// { +// if (_activeTask is null || _activeTask.IsCompleted) +// { +// ShowHost(false); +// _navigationService.Navigate(typeof(T)); +// } +// else +// { +// _activeTask = _activeTask.ContinueWith(_ => +// { +// ShowHost(false); +// _navigationService.Navigate(typeof(T)); +// }, TaskScheduler.FromCurrentSynchronizationContext()); +// } +// } +// +// public void RunService(Action handler) where T : class +// { +// if (_activeTask is null || _activeTask.IsCompleted) +// { +// InvokeService(handler); +// } +// else +// { +// _activeTask = _activeTask.ContinueWith(_ => InvokeService(handler), TaskScheduler.FromCurrentSynchronizationContext()); +// } +// } +// +// private void InvokeService(Action handler) where T : class +// { +// var service = _scope.ServiceProvider.GetRequiredService(); +// handler.Invoke(service); +// } +// +// private void ShowHost(bool modal) +// { +// if (_parent is null) +// { +// _host.WindowStartupLocation = WindowStartupLocation.CenterScreen; +// } +// else +// { +// _host.WindowStartupLocation = WindowStartupLocation.Manual; +// _host.Left = _parent.Left + 47; +// _host.Top = _parent.Top + 49; +// } +// +// if (modal) +// { +// _host.ShowDialog(); +// } +// else +// { +// _host.Show(); +// } +// } +// } +// } \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Styles/ComponentStyles/MembersGrid/DataGridRowStyleSelector.cs b/source/RevitLookup.UI.Playground/Styles/ComponentStyles/MembersGrid/DataGridRowStyleSelector.cs index 815a6383..b6c8c896 100644 --- a/source/RevitLookup.UI.Playground/Styles/ComponentStyles/MembersGrid/DataGridRowStyleSelector.cs +++ b/source/RevitLookup.UI.Playground/Styles/ComponentStyles/MembersGrid/DataGridRowStyleSelector.cs @@ -1,5 +1,4 @@ -using System.Collections; -using System.Windows; +using System.Windows; using System.Windows.Controls; using LookupEngine.Abstractions.ComponentModel; using LookupEngine.Abstractions.Configuration; @@ -28,7 +27,6 @@ public sealed class DataGridRowStyleSelector : StyleSelector return value switch { Exception => "ExceptionDataGridRowStyle", - ICollection { Count: > 0 } => "HandledDataGridRowStyle", _ => null }; } @@ -37,7 +35,8 @@ private static string SelectByDescriptor(Descriptor? descriptor) { return descriptor switch { - IDescriptorEnumerator { IsEmpty: false } => "HandleDataGridRowStyle", + IDescriptorEnumerator { IsEmpty: false } => "HandledDataGridRowStyle", + IDescriptorEnumerator => "DefaultLookupDataGridRowStyle", IDescriptorCollector => "HandledDataGridRowStyle", _ => "DefaultLookupDataGridRowStyle" }; diff --git a/source/RevitLookup.UI.Playground/ViewModels/Dashboard/MockDashboardViewModel.cs b/source/RevitLookup.UI.Playground/ViewModels/Dashboard/MockDashboardViewModel.cs index a7d8cb0e..59b15e21 100644 --- a/source/RevitLookup.UI.Playground/ViewModels/Dashboard/MockDashboardViewModel.cs +++ b/source/RevitLookup.UI.Playground/ViewModels/Dashboard/MockDashboardViewModel.cs @@ -1,7 +1,13 @@ using CommunityToolkit.Mvvm.Input; using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; +using RevitLookup.Abstractions.Models.Summary; using RevitLookup.Abstractions.Models.UserInterface; +using RevitLookup.Abstractions.Services; using RevitLookup.Abstractions.ViewModels.Dashboard; +using RevitLookup.UI.Framework.Views.Summary; +using RevitLookup.UI.Framework.Views.Tools; +using Wpf.Ui; using Wpf.Ui.Controls; namespace RevitLookup.UI.Playground.ViewModels.Dashboard; @@ -9,8 +15,22 @@ namespace RevitLookup.UI.Playground.ViewModels.Dashboard; [UsedImplicitly] public sealed partial class MockDashboardViewModel : IDashboardViewModel { - public MockDashboardViewModel() + private readonly IServiceProvider _serviceProvider; + private readonly INavigationService _navigationService; + private readonly INotificationService _notificationService; + private readonly IVisualDecompositionService _visualDecompositionService; + + public MockDashboardViewModel( + IServiceProvider serviceProvider, + INavigationService navigationService, + INotificationService notificationService, + IVisualDecompositionService visualDecompositionService) { + _serviceProvider = serviceProvider; + _navigationService = navigationService; + _notificationService = notificationService; + _visualDecompositionService = visualDecompositionService; + NavigationGroups = [ new NavigationCardGroup @@ -255,12 +275,119 @@ public MockDashboardViewModel() [RelayCommand] private async Task NavigatePage(string? parameter) { - await Task.CompletedTask; + switch (parameter) + { + case "view": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.View); + _navigationService.Navigate(typeof(SnoopSummaryPage)); + break; + case "document": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.Document); + _navigationService.Navigate(typeof(SnoopSummaryPage)); + break; + case "application": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.Application); + _navigationService.Navigate(typeof(SnoopSummaryPage)); + break; + case "uiApplication": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.UiApplication); + _navigationService.Navigate(typeof(SnoopSummaryPage)); + break; + case "database": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.Database); + _navigationService.Navigate(typeof(SnoopSummaryPage)); + break; + case "dependents": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.DependentElements); + _navigationService.Navigate(typeof(SnoopSummaryPage)); + break; + case "selection": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.Selection); + _navigationService.Navigate(typeof(SnoopSummaryPage)); + break; + case "linked": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.LinkedElement); + _navigationService.Navigate(typeof(SnoopSummaryPage)); + break; + case "face": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.Face); + _navigationService.Navigate(typeof(SnoopSummaryPage)); + break; + case "edge": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.Edge); + _navigationService.Navigate(typeof(SnoopSummaryPage)); + break; + case "point": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.Point); + _navigationService.Navigate(typeof(SnoopSummaryPage)); + break; + case "subElement": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.SubElement); + _navigationService.Navigate(typeof(SnoopSummaryPage)); + break; + case "components": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.ComponentManager); + _navigationService.Navigate(typeof(SnoopSummaryPage)); + break; + case "performance": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.PerformanceAdviser); + _navigationService.Navigate(typeof(SnoopSummaryPage)); + break; + case "updaters": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.UpdaterRegistry); + _navigationService.Navigate(typeof(SnoopSummaryPage)); + break; + case "services": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.Services); + _navigationService.Navigate(typeof(SnoopSummaryPage)); + break; + case "schemas": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.Schemas); + _navigationService.Navigate(typeof(SnoopSummaryPage)); + break; + case "events": + // _navigationService.Navigate(typeof(EventsPage)); + break; + case "revitSettings": + // _navigationService.NavigateWithHierarchy(typeof(RevitSettingsPage)); + break; + default: + throw new ArgumentOutOfRangeException(nameof(parameter), parameter); + } } [RelayCommand] private async Task OpenDialog(string parameter) { - await Task.CompletedTask; + try + { + switch (parameter) + { + case "parameters": + var unitsDialog = _serviceProvider.GetRequiredService(); + await unitsDialog.ShowParametersDialogAsync(); + return; + case "categories": + unitsDialog = _serviceProvider.GetRequiredService(); + await unitsDialog.ShowCategoriesDialogAsync(); + return; + case "forge": + unitsDialog = _serviceProvider.GetRequiredService(); + await unitsDialog.ShowForgeSchemaDialogAsync(); + return; + case "search": + var searchDialog = _serviceProvider.GetRequiredService(); + await searchDialog.ShowAsync(); + return; + case "modules": + var modulesDialog = _serviceProvider.GetRequiredService(); + await modulesDialog.ShowAsync(); + return; + } + } + catch (Exception exception) + { + _notificationService.ShowError("Failed to open dialog", exception); + } } } \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/ViewModels/Summary/MockSnoopSummaryViewModel.cs b/source/RevitLookup.UI.Playground/ViewModels/Summary/MockSnoopSummaryViewModel.cs index bb0d4617..83b9e7af 100644 --- a/source/RevitLookup.UI.Playground/ViewModels/Summary/MockSnoopSummaryViewModel.cs +++ b/source/RevitLookup.UI.Playground/ViewModels/Summary/MockSnoopSummaryViewModel.cs @@ -1,12 +1,13 @@ -using System.Collections; -using System.Windows.Media; -using Bogus; +using System.Diagnostics.CodeAnalysis; using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; using JetBrains.Annotations; using LookupEngine; +using Microsoft.Extensions.Logging; using RevitLookup.Abstractions.ObservableModels.Decomposition; using RevitLookup.Abstractions.Services; using RevitLookup.Abstractions.ViewModels.Summary; +using RevitLookup.UI.Framework.Views.Summary; using RevitLookup.UI.Playground.Mappers; #if NETFRAMEWORK using RevitLookup.UI.Framework.Extensions; @@ -15,75 +16,64 @@ namespace RevitLookup.UI.Playground.ViewModels.Summary; [UsedImplicitly] -public sealed partial class MockSnoopSummaryViewModel : ObservableObject, ISnoopSummaryViewModel +public sealed partial class MockSnoopSummaryViewModel( + ISettingsService settingsService, + IWindowIntercomService intercomService, + INotificationService notificationService, + ILogger logger) + : ObservableObject, ISnoopSummaryViewModel { [ObservableProperty] private string _searchText = string.Empty; [ObservableProperty] private ObservableDecomposedObject? _selectedDecomposedObject; [ObservableProperty] private List _decomposedObjects = []; [ObservableProperty] private List _filteredDecomposedObjects = []; - [ObservableProperty] private List _members = []; - [ObservableProperty] private List _filteredMembers = []; - public MockSnoopSummaryViewModel(ISettingsService settingsService) + [RelayCommand] + private async Task RefreshMembersAsync() { - var globalFaker = new Faker(); - var strings = new Faker() - .CustomInstantiator(faker => faker.Lorem.Sentence(40)) - .GenerateBetween(1, 10); - - var integers = Enumerable.Range(0, globalFaker.Random.Int(1, 10)) - .Select(_ => globalFaker.Random.Int()) - .ToList(); - - var colors = Enumerable.Range(0, globalFaker.Random.Int(1, 10)) - .Select(_ => Color.FromRgb(globalFaker.Random.Byte(), globalFaker.Random.Byte(), globalFaker.Random.Byte())) - .ToList(); - - var objects = new ArrayList(); - objects.AddRange(strings); - objects.AddRange(integers); - objects.AddRange(colors); - - var options = new DecomposeOptions + foreach (var decomposedObject in DecomposedObjects) { - IncludeRoot = settingsService.GeneralSettings.IncludeRootHierarchy, - IncludeFields = settingsService.GeneralSettings.IncludeFields, - IncludeEvents = settingsService.GeneralSettings.IncludeEvents, - IncludeUnsupported = settingsService.GeneralSettings.IncludeUnsupported, - IgnorePrivateMembers = !settingsService.GeneralSettings.IncludePrivate, - IgnoreStaticMembers = !settingsService.GeneralSettings.IncludeStatic, - EnableExtensions = settingsService.GeneralSettings.IncludeExtensions - }; + decomposedObject.Members.Clear(); + } - var decomposedObjects = new List(); - foreach (var obj in objects) + try { - var decomposedObject = LookupComposer.Decompose(obj, options); - decomposedObjects.Add(DecompositionResultMapper.Convert(decomposedObject)); + await FetchMembersAsync(SelectedDecomposedObject); + } + catch (Exception exception) + { + logger.LogError(exception, "Members decomposing failed"); + notificationService.ShowError("Lookup engine error", exception); } - - DecomposedObjects = decomposedObjects; } - partial void OnDecomposedObjectsChanged(List value) + public void Navigate(object? value) { - OnSearchTextChanged(SearchText); + Host.GetService() + .Decompose(value) + .DependsOn(intercomService.GetHost()) + .Show(); } - partial void OnSelectedDecomposedObjectChanged(ObservableDecomposedObject? value) + public void Navigate(ObservableDecomposedObject value) { - if (value is null) - { - Members = []; - return; - } + Host.GetService() + .Decompose(value) + .DependsOn(intercomService.GetHost()) + .Show(); + } - Members = value.Members; + public void Navigate(List values) + { + Host.GetService() + .Decompose(values) + .DependsOn(intercomService.GetHost()) + .Show(); } - partial void OnMembersChanged(List value) + partial void OnDecomposedObjectsChanged(List value) { - FilteredMembers = value; + OnSearchTextChanged(SearchText); } async partial void OnSearchTextChanged(string value) @@ -111,6 +101,11 @@ async partial void OnSearchTextChanged(string value) return ApplyGrouping(searchResults); }); + + if (FilteredDecomposedObjects.Count == 0) + { + SelectedDecomposedObject = null; + } } catch { @@ -118,6 +113,55 @@ async partial void OnSearchTextChanged(string value) } } + async partial void OnSelectedDecomposedObjectChanged(ObservableDecomposedObject? value) + { + try + { + await FetchMembersAsync(value); + } + catch (Exception exception) + { + logger.LogError(exception, "Members decomposing failed"); + notificationService.ShowError("Lookup engine error", exception); + } + } + + private async Task FetchMembersAsync(ObservableDecomposedObject? value) + { + if (value is null) return; + if (value.Members.Count > 0) return; + + value.Members = await DecomposeMembersAsync(value); + } + + [SuppressMessage("ReSharper", "ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator")] + private async Task> DecomposeMembersAsync(ObservableDecomposedObject decomposedObject) + { + var options = new DecomposeOptions + { + IncludeRoot = settingsService.GeneralSettings.IncludeRootHierarchy, + IncludeFields = settingsService.GeneralSettings.IncludeFields, + IncludeEvents = settingsService.GeneralSettings.IncludeEvents, + IncludeUnsupported = settingsService.GeneralSettings.IncludeUnsupported, + IncludePrivateMembers = settingsService.GeneralSettings.IncludePrivate, + IncludeStaticMembers = settingsService.GeneralSettings.IncludeStatic, + EnableExtensions = settingsService.GeneralSettings.IncludeExtensions + }; + + return await Task.Run(() => + { + var decomposedMembers = LookupComposer.DecomposeMembers(decomposedObject.RawValue, options); + var members = new List(decomposedMembers.Count); + + foreach (var decomposedMember in decomposedMembers) + { + members.Add(DecompositionResultMapper.Convert(decomposedMember)); + } + + return members; + }); + } + private List ApplyGrouping(List objects) { return objects diff --git a/source/RevitLookup.UI.Playground/ViewModels/Tools/MockModulesViewModel.cs b/source/RevitLookup.UI.Playground/ViewModels/Tools/MockModulesViewModel.cs index f5618e2b..f7487945 100644 --- a/source/RevitLookup.UI.Playground/ViewModels/Tools/MockModulesViewModel.cs +++ b/source/RevitLookup.UI.Playground/ViewModels/Tools/MockModulesViewModel.cs @@ -25,6 +25,7 @@ #if NETCOREAPP using System.Runtime.Loader; #endif + #if NETFRAMEWORK using RevitLookup.UI.Framework.Extensions; #endif @@ -65,29 +66,36 @@ public MockModulesViewModel() async partial void OnSearchTextChanged(string value) { - if (string.IsNullOrEmpty(SearchText)) + try { - FilteredModules = Modules; - return; - } + if (string.IsNullOrEmpty(SearchText)) + { + FilteredModules = Modules; + return; + } - FilteredModules = await Task.Run(() => - { - var formattedText = value.Trim(); - var searchResults = new List(); - // ReSharper disable once LoopCanBeConvertedToQuery - foreach (var module in Modules) + FilteredModules = await Task.Run(() => { - if (module.Name.Contains(formattedText, StringComparison.OrdinalIgnoreCase) || - module.Path.Contains(formattedText, StringComparison.OrdinalIgnoreCase) || - module.Version.Contains(formattedText, StringComparison.OrdinalIgnoreCase)) + var formattedText = value.Trim(); + var searchResults = new List(); + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var module in Modules) { - searchResults.Add(module); + if (module.Name.Contains(formattedText, StringComparison.OrdinalIgnoreCase) || + module.Path.Contains(formattedText, StringComparison.OrdinalIgnoreCase) || + module.Version.Contains(formattedText, StringComparison.OrdinalIgnoreCase)) + { + searchResults.Add(module); + } } - } - return searchResults; - }); + return searchResults; + }); + } + catch + { + // ignored + } } partial void OnModulesChanged(List value) diff --git a/source/RevitLookup.UI.Playground/ViewModels/Tools/MockSearchElementsViewModel.cs b/source/RevitLookup.UI.Playground/ViewModels/Tools/MockSearchElementsViewModel.cs index 8ddd81d5..49f7dbf4 100644 --- a/source/RevitLookup.UI.Playground/ViewModels/Tools/MockSearchElementsViewModel.cs +++ b/source/RevitLookup.UI.Playground/ViewModels/Tools/MockSearchElementsViewModel.cs @@ -1,18 +1,26 @@ using CommunityToolkit.Mvvm.ComponentModel; +using JetBrains.Annotations; using RevitLookup.Abstractions.Services; using RevitLookup.Abstractions.ViewModels.Tools; namespace RevitLookup.UI.Playground.ViewModels.Tools; -public sealed partial class MockSearchElementsViewModel(INotificationService notificationService) : ObservableObject, ISearchElementsViewModel +[UsedImplicitly] +public sealed partial class MockSearchElementsViewModel( + INotificationService notificationService, + IVisualDecompositionService decompositionService) + : ObservableObject, ISearchElementsViewModel { [ObservableProperty] private string _searchText = string.Empty; public bool SearchElements() { var result = SearchText != string.Empty; - - if (!result) + if (result) + { + decompositionService.VisualizeDecompositionAsync((object)SearchText); + } + else { notificationService.ShowWarning("Search elements", "There are no elements found for your request"); } From a41599a1f54a9b1d3180fb0ecfbf1a17a7e48605 Mon Sep 17 00:00:00 2001 From: Nice3point Date: Wed, 11 Dec 2024 00:34:22 +0300 Subject: [PATCH 034/121] Add Events view --- .../Summary/IDecompositionSummaryViewModel.cs | 8 + .../Summary/IEventsSummaryViewModel.cs | 10 + ...mmaryViewModel.cs => ISummaryViewModel.cs} | 5 +- ...age.xaml => DecompositionSummaryPage.xaml} | 18 +- .../Summary/DecompositionSummaryPage.xaml.cs | 46 ++++ .../Views/Summary/EventsSummaryPage.xaml | 193 +++++++++++++++ ...Page.xaml.cs => EventsSummaryPage.xaml.cs} | 7 +- .../Views/Summary/SummaryViewBase.xaml.cs | 4 +- .../Views/Windows/RevitLookupView.xaml | 12 +- ...s => MockDecompositionSummaryViewModel.cs} | 12 +- .../Summary/MockEventsSummaryViewModel.cs | 223 ++++++++++++++++++ 11 files changed, 511 insertions(+), 27 deletions(-) create mode 100644 source/RevitLookup.Abstractions/ViewModels/Summary/IDecompositionSummaryViewModel.cs create mode 100644 source/RevitLookup.Abstractions/ViewModels/Summary/IEventsSummaryViewModel.cs rename source/RevitLookup.Abstractions/ViewModels/Summary/{ISnoopSummaryViewModel.cs => ISummaryViewModel.cs} (78%) rename source/RevitLookup.UI.Framework/Views/Summary/{SnoopSummaryPage.xaml => DecompositionSummaryPage.xaml} (92%) create mode 100644 source/RevitLookup.UI.Framework/Views/Summary/DecompositionSummaryPage.xaml.cs create mode 100644 source/RevitLookup.UI.Framework/Views/Summary/EventsSummaryPage.xaml rename source/RevitLookup.UI.Framework/Views/Summary/{SnoopSummaryPage.xaml.cs => EventsSummaryPage.xaml.cs} (93%) rename source/RevitLookup.UI.Playground/ViewModels/Summary/{MockSnoopSummaryViewModel.cs => MockDecompositionSummaryViewModel.cs} (94%) create mode 100644 source/RevitLookup.UI.Playground/ViewModels/Summary/MockEventsSummaryViewModel.cs diff --git a/source/RevitLookup.Abstractions/ViewModels/Summary/IDecompositionSummaryViewModel.cs b/source/RevitLookup.Abstractions/ViewModels/Summary/IDecompositionSummaryViewModel.cs new file mode 100644 index 00000000..a3f11c20 --- /dev/null +++ b/source/RevitLookup.Abstractions/ViewModels/Summary/IDecompositionSummaryViewModel.cs @@ -0,0 +1,8 @@ +using RevitLookup.Abstractions.ObservableModels.Decomposition; + +namespace RevitLookup.Abstractions.ViewModels.Summary; + +public interface IDecompositionSummaryViewModel : ISummaryViewModel +{ + List FilteredDecomposedObjects { get; } +} \ No newline at end of file diff --git a/source/RevitLookup.Abstractions/ViewModels/Summary/IEventsSummaryViewModel.cs b/source/RevitLookup.Abstractions/ViewModels/Summary/IEventsSummaryViewModel.cs new file mode 100644 index 00000000..563592f8 --- /dev/null +++ b/source/RevitLookup.Abstractions/ViewModels/Summary/IEventsSummaryViewModel.cs @@ -0,0 +1,10 @@ +using System.Collections.ObjectModel; +using RevitLookup.Abstractions.ObservableModels.Decomposition; +using Wpf.Ui.Abstractions.Controls; + +namespace RevitLookup.Abstractions.ViewModels.Summary; + +public interface IEventsSummaryViewModel : ISummaryViewModel, INavigationAware +{ + ObservableCollection FilteredDecomposedObjects { get; } +} \ No newline at end of file diff --git a/source/RevitLookup.Abstractions/ViewModels/Summary/ISnoopSummaryViewModel.cs b/source/RevitLookup.Abstractions/ViewModels/Summary/ISummaryViewModel.cs similarity index 78% rename from source/RevitLookup.Abstractions/ViewModels/Summary/ISnoopSummaryViewModel.cs rename to source/RevitLookup.Abstractions/ViewModels/Summary/ISummaryViewModel.cs index 15f2b550..6537c240 100644 --- a/source/RevitLookup.Abstractions/ViewModels/Summary/ISnoopSummaryViewModel.cs +++ b/source/RevitLookup.Abstractions/ViewModels/Summary/ISummaryViewModel.cs @@ -3,14 +3,13 @@ namespace RevitLookup.Abstractions.ViewModels.Summary; -public interface ISnoopSummaryViewModel +public interface ISummaryViewModel { string SearchText { get; set; } //Objects ObservableDecomposedObject? SelectedDecomposedObject { get; set; } List DecomposedObjects { get; set; } - List FilteredDecomposedObjects { get; } //Commands IAsyncRelayCommand RefreshMembersCommand { get; } @@ -19,6 +18,4 @@ public interface ISnoopSummaryViewModel void Navigate(object? value); void Navigate(ObservableDecomposedObject value); void Navigate(List values); - - // void RemoveObject(object obj); } \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml b/source/RevitLookup.UI.Framework/Views/Summary/DecompositionSummaryPage.xaml similarity index 92% rename from source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml rename to source/RevitLookup.UI.Framework/Views/Summary/DecompositionSummaryPage.xaml index 9b0dcdb3..6d8c194c 100644 --- a/source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml +++ b/source/RevitLookup.UI.Framework/Views/Summary/DecompositionSummaryPage.xaml @@ -1,5 +1,5 @@ + @@ -26,6 +28,7 @@ Text="Nothing to show" /> + @@ -52,7 +55,7 @@ x:Name="SummaryTreeView" Margin="0 8 0 0" ItemTemplate="{StaticResource DefaultSummaryTreeGroupTemplate}" - ItemsSource="{Binding ViewModel.FilteredDecomposedObjects}" + ItemsSource="{Binding Path=ViewModel.(viewModels:IDecompositionSummaryViewModel.FilteredDecomposedObjects)}" VirtualizingPanel.IsVirtualizing="True"> @@ -76,12 +79,17 @@ RowStyle="{x:Null}" RowStyleSelector="{StaticResource DataGridRowStyleSelector}" ScrollViewer.CanContentScroll="True" - VirtualizingPanel.IsVirtualizingWhenGrouping="True" - VirtualizingPanel.VirtualizationMode="Recycling"> + VirtualizingPanel.IsVirtualizingWhenGrouping="True"> + + + + + diff --git a/source/RevitLookup.UI.Framework/Views/Summary/DecompositionSummaryPage.xaml.cs b/source/RevitLookup.UI.Framework/Views/Summary/DecompositionSummaryPage.xaml.cs new file mode 100644 index 00000000..4ac00ac6 --- /dev/null +++ b/source/RevitLookup.UI.Framework/Views/Summary/DecompositionSummaryPage.xaml.cs @@ -0,0 +1,46 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +using Microsoft.Extensions.Logging; +using RevitLookup.Abstractions.Services; +using RevitLookup.Abstractions.ViewModels.Summary; + +namespace RevitLookup.UI.Framework.Views.Summary; + +public sealed partial class DecompositionSummaryPage +{ + public DecompositionSummaryPage( + IDecompositionSummaryViewModel viewModel, + ISettingsService settingsService, + IWindowIntercomService intercomService, + INotificationService notificationService, + ILoggerFactory loggerFactory) + : base(settingsService, intercomService, notificationService, loggerFactory) + { + DataContext = this; + ViewModel = viewModel; + InitializeComponent(); + + SearchBoxControl = SummarySearchBox; + TreeViewControl = SummaryTreeView; + DataGridControl = SummaryDataGrid; + InitializeControls(); + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/Summary/EventsSummaryPage.xaml b/source/RevitLookup.UI.Framework/Views/Summary/EventsSummaryPage.xaml new file mode 100644 index 00000000..1a5e02da --- /dev/null +++ b/source/RevitLookup.UI.Framework/Views/Summary/EventsSummaryPage.xaml @@ -0,0 +1,193 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml.cs b/source/RevitLookup.UI.Framework/Views/Summary/EventsSummaryPage.xaml.cs similarity index 93% rename from source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml.cs rename to source/RevitLookup.UI.Framework/Views/Summary/EventsSummaryPage.xaml.cs index 33781384..72101f59 100644 --- a/source/RevitLookup.UI.Framework/Views/Summary/SnoopSummaryPage.xaml.cs +++ b/source/RevitLookup.UI.Framework/Views/Summary/EventsSummaryPage.xaml.cs @@ -24,16 +24,15 @@ namespace RevitLookup.UI.Framework.Views.Summary; -public sealed partial class SnoopSummaryPage +public sealed partial class EventsSummaryPage { - public SnoopSummaryPage( - ISnoopSummaryViewModel viewModel, + public EventsSummaryPage( + IEventsSummaryViewModel viewModel, ISettingsService settingsService, IWindowIntercomService intercomService, INotificationService notificationService, ILoggerFactory loggerFactory) : base(settingsService, intercomService, notificationService, loggerFactory) - { DataContext = this; ViewModel = viewModel; diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs index f3bb44c6..552e45f2 100644 --- a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs +++ b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.xaml.cs @@ -38,7 +38,7 @@ namespace RevitLookup.UI.Framework.Views.Summary; -public partial class SummaryViewBase : Page, INavigableView +public partial class SummaryViewBase : Page, INavigableView { private readonly ISettingsService _settingsService; private readonly IWindowIntercomService _intercomService; @@ -61,7 +61,7 @@ protected SummaryViewBase(ISettingsService settingsService, public required UIElement SearchBoxControl { get; init; } public required TreeView TreeViewControl { get; init; } public required DataGrid DataGridControl { get; init; } - public required ISnoopSummaryViewModel ViewModel { get; init; } + public required ISummaryViewModel ViewModel { get; init; } protected void InitializeControls() { diff --git a/source/RevitLookup.UI.Framework/Views/Windows/RevitLookupView.xaml b/source/RevitLookup.UI.Framework/Views/Windows/RevitLookupView.xaml index a89367be..6e472329 100644 --- a/source/RevitLookup.UI.Framework/Views/Windows/RevitLookupView.xaml +++ b/source/RevitLookup.UI.Framework/Views/Windows/RevitLookupView.xaml @@ -38,13 +38,13 @@ TargetPageType="{x:Type dashboard:DashboardPage}" Icon="{rl:SymbolIcon AppGeneric24}" /> - - - - + logger) - : ObservableObject, ISnoopSummaryViewModel + ILogger logger) + : ObservableObject, IDecompositionSummaryViewModel { [ObservableProperty] private string _searchText = string.Empty; [ObservableProperty] private ObservableDecomposedObject? _selectedDecomposedObject; @@ -52,7 +52,7 @@ public void Navigate(object? value) Host.GetService() .Decompose(value) .DependsOn(intercomService.GetHost()) - .Show(); + .Show(); } public void Navigate(ObservableDecomposedObject value) @@ -60,7 +60,7 @@ public void Navigate(ObservableDecomposedObject value) Host.GetService() .Decompose(value) .DependsOn(intercomService.GetHost()) - .Show(); + .Show(); } public void Navigate(List values) @@ -68,7 +68,7 @@ public void Navigate(List values) Host.GetService() .Decompose(values) .DependsOn(intercomService.GetHost()) - .Show(); + .Show(); } partial void OnDecomposedObjectsChanged(List value) diff --git a/source/RevitLookup.UI.Playground/ViewModels/Summary/MockEventsSummaryViewModel.cs b/source/RevitLookup.UI.Playground/ViewModels/Summary/MockEventsSummaryViewModel.cs new file mode 100644 index 00000000..2c5f1dbb --- /dev/null +++ b/source/RevitLookup.UI.Playground/ViewModels/Summary/MockEventsSummaryViewModel.cs @@ -0,0 +1,223 @@ +using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; +using System.Windows.Media; +using Bogus; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using JetBrains.Annotations; +using LookupEngine; +using Microsoft.Extensions.Logging; +using RevitLookup.Abstractions.ObservableModels.Decomposition; +using RevitLookup.Abstractions.Services; +using RevitLookup.Abstractions.ViewModels.Summary; +using RevitLookup.UI.Framework.Extensions; +using RevitLookup.UI.Framework.Views.Summary; +using RevitLookup.UI.Playground.Mappers; +#if NETFRAMEWORK +using RevitLookup.UI.Framework.Extensions; +#endif + +namespace RevitLookup.UI.Playground.ViewModels.Summary; + +[UsedImplicitly] +public sealed partial class MockEventsSummaryViewModel( + ISettingsService settingsService, + IWindowIntercomService intercomService, + INotificationService notificationService, + ILogger logger) + : ObservableObject, IEventsSummaryViewModel +{ + private CancellationTokenSource? _cancellationTokenSource; + + [ObservableProperty] private string _searchText = string.Empty; + [ObservableProperty] private ObservableDecomposedObject? _selectedDecomposedObject; + [ObservableProperty] private List _decomposedObjects = []; + [ObservableProperty] private ObservableCollection _filteredDecomposedObjects = []; + + [RelayCommand] + private async Task RefreshMembersAsync() + { + foreach (var decomposedObject in DecomposedObjects) + { + decomposedObject.Members.Clear(); + } + + try + { + await FetchMembersAsync(SelectedDecomposedObject); + } + catch (Exception exception) + { + logger.LogError(exception, "Members decomposing failed"); + notificationService.ShowError("Lookup engine error", exception); + } + } + + public async Task OnNavigatedToAsync() + { + _cancellationTokenSource = new CancellationTokenSource(); + + var faker = new Faker(); + var cancellationToken = _cancellationTokenSource.Token; + while (!cancellationToken.IsCancellationRequested) + { + var decomposedObject = GenerateRandomObject(faker); + DecomposedObjects.Insert(0, decomposedObject); + + if (SearchText == string.Empty) FilteredDecomposedObjects.Insert(0, decomposedObject); + else OnSearchTextChanged(SearchText); + + await Task.Delay(1000, cancellationToken); + } + } + + public Task OnNavigatedFromAsync() + { + if (_cancellationTokenSource is null) return Task.CompletedTask; + + _cancellationTokenSource.Cancel(); + _cancellationTokenSource.Dispose(); + _cancellationTokenSource = null; + + return Task.CompletedTask; + } + + public void Navigate(object? value) + { + Host.GetService() + .Decompose(value) + .DependsOn(intercomService.GetHost()) + .Show(); + } + + public void Navigate(ObservableDecomposedObject value) + { + Host.GetService() + .Decompose(value) + .DependsOn(intercomService.GetHost()) + .Show(); + } + + public void Navigate(List values) + { + Host.GetService() + .Decompose(values) + .DependsOn(intercomService.GetHost()) + .Show(); + } + + partial void OnDecomposedObjectsChanged(List value) + { + OnSearchTextChanged(SearchText); + } + + async partial void OnSearchTextChanged(string value) + { + try + { + if (string.IsNullOrEmpty(SearchText)) + { + FilteredDecomposedObjects = DecomposedObjects.ToObservableCollection(); + return; + } + + FilteredDecomposedObjects = await Task.Run(() => + { + var formattedText = value.Trim(); + var searchResults = new List(); + // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator + foreach (var item in DecomposedObjects) + { + if (item.Name.Contains(formattedText, StringComparison.OrdinalIgnoreCase) || item.Name.Contains(formattedText, StringComparison.OrdinalIgnoreCase)) + { + searchResults.Add(item); + } + } + + return searchResults.ToObservableCollection(); + }); + + if (FilteredDecomposedObjects.Count == 0) + { + SelectedDecomposedObject = null; + } + } + catch + { + // ignored + } + } + + async partial void OnSelectedDecomposedObjectChanged(ObservableDecomposedObject? value) + { + try + { + await FetchMembersAsync(value); + } + catch (Exception exception) + { + logger.LogError(exception, "Members decomposing failed"); + notificationService.ShowError("Lookup engine error", exception); + } + } + + private async Task FetchMembersAsync(ObservableDecomposedObject? decomposedObject) + { + if (decomposedObject is null) return; + if (decomposedObject.Members.Count > 0) return; + + decomposedObject.Members = await DecomposeMembersAsync(decomposedObject); + } + + [SuppressMessage("ReSharper", "ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator")] + private async Task> DecomposeMembersAsync(ObservableDecomposedObject decomposedObject) + { + var options = CreateDecomposingOptions(); + return await Task.Run(() => + { + var decomposedMembers = LookupComposer.DecomposeMembers(decomposedObject.RawValue, options); + var members = new List(decomposedMembers.Count); + + foreach (var decomposedMember in decomposedMembers) + { + members.Add(DecompositionResultMapper.Convert(decomposedMember)); + } + + return members; + }); + } + + private ObservableDecomposedObject GenerateRandomObject(Faker faker) + { + var options = CreateDecomposingOptions(); + object item = faker.Random.Int(0, 100) switch + { + < 10 => faker.Random.Int(0, 100), + < 20 => faker.Random.Bool(), + < 30 => faker.Random.Uuid(), + < 40 => faker.Random.Hexadecimal(), + < 50 => faker.Date.Future(), + < 60 => faker.Internet.Url(), + < 70 => faker.Internet.Email(), + < 80 => Color.FromArgb(faker.Random.Byte(), faker.Random.Byte(), faker.Random.Byte(), faker.Random.Byte()), + < 90 => faker.Random.Double(0, 100), + _ => faker.Lorem.Word() + }; + + return DecompositionResultMapper.Convert(LookupComposer.DecomposeObject(item, options)); + } + + private DecomposeOptions CreateDecomposingOptions() + { + return new DecomposeOptions + { + IncludeRoot = settingsService.GeneralSettings.IncludeRootHierarchy, + IncludeFields = settingsService.GeneralSettings.IncludeFields, + IncludeEvents = settingsService.GeneralSettings.IncludeEvents, + IncludeUnsupported = settingsService.GeneralSettings.IncludeUnsupported, + IncludePrivateMembers = settingsService.GeneralSettings.IncludePrivate, + IncludeStaticMembers = settingsService.GeneralSettings.IncludeStatic, + EnableExtensions = settingsService.GeneralSettings.IncludeExtensions + }; + } +} \ No newline at end of file From c3af95db0a15bfe8bc50118a320f61f2dbfc8e88 Mon Sep 17 00:00:00 2001 From: Nice3point Date: Wed, 11 Dec 2024 00:35:11 +0300 Subject: [PATCH 035/121] Set recycling mode --- .../ViewModels/Pages/WindowsViewModel.cs | 19 +++++++++++++------ .../MockVisualDecompositionService.cs | 2 +- .../MembersGrid/DataGridGroupStyles.xaml | 7 ++++++- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/WindowsViewModel.cs b/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/WindowsViewModel.cs index c9aa2b02..7227eabd 100644 --- a/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/WindowsViewModel.cs +++ b/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/WindowsViewModel.cs @@ -21,7 +21,14 @@ private void ShowRevitLookupWindow() } [RelayCommand] - private void DecomposeColors() + private void ShowEventsWindow() + { + Host.GetService() + .Show(); + } + + [RelayCommand] + private void ShowDecomposeColorsWindow() { var faker = new Faker(); @@ -38,11 +45,11 @@ private void DecomposeColors() Host.GetService() .Decompose(colors) - .Show(); + .Show(); } [RelayCommand] - private void DecomposeText() + private void ShowDecomposeTextWindow() { var faker = new Faker(); @@ -54,15 +61,15 @@ private void DecomposeText() Host.GetService() .Decompose(strings) - .Show(); + .Show(); } [RelayCommand] - private void DecomposeAssembly() + private void ShowDecomposeAssemblyWindow() { var assembly = Assembly.GetExecutingAssembly(); Host.GetService() .Decompose(assembly.GetTypes()) - .Show(); + .Show(); } } \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Services/MockVisualDecompositionService.cs b/source/RevitLookup.UI.Playground/Services/MockVisualDecompositionService.cs index 544513e0..6f2f86fe 100644 --- a/source/RevitLookup.UI.Playground/Services/MockVisualDecompositionService.cs +++ b/source/RevitLookup.UI.Playground/Services/MockVisualDecompositionService.cs @@ -13,7 +13,7 @@ namespace RevitLookup.UI.Playground.Services; public sealed class MockVisualDecompositionService( IWindowIntercomService intercomService, INotificationService notificationService, - ISnoopSummaryViewModel summaryViewModel) + IDecompositionSummaryViewModel summaryViewModel) : IVisualDecompositionService { public async Task VisualizeDecompositionAsync(KnownDecompositionObject decompositionObject) diff --git a/source/RevitLookup.UI.Playground/Styles/ComponentStyles/MembersGrid/DataGridGroupStyles.xaml b/source/RevitLookup.UI.Playground/Styles/ComponentStyles/MembersGrid/DataGridGroupStyles.xaml index 8227e0ce..dadab124 100644 --- a/source/RevitLookup.UI.Playground/Styles/ComponentStyles/MembersGrid/DataGridGroupStyles.xaml +++ b/source/RevitLookup.UI.Playground/Styles/ComponentStyles/MembersGrid/DataGridGroupStyles.xaml @@ -13,7 +13,12 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/Tools/RevitSettingsPage.xaml.cs b/source/RevitLookup.UI.Framework/Views/Tools/RevitSettingsPage.xaml.cs new file mode 100644 index 00000000..dc4fc826 --- /dev/null +++ b/source/RevitLookup.UI.Framework/Views/Tools/RevitSettingsPage.xaml.cs @@ -0,0 +1,113 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +using System.Windows; +using System.Windows.Controls.Primitives; +using System.Windows.Data; +using System.Windows.Input; +using RevitLookup.Abstractions.ObservableModels.Entries; +using RevitLookup.Abstractions.Services; +using RevitLookup.Abstractions.ViewModels.Tools; +using Wpf.Ui; +using Wpf.Ui.Abstractions.Controls; +using Wpf.Ui.Controls; +using Wpf.Ui.Extensions; + +namespace RevitLookup.UI.Framework.Views.Tools; + +public sealed partial class RevitSettingsPage : INavigableView +{ + private readonly INotificationService _notificationService; + + public RevitSettingsPage( + IRevitSettingsViewModel viewModel, + IContentDialogService dialogService, + INavigationService navigationService, + INotificationService notificationService) + { + _notificationService = notificationService; + + ViewModel = viewModel; + DataContext = this; + + InitializeComponent(); + ApplyGrouping(); + + if (viewModel.Entries.Count == 0) + { + ShowWarningDialog(dialogService, navigationService); + } + } + + private void ApplyGrouping() + { + EntriesList.Items.GroupDescriptions!.Clear(); + EntriesList.Items.GroupDescriptions.Add(new PropertyGroupDescription(nameof(ObservableIniEntry.Category))); + } + + public IRevitSettingsViewModel ViewModel { get; } + + private async void ShowWarningDialog(IContentDialogService dialogService, INavigationService navigationService) + { + try + { + var options = new SimpleContentDialogCreateOptions + { + Title = "Proceed with caution", + Content = "Changing advanced configuration preferences can impact Revit performance or security", + PrimaryButtonText = "Accept the Risk and Continue", + CloseButtonText = "Quit" + }; + + var result = await dialogService.ShowSimpleDialogAsync(options); + if (result != ContentDialogResult.Primary) + { + navigationService.GoBack(); + } + else + { + await ViewModel.InitializeAsync(); + } + } + catch (Exception exception) + { + _notificationService.ShowError("Initialization error", exception.Message); + } + } + + private async void OnEntryClicked(object sender, MouseButtonEventArgs args) + { + try + { + if (args.OriginalSource is ButtonBase) return; + + await ViewModel.UpdateEntryAsync(); + } + catch (Exception exception) + { + _notificationService.ShowError("Entry updating error", exception.Message); + } + } + + private void OnFilterClicked(object sender, RoutedEventArgs args) + { + FilterFlyout.IsOpen = !FilterFlyout.IsOpen; + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/Tools/SearchElementsDialog.xaml.cs b/source/RevitLookup.UI.Framework/Views/Tools/SearchElementsDialog.xaml.cs index 0699d521..35f7478b 100644 --- a/source/RevitLookup.UI.Framework/Views/Tools/SearchElementsDialog.xaml.cs +++ b/source/RevitLookup.UI.Framework/Views/Tools/SearchElementsDialog.xaml.cs @@ -53,7 +53,7 @@ protected override void OnButtonClick(ContentDialogButton button) return; } - _navigationService.Navigate(typeof(SnoopSummaryPage)); + _navigationService.Navigate(typeof(DecompositionSummaryPage)); } base.OnButtonClick(button); diff --git a/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/PagesViewModel.cs b/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/PagesViewModel.cs index 82edb969..6dd1cd22 100644 --- a/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/PagesViewModel.cs +++ b/source/RevitLookup.UI.Playground/Client/ViewModels/Pages/PagesViewModel.cs @@ -4,10 +4,12 @@ using CommunityToolkit.Mvvm.Input; using JetBrains.Annotations; using RevitLookup.Abstractions.Services; +using RevitLookup.Abstractions.ViewModels.Summary; using RevitLookup.UI.Framework.Views.AboutProgram; using RevitLookup.UI.Framework.Views.Dashboard; using RevitLookup.UI.Framework.Views.Settings; using RevitLookup.UI.Framework.Views.Summary; +using RevitLookup.UI.Framework.Views.Tools; using RevitLookup.UI.Playground.Client.Controls; namespace RevitLookup.UI.Playground.Client.ViewModels.Pages; @@ -25,7 +27,7 @@ private void ShowDashboardPage() } [RelayCommand] - private void ShowSnoopSummaryPage() + private void ShowDecompositionSummaryPage() { var viewer = Host.CreateScope(); viewer.SizeToContent = SizeToContent.Manual; @@ -44,7 +46,25 @@ private void ShowSnoopSummaryPage() service.VisualizeDecompositionAsync(strings); }); - viewer.ShowPage(); + viewer.ShowPage(); + } + + [RelayCommand] + private void ShowEventsSummaryPage() + { + var viewer = Host.CreateScope(); + viewer.SizeToContent = SizeToContent.Manual; + viewer.Height = 500; + viewer.Width = 900; + viewer.RunService(service => { service.OnNavigatedToAsync(); }); + + viewer.Closing += (sender, _) => + { + var self = (PageViewer)sender!; + self.RunService(service => { service.OnNavigatedFromAsync(); }); + }; + + viewer.ShowPage(); } [RelayCommand] @@ -60,4 +80,14 @@ private void ShowAboutPage() var viewer = Host.CreateScope(); viewer.ShowPage(); } + + [RelayCommand] + private void ShowRevitSettingsPage() + { + var viewer = Host.CreateScope(); + viewer.SizeToContent = SizeToContent.Manual; + viewer.Height = 850; + viewer.Width = 500; + viewer.ShowPage(); + } } \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Client/Views/Pages/DesignGuidance/SymbolIconsPage.xaml b/source/RevitLookup.UI.Playground/Client/Views/Pages/DesignGuidance/SymbolIconsPage.xaml index 5522af0e..e24200cc 100644 --- a/source/RevitLookup.UI.Playground/Client/Views/Pages/DesignGuidance/SymbolIconsPage.xaml +++ b/source/RevitLookup.UI.Playground/Client/Views/Pages/DesignGuidance/SymbolIconsPage.xaml @@ -232,11 +232,10 @@ - + + + + + \ No newline at end of file diff --git a/source/RevitLookup2/Styles/ComponentStyles/MembersGrid/DataGridRowStyle.xaml b/source/RevitLookup2/Styles/ComponentStyles/MembersGrid/DataGridRowStyle.xaml new file mode 100644 index 00000000..cf477814 --- /dev/null +++ b/source/RevitLookup2/Styles/ComponentStyles/MembersGrid/DataGridRowStyle.xaml @@ -0,0 +1,49 @@ + + + + + + + + + + \ No newline at end of file diff --git a/source/RevitLookup2/Styles/ComponentStyles/MembersGrid/DataGridRowStyleSelector.cs b/source/RevitLookup2/Styles/ComponentStyles/MembersGrid/DataGridRowStyleSelector.cs new file mode 100644 index 00000000..b204d78c --- /dev/null +++ b/source/RevitLookup2/Styles/ComponentStyles/MembersGrid/DataGridRowStyleSelector.cs @@ -0,0 +1,44 @@ +using System.Windows; +using System.Windows.Controls; +using LookupEngine.Abstractions.ComponentModel; +using LookupEngine.Abstractions.Configuration; +using RevitLookup.Abstractions.ObservableModels.Decomposition; + +namespace RevitLookup2.Styles.ComponentStyles.MembersGrid; + +/// +/// Data grid row style selector +/// +public sealed class DataGridRowStyleSelector : StyleSelector +{ + public override Style? SelectStyle(object item, DependencyObject container) + { + var member = (ObservableDecomposedMember) item; + var presenter = (FrameworkElement) container; + + var styleName = SelectByType(member.Value.RawValue) ?? + SelectByDescriptor(member.Value.Descriptor); + + return (Style) presenter.FindResource(styleName); + } + + private static string? SelectByType(object? value) + { + return value switch + { + Exception => "ExceptionDataGridRowStyle", + _ => null + }; + } + + private static string SelectByDescriptor(Descriptor? descriptor) + { + return descriptor switch + { + IDescriptorEnumerator {IsEmpty: false} => "HandledDataGridRowStyle", + IDescriptorEnumerator => "DefaultLookupDataGridRowStyle", + IDescriptorCollector => "HandledDataGridRowStyle", + _ => "DefaultLookupDataGridRowStyle" + }; + } +} \ No newline at end of file diff --git a/source/RevitLookup2/Styles/ComponentStyles/ObjectsTree/TreeGroupTemplates.xaml b/source/RevitLookup2/Styles/ComponentStyles/ObjectsTree/TreeGroupTemplates.xaml new file mode 100644 index 00000000..51eb92d3 --- /dev/null +++ b/source/RevitLookup2/Styles/ComponentStyles/ObjectsTree/TreeGroupTemplates.xaml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/RevitLookup2/Styles/ComponentStyles/ObjectsTree/TreeViewItemTemplateSelector.cs b/source/RevitLookup2/Styles/ComponentStyles/ObjectsTree/TreeViewItemTemplateSelector.cs new file mode 100644 index 00000000..be83046c --- /dev/null +++ b/source/RevitLookup2/Styles/ComponentStyles/ObjectsTree/TreeViewItemTemplateSelector.cs @@ -0,0 +1,27 @@ +using System.Windows; +using System.Windows.Controls; +using RevitLookup.Abstractions.ObservableModels.Decomposition; +using Color = System.Windows.Media.Color; + +namespace RevitLookup2.Styles.ComponentStyles.ObjectsTree; + +public sealed class TreeViewItemTemplateSelector : DataTemplateSelector +{ + /// + /// Tree view row style selector + /// + public override DataTemplate? SelectTemplate(object? item, DependencyObject container) + { + if (item is null) return null; + + var presenter = (FrameworkElement) container; + var decomposedObject = (ObservableDecomposedObject) item; + var templateName = decomposedObject.RawValue switch + { + Color => "SummaryMediaColorItemTemplate", + _ => "DefaultSummaryTreeItemTemplate" + }; + + return (DataTemplate) presenter.FindResource(templateName); + } +} \ No newline at end of file diff --git a/source/RevitLookup2/Styles/Converters/ObjectColorConverter.cs b/source/RevitLookup2/Styles/Converters/ObjectColorConverter.cs new file mode 100644 index 00000000..b946478d --- /dev/null +++ b/source/RevitLookup2/Styles/Converters/ObjectColorConverter.cs @@ -0,0 +1,28 @@ +using System.Globalization; +using System.Windows.Data; +using System.Windows.Markup; +using Color = System.Windows.Media.Color; + +namespace RevitLookup2.Styles.Converters; + +public sealed class ObjectColorConverter : MarkupExtension, IValueConverter +{ + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + return value switch + { + Color color => color, + _ => throw new ArgumentOutOfRangeException(nameof(value), value, null) + }; + } + + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + return this; + } +} \ No newline at end of file diff --git a/source/RevitLookup2/Utils/RibbonExtensions.cs b/source/RevitLookup2/Utils/RibbonExtensions.cs new file mode 100644 index 00000000..56a15ff1 --- /dev/null +++ b/source/RevitLookup2/Utils/RibbonExtensions.cs @@ -0,0 +1,114 @@ +using System.Reflection; +using Autodesk.Revit.UI; +using Autodesk.Windows; +using RibbonPanel = Autodesk.Revit.UI.RibbonPanel; + +namespace RevitLookup2.Utils; + +/// +/// Contains extension methods for creating and managing custom Ribbon elements in the Revit UI. +/// These extensions provide simplified methods for adding panels, buttons, and other controls +/// to the "Add-ins" tab or any custom tab in the Revit ribbon. +/// These utilities streamline the process of integrating external commands and tools +/// into the Revit user interface. +/// +public static class RibbonExtensions +{ + /// + /// Creates or retrieves an existing panel in a specified tab of the Revit ribbon. + /// + /// The Revit application instance. + /// The name of the panel to create. + /// The name of the tab in which the panel should be located. + /// The created or existing Ribbon panel. + /// + /// If the tab doesn't exist, it will be created first. + /// If a panel with the specified name already exists within the tab, it will return that panel; otherwise, a new one will be created. + /// + /// Thrown when panelName or tabName is empty. + /// + /// Thrown if more than 100 panels were created, or if the maximum number of custom tabs (20) has been exceeded. + /// + public static RibbonPanel CreatePanel(this UIControlledApplication application, string panelName, string tabName) + { + foreach (var tab in ComponentManager.Ribbon.Tabs) + { + if ((tab.Title == tabName || tab.Id == tabName) && tab.IsVisible) + { + var cachedTabs = GetCachedTabs(); + if (cachedTabs.TryGetValue(tab.Id, out var cachedPanels)) + { + if (cachedPanels.TryGetValue(panelName, out var cachedPanel)) + { + return cachedPanel; + } + } + + var (internalPanel, panel) = CreateInternalPanel(tab.Id, panelName); + tab.Panels.Add(internalPanel); + return panel; + } + } + + application.CreateRibbonTab(tabName); + return application.CreateRibbonPanel(tabName, panelName); + } + + /// + /// Retrieves the internal instance associated with the specified . + /// This method uses reflection to access the private field "m_RibbonPanel" within the provided . + /// + /// The to extract the internal from. + /// The internal instance. + public static Autodesk.Windows.RibbonPanel GetInternalPanel(this RibbonPanel panel) + { + var panelField = panel.GetType().GetField("m_RibbonPanel", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)!; + return (Autodesk.Windows.RibbonPanel) panelField.GetValue(panel)!; + } + + [Pure] + public static Dictionary> GetCachedTabs() + { + var applicationType = typeof(UIApplication); + var panelsField = applicationType.GetField("m_ItemsNameDictionary", BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly)!; + return (Dictionary>) panelsField.GetValue(null)!; + } + + private static RibbonPanel CreatePanel(Autodesk.Windows.RibbonPanel panel, string tabId) + { + var type = typeof(RibbonPanel); +#if NETCOREAPP + var constructorInfo = type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly, + [typeof(Autodesk.Windows.RibbonPanel), typeof(string)])!; +#else + var constructorInfo = type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly, + null, + [typeof(Autodesk.Windows.RibbonPanel), typeof(string)], + null)!; +#endif + return (RibbonPanel) constructorInfo.Invoke([panel, tabId]); + } + + private static (Autodesk.Windows.RibbonPanel internalPanel, RibbonPanel panel) CreateInternalPanel(string tabId, string panelName) + { + var internalPanel = new Autodesk.Windows.RibbonPanel + { + Source = new RibbonPanelSource + { + Title = panelName + } + }; + + var cachedTabs = GetCachedTabs(); + if (!cachedTabs.TryGetValue(tabId, out var cachedPanels)) + { + cachedTabs[tabId] = cachedPanels = new Dictionary(); + } + + var panel = CreatePanel(internalPanel, tabId); + panel.Name = panelName; + cachedPanels[panelName] = panel; + + return (internalPanel, panel); + } +} \ No newline at end of file diff --git a/source/RevitLookup2/ViewModels/AboutProgram/AboutViewModel.cs b/source/RevitLookup2/ViewModels/AboutProgram/AboutViewModel.cs new file mode 100644 index 00000000..fee7d849 --- /dev/null +++ b/source/RevitLookup2/ViewModels/AboutProgram/AboutViewModel.cs @@ -0,0 +1,143 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +using System.Net.Http; +using System.Runtime; +using System.Text; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using RevitLookup.Abstractions.Options; +using RevitLookup.Abstractions.Services; +using RevitLookup.Abstractions.States; +using RevitLookup.Abstractions.ViewModels.AboutProgram; +using RevitLookup.UI.Framework.Views.AboutProgram; + +namespace RevitLookup2.ViewModels.AboutProgram; + +[UsedImplicitly] +public sealed partial class AboutViewModel : ObservableObject, IAboutViewModel +{ + private readonly IServiceProvider _serviceProvider; + private readonly ISoftwareUpdateService _updateService; + private readonly ILogger _logger; + + [ObservableProperty] private SoftwareUpdateState _state = (SoftwareUpdateState) (-1); + [ObservableProperty] private Version _currentVersion; + [ObservableProperty] private string? _newVersion; + [ObservableProperty] private string? _releaseNotesUrl; + [ObservableProperty] private string? _latestCheckDate; + [ObservableProperty] private string? _errorMessage; + [ObservableProperty] private string _runtime; + + public AboutViewModel( + IServiceProvider serviceProvider, + ISoftwareUpdateService updateService, + IOptions assemblyOptions, + ILogger logger) + { + _serviceProvider = serviceProvider; + _updateService = updateService; + _logger = logger; + + CurrentVersion = assemblyOptions.Value.Version; + Runtime = new StringBuilder() + .Append(assemblyOptions.Value.Framework) + .Append(' ') + .Append(Environment.Is64BitProcess ? "x64" : "x86") + .Append(" (") + .Append(GCSettings.IsServerGC ? "Server" : "Workstation") + .Append(" GC)") + .ToString(); + + LatestCheckDate = _updateService.LatestCheckDate?.ToString("yyyy.MM.dd HH:mm:ss"); + UpdateSoftwareState(); + } + + [RelayCommand] + private async Task CheckUpdatesAsync() + { + try + { + var result = await _updateService.CheckUpdatesAsync(); + + if (!result) + { + State = SoftwareUpdateState.UpToDate; + return; + } + + UpdateSoftwareState(); + } + catch (HttpRequestException exception) + { + State = SoftwareUpdateState.UpToDate; + _logger.LogError(exception, "Checking updates fail"); + } + catch (Exception exception) + { + State = SoftwareUpdateState.Error; + ErrorMessage = "An unknown error occurred while checking for updates"; + _logger.LogError(exception, "Checking updates fail"); + } + finally + { + LatestCheckDate = _updateService.LatestCheckDate?.ToString("yyyy.MM.dd HH:mm:ss"); + } + } + + [RelayCommand] + private async Task DownloadUpdateAsync() + { + try + { + await _updateService.DownloadUpdate(); + State = SoftwareUpdateState.ReadyToInstall; + } + catch (Exception exception) + { + State = SoftwareUpdateState.Error; + ErrorMessage = "An error occurred while downloading the update"; + _logger.LogError(exception, "Downloading updates fail"); + } + } + + [RelayCommand] + private async Task ShowSoftwareDialogAsync() + { + var dialog = _serviceProvider.GetRequiredService(); + await dialog.ShowAsync(); + } + + private void UpdateSoftwareState() + { + if (_updateService.LocalFilePath is not null) + { + State = SoftwareUpdateState.ReadyToInstall; + return; + } + + if (_updateService.NewVersion is null) return; + + NewVersion = _updateService.NewVersion; + ReleaseNotesUrl = _updateService.ReleaseNotesUrl; + State = SoftwareUpdateState.ReadyToDownload; + } +} \ No newline at end of file diff --git a/source/RevitLookup2/ViewModels/AboutProgram/OpenSourceViewModel.cs b/source/RevitLookup2/ViewModels/AboutProgram/OpenSourceViewModel.cs new file mode 100644 index 00000000..5b14d0e4 --- /dev/null +++ b/source/RevitLookup2/ViewModels/AboutProgram/OpenSourceViewModel.cs @@ -0,0 +1,95 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +using RevitLookup.Abstractions.Models.AboutProgram; +using RevitLookup.Abstractions.ViewModels.AboutProgram; + +namespace RevitLookup2.ViewModels.AboutProgram; + +[UsedImplicitly] +public sealed class OpenSourceViewModel : ObservableObject, IOpenSourceViewModel +{ + public List Software { get; } = + [ + new() + { + SoftwareName = "CommunityToolkit.Mvvm", + SoftwareUri = "https://github.com/CommunityToolkit/dotnet", + LicenseName = "MIT License", + LicenseUri = "https://github.com/CommunityToolkit/dotnet/blob/main/License.md" + }, + new() + { + SoftwareName = "Microsoft.Extensions.Hosting", + SoftwareUri = "https://github.com/dotnet/runtime", + LicenseName = "MIT License", + LicenseUri = "https://github.com/dotnet/runtime/blob/main/LICENSE.TXT" + }, + new() + { + SoftwareName = "Nice3point.Revit.Api", + SoftwareUri = "https://github.com/Nice3point/RevitApi", + LicenseName = "MIT License", + LicenseUri = "https://github.com/Nice3point/RevitApi/blob/main/License.md" + }, + new() + { + SoftwareName = "Nice3point.Revit.Extensions", + SoftwareUri = "https://github.com/Nice3point/RevitExtensions", + LicenseName = "MIT License", + LicenseUri = "https://github.com/Nice3point/RevitExtensions/blob/main/License.md" + }, + new() + { + SoftwareName = "Nice3point.Revit.Templates", + SoftwareUri = "https://github.com/Nice3point/RevitTemplates", + LicenseName = "MIT License", + LicenseUri = "https://github.com/Nice3point/RevitTemplates/blob/main/License.md" + }, + new() + { + SoftwareName = "Nice3point.Revit.Toolkit", + SoftwareUri = "https://github.com/Nice3point/RevitToolkit", + LicenseName = "MIT License", + LicenseUri = "https://github.com/Nice3point/RevitToolkit/blob/main/License.md" + }, + new() + { + SoftwareName = "PolySharp", + SoftwareUri = "https://github.com/Sergio0694/PolySharp", + LicenseName = "MIT License", + LicenseUri = "https://github.com/Sergio0694/PolySharp/blob/main/LICENSE" + }, + new() + { + SoftwareName = "Serilog", + SoftwareUri = "https://github.com/serilog/serilog", + LicenseName = "Apache License 2.0", + LicenseUri = "https://github.com/serilog/serilog/blob/dev/LICENSE" + }, + new() + { + SoftwareName = "WPF-UI", + SoftwareUri = "https://github.com/lepoco/wpfui", + LicenseName = "MIT License", + LicenseUri = "https://github.com/lepoco/wpfui/blob/main/LICENSE" + } + ]; +} \ No newline at end of file diff --git a/source/RevitLookup2/ViewModels/Dashboard/DashboardViewModel.cs b/source/RevitLookup2/ViewModels/Dashboard/DashboardViewModel.cs new file mode 100644 index 00000000..696de85f --- /dev/null +++ b/source/RevitLookup2/ViewModels/Dashboard/DashboardViewModel.cs @@ -0,0 +1,412 @@ +using Microsoft.Extensions.DependencyInjection; +using RevitLookup.Abstractions.Models.Summary; +using RevitLookup.Abstractions.Models.UserInterface; +using RevitLookup.Abstractions.Services; +using RevitLookup.Abstractions.ViewModels.Dashboard; +using RevitLookup.UI.Framework.Views.Summary; +using RevitLookup.UI.Framework.Views.Tools; +using Wpf.Ui; +using Wpf.Ui.Controls; +using ArgumentOutOfRangeException = System.ArgumentOutOfRangeException; + +namespace RevitLookup2.ViewModels.Dashboard; + +[UsedImplicitly] +public sealed partial class DashboardViewModel : IDashboardViewModel +{ + private readonly IServiceProvider _serviceProvider; + private readonly INavigationService _navigationService; + private readonly INotificationService _notificationService; + private readonly IVisualDecompositionService _visualDecompositionService; + + public DashboardViewModel( + IServiceProvider serviceProvider, + INavigationService navigationService, + INotificationService notificationService, + IVisualDecompositionService visualDecompositionService) + { + _serviceProvider = serviceProvider; + _navigationService = navigationService; + _notificationService = notificationService; + _visualDecompositionService = visualDecompositionService; + + NavigationGroups = + [ + new NavigationCardGroup + { + GroupName = "Workspace", + Items = + [ + new NavigationCardItem + { + Title = "Active view", + Description = "Explore the active view, which defines how the model is presented visually", + Icon = SymbolRegular.Image24, + Command = NavigatePageCommand, + CommandParameter = "view" + }, + new NavigationCardItem + { + Title = "Active document", + Description = "Explore the document currently open in the Revit session", + Icon = SymbolRegular.Document24, + Command = NavigatePageCommand, + CommandParameter = "document" + }, + new NavigationCardItem + { + Title = "Application", + Description = "Explore the application object, providing access to application wide data and settings", + Icon = SymbolRegular.Apps24, + Command = NavigatePageCommand, + CommandParameter = "application" + }, + new NavigationCardItem + { + Title = "UI Application", + Description = "Explore an active session of the Autodesk Revit user interface and its customization options", + Icon = SymbolRegular.WindowApps24, + Command = NavigatePageCommand, + CommandParameter = "uiApplication" + }, + new NavigationCardItem + { + Title = "Database", + Description = "Explore the Revit model database containing all elements and their relationships", + Icon = SymbolRegular.Database24, + Command = NavigatePageCommand, + CommandParameter = "database" + }, + new NavigationCardItem + { + Title = "Dependent elements", + Description = "Explore the children of the selected elements", + Icon = SymbolRegular.DataLine24, + Command = NavigatePageCommand, + CommandParameter = "dependents" + } + ] + }, + new NavigationCardGroup + { + GroupName = "Interaction", + Items = + [ + new NavigationCardItem + { + Title = "Selection", + Description = "Explore the currently selected elements in the model", + Icon = SymbolRegular.SquareHint24, + Command = NavigatePageCommand, + CommandParameter = "selection" + }, + new NavigationCardItem + { + Title = "Linked element", + Description = "Select and explore an element from linked model", + Icon = SymbolRegular.LinkSquare24, + Command = NavigatePageCommand, + CommandParameter = "linked" + }, + new NavigationCardItem + { + Title = "Face", + Description = "Select and explore a face of the element's geometry", + Icon = SymbolRegular.LayerDiagonal20, + Command = NavigatePageCommand, + CommandParameter = "face" + }, + new NavigationCardItem + { + Title = "Edge", + Description = "Select and explore the edge of the element's geometry", + Icon = SymbolRegular.Line24, + Command = NavigatePageCommand, + CommandParameter = "edge" + }, + new NavigationCardItem + { + Title = "Point", + Description = "Select and explore a point in the model, such as specific location or coordinate", + Icon = SymbolRegular.Location24, + Command = NavigatePageCommand, + CommandParameter = "point" + }, + new NavigationCardItem + { + Title = "Sub-element", + Description = "Select and explore a sub-element, such as a part or detail of an element", + Icon = SymbolRegular.Subtitles24, + Command = NavigatePageCommand, + CommandParameter = "subElement" + } + ] + }, + new NavigationCardGroup + { + GroupName = "Maintenance", + Items = + [ + new NavigationCardItem + { + Title = "Component manager", + Description = "Explore the component manager, managing the low-level visual representation of Revit", + Icon = SymbolRegular.SlideTextMultiple32, + Command = NavigatePageCommand, + CommandParameter = "components" + }, + new NavigationCardItem + { + Title = "Performance adviser", + Description = "Explore a tool to report performance problems in the active document", + Icon = SymbolRegular.HeartPulse24, + Command = NavigatePageCommand, + CommandParameter = "performance" + } + ] + }, + new NavigationCardGroup + { + GroupName = "Registry", + Items = + [ + new NavigationCardItem + { + Title = "Updaters", + Description = "Explore the storage of all updaters registered in the current session", + Icon = SymbolRegular.Whiteboard24, + Command = NavigatePageCommand, + CommandParameter = "updaters" + }, + new NavigationCardItem + { + Title = "Schemas", + Description = "Explore the memory storage of all schemas registered in the Extensible Storage framework", + Icon = SymbolRegular.Box24, + Command = NavigatePageCommand, + CommandParameter = "schemas" + }, + new NavigationCardItem + { + Title = "Services", + Description = "Explore the services that provide extended functionality in Revit", + Icon = SymbolRegular.WeatherCloudy24, + Command = NavigatePageCommand, + CommandParameter = "services" + } + ] + }, + new NavigationCardGroup + { + GroupName = "Units", + Items = + [ + new NavigationCardItem + { + Title = "BuiltIn Parameters", + Description = "Explore parameters predefined in Revit", + Icon = SymbolRegular.LeafOne24, + Command = OpenDialogCommand, + CommandParameter = "parameters" + }, + new NavigationCardItem + { + Title = "BuiltIn Categories", + Description = "Explore categories predefined in Revit", + Icon = SymbolRegular.LeafTwo24, + Command = OpenDialogCommand, + CommandParameter = "categories" + }, + new NavigationCardItem + { + Title = "Forge Schema", + Description = "Explore Forge schema definitions used in Revit", + Icon = SymbolRegular.LeafThree24, + Command = OpenDialogCommand, + CommandParameter = "forge" + } + ] + }, + new NavigationCardGroup + { + GroupName = "Tools", + Items = + [ + new NavigationCardItem + { + Title = "Search elements", + Description = "Search specific elements in Revit", + Icon = SymbolRegular.SlideSearch24, + Command = OpenDialogCommand, + CommandParameter = "search" + }, + new NavigationCardItem + { + Title = "Event monitor", + Description = "Monitor all incoming events in a Revit session", + Icon = SymbolRegular.DesktopPulse24, + Command = NavigatePageCommand, + CommandParameter = "events" + }, + new NavigationCardItem + { + Title = "Revit settings", + Description = "Inspect configuration and Revit settings available in the application", + Icon = SymbolRegular.LauncherSettings24, + Command = NavigatePageCommand, + CommandParameter = "revitSettings" + }, + new NavigationCardItem + { + Title = "Modules", + Description = "Inspect the dynamic link libraries (DLLs) and executables that Revit uses", + Icon = SymbolRegular.BroadActivityFeed24, + Command = OpenDialogCommand, + CommandParameter = "modules" + } + ] + } + ]; + } + + public List NavigationGroups { get; } + + [RelayCommand] + private async Task NavigatePage(string? parameter) + { + if (!ValidateContext()) return; + + try + { + switch (parameter) + { + case "view": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.View); + _navigationService.Navigate(typeof(DecompositionSummaryPage)); + break; + case "document": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.Document); + _navigationService.Navigate(typeof(DecompositionSummaryPage)); + break; + case "application": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.Application); + _navigationService.Navigate(typeof(DecompositionSummaryPage)); + break; + case "uiApplication": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.UiApplication); + _navigationService.Navigate(typeof(DecompositionSummaryPage)); + break; + case "database": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.Database); + _navigationService.Navigate(typeof(DecompositionSummaryPage)); + break; + case "dependents": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.DependentElements); + _navigationService.Navigate(typeof(DecompositionSummaryPage)); + break; + case "selection": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.Selection); + _navigationService.Navigate(typeof(DecompositionSummaryPage)); + break; + case "linked": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.LinkedElement); + _navigationService.Navigate(typeof(DecompositionSummaryPage)); + break; + case "face": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.Face); + _navigationService.Navigate(typeof(DecompositionSummaryPage)); + break; + case "edge": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.Edge); + _navigationService.Navigate(typeof(DecompositionSummaryPage)); + break; + case "point": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.Point); + _navigationService.Navigate(typeof(DecompositionSummaryPage)); + break; + case "subElement": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.SubElement); + _navigationService.Navigate(typeof(DecompositionSummaryPage)); + break; + case "components": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.ComponentManager); + _navigationService.Navigate(typeof(DecompositionSummaryPage)); + break; + case "performance": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.PerformanceAdviser); + _navigationService.Navigate(typeof(DecompositionSummaryPage)); + break; + case "updaters": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.UpdaterRegistry); + _navigationService.Navigate(typeof(DecompositionSummaryPage)); + break; + case "services": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.Services); + _navigationService.Navigate(typeof(DecompositionSummaryPage)); + break; + case "schemas": + await _visualDecompositionService.VisualizeDecompositionAsync(KnownDecompositionObject.Schemas); + _navigationService.Navigate(typeof(DecompositionSummaryPage)); + break; + case "events": + _navigationService.Navigate(typeof(EventsSummaryPage)); + break; + case "revitSettings": + _navigationService.NavigateWithHierarchy(typeof(RevitSettingsPage)); + break; + default: + throw new ArgumentOutOfRangeException(nameof(parameter), parameter); + } + } + catch (Exception exception) + { + _notificationService.ShowError("Failed to run tool", exception); + } + } + + [RelayCommand] + private async Task OpenDialog(string parameter) + { + try + { + if (!ValidateContext()) return; + + switch (parameter) + { + case "parameters": + var unitsDialog = _serviceProvider.GetRequiredService(); + await unitsDialog.ShowParametersDialogAsync(); + return; + case "categories": + unitsDialog = _serviceProvider.GetRequiredService(); + await unitsDialog.ShowCategoriesDialogAsync(); + return; + case "forge": + unitsDialog = _serviceProvider.GetRequiredService(); + await unitsDialog.ShowForgeSchemaDialogAsync(); + return; + case "search": + var searchDialog = _serviceProvider.GetRequiredService(); + await searchDialog.ShowAsync(); + return; + case "modules": + var modulesDialog = _serviceProvider.GetRequiredService(); + await modulesDialog.ShowAsync(); + return; + } + } + catch (Exception exception) + { + _notificationService.ShowError("Failed to open dialog", exception); + } + } + + //TODO: allow context independent commands + private bool ValidateContext() + { + if (Context.ActiveUiDocument is not null) return true; + + _notificationService.ShowWarning("Invalid context", "There are no open documents"); + return false; + } +} \ No newline at end of file diff --git a/source/RevitLookup2/ViewModels/Settings/SettingsViewModel.cs b/source/RevitLookup2/ViewModels/Settings/SettingsViewModel.cs new file mode 100644 index 00000000..bf02c51d --- /dev/null +++ b/source/RevitLookup2/ViewModels/Settings/SettingsViewModel.cs @@ -0,0 +1,194 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +using System.Diagnostics; +using Microsoft.Extensions.DependencyInjection; +using RevitLookup.Abstractions.Services; +using RevitLookup.Abstractions.ViewModels.Settings; +using RevitLookup.UI.Framework.Views.Settings; +using RevitLookup.UI.Framework.Views.Windows; +using RevitLookup2.Services.UserInterface; +using Wpf.Ui; +using Wpf.Ui.Animations; +using Wpf.Ui.Appearance; +using Wpf.Ui.Controls; + +namespace RevitLookup2.ViewModels.Settings; + +[UsedImplicitly] +public sealed partial class SettingsViewModel : ObservableObject, ISettingsViewModel +{ + private readonly IServiceProvider _serviceProvider; + private readonly INavigationService _navigationService; + private readonly INotificationService _notificationService; + private readonly ISettingsService _settingsService; + private readonly IWindowIntercomService _intercomService; + private readonly RevitThemeWatcherService _themeWatcherService; + private readonly bool _initialized; + + [ObservableProperty] private ApplicationTheme _theme; + [ObservableProperty] private WindowBackdropType _background; + + [ObservableProperty] private bool _useTransition; + [ObservableProperty] private bool _useHardwareRendering; + [ObservableProperty] private bool _useSizeRestoring; + [ObservableProperty] private bool _useModifyTab; + + public SettingsViewModel( + IServiceProvider serviceProvider, + INavigationService navigationService, + INotificationService notificationService, + ISettingsService settingsService, + IWindowIntercomService intercomService, + RevitThemeWatcherService themeWatcherService) + { + _serviceProvider = serviceProvider; + _navigationService = navigationService; + _notificationService = notificationService; + _settingsService = settingsService; + _intercomService = intercomService; + _themeWatcherService = themeWatcherService; + + ApplySettings(); + _initialized = true; + } + + public List Themes { get; } = + [ + ApplicationTheme.Auto, + ApplicationTheme.Light, + ApplicationTheme.Dark, + ApplicationTheme.HighContrast + ]; + + public List BackgroundEffects { get; } = + [ + WindowBackdropType.None, + WindowBackdropType.Acrylic, + WindowBackdropType.Tabbed, + WindowBackdropType.Mica + ]; + + [RelayCommand] + private async Task ResetSettings() + { + try + { + var dialog = _serviceProvider.GetRequiredService(); + var result = await dialog.ShowAsync(); + if (result != ContentDialogResult.Primary) return; + + _settingsService.ResetSettings(); + ApplySettings(); + + _notificationService.ShowSuccess("Reset settings", "Settings successfully reset to default"); + } + catch (Exception exception) + { + _notificationService.ShowError("Reset settings error", exception); + } + } + + partial void OnThemeChanged(ApplicationTheme value) + { + if (!_initialized) return; + + _settingsService.GeneralSettings.Theme = value; + _themeWatcherService.Watch(); + } + + partial void OnThemeChanged(ApplicationTheme oldValue, ApplicationTheme newValue) + { + if (!_initialized) return; + + if (oldValue == ApplicationTheme.Auto) + { + _themeWatcherService.Unwatch(); + } + } + + partial void OnBackgroundChanged(WindowBackdropType value) + { + if (!_initialized) return; + + _settingsService.GeneralSettings.Background = value; + _themeWatcherService.Watch(); + } + + partial void OnUseTransitionChanged(bool value) + { + if (!_initialized) return; + + var navigationControl = _navigationService.GetNavigationControl(); + var transition = _settingsService.GeneralSettings.Transition = value + ? (Transition) NavigationView.TransitionProperty.DefaultMetadata.DefaultValue + : Transition.None; + + _settingsService.GeneralSettings.Transition = transition; + navigationControl.Transition = transition; + } + + partial void OnUseHardwareRenderingChanged(bool value) + { + if (!_initialized) return; + + _settingsService.GeneralSettings.UseHardwareRendering = value; + if (value) Application.EnableHardwareRendering(); + else Application.DisableHardwareRendering(); + } + + partial void OnUseSizeRestoringChanged(bool value) + { + if (!_initialized) return; + + _settingsService.GeneralSettings.UseSizeRestoring = value; + if (_intercomService.GetHost() is not RevitLookupView lookupView) + { + Debug.Fail("Settings page running inside invalid host"); + return; + } + + if (value) + { + lookupView.EnableSizeTracking(); + } + else + { + lookupView.DisableSizeTracking(); + } + } + + partial void OnUseModifyTabChanged(bool value) + { + if (!_initialized) return; + + _settingsService.GeneralSettings.UseModifyTab = value; + } + + private void ApplySettings() + { + Theme = _settingsService.GeneralSettings.Theme; + Background = _settingsService.GeneralSettings.Background; + UseTransition = _settingsService.GeneralSettings.Transition != Transition.None; + UseHardwareRendering = _settingsService.GeneralSettings.UseHardwareRendering; + UseSizeRestoring = _settingsService.GeneralSettings.UseSizeRestoring; + UseModifyTab = _settingsService.GeneralSettings.UseModifyTab; + } +} \ No newline at end of file diff --git a/source/RevitLookup2/ViewModels/Summary/MockDecompositionSummaryViewModel.cs b/source/RevitLookup2/ViewModels/Summary/MockDecompositionSummaryViewModel.cs new file mode 100644 index 00000000..003fa371 --- /dev/null +++ b/source/RevitLookup2/ViewModels/Summary/MockDecompositionSummaryViewModel.cs @@ -0,0 +1,177 @@ +using System.Diagnostics.CodeAnalysis; +using LookupEngine; +using Microsoft.Extensions.Logging; +using RevitLookup.Abstractions.ObservableModels.Decomposition; +using RevitLookup.Abstractions.Services; +using RevitLookup.Abstractions.ViewModels.Summary; +using RevitLookup.UI.Framework.Views.Summary; +using RevitLookup2.Core; +using RevitLookup2.Mappers; +#if NETFRAMEWORK +using RevitLookup.UI.Framework.Extensions; +#endif + +namespace RevitLookup2.ViewModels.Summary; + +[UsedImplicitly] +public sealed partial class MockDecompositionSummaryViewModel( + ISettingsService settingsService, + IWindowIntercomService intercomService, + INotificationService notificationService, + ILogger logger) + : ObservableObject, IDecompositionSummaryViewModel +{ + [ObservableProperty] private string _searchText = string.Empty; + [ObservableProperty] private ObservableDecomposedObject? _selectedDecomposedObject; + [ObservableProperty] private List _decomposedObjects = []; + [ObservableProperty] private List _filteredDecomposedObjects = []; + + [RelayCommand] + private async Task RefreshMembersAsync() + { + foreach (var decomposedObject in DecomposedObjects) + { + decomposedObject.Members.Clear(); + } + + try + { + await FetchMembersAsync(SelectedDecomposedObject); + } + catch (Exception exception) + { + logger.LogError(exception, "Members decomposing failed"); + notificationService.ShowError("Lookup engine error", exception); + } + } + + public void Navigate(object? value) + { + Host.GetService() + .Decompose(value) + .DependsOn(intercomService.GetHost()) + .Show(); + } + + public void Navigate(ObservableDecomposedObject value) + { + Host.GetService() + .Decompose(value) + .DependsOn(intercomService.GetHost()) + .Show(); + } + + public void Navigate(List values) + { + Host.GetService() + .Decompose(values) + .DependsOn(intercomService.GetHost()) + .Show(); + } + + partial void OnDecomposedObjectsChanged(List value) + { + OnSearchTextChanged(SearchText); + } + + async partial void OnSearchTextChanged(string value) + { + try + { + if (string.IsNullOrEmpty(SearchText)) + { + FilteredDecomposedObjects = ApplyGrouping(DecomposedObjects); + return; + } + + FilteredDecomposedObjects = await Task.Run(() => + { + var formattedText = value.Trim(); + var searchResults = new List(); + // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator + foreach (var item in DecomposedObjects) + { + if (item.Name.Contains(formattedText, StringComparison.OrdinalIgnoreCase) || item.Name.Contains(formattedText, StringComparison.OrdinalIgnoreCase)) + { + searchResults.Add(item); + } + } + + return ApplyGrouping(searchResults); + }); + + if (FilteredDecomposedObjects.Count == 0) + { + SelectedDecomposedObject = null; + } + } + catch + { + // ignored + } + } + + async partial void OnSelectedDecomposedObjectChanged(ObservableDecomposedObject? value) + { + try + { + await FetchMembersAsync(value); + } + catch (Exception exception) + { + logger.LogError(exception, "Members decomposing failed"); + notificationService.ShowError("Lookup engine error", exception); + } + } + + private async Task FetchMembersAsync(ObservableDecomposedObject? value) + { + if (value is null) return; + if (value.Members.Count > 0) return; + + value.Members = await DecomposeMembersAsync(value); + } + + [SuppressMessage("ReSharper", "ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator")] + private async Task> DecomposeMembersAsync(ObservableDecomposedObject decomposedObject) + { + var options = new DecomposeOptions + { + IncludeRoot = settingsService.GeneralSettings.IncludeRootHierarchy, + IncludeFields = settingsService.GeneralSettings.IncludeFields, + IncludeEvents = settingsService.GeneralSettings.IncludeEvents, + IncludeUnsupported = settingsService.GeneralSettings.IncludeUnsupported, + IncludePrivateMembers = settingsService.GeneralSettings.IncludePrivate, + IncludeStaticMembers = settingsService.GeneralSettings.IncludeStatic, + EnableExtensions = settingsService.GeneralSettings.IncludeExtensions + }; + + //TODO test task run + return await RevitShell.AsyncMembersHandler.RaiseAsync(_ => + { + var decomposedMembers = LookupComposer.DecomposeMembers(decomposedObject.RawValue, options); + var members = new List(decomposedMembers.Count); + + foreach (var decomposedMember in decomposedMembers) + { + members.Add(DecompositionResultMapper.Convert(decomposedMember)); + } + + return members; + }); + } + + private List ApplyGrouping(List objects) + { + return objects + .OrderBy(data => data.TypeName) + .ThenBy(data => data.Name) + .GroupBy(data => data.TypeName) + .Select(group => new ObservableDecomposedObjectsGroup + { + GroupName = group.Key, + GroupItems = group.ToList() + }) + .ToList(); + } +} \ No newline at end of file diff --git a/source/RevitLookup2/ViewModels/Summary/MockEventsSummaryViewModel.cs b/source/RevitLookup2/ViewModels/Summary/MockEventsSummaryViewModel.cs new file mode 100644 index 00000000..490478b8 --- /dev/null +++ b/source/RevitLookup2/ViewModels/Summary/MockEventsSummaryViewModel.cs @@ -0,0 +1,214 @@ +using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; +using LookupEngine; +using Microsoft.Extensions.Logging; +using RevitLookup.Abstractions.Models.EventArgs; +using RevitLookup.Abstractions.ObservableModels.Decomposition; +using RevitLookup.Abstractions.Services; +using RevitLookup.Abstractions.ViewModels.Summary; +using RevitLookup.UI.Framework.Extensions; +using RevitLookup.UI.Framework.Views.Summary; +using RevitLookup2.Core; +using RevitLookup2.Mappers; +using RevitLookup2.Services.Summary; +#if NETFRAMEWORK +using RevitLookup.UI.Framework.Extensions; +#endif + +namespace RevitLookup2.ViewModels.Summary; + +[UsedImplicitly] +public sealed partial class MockEventsSummaryViewModel( + ISettingsService settingsService, + IWindowIntercomService intercomService, + INotificationService notificationService, + EventsMonitoringService monitoringService, + ILogger logger) + : ObservableObject, IEventsSummaryViewModel +{ + [ObservableProperty] private string _searchText = string.Empty; + [ObservableProperty] private ObservableDecomposedObject? _selectedDecomposedObject; + [ObservableProperty] private List _decomposedObjects = []; + [ObservableProperty] private ObservableCollection _filteredDecomposedObjects = []; + + [RelayCommand] + private async Task RefreshMembersAsync() + { + foreach (var decomposedObject in DecomposedObjects) + { + decomposedObject.Members.Clear(); + } + + try + { + await FetchMembersAsync(SelectedDecomposedObject); + } + catch (Exception exception) + { + logger.LogError(exception, "Members decomposing failed"); + notificationService.ShowError("Lookup engine error", exception); + } + } + + public Task OnNavigatedToAsync() + { + monitoringService.Subscribe(); + monitoringService.EventInvoked += OnEventInvoked; + + return Task.CompletedTask; + } + + public Task OnNavigatedFromAsync() + { + monitoringService.EventInvoked -= OnEventInvoked; + monitoringService.Unsubscribe(); + + return Task.CompletedTask; + } + + public void Navigate(object? value) + { + Host.GetService() + .Decompose(value) + .DependsOn(intercomService.GetHost()) + .Show(); + } + + public void Navigate(ObservableDecomposedObject value) + { + Host.GetService() + .Decompose(value) + .DependsOn(intercomService.GetHost()) + .Show(); + } + + public void Navigate(List values) + { + Host.GetService() + .Decompose(values) + .DependsOn(intercomService.GetHost()) + .Show(); + } + + partial void OnDecomposedObjectsChanged(List value) + { + OnSearchTextChanged(SearchText); + } + + async partial void OnSearchTextChanged(string value) + { + try + { + if (string.IsNullOrEmpty(SearchText)) + { + FilteredDecomposedObjects = DecomposedObjects.ToObservableCollection(); + return; + } + + FilteredDecomposedObjects = await Task.Run(() => + { + var formattedText = value.Trim(); + var searchResults = new List(); + // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator + foreach (var item in DecomposedObjects) + { + if (item.Name.Contains(formattedText, StringComparison.OrdinalIgnoreCase) || item.Name.Contains(formattedText, StringComparison.OrdinalIgnoreCase)) + { + searchResults.Add(item); + } + } + + return searchResults.ToObservableCollection(); + }); + + if (FilteredDecomposedObjects.Count == 0) + { + SelectedDecomposedObject = null; + } + } + catch + { + // ignored + } + } + + async partial void OnSelectedDecomposedObjectChanged(ObservableDecomposedObject? value) + { + try + { + await FetchMembersAsync(value); + } + catch (Exception exception) + { + logger.LogError(exception, "Members decomposing failed"); + notificationService.ShowError("Lookup engine error", exception); + } + } + + private async void OnEventInvoked(object? sender, EventInfoArgs args) + { + try + { + var options = CreateDecomposeOptions(); + var decomposedObject = await RevitShell.AsyncObjectHandler.RaiseAsync(application => + { + var result = LookupComposer.Decompose(args.Arguments, options); + var convert = DecompositionResultMapper.Convert(result); + convert.Name += DateTime.Now.ToString("HH:mm:ss"); + return convert; + }); + + DecomposedObjects.Insert(0, decomposedObject); + + if (SearchText == string.Empty) FilteredDecomposedObjects.Insert(0, decomposedObject); + else OnSearchTextChanged(SearchText); + } + catch (Exception exception) + { + logger.LogError(exception, "Events data parsing error"); + notificationService.ShowError("Events data parsing error", exception); + } + } + + private async Task FetchMembersAsync(ObservableDecomposedObject? decomposedObject) + { + if (decomposedObject is null) return; + if (decomposedObject.Members.Count > 0) return; + + decomposedObject.Members = await DecomposeMembersAsync(decomposedObject); + } + + [SuppressMessage("ReSharper", "ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator")] + private async Task> DecomposeMembersAsync(ObservableDecomposedObject decomposedObject) + { + var options = CreateDecomposeOptions(); + + //TODO test task run + return await RevitShell.AsyncMembersHandler.RaiseAsync(_ => + { + var decomposedMembers = LookupComposer.DecomposeMembers(decomposedObject.RawValue, options); + var members = new List(decomposedMembers.Count); + + foreach (var decomposedMember in decomposedMembers) + { + members.Add(DecompositionResultMapper.Convert(decomposedMember)); + } + + return members; + }); + } + + private DecomposeOptions CreateDecomposeOptions() + { + return new DecomposeOptions + { + IncludeRoot = settingsService.GeneralSettings.IncludeRootHierarchy, + IncludeFields = settingsService.GeneralSettings.IncludeFields, + IncludeEvents = settingsService.GeneralSettings.IncludeEvents, + IncludeUnsupported = settingsService.GeneralSettings.IncludeUnsupported, + IncludePrivateMembers = settingsService.GeneralSettings.IncludePrivate, + IncludeStaticMembers = settingsService.GeneralSettings.IncludeStatic, + EnableExtensions = settingsService.GeneralSettings.IncludeExtensions + }; + } +} \ No newline at end of file diff --git a/source/RevitLookup2/ViewModels/Tools/ModulesViewModel.cs b/source/RevitLookup2/ViewModels/Tools/ModulesViewModel.cs new file mode 100644 index 00000000..666fef42 --- /dev/null +++ b/source/RevitLookup2/ViewModels/Tools/ModulesViewModel.cs @@ -0,0 +1,104 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + + +using System.Diagnostics.CodeAnalysis; +using System.Runtime.Loader; +using RevitLookup.Abstractions.Models.Tools; +using RevitLookup.Abstractions.ViewModels.Tools; +#if NETFRAMEWORK +using RevitLookup.UI.Framework.Extensions; +#endif + +namespace RevitLookup2.ViewModels.Tools; + +[UsedImplicitly] +public sealed partial class ModulesViewModel : ObservableObject, IModulesViewModel +{ + [ObservableProperty] private string _searchText = string.Empty; + [ObservableProperty] private List _modules = []; + [ObservableProperty] private List _filteredModules = []; + + public ModulesViewModel() + { + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + Modules = new List(assemblies.Length); + + for (var i = 0; i < assemblies.Length; i++) + { + var assembly = assemblies[i]; + var assemblyName = assembly.GetName(); + var module = new ModuleInfo + { + Name = assemblyName.Name ?? string.Empty, + Path = assembly.IsDynamic ? string.Empty : assembly.Location, + Order = i + 1, + Version = assemblyName.Version is null ? string.Empty : assemblyName.Version.ToString(), +#if NETCOREAPP + Container = AssemblyLoadContext.GetLoadContext(assembly)?.Name ?? string.Empty +#else + Container = AppDomain.CurrentDomain.FriendlyName +#endif + }; + + Modules.Add(module); + } + } + + [SuppressMessage("ReSharper", "ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator")] + async partial void OnSearchTextChanged(string value) + { + try + { + if (string.IsNullOrEmpty(SearchText)) + { + FilteredModules = Modules; + return; + } + + FilteredModules = await Task.Run(() => + { + var formattedText = value.Trim(); + var searchResults = new List(); + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var module in Modules) + { + if (module.Name.Contains(formattedText, StringComparison.OrdinalIgnoreCase) || + module.Path.Contains(formattedText, StringComparison.OrdinalIgnoreCase) || + module.Version.Contains(formattedText, StringComparison.OrdinalIgnoreCase)) + { + searchResults.Add(module); + } + } + + return searchResults; + }); + } + catch + { + // ignored + } + } + + partial void OnModulesChanged(List value) + { + FilteredModules = value; + } +} \ No newline at end of file diff --git a/source/RevitLookup2/ViewModels/Tools/RevitSettingsViewModel.cs b/source/RevitLookup2/ViewModels/Tools/RevitSettingsViewModel.cs new file mode 100644 index 00000000..557e960b --- /dev/null +++ b/source/RevitLookup2/ViewModels/Tools/RevitSettingsViewModel.cs @@ -0,0 +1,263 @@ +using System.Collections.ObjectModel; +using System.IO; +using System.Linq.Expressions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using RevitLookup.Abstractions.ObservableModels.Entries; +using RevitLookup.Abstractions.Services; +using RevitLookup.Abstractions.ViewModels.Tools; +using RevitLookup.Common.Tools; +using RevitLookup.UI.Framework.Views.EditDialogs; +using RevitLookup2.Core.Tools; +using Wpf.Ui.Controls; + +namespace RevitLookup2.ViewModels.Tools; + +[UsedImplicitly] +public sealed partial class RevitSettingsViewModel( + IServiceProvider serviceProvider, + INotificationService notificationService, + ILogger logger) + : ObservableObject, IRevitSettingsViewModel +{ + private readonly RevitConfigurator _configurator = new(); + private TaskNotifier>? _initializationTask; + + [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(ClearFiltersCommand))] private bool _filtered; + [ObservableProperty] private string _categoryFilter = string.Empty; + [ObservableProperty] private string _propertyFilter = string.Empty; + [ObservableProperty] private string _valueFilter = string.Empty; + [ObservableProperty] private bool _showUserSettingsFilter; + [ObservableProperty] private ObservableIniEntry? _selectedEntry; + + [ObservableProperty] private List _entries = []; + [ObservableProperty] private ObservableCollection _filteredEntries = []; + + public Task>? InitializationTask + { + get => _initializationTask!; + private set => SetPropertyAndNotifyOnCompletion(ref _initializationTask, value); + } + + public async Task InitializeAsync() + { + try + { + InitializationTask = _configurator.ReadAsync(); + Entries = await InitializationTask; + } + catch (Exception exception) + { + const string message = "Unavailable to parse Revit configuration"; + + logger.LogError(exception, message); + notificationService.ShowError(message, exception); + } + } + + [RelayCommand] + private async Task CreateEntry() + { + try + { + var dialog = serviceProvider.GetRequiredService(); + var result = await dialog.ShowCreateDialogAsync(SelectedEntry); + if (result == ContentDialogResult.Primary) + { + if (dialog.Entry.Category.IsNullOrWhiteSpace()) return; + if (dialog.Entry.Property.IsNullOrWhiteSpace()) return; + + Entries.Add(dialog.Entry); + FilteredEntries.Add(dialog.Entry); + _ = Task.Run(SaveAsync); + } + } + catch (Exception exception) + { + const string message = "Failed to create a new entry"; + + logger.LogError(exception, message); + notificationService.ShowError(message, exception); + } + } + + [RelayCommand] + private void ActivateEntry(ObservableIniEntry entry) + { + Task.Run(SaveAsync); + } + + [RelayCommand] + private void DeleteEntry(ObservableIniEntry entry) + { + Entries.Remove(entry); + FilteredEntries.Remove(entry); + Task.Run(SaveAsync); + } + + [RelayCommand] + private void RestoreDefault(ObservableIniEntry entry) + { + entry.Value = entry.DefaultValue ?? string.Empty; + Task.Run(SaveAsync); + } + + [RelayCommand] + private void ShowHelp() + { + var version = Context.Application.VersionNumber; + ProcessTasks.StartShell($"https://help.autodesk.com/view/RVT/{version}/ENU/?guid=GUID-9ECD669E-81D3-43E5-9970-9FA1C38E8507"); + } + + [RelayCommand] + private void OpenSettings() + { + var iniFile = Context.Application.CurrentUsersDataFolderPath.AppendPath("Revit.ini"); + if (!File.Exists(iniFile)) + { + notificationService.ShowWarning("Missing settings", "Revit.ini file does not exists"); + return; + } + + ProcessTasks.StartShell(iniFile); + } + + [RelayCommand(CanExecute = nameof(CanClearFiltersExecute))] + private void ClearFilters() + { + CategoryFilter = string.Empty; + PropertyFilter = string.Empty; + ValueFilter = string.Empty; + ShowUserSettingsFilter = false; + + ApplyFilters(); + } + + partial void OnEntriesChanged(List? value) + { + if (value is null) return; + + ApplyFilters(); + } + + partial void OnCategoryFilterChanged(string value) + { + ApplyFilters(); + } + + partial void OnPropertyFilterChanged(string value) + { + ApplyFilters(); + } + + partial void OnValueFilterChanged(string value) + { + ApplyFilters(); + } + + partial void OnShowUserSettingsFilterChanged(bool value) + { + ApplyFilters(); + } + + public async Task UpdateEntryAsync() + { + if (SelectedEntry is null) return; + + try + { + var editingValue = SelectedEntry.Clone(); + var dialog = serviceProvider.GetRequiredService(); + var result = await dialog.ShowUpdateDialogAsync(editingValue); + if (result == ContentDialogResult.Primary) UpdateEntry(editingValue); + } + catch (Exception exception) + { + const string message = "Unavailable to update Revit configuration"; + + logger.LogError(exception, message); + notificationService.ShowError(message, exception); + } + } + + private void UpdateEntry(ObservableIniEntry entry) + { + if (SelectedEntry is null) return; + + var forceRefresh = SelectedEntry.Category != entry.Category || SelectedEntry.Property != entry.Property; + + SelectedEntry.Category = entry.Category; + SelectedEntry.Property = entry.Property; + SelectedEntry.Value = entry.Value; + SelectedEntry.IsActive = true; + + if (forceRefresh) + { + ApplyFilters(); + } + + Task.Run(SaveAsync); + } + + private void ApplyFilters() + { + var expressions = new List>>(4); + + if (!string.IsNullOrWhiteSpace(CategoryFilter)) + { + expressions.Add(entry => entry.Category.Contains(CategoryFilter, StringComparison.OrdinalIgnoreCase)); + } + + if (!string.IsNullOrWhiteSpace(PropertyFilter)) + { + expressions.Add(entry => entry.Property.Contains(PropertyFilter, StringComparison.OrdinalIgnoreCase)); + } + + if (!string.IsNullOrWhiteSpace(ValueFilter)) + { + expressions.Add(entry => entry.Value.Contains(ValueFilter, StringComparison.OrdinalIgnoreCase)); + } + + if (ShowUserSettingsFilter) + { + expressions.Add(entry => entry.IsActive); + } + + if (expressions.Count == 0) + { + FilteredEntries = new ObservableCollection(Entries); + Filtered = false; + } + else + { + IEnumerable filtered = Entries; + foreach (var expression in expressions) + { + filtered = filtered.Where(expression.Compile()); + } + + FilteredEntries = new ObservableCollection(filtered.ToList()); + Filtered = true; + } + } + + private bool CanClearFiltersExecute() + { + return Filtered; + } + + private async Task SaveAsync() + { + try + { + await _configurator.WriteAsync(Entries); + } + catch (Exception exception) + { + const string message = "Failed to save configuration file"; + + logger.LogError(exception, message); + notificationService.ShowError(message, exception); + } + } +} \ No newline at end of file diff --git a/source/RevitLookup2/ViewModels/Tools/SearchElementsViewModel.cs b/source/RevitLookup2/ViewModels/Tools/SearchElementsViewModel.cs new file mode 100644 index 00000000..0fde9f15 --- /dev/null +++ b/source/RevitLookup2/ViewModels/Tools/SearchElementsViewModel.cs @@ -0,0 +1,30 @@ +using RevitLookup.Abstractions.Services; +using RevitLookup.Abstractions.ViewModels.Tools; +using RevitLookup2.Core.Tools; + +namespace RevitLookup2.ViewModels.Tools; + +[UsedImplicitly] +public sealed partial class SearchElementsViewModel( + INotificationService notificationService, + IVisualDecompositionService decompositionService) + : ObservableObject, ISearchElementsViewModel +{ + [ObservableProperty] private string _searchText = string.Empty; + + public async Task SearchElementsAsync() + { + var result = SearchText != string.Empty; + if (result) + { + var elements = ElementsFinder.SearchElements(SearchText); + await decompositionService.VisualizeDecompositionAsync(elements); + } + else + { + notificationService.ShowWarning("Search elements", "There are no elements found for your request"); + } + + return result; + } +} \ No newline at end of file diff --git a/source/RevitLookup2/ViewModels/Tools/UnitsViewModel.cs b/source/RevitLookup2/ViewModels/Tools/UnitsViewModel.cs new file mode 100644 index 00000000..bfb46a17 --- /dev/null +++ b/source/RevitLookup2/ViewModels/Tools/UnitsViewModel.cs @@ -0,0 +1,104 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +using System.Diagnostics.CodeAnalysis; +using RevitLookup.Abstractions.Models.Tools; +using RevitLookup.Abstractions.Services; +using RevitLookup.Abstractions.ViewModels.Tools; +using RevitLookup2.Core; +using RevitLookup2.Core.Tools; +#if NETFRAMEWORK +using RevitLookup.UI.Framework.Extensions; +#endif + +namespace RevitLookup2.ViewModels.Tools; + +[UsedImplicitly] +public sealed partial class UnitsViewModel(IVisualDecompositionService decompositionService) : ObservableObject, IUnitsViewModel +{ + [ObservableProperty] private List _units = []; + [ObservableProperty] private List _filteredUnits = []; + [ObservableProperty] private string _searchText = string.Empty; + + public void InitializeParameters() + { + Units = UnitsCollector.GetBuiltinParametersInfo(); + } + + public void InitializeCategories() + { + Units = UnitsCollector.GetBuiltinCategoriesInfo(); + } + + public void InitializeForgeSchema() + { + Units = UnitsCollector.GetForgeInfo(); + } + + public async Task DecomposeAsync(UnitInfo unitInfo) + { + var obj = unitInfo.Value switch + { + BuiltInParameter parameter => RevitShell.GetBuiltinParameter(parameter), + BuiltInCategory category => RevitShell.GetBuiltinCategory(category), + _ => unitInfo.Value + }; + + await decompositionService.VisualizeDecompositionAsync(obj); + } + + [SuppressMessage("ReSharper", "ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator")] + async partial void OnSearchTextChanged(string value) + { + try + { + if (string.IsNullOrEmpty(SearchText)) + { + FilteredUnits = Units; + return; + } + + FilteredUnits = await Task.Run(() => + { + var formattedText = value.Trim(); + var searchResults = new List(); + foreach (var family in Units) + { + if (family.Label.Contains(formattedText, StringComparison.OrdinalIgnoreCase) || + family.Unit.Contains(formattedText, StringComparison.OrdinalIgnoreCase)) + { + searchResults.Add(family); + } + } + + return searchResults; + }); + } + catch + { + // ignored + } + } + + partial void OnUnitsChanged(List value) + { + FilteredUnits = value; + } +} \ No newline at end of file diff --git a/source/RevitLookup2/ViewModels/Visualization/MockBoundingBoxVisualizationViewModel.cs b/source/RevitLookup2/ViewModels/Visualization/MockBoundingBoxVisualizationViewModel.cs new file mode 100644 index 00000000..bbb39c4f --- /dev/null +++ b/source/RevitLookup2/ViewModels/Visualization/MockBoundingBoxVisualizationViewModel.cs @@ -0,0 +1,39 @@ +using RevitLookup.Abstractions.ViewModels.Visualization; + +namespace RevitLookup2.ViewModels.Visualization; + +[UsedImplicitly] +public sealed partial class MockBoundingBoxVisualizationViewModel : ObservableObject, IBoundingBoxVisualizationViewModel +{ + [ObservableProperty] private double _transparency; + + [ObservableProperty] private Color _surfaceColor; + [ObservableProperty] private Color _edgeColor; + [ObservableProperty] private Color _axisColor; + + [ObservableProperty] private bool _showSurface; + [ObservableProperty] private bool _showEdge; + [ObservableProperty] private bool _showAxis; + + public MockBoundingBoxVisualizationViewModel() + { + var faker = new Faker(); + + Transparency = faker.Random.Double(0, 100); + SurfaceColor = Color.FromRgb(faker.Random.Byte(), faker.Random.Byte(), faker.Random.Byte()); + EdgeColor = Color.FromRgb(faker.Random.Byte(), faker.Random.Byte(), faker.Random.Byte()); + AxisColor = Color.FromRgb(faker.Random.Byte(), faker.Random.Byte(), faker.Random.Byte()); + + ShowSurface = faker.Random.Bool(); + ShowEdge = faker.Random.Bool(); + ShowAxis = faker.Random.Bool(); + } + + public void RegisterServer(object boundingBoxXyz) + { + } + + public void UnregisterServer() + { + } +} \ No newline at end of file diff --git a/source/RevitLookup2/ViewModels/Visualization/MockFaceVisualizationViewModel.cs b/source/RevitLookup2/ViewModels/Visualization/MockFaceVisualizationViewModel.cs new file mode 100644 index 00000000..9e2beb52 --- /dev/null +++ b/source/RevitLookup2/ViewModels/Visualization/MockFaceVisualizationViewModel.cs @@ -0,0 +1,44 @@ +using RevitLookup.Abstractions.ViewModels.Visualization; + +namespace RevitLookup2.ViewModels.Visualization; + +[UsedImplicitly] +public sealed partial class MockFaceVisualizationViewModel : ObservableObject, IFaceVisualizationViewModel +{ + [ObservableProperty] private double _extrusion; + [ObservableProperty] private double _transparency; + + [ObservableProperty] private Color _surfaceColor; + [ObservableProperty] private Color _meshColor; + [ObservableProperty] private Color _normalVectorColor; + + [ObservableProperty] private bool _showSurface; + [ObservableProperty] private bool _showMeshGrid; + [ObservableProperty] private bool _showNormalVector; + + public double MinExtrusion { get; } + + public MockFaceVisualizationViewModel() + { + var faker = new Faker(); + + MinExtrusion = 0; + Transparency = faker.Random.Double(0, 100); + Extrusion = faker.Random.Double(0, 24); + SurfaceColor = Color.FromRgb(faker.Random.Byte(), faker.Random.Byte(), faker.Random.Byte()); + MeshColor = Color.FromRgb(faker.Random.Byte(), faker.Random.Byte(), faker.Random.Byte()); + NormalVectorColor = Color.FromRgb(faker.Random.Byte(), faker.Random.Byte(), faker.Random.Byte()); + + ShowSurface = faker.Random.Bool(); + ShowMeshGrid = faker.Random.Bool(); + ShowNormalVector = faker.Random.Bool(); + } + + public void RegisterServer(object face) + { + } + + public void UnregisterServer() + { + } +} \ No newline at end of file diff --git a/source/RevitLookup2/ViewModels/Visualization/MockMeshVisualizationViewModel.cs b/source/RevitLookup2/ViewModels/Visualization/MockMeshVisualizationViewModel.cs new file mode 100644 index 00000000..2f6c5e33 --- /dev/null +++ b/source/RevitLookup2/ViewModels/Visualization/MockMeshVisualizationViewModel.cs @@ -0,0 +1,44 @@ +using RevitLookup.Abstractions.ViewModels.Visualization; + +namespace RevitLookup2.ViewModels.Visualization; + +[UsedImplicitly] +public sealed partial class MockMeshVisualizationViewModel : ObservableObject, IMeshVisualizationViewModel +{ + [ObservableProperty] private double _extrusion; + [ObservableProperty] private double _transparency; + + [ObservableProperty] private Color _surfaceColor; + [ObservableProperty] private Color _meshColor; + [ObservableProperty] private Color _normalVectorColor; + + [ObservableProperty] private bool _showSurface; + [ObservableProperty] private bool _showMeshGrid; + [ObservableProperty] private bool _showNormalVector; + + public MockMeshVisualizationViewModel() + { + var faker = new Faker(); + + MinExtrusion = 0; + Transparency = faker.Random.Double(0, 100); + Extrusion = faker.Random.Double(0, 24); + SurfaceColor = Color.FromRgb(faker.Random.Byte(), faker.Random.Byte(), faker.Random.Byte()); + MeshColor = Color.FromRgb(faker.Random.Byte(), faker.Random.Byte(), faker.Random.Byte()); + NormalVectorColor = Color.FromRgb(faker.Random.Byte(), faker.Random.Byte(), faker.Random.Byte()); + + ShowSurface = faker.Random.Bool(); + ShowMeshGrid = faker.Random.Bool(); + ShowNormalVector = faker.Random.Bool(); + } + + public double MinExtrusion { get; } + + public void RegisterServer(object mesh) + { + } + + public void UnregisterServer() + { + } +} \ No newline at end of file diff --git a/source/RevitLookup2/ViewModels/Visualization/MockPolylineVisualizationViewModel.cs b/source/RevitLookup2/ViewModels/Visualization/MockPolylineVisualizationViewModel.cs new file mode 100644 index 00000000..b3c1fce4 --- /dev/null +++ b/source/RevitLookup2/ViewModels/Visualization/MockPolylineVisualizationViewModel.cs @@ -0,0 +1,44 @@ +using RevitLookup.Abstractions.ViewModels.Visualization; + +namespace RevitLookup2.ViewModels.Visualization; + +[UsedImplicitly] +public sealed partial class MockPolylineVisualizationViewModel : ObservableObject, IPolylineVisualizationViewModel +{ + [ObservableProperty] private double _diameter; + [ObservableProperty] private double _transparency; + + [ObservableProperty] private Color _surfaceColor; + [ObservableProperty] private Color _curveColor; + [ObservableProperty] private Color _directionColor; + + [ObservableProperty] private bool _showSurface; + [ObservableProperty] private bool _showCurve; + [ObservableProperty] private bool _showDirection; + + public MockPolylineVisualizationViewModel() + { + var faker = new Faker(); + + MinThickness = 0; + Transparency = faker.Random.Double(0, 100); + Diameter = faker.Random.Double(0, 6); + SurfaceColor = Color.FromRgb(faker.Random.Byte(), faker.Random.Byte(), faker.Random.Byte()); + CurveColor = Color.FromRgb(faker.Random.Byte(), faker.Random.Byte(), faker.Random.Byte()); + DirectionColor = Color.FromRgb(faker.Random.Byte(), faker.Random.Byte(), faker.Random.Byte()); + + ShowSurface = faker.Random.Bool(); + ShowCurve = faker.Random.Bool(); + ShowDirection = faker.Random.Bool(); + } + + public double MinThickness { get; } + + public void RegisterServer(object curveOrEdge) + { + } + + public void UnregisterServer() + { + } +} \ No newline at end of file diff --git a/source/RevitLookup2/ViewModels/Visualization/MockSolidVisualizationViewModel.cs b/source/RevitLookup2/ViewModels/Visualization/MockSolidVisualizationViewModel.cs new file mode 100644 index 00000000..ed23613f --- /dev/null +++ b/source/RevitLookup2/ViewModels/Visualization/MockSolidVisualizationViewModel.cs @@ -0,0 +1,37 @@ +using RevitLookup.Abstractions.ViewModels.Visualization; + +namespace RevitLookup2.ViewModels.Visualization; + +[UsedImplicitly] +public sealed partial class MockSolidVisualizationViewModel : ObservableObject, ISolidVisualizationViewModel +{ + [ObservableProperty] private double _scale; + [ObservableProperty] private double _transparency; + + [ObservableProperty] private Color _faceColor; + [ObservableProperty] private Color _edgeColor; + + [ObservableProperty] private bool _showFace; + [ObservableProperty] private bool _showEdge; + + public MockSolidVisualizationViewModel() + { + var faker = new Faker(); + + Transparency = faker.Random.Double(0, 100); + Scale = faker.Random.Double(100, 400); + FaceColor = Color.FromRgb(faker.Random.Byte(), faker.Random.Byte(), faker.Random.Byte()); + EdgeColor = Color.FromRgb(faker.Random.Byte(), faker.Random.Byte(), faker.Random.Byte()); + + ShowFace = faker.Random.Bool(); + ShowEdge = faker.Random.Bool(); + } + + public void RegisterServer(object solid) + { + } + + public void UnregisterServer() + { + } +} \ No newline at end of file diff --git a/source/RevitLookup2/ViewModels/Visualization/XyzVisualizationViewModel.cs b/source/RevitLookup2/ViewModels/Visualization/XyzVisualizationViewModel.cs new file mode 100644 index 00000000..44f83d48 --- /dev/null +++ b/source/RevitLookup2/ViewModels/Visualization/XyzVisualizationViewModel.cs @@ -0,0 +1,46 @@ +using RevitLookup.Abstractions.ViewModels.Visualization; + +namespace RevitLookup2.ViewModels.Visualization; + +[UsedImplicitly] +public sealed partial class XyzVisualizationViewModel : ObservableObject, IXyzVisualizationViewModel +{ + [ObservableProperty] private double _axisLength; + [ObservableProperty] private double _transparency; + + [ObservableProperty] private Color _xColor; + [ObservableProperty] private Color _yColor; + [ObservableProperty] private Color _zColor; + + [ObservableProperty] private bool _showPlane; + [ObservableProperty] private bool _showXAxis; + [ObservableProperty] private bool _showYAxis; + [ObservableProperty] private bool _showZAxis; + + public XyzVisualizationViewModel() + { + var faker = new Faker(); + + MinAxisLength = 0; + Transparency = faker.Random.Double(0, 100); + AxisLength = faker.Random.Double(0, 24); + XColor = Color.FromRgb(faker.Random.Byte(), faker.Random.Byte(), faker.Random.Byte()); + YColor = Color.FromRgb(faker.Random.Byte(), faker.Random.Byte(), faker.Random.Byte()); + ZColor = Color.FromRgb(faker.Random.Byte(), faker.Random.Byte(), faker.Random.Byte()); + + ShowPlane = faker.Random.Bool(); + ShowXAxis = faker.Random.Bool(); + ShowYAxis = faker.Random.Bool(); + ShowZAxis = faker.Random.Bool(); + } + + public double MinAxisLength { get; } + + public void RegisterServer(object xyz) + { + } + + public void UnregisterServer() + { + } +} \ No newline at end of file From 82ac83d1daa1316bcbb4a4d7c64402fb5313aad1 Mon Sep 17 00:00:00 2001 From: SergeyNefyodov Date: Thu, 19 Dec 2024 19:01:01 +0100 Subject: [PATCH 044/121] Add compound structure support --- .../Core/ComponentModel/DescriptorMap.cs | 1 + .../CompoundStructureDescriptor.cs | 292 ++++++++++++++++++ 2 files changed, 293 insertions(+) create mode 100644 source/RevitLookup/Core/ComponentModel/Descriptors/CompoundStructureDescriptor.cs diff --git a/source/RevitLookup/Core/ComponentModel/DescriptorMap.cs b/source/RevitLookup/Core/ComponentModel/DescriptorMap.cs index fbddf8ef..64dc6bf9 100644 --- a/source/RevitLookup/Core/ComponentModel/DescriptorMap.cs +++ b/source/RevitLookup/Core/ComponentModel/DescriptorMap.cs @@ -155,6 +155,7 @@ public static Descriptor FindDescriptor(object obj, Type type) FamilySizeTableManager value when type is null || type == typeof(FamilySizeTableManager) => new FamilySizeTableManagerDescriptor(value), FamilySizeTable value when type is null || type == typeof(FamilySizeTable) => new FamilySizeTableDescriptor(value), FamilySizeTableColumn value when type is null || type == typeof(FamilySizeTableColumn) => new FamilySizeTableColumnDescriptor(value), + CompoundStructure value when type is null || type == typeof(CompoundStructure) => new CompoundStructureDescriptor(value), #if REVIT2024_OR_GREATER EvaluatedParameter value when type is null || type == typeof(EvaluatedParameter) => new EvaluatedParameterDescriptor(value), #endif diff --git a/source/RevitLookup/Core/ComponentModel/Descriptors/CompoundStructureDescriptor.cs b/source/RevitLookup/Core/ComponentModel/Descriptors/CompoundStructureDescriptor.cs new file mode 100644 index 00000000..8f419b72 --- /dev/null +++ b/source/RevitLookup/Core/ComponentModel/Descriptors/CompoundStructureDescriptor.cs @@ -0,0 +1,292 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +using System.Reflection; + +namespace RevitLookup.Core.ComponentModel.Descriptors; + +public class CompoundStructureDescriptor(CompoundStructure compoundStructure) : Descriptor, IDescriptorResolver +{ + public Func Resolve(Document context, string target, ParameterInfo[] parameters) + { + return target switch + { + nameof(CompoundStructure.CanLayerBeStructuralMaterial) => ResolveCanLayerBeStructuralMaterial, + nameof(CompoundStructure.CanLayerBeVariable) => ResolveCanLayerBeVariable, + nameof(CompoundStructure.CanLayerWidthBeNonZero) => ResolveCanLayerWidthBeNonZero, + nameof(CompoundStructure.GetAdjacentRegions) => ResolveGetAdjacentRegions, + nameof(CompoundStructure.GetCoreBoundaryLayerIndex) => ResolveGetCoreBoundaryLayerIndex, + nameof(CompoundStructure.GetDeckEmbeddingType) => ResolveGetDeckEmbeddingType, + nameof(CompoundStructure.GetDeckProfileId) => ResolveGetDeckProfileId, + nameof(CompoundStructure.GetLayerAssociatedToRegion) => ResolveGetLayerAssociatedToRegion, + nameof(CompoundStructure.GetLayerFunction) => ResolveGetLayerFunction, + nameof(CompoundStructure.GetLayerWidth) => ResolveGetLayerWidth, + nameof(CompoundStructure.GetMaterialId) => ResolveGetMaterialId, + nameof(CompoundStructure.GetNumberOfShellLayers) => ResolveGetNumberOfShellLayers, + nameof(CompoundStructure.GetOffsetForLocationLine) => ResolveGetOffsetForLocationLine, + nameof(CompoundStructure.GetPreviousNonZeroLayerIndex) => ResolveGetPreviousNonZeroLayerIndex, + nameof(CompoundStructure.GetRegionEnvelope) => ResolveGetRegionEnvelope, + nameof(CompoundStructure.GetRegionsAssociatedToLayer) => ResolveGetRegionsAssociatedToLayer, + nameof(CompoundStructure.GetSegmentCoordinate) => ResolveGetSegmentCoordinate, + nameof(CompoundStructure.GetSegmentOrientation) => ResolveGetSegmentOrientation, + _ => null + }; + + IVariants ResolveCanLayerBeStructuralMaterial() + { + var layerCount = compoundStructure.LayerCount; + var variants = new Variants(layerCount); + for (var i = 0; i < layerCount; i++) + { + var result = compoundStructure.CanLayerBeStructuralMaterial(i); + variants.Add(result, $"Layer {i}: {result}"); + } + + return variants; + } + + IVariants ResolveCanLayerBeVariable() + { + var layerCount = compoundStructure.LayerCount; + var variants = new Variants(layerCount); + for (var i = 0; i < layerCount; i++) + { + var result = compoundStructure.CanLayerBeVariable(i); + variants.Add(result, $"Layer {i}: {result}"); + } + + return variants; + } + + IVariants ResolveCanLayerWidthBeNonZero() + { + var layerCount = compoundStructure.LayerCount; + var variants = new Variants(layerCount); + for (var i = 0; i < layerCount; i++) + { + var result = compoundStructure.CanLayerWidthBeNonZero(i); + variants.Add(result, $"Layer {i}: {result}"); + } + return variants; + } + + IVariants ResolveGetAdjacentRegions() + { + var regionsCount = compoundStructure.GetRegionIds().Count; + var variants = new Variants>(regionsCount); + + for (var i = 0; i < regionsCount; i++) + { + var result = compoundStructure.GetAdjacentRegions(i); + variants.Add(result, $"Region" + + $" {i}"); + } + + return variants; + } + + IVariants ResolveGetCoreBoundaryLayerIndex() + { + var values = Enum.GetValues(typeof(ShellLayerType)); + var variants = new Variants(values.Length); + + foreach (ShellLayerType value in values) + { + var result = compoundStructure.GetCoreBoundaryLayerIndex(value); + variants.Add(result, $"{value.ToString()}: {result}"); + } + + return variants; + } + + IVariants ResolveGetDeckEmbeddingType() + { + var layerCount = compoundStructure.LayerCount; + var variants = new Variants(layerCount); + for (var i = 0; i < layerCount; i++) + { + var result = compoundStructure.GetDeckEmbeddingType(i); + variants.Add(result, $"Layer {i}: {result}"); + } + + return variants; + } + + IVariants ResolveGetLayerAssociatedToRegion() + { + var regionsCount = compoundStructure.GetRegionIds().Count; + var variants = new Variants(regionsCount); + + for (var i = 0; i < regionsCount; i++) + { + var result = compoundStructure.GetLayerAssociatedToRegion(i); + variants.Add(result, $"Region {i}: {result}"); + } + + return variants; + } + + IVariants ResolveGetLayerFunction() + { + var layerCount = compoundStructure.LayerCount; + var variants = new Variants(layerCount); + for (var i = 0; i < layerCount; i++) + { + var result = compoundStructure.GetLayerFunction(i); + variants.Add(result, $"Layer {i}: {result}"); + } + + return variants; + } + + IVariants ResolveGetDeckProfileId() + { + var layerCount = compoundStructure.LayerCount; + var variants = new Variants(layerCount); + for (var i = 0; i < layerCount; i++) + { + var result = compoundStructure.GetDeckProfileId(i); + variants.Add(result, $"Layer {i}: {result}"); + } + + return variants; + } + + IVariants ResolveGetLayerWidth() + { + var layerCount = compoundStructure.LayerCount; + var variants = new Variants(layerCount); + for (var i = 0; i < layerCount; i++) + { + var result = compoundStructure.GetLayerWidth(i); + variants.Add(result, $"Layer {i}: {result}"); + } + + return variants; + } + + IVariants ResolveGetMaterialId() + { + var layerCount = compoundStructure.LayerCount; + var variants = new Variants(layerCount); + for (var i = 0; i < layerCount; i++) + { + var result = compoundStructure.GetMaterialId(i); + variants.Add(result, $"Layer {i}: {result}"); + } + + return variants; + } + + IVariants ResolveGetNumberOfShellLayers() + { + var values = Enum.GetValues(typeof(ShellLayerType)); + var variants = new Variants(values.Length); + + foreach (ShellLayerType value in values) + { + var result = compoundStructure.GetNumberOfShellLayers(value); + variants.Add(result, $"{value.ToString()}: {result}"); + } + + return variants; + } + + IVariants ResolveGetOffsetForLocationLine() + { + var values = Enum.GetValues(typeof(WallLocationLine)); + var variants = new Variants(values.Length); + + foreach (WallLocationLine value in values) + { + var result = compoundStructure.GetOffsetForLocationLine(value); + variants.Add(result, $"{value.ToString()}: {result}"); + } + + return variants; + } + + IVariants ResolveGetPreviousNonZeroLayerIndex() + { + var layerCount = compoundStructure.LayerCount; + var variants = new Variants(layerCount); + for (var i = 0; i < layerCount; i++) + { + var result = compoundStructure.GetPreviousNonZeroLayerIndex(i); + variants.Add(result, $"Layer {i}: {result}"); + } + + return variants; + } + + IVariants ResolveGetRegionEnvelope() + { + var regionsCount = compoundStructure.GetRegionIds().Count; + var variants = new Variants(regionsCount); + + for (var i = 0; i < regionsCount; i++) + { + var result = compoundStructure.GetRegionEnvelope(i); + variants.Add(result, $"Region {i}: {result}"); + } + + return variants; + } + + IVariants ResolveGetRegionsAssociatedToLayer() + { + var layerCount = compoundStructure.LayerCount; + var variants = new Variants>(layerCount); + for (var i = 0; i < layerCount; i++) + { + var result = compoundStructure.GetRegionsAssociatedToLayer(i); + variants.Add(result, $"Layer {i}"); + } + + return variants; + } + + IVariants ResolveGetSegmentCoordinate() + { + var segmentCount = compoundStructure.GetSegmentIds().Count; + var variants = new Variants(segmentCount); + for (var i = 0; i < segmentCount; i++) + { + var result = compoundStructure.GetSegmentCoordinate(i); + variants.Add(result, $"Segment {i}: {result}"); + } + + return variants; + } + + IVariants ResolveGetSegmentOrientation() + { + var segmentCount = compoundStructure.GetSegmentIds().Count; + var variants = new Variants(segmentCount); + for (var i = 0; i < segmentCount; i++) + { + var result = compoundStructure.GetSegmentOrientation(i); + variants.Add(result, $"Segment {i}: {result}"); + } + + return variants; + } + } +} \ No newline at end of file From 31ef3c3ff08878f7070551dc15764fc3356c36d5 Mon Sep 17 00:00:00 2001 From: SergeyNefyodov Date: Thu, 19 Dec 2024 19:14:52 +0100 Subject: [PATCH 045/121] Add compound structure support --- .../CompoundStructureDescriptor.cs | 185 ++++++++++++++---- 1 file changed, 144 insertions(+), 41 deletions(-) diff --git a/source/RevitLookup/Core/ComponentModel/Descriptors/CompoundStructureDescriptor.cs b/source/RevitLookup/Core/ComponentModel/Descriptors/CompoundStructureDescriptor.cs index 8f419b72..8cd9bcf6 100644 --- a/source/RevitLookup/Core/ComponentModel/Descriptors/CompoundStructureDescriptor.cs +++ b/source/RevitLookup/Core/ComponentModel/Descriptors/CompoundStructureDescriptor.cs @@ -46,6 +46,13 @@ public Func Resolve(Document context, string target, ParameterInfo[] nameof(CompoundStructure.GetRegionsAssociatedToLayer) => ResolveGetRegionsAssociatedToLayer, nameof(CompoundStructure.GetSegmentCoordinate) => ResolveGetSegmentCoordinate, nameof(CompoundStructure.GetSegmentOrientation) => ResolveGetSegmentOrientation, + nameof(CompoundStructure.GetWallSweepsInfo) => ResolveGetWallSweepsInfo, + nameof(CompoundStructure.GetWidth) when parameters.Length == 1 => ResolveGetWidth, + nameof(CompoundStructure.IsCoreLayer) => ResolveIsCoreLayer, + nameof(CompoundStructure.IsRectangularRegion) => ResolveIsRectangularRegion, + nameof(CompoundStructure.IsSimpleRegion) => ResolveIsSimpleRegion, + nameof(CompoundStructure.IsStructuralDeck) => ResolveIsStructuralDeck, + nameof(CompoundStructure.ParticipatesInWrapping) => ResolveParticipatesInWrapping, _ => null }; @@ -58,10 +65,10 @@ IVariants ResolveCanLayerBeStructuralMaterial() var result = compoundStructure.CanLayerBeStructuralMaterial(i); variants.Add(result, $"Layer {i}: {result}"); } - + return variants; } - + IVariants ResolveCanLayerBeVariable() { var layerCount = compoundStructure.LayerCount; @@ -71,10 +78,10 @@ IVariants ResolveCanLayerBeVariable() var result = compoundStructure.CanLayerBeVariable(i); variants.Add(result, $"Layer {i}: {result}"); } - + return variants; } - + IVariants ResolveCanLayerWidthBeNonZero() { var layerCount = compoundStructure.LayerCount; @@ -84,38 +91,39 @@ IVariants ResolveCanLayerWidthBeNonZero() var result = compoundStructure.CanLayerWidthBeNonZero(i); variants.Add(result, $"Layer {i}: {result}"); } + return variants; } - + IVariants ResolveGetAdjacentRegions() { var regionsCount = compoundStructure.GetRegionIds().Count; var variants = new Variants>(regionsCount); - + for (var i = 0; i < regionsCount; i++) { var result = compoundStructure.GetAdjacentRegions(i); variants.Add(result, $"Region" + $" {i}"); } - + return variants; } - + IVariants ResolveGetCoreBoundaryLayerIndex() { var values = Enum.GetValues(typeof(ShellLayerType)); var variants = new Variants(values.Length); - + foreach (ShellLayerType value in values) { var result = compoundStructure.GetCoreBoundaryLayerIndex(value); variants.Add(result, $"{value.ToString()}: {result}"); } - + return variants; } - + IVariants ResolveGetDeckEmbeddingType() { var layerCount = compoundStructure.LayerCount; @@ -125,24 +133,24 @@ IVariants ResolveGetDeckEmbeddingType() var result = compoundStructure.GetDeckEmbeddingType(i); variants.Add(result, $"Layer {i}: {result}"); } - + return variants; } - + IVariants ResolveGetLayerAssociatedToRegion() { var regionsCount = compoundStructure.GetRegionIds().Count; var variants = new Variants(regionsCount); - + for (var i = 0; i < regionsCount; i++) { var result = compoundStructure.GetLayerAssociatedToRegion(i); variants.Add(result, $"Region {i}: {result}"); } - + return variants; } - + IVariants ResolveGetLayerFunction() { var layerCount = compoundStructure.LayerCount; @@ -152,10 +160,10 @@ IVariants ResolveGetLayerFunction() var result = compoundStructure.GetLayerFunction(i); variants.Add(result, $"Layer {i}: {result}"); } - + return variants; } - + IVariants ResolveGetDeckProfileId() { var layerCount = compoundStructure.LayerCount; @@ -165,10 +173,10 @@ IVariants ResolveGetDeckProfileId() var result = compoundStructure.GetDeckProfileId(i); variants.Add(result, $"Layer {i}: {result}"); } - + return variants; } - + IVariants ResolveGetLayerWidth() { var layerCount = compoundStructure.LayerCount; @@ -178,10 +186,10 @@ IVariants ResolveGetLayerWidth() var result = compoundStructure.GetLayerWidth(i); variants.Add(result, $"Layer {i}: {result}"); } - + return variants; } - + IVariants ResolveGetMaterialId() { var layerCount = compoundStructure.LayerCount; @@ -191,38 +199,38 @@ IVariants ResolveGetMaterialId() var result = compoundStructure.GetMaterialId(i); variants.Add(result, $"Layer {i}: {result}"); } - + return variants; } - + IVariants ResolveGetNumberOfShellLayers() { var values = Enum.GetValues(typeof(ShellLayerType)); var variants = new Variants(values.Length); - + foreach (ShellLayerType value in values) { var result = compoundStructure.GetNumberOfShellLayers(value); variants.Add(result, $"{value.ToString()}: {result}"); } - + return variants; } - + IVariants ResolveGetOffsetForLocationLine() { var values = Enum.GetValues(typeof(WallLocationLine)); var variants = new Variants(values.Length); - + foreach (WallLocationLine value in values) { var result = compoundStructure.GetOffsetForLocationLine(value); variants.Add(result, $"{value.ToString()}: {result}"); } - + return variants; } - + IVariants ResolveGetPreviousNonZeroLayerIndex() { var layerCount = compoundStructure.LayerCount; @@ -232,24 +240,24 @@ IVariants ResolveGetPreviousNonZeroLayerIndex() var result = compoundStructure.GetPreviousNonZeroLayerIndex(i); variants.Add(result, $"Layer {i}: {result}"); } - + return variants; } - + IVariants ResolveGetRegionEnvelope() { var regionsCount = compoundStructure.GetRegionIds().Count; var variants = new Variants(regionsCount); - + for (var i = 0; i < regionsCount; i++) { var result = compoundStructure.GetRegionEnvelope(i); - variants.Add(result, $"Region {i}: {result}"); + variants.Add(result, $"Region {i}"); } - + return variants; } - + IVariants ResolveGetRegionsAssociatedToLayer() { var layerCount = compoundStructure.LayerCount; @@ -259,10 +267,10 @@ IVariants ResolveGetRegionsAssociatedToLayer() var result = compoundStructure.GetRegionsAssociatedToLayer(i); variants.Add(result, $"Layer {i}"); } - + return variants; } - + IVariants ResolveGetSegmentCoordinate() { var segmentCount = compoundStructure.GetSegmentIds().Count; @@ -272,10 +280,10 @@ IVariants ResolveGetSegmentCoordinate() var result = compoundStructure.GetSegmentCoordinate(i); variants.Add(result, $"Segment {i}: {result}"); } - + return variants; } - + IVariants ResolveGetSegmentOrientation() { var segmentCount = compoundStructure.GetSegmentIds().Count; @@ -285,7 +293,102 @@ IVariants ResolveGetSegmentOrientation() var result = compoundStructure.GetSegmentOrientation(i); variants.Add(result, $"Segment {i}: {result}"); } - + + return variants; + } + + IVariants ResolveGetWallSweepsInfo() + { + var values = Enum.GetValues(typeof(WallSweepType)); + var variants = new Variants>(values.Length); + + foreach (WallSweepType value in values) + { + var result = compoundStructure.GetWallSweepsInfo(value); + variants.Add(result, value.ToString()); + } + + return variants; + } + + IVariants ResolveGetWidth() + { + var regionsCount = compoundStructure.GetRegionIds().Count; + var variants = new Variants(regionsCount); + + for (var i = 0; i < regionsCount; i++) + { + var result = compoundStructure.GetWidth(i); + variants.Add(result, $"Region {i}: {result}"); + } + + return variants; + } + + IVariants ResolveIsCoreLayer() + { + var layerCount = compoundStructure.LayerCount; + var variants = new Variants(layerCount); + for (var i = 0; i < layerCount; i++) + { + var result = compoundStructure.IsCoreLayer(i); + variants.Add(result, $"Layer {i}: {result}"); + } + + return variants; + } + + IVariants ResolveIsRectangularRegion() + { + var regionsCount = compoundStructure.GetRegionIds().Count; + var variants = new Variants(regionsCount); + + for (var i = 0; i < regionsCount; i++) + { + var result = compoundStructure.IsRectangularRegion(i); + variants.Add(result, $"Region {i}: {result}"); + } + + return variants; + } + + IVariants ResolveIsSimpleRegion() + { + var regionsCount = compoundStructure.GetRegionIds().Count; + var variants = new Variants(regionsCount); + + for (var i = 0; i < regionsCount; i++) + { + var result = compoundStructure.IsSimpleRegion(i); + variants.Add(result, $"Region {i}: {result}"); + } + + return variants; + } + + IVariants ResolveIsStructuralDeck() + { + var layerCount = compoundStructure.LayerCount; + var variants = new Variants(layerCount); + for (var i = 0; i < layerCount; i++) + { + var result = compoundStructure.IsStructuralDeck(i); + variants.Add(result, $"Layer {i}: {result}"); + } + + return variants; + } + + IVariants ResolveParticipatesInWrapping() + { + var layerCount = compoundStructure.LayerCount; + var variants = new Variants(layerCount); + for (var i = 0; i < layerCount; i++) + { + var result = compoundStructure.ParticipatesInWrapping(i); + variants.Add(result, $"Layer {i}: {result}"); + } + return variants; } } From bd675add374b4f768609545e9a9a4067f569dc30 Mon Sep 17 00:00:00 2001 From: Nice3point Date: Sun, 22 Dec 2024 14:47:58 +0300 Subject: [PATCH 046/121] Rewrite basic services --- RevitLookup.slnx | 16 +- .../Appearance/IThemeWatcherService.cs | 11 + .../IRevitLookupUiService.cs | 2 +- .../INotificationService.cs | 2 +- .../IWindowIntercomService.cs | 2 +- .../{ => Settings}/ISettingsService.cs | 5 +- .../{ => Settings}/ISoftwareUpdateService.cs | 2 +- .../IVisualDecompositionService.cs | 2 +- .../BooleanCollapsedVisibilityConverter.cs | 7 +- .../BooleanHiddenVisibilityConverter.cs | 7 +- .../EnumCollapsedVisibilityConverter.cs | 4 +- .../EnumHiddenVisibilityConverter.cs | 4 +- ...erseBooleanCollapsedVisibilityConverter.cs | 5 +- ...InverseBooleanHiddenVisibilityConverter.cs | 5 +- .../Extensions/CollectionExtensions.cs | 3 +- .../Extensions/ContextMenuExtensions.cs | 15 +- .../Extensions/StringExtensions.cs | 3 +- .../{ => Presentation}/NotificationService.cs | 4 +- .../WindowIntercomService.cs | 14 +- .../Views/AboutProgram/AboutPage.xaml.cs | 5 +- .../Views/AboutProgram/OpenSourceDialog.xaml | 6 +- .../AboutProgram/OpenSourceDialog.xaml.cs | 11 +- .../Views/Dashboard/DashboardPage.xaml.cs | 5 +- .../Views/EditDialogs/EditIniEntryDialog.xaml | 6 +- .../EditDialogs/EditIniEntryDialog.xaml.cs | 7 +- .../Views/EditDialogs/EditValueDialog.xaml | 6 +- .../Views/EditDialogs/EditValueDialog.xaml.cs | 4 +- .../Views/Settings/ResetSettingsDialog.xaml | 6 +- .../Settings/ResetSettingsDialog.xaml.cs | 17 +- .../Views/Settings/SettingsPage.xaml.cs | 5 +- .../Summary/DecompositionSummaryPage.xaml | 3 +- .../Summary/DecompositionSummaryPage.xaml.cs | 7 +- .../Views/Summary/EventsSummaryPage.xaml | 3 +- .../Views/Summary/EventsSummaryPage.xaml.cs | 7 +- .../Summary/SummaryViewBase.ContextMenu.cs | 2 +- .../Views/Summary/SummaryViewBase.xaml.cs | 25 +- .../Views/Tools/ModulesDialog.xaml | 6 +- .../Views/Tools/ModulesDialog.xaml.cs | 8 +- .../Views/Tools/RevitSettingsPage.xaml | 3 +- .../Views/Tools/RevitSettingsPage.xaml.cs | 6 +- .../Views/Tools/SearchElementsDialog.xaml | 6 +- .../Views/Tools/SearchElementsDialog.xaml.cs | 6 +- .../Views/Tools/UnitsDialog.xaml | 12 +- .../Views/Tools/UnitsDialog.xaml.cs | 8 +- .../BoundingBoxVisualizationDialog.xaml | 6 +- .../BoundingBoxVisualizationDialog.xaml.cs | 9 +- .../FaceVisualizationDialog.xaml | 6 +- .../FaceVisualizationDialog.xaml.cs | 7 +- .../MeshVisualizationDialog.xaml | 6 +- .../MeshVisualizationDialog.xaml.cs | 7 +- .../PolylineVisualizationDialog.xaml | 6 +- .../PolylineVisualizationDialog.xaml.cs | 7 +- .../SolidVisualizationDialog.xaml | 6 +- .../SolidVisualizationDialog.xaml.cs | 9 +- .../Visualization/XyzVisualizationDialog.xaml | 6 +- .../XyzVisualizationDialog.xaml.cs | 7 +- .../Views/Windows/RevitLookupView.xaml | 2 +- .../Views/Windows/RevitLookupView.xaml.cs | 15 +- source/RevitLookup.UI.Playground/App.xaml.cs | 1 + .../Client/Controls/PageViewer.xaml.cs | 1 + ...tionExtensions.cs => ViewModelServices.cs} | 2 +- ...istrationExtensions.cs => ViewServices.cs} | 2 +- .../Client/ViewModels/Pages/PagesViewModel.cs | 1 + .../ViewModels/Pages/WindowsViewModel.cs | 1 + .../Client/Views/PlaygroundView.xaml.cs | 1 + source/RevitLookup.UI.Playground/Host.cs | 11 + .../Appearance/MockThemeWatcherService.cs | 72 +++ .../MockRevitLookupUiService.cs | 4 +- .../{ => Settings}/MockSettingsService.cs | 13 +- .../MockSoftwareUpdateService.cs | 3 +- .../MockVisualDecompositionService.cs | 4 +- .../AboutProgram/MockAboutViewModel.cs | 1 + .../Dashboard/MockDashboardViewModel.cs | 2 + .../Settings/MockSettingsViewModel.cs | 22 +- .../MockDecompositionSummaryViewModel.cs | 3 + .../Summary/MockEventsSummaryViewModel.cs | 3 + .../Tools/MockRevitSettingsViewModel.cs | 1 + .../Tools/MockSearchElementsViewModel.cs | 2 + .../ViewModels/Tools/MockUnitsViewModel.cs | 1 + source/RevitLookup/Application.cs | 37 +- .../RevitLookup/Commands/DashboardCommand.cs | 7 +- .../Commands/EventMonitorCommand.cs | 7 +- .../Commands/SearchElementsCommand.cs | 10 +- .../Commands/SnoopDatabaseCommand.cs | 12 +- .../Commands/SnoopDocumentCommand.cs | 12 +- .../RevitLookup/Commands/SnoopEdgeCommand.cs | 12 +- .../RevitLookup/Commands/SnoopFaceCommand.cs | 12 +- .../Commands/SnoopLinkedElementCommand.cs | 12 +- .../RevitLookup/Commands/SnoopPointCommand.cs | 12 +- .../Commands/SnoopSelectionCommand.cs | 12 +- .../Commands/SnoopSubElementCommand.cs | 12 +- .../RevitLookup/Commands/SnoopViewCommand.cs | 12 +- .../Config/ApplicationOptions.cs | 2 +- .../RevitLookup/Config/LoggerConfigurator.cs | 8 +- .../Config/SerializerOptions.cs | 2 +- .../Core/RevitShell.API.cs | 2 +- .../Core/RevitShell.Handlers.cs | 2 +- .../Tools/RevitSettings}/RevitConfigurator.cs | 2 +- .../Core/Tools/Search}/ElementsFinder.cs | 2 +- .../Core/Tools/Units}/UnitsCollector.cs | 2 +- .../BoundingBoxVisualizationServer.cs | 313 +++++++++++ .../Buffers/RenderingBufferStorage.cs | 25 + .../Events/RenderFailedEventArgs.cs | 26 + .../Visualization/FaceVisualizationServer.cs | 305 +++++++++++ .../Helpers/RenderGeometryHelper.cs | 231 ++++++++ .../Visualization/Helpers/RenderHelper.cs | 514 ++++++++++++++++++ .../Visualization/MeshVisualizationServer.cs | 313 +++++++++++ .../PolylineVisualizationServer.cs | 348 ++++++++++++ .../Visualization/SolidVisualizationServer.cs | 309 +++++++++++ .../Visualization/XyzVisualizationServer.cs | 356 ++++++++++++ source/RevitLookup/Host.cs | 124 ++--- .../Mappers/DecompositionResultMapper.cs | 2 +- source/RevitLookup/RevitLookup.csproj | 116 ++-- .../RevitLookup.csproj.DotSettings | 9 +- .../Appearance/ThemeWatcherService.cs | 147 +++++ .../Application/HostBackgroundService.cs | 4 +- .../Application/RevitLookupUiService.cs | 5 +- .../Services/Application/RibbonController.cs | 2 +- .../Services/RevitLookupUiService.cs | 2 +- .../Services/Settings/SettingsService.cs | 14 +- .../Settings/SoftwareUpdateService.cs | 4 +- .../Summary/EventsMonitoringService.cs | 4 +- .../Summary/VisualDecompositionService.cs | 9 +- .../Services/ViewModelServices.cs | 2 +- .../Services/ViewServices.cs | 2 +- source/RevitLookup/Styles/App.Resources.xaml | 28 + .../MembersGrid/DataGridCellTemplate.xaml | 2 +- .../DataGridCellTemplateSelector.cs | 2 +- .../MembersGrid/DataGridGroupStyles.xaml | 2 +- .../MembersGrid/DataGridRowStyle.xaml | 5 + .../MembersGrid/DataGridRowStyleSelector.cs | 2 +- .../ObjectsTree/TreeGroupTemplates.xaml | 4 +- .../TreeViewItemTemplateSelector.cs | 2 +- .../Styles/Converters/ObjectColorConverter.cs | 2 +- .../Utils/RibbonExtensions.cs | 2 +- .../ViewModels/AboutProgram/AboutViewModel.cs | 4 +- .../AboutProgram/OpenSourceViewModel.cs | 2 +- .../Dashboard/DashboardViewModel.cs | 5 +- .../ViewModels/Settings/SettingsViewModel.cs | 28 +- .../MockDecompositionSummaryViewModel.cs | 10 +- .../Summary/MockEventsSummaryViewModel.cs | 12 +- .../ViewModels/Tools/ModulesViewModel.cs | 2 +- .../Tools/RevitSettingsViewModel.cs | 16 +- .../Tools/SearchElementsViewModel.cs | 7 +- .../ViewModels/Tools/UnitsViewModel.cs | 8 +- .../BoundingBoxVisualizationViewModel.cs | 139 +++++ .../FaceVisualizationViewModel.cs | 154 ++++++ .../MeshVisualizationViewModel.cs | 154 ++++++ .../PolylineVisualizationViewModel.cs | 164 ++++++ .../SolidVisualizationViewModel.cs | 126 +++++ .../XyzVisualizationViewModel.cs | 167 ++++++ source/RevitLookup2/App.xaml | 33 -- source/RevitLookup2/Host.cs | 98 ---- source/RevitLookup2/RevitLookup2.csproj | 85 --- .../RevitLookup2.csproj.DotSettings | 4 - .../UserInterface/RevitThemeWatcherService.cs | 88 --- .../MockBoundingBoxVisualizationViewModel.cs | 39 -- .../MockFaceVisualizationViewModel.cs | 44 -- .../MockMeshVisualizationViewModel.cs | 44 -- .../MockPolylineVisualizationViewModel.cs | 44 -- .../MockSolidVisualizationViewModel.cs | 37 -- .../XyzVisualizationViewModel.cs | 46 -- .../.editorconfig | 0 .../App.xaml | 0 .../Application.cs | 33 +- .../Commands/DashboardCommand.cs | 9 +- .../Commands/EventMonitorCommand.cs | 9 +- .../Commands/SearchElementsCommand.cs | 12 +- .../Commands/SnoopDatabaseCommand.cs | 14 +- .../Commands/SnoopDocumentCommand.cs | 14 +- .../Commands/SnoopEdgeCommand.cs | 14 +- .../Commands/SnoopFaceCommand.cs | 14 +- .../Commands/SnoopLinkedElementCommand.cs | 14 +- .../Commands/SnoopPointCommand.cs | 14 +- .../Commands/SnoopSelectionCommand.cs | 14 +- .../Commands/SnoopSubElementCommand.cs | 14 +- .../Commands/SnoopViewCommand.cs | 14 +- .../Config/GeneralConfiguration.cs | 0 .../Config/LoggerConfigurator.cs | 10 +- .../Config/OptionsConfiguration.cs | 0 .../Config/RenderConfiguration.cs | 0 .../Core/ComponentModel/DescriptorMap.cs | 0 .../Descriptors/APIObjectDescriptor.cs | 0 .../AnalyticalLinkTypeDescriptor.cs | 0 .../Descriptors/ApplicationDescriptor.cs | 0 .../AreaVolumeSettingsDescriptor.cs | 0 .../Descriptors/AssetPropertiesDescriptor.cs | 0 .../Descriptors/AssetPropertyDescriptor.cs | 0 .../Descriptors/BasePointDescriptor.cs | 0 .../Descriptors/BoundarySegmentDescriptor.cs | 0 .../Descriptors/BoundingBoxXyzDescriptor.cs | 0 .../Descriptors/CategoryDescriptor.cs | 0 .../Descriptors/CityDescriptor.cs | 0 .../Descriptors/ColorDescriptor.cs | 0 .../Descriptors/ColorMediaDescriptor.cs | 0 .../CompoundStructureLayerDescriptor.cs | 0 .../Descriptors/ConnectorManagerDescriptor.cs | 0 .../Descriptors/CurtainGridDescriptor.cs | 0 .../Descriptors/CurveDescriptor.cs | 0 .../Descriptors/CurveElementDescriptor.cs | 0 .../Descriptors/CylindricalFaceDescriptor.cs | 0 .../Descriptors/DatumPlaneDescriptor.cs | 0 .../DefinitionBindingMapIteratorDescriptor.cs | 0 .../Descriptors/DefinitionDescriptor.cs | 0 .../Descriptors/DefinitionGroupDescriptor.cs | 0 .../Descriptors/DependencyObjectDescriptor.cs | 0 .../Descriptors/DisposableDescriptor.cs | 0 .../Descriptors/DocumentDescriptor.cs | 0 .../Descriptors/EdgeDescriptor.cs | 0 .../Descriptors/ElementDescriptor.cs | 0 .../Descriptors/ElementIdDescriptor.cs | 0 .../Descriptors/ElevationMarkerDescriptor.cs | 0 .../Descriptors/EntityDescriptor.cs | 0 .../Descriptors/EnumerableDescriptor.cs | 0 .../Descriptors/EnumeratorDescriptor.cs | 0 .../EvaluatedParameterDescriptor.cs | 0 .../Descriptors/ExternalServiceDescriptor.cs | 0 .../Descriptors/FaceDescriptor.cs | 0 .../Descriptors/FailureMessageDescriptor.cs | 0 .../Descriptors/FamilyDescriptor.cs | 0 .../Descriptors/FamilyInstanceDescriptor.cs | 0 .../Descriptors/FamilyManagerDescriptor.cs | 0 .../Descriptors/FamilyParameterDescriptor.cs | 0 .../FamilySizeTableColumnDescriptor.cs | 0 .../Descriptors/FamilySizeTableDescriptor.cs | 0 .../FamilySizeTableManagerDescriptor.cs | 0 .../Descriptors/FieldDescriptor.cs | 0 .../Descriptors/ForgeTypeIdDescriptor.cs | 0 .../Descriptors/GuidEnumDescriptor.cs | 0 .../Descriptors/HostObjectDescriptor.cs | 0 .../Descriptors/IndependentTagDescriptor.cs | 0 .../Descriptors/InternalOriginDescriptor.cs | 0 .../Descriptors/LightFamilyDescriptor.cs | 0 .../Descriptors/LocationCurveDescriptor.cs | 0 .../Descriptors/MacroManagerDescriptor.cs | 0 .../Descriptors/MepSectionDescriptor.cs | 0 .../Descriptors/MepSystemDescriptor.cs | 0 .../Descriptors/MeshDescriptor.cs | 0 .../Descriptors/PanelDescriptor.cs | 0 .../ComponentModel/Descriptors/PaperSize.cs | 0 .../Descriptors/ParameterDescriptor.cs | 0 .../Descriptors/PartDescriptor.cs | 0 .../Descriptors/PartMakerDescriptor.cs | 0 .../PerformanceAdviserDescriptor.cs | 0 .../Descriptors/PipeDescriptor.cs | 0 .../Descriptors/PlanViewRangeDescriptor.cs | 0 .../Descriptors/PrintManagerDescriptor.cs | 0 .../Descriptors/ReferenceDescriptor.cs | 0 .../RevisionNumberingSequenceDescriptor.cs | 0 .../Descriptors/RevitLinkTypeDescriptor.cs | 0 .../Descriptors/RibbonItemDescriptor.cs | 0 .../Descriptors/RibbonPanelDescriptor.cs | 0 .../Descriptors/RibbonTabDescriptor.cs | 0 .../Descriptors/SchedulableFieldDescriptor.cs | 0 .../ScheduleDefinitionDescriptor.cs | 0 .../Descriptors/SchemaDescriptor.cs | 0 .../Descriptors/SolidDescriptor.cs | 0 .../Descriptors/SpatialElementDescriptor.cs | 0 .../Descriptors/StringDescriptor.cs | 0 .../StructuralSettingsDescriptor.cs | 0 .../SunAndShadowSettingsDescriptor.cs | 0 .../Descriptors/TableDataDescriptor.cs | 0 .../Descriptors/TableSectionDataDescriptor.cs | 0 .../Descriptors/TableViewDescriptor.cs | 0 .../Descriptors/UiApplicationDescriptor.cs | 0 .../Descriptors/UiElementDescriptor.cs | 0 .../Descriptors/UiObjectDescriptor.cs | 0 .../Descriptors/UpdaterInfoDescriptor.cs | 0 .../Descriptors/VariantDescriptor.cs | 0 .../Descriptors/VariantsDescriptor.cs | 0 .../Descriptors/ViewDescriptor.cs | 0 .../Descriptors/ViewScheduleDescriptor.cs | 0 .../Descriptors/WireDescriptor.cs | 0 .../Descriptors/WorksetDescriptor.cs | 0 .../Descriptors/WorksetTableDescriptor.cs | 0 .../Descriptors/XyzDescriptor.cs | 0 .../Core/Enums/SearchOption.cs | 0 .../Configuration/RevitConfigurator.cs | 0 .../Core/Modules/Events/EventMonitor.cs | 0 .../BoundingBoxVisualizationServer.cs | 0 .../Visualization/FaceVisualizationServer.cs | 0 .../Helpers/RenderGeometryHelper.cs | 0 .../Visualization/Helpers/RenderHelper.cs | 0 .../Visualization/MeshVisualizationServer.cs | 0 .../PolylineVisualizationServer.cs | 0 .../Visualization/SolidVisualizationServer.cs | 0 .../Visualization/XyzVisualizationServer.cs | 0 .../Core/RevitShell.cs | 0 .../Core/Selector.cs | 0 .../Core/Utils/ContextUtils.cs | 0 source/RevitLookupObsolete/Host.cs | 136 +++++ .../Models/GitHubResponse.cs | 0 .../Models/OpenSourceSoftware.cs | 0 .../Models/Options/AssemblyInfo.cs | 0 .../Models/Options/FolderLocations.cs | 0 .../Models/Render/RenderFailedEventArgs.cs | 0 .../Models/Render/RenderingBufferStorage.cs | 0 .../Models/Settings/GeneralSettings.cs | 0 .../Models/Settings/RenderSettings.cs | 0 .../Models/UnitInfo.cs | 0 .../Resources/Images/RibbonIcon16.png | Bin .../Resources/Images/RibbonIcon32.png | Bin .../Resources/Localization/Colors.Designer.cs | 0 .../Resources/Localization/Colors.resx | 0 .../Resources/Localization/Colors.ru.resx | 0 .../RevitLookup.addin | 4 +- .../RevitLookupObsolete.csproj | 121 +++++ .../RevitLookupObsolete.csproj.DotSettings | 5 + .../RibbonController.cs | 0 .../Services/Contracts/ILookupService.cs | 0 .../Services/Contracts/ISettings.cs | 0 .../Services/Contracts/ISettingsService.cs | 0 .../Services/Contracts/ISnoopVisualService.cs | 0 .../Services/Contracts/IWindow.cs | 0 .../Services/Enums/SnoopableType.cs | 0 .../Services/HostedLifecycleService.cs | 0 .../Services/LookupService.cs | 0 .../Services/SettingsService.cs | 0 .../Services/SnoopVisualService.cs | 0 .../Services/SoftwareUpdateService.cs | 0 .../Utils/AccessUtils.cs | 0 .../Utils/ColorFormatUtils.cs | 0 .../Utils/ColorRepresentationUtils.cs | 0 .../Utils/ProcessTasks.cs | 0 .../Utils/RibbonUtils.cs | 0 .../Utils/TypeExtensions.cs | 0 .../Contracts/IDashboardViewModel.cs | 0 .../ViewModels/Contracts/IEventsViewModel.cs | 0 .../ViewModels/Contracts/ISnoopViewModel.cs | 0 .../Converters/BooleanVisibilityConverter.cs | 0 .../Converters/InverseBooleanConverter.cs | 0 .../InverseBooleanVisibilityConverter.cs | 0 .../ViewModels/Converters/UpdateConverter.cs | 0 .../Dialogs/EditParameterViewModel.cs | 0 .../FamilySizeTableEditDialogViewModel.cs | 0 .../FamilySizeTableSelectDialogViewModel.cs | 0 .../ViewModels/Dialogs/ModulesViewModel.cs | 0 .../ViewModels/Dialogs/OpenSourceViewModel.cs | 0 .../Dialogs/SearchElementsViewModel.cs | 0 .../ViewModels/Dialogs/UnitsViewModel.cs | 0 .../BoundingBoxVisualizationViewModel.cs | 0 .../FaceVisualizationViewModel.cs | 0 .../MeshVisualizationViewModel.cs | 0 .../PolylineVisualizationViewModel.cs | 0 .../SolidVisualizationViewModel.cs | 0 .../XyzVisualizationViewModel.cs | 0 .../ObservableRevitSettingsEntry.cs | 0 .../ViewModels/Pages/AboutViewModel.cs | 0 .../ViewModels/Pages/DashboardViewModel.cs | 0 .../ViewModels/Pages/EventsViewModel.cs | 0 .../Pages/RevitSettingsViewModel.cs | 0 .../ViewModels/Pages/SettingsViewModel.cs | 0 .../ViewModels/Pages/SnoopViewModel.cs | 0 .../ViewModels/Pages/SnoopViewModelBase.cs | 0 .../ViewModels/Utils/SearchEngine.cs | 0 .../Views/Appearance/RevitThemeWatcher.cs | 0 .../DataGrid/DataGridCellTemplate.xaml | 0 .../Controls/DataGrid/DataGridGroupStyle.xaml | 0 .../DataGrid/DataGridGroupStyle.xaml.cs | 0 .../Controls/DataGrid/DataGridRowStyle.xaml | 0 .../TreeView/TreeViewItemTemplate.xaml | 0 .../Converters/BytesToStringConverter.cs | 0 ...ectionEmptyAfterInitializationConverter.cs | 0 .../CollectionEmptyVisibilityConverter.cs | 0 .../Converters/DescriptorLabelConverters.cs | 0 .../EmptySearchResultsVisibilityConverter.cs | 0 .../Converters/IconDescriptorConverter.cs | 0 ...verseCollectionEmptyVisibilityConverter.cs | 0 .../Views/Converters/ObjectColorConverter.cs | 0 .../Views/Converters/TimeToStringConverter.cs | 0 .../Converters/TreeViewSourceConverter.cs | 0 .../Views/Dialogs/EditParameterDialog.xaml | 0 .../Views/Dialogs/EditParameterDialog.xaml.cs | 0 .../Dialogs/EditSettingsEntryDialog.xaml | 0 .../Dialogs/EditSettingsEntryDialog.xaml.cs | 0 .../Dialogs/FamilySizeTableEditDialog.xaml | 3 +- .../Dialogs/FamilySizeTableEditDialog.xaml.cs | 0 .../Dialogs/FamilySizeTableSelectDialog.xaml | 0 .../FamilySizeTableSelectDialog.xaml.cs | 0 .../Views/Dialogs/ModulesDialog.xaml | 0 .../Views/Dialogs/ModulesDialog.xaml.cs | 0 .../Views/Dialogs/OpenSourceDialog.xaml | 0 .../Views/Dialogs/OpenSourceDialog.xaml.cs | 0 .../Views/Dialogs/ResetSettingsDialog.xaml | 0 .../Views/Dialogs/ResetSettingsDialog.xaml.cs | 0 .../Views/Dialogs/SearchElementsDialog.xaml | 0 .../Dialogs/SearchElementsDialog.xaml.cs | 0 .../Views/Dialogs/UnitsDialog.xaml | 0 .../Views/Dialogs/UnitsDialog.xaml.cs | 0 .../BoundingBoxVisualizationDialog.xaml | 0 .../BoundingBoxVisualizationDialog.xaml.cs | 0 .../FaceVisualizationDialog.xaml | 0 .../FaceVisualizationDialog.xaml.cs | 0 .../MeshVisualizationDialog.xaml | 0 .../MeshVisualizationDialog.xaml.cs | 0 .../PolylineVisualizationDialog.xaml | 0 .../PolylineVisualizationDialog.xaml.cs | 0 .../SolidVisualizationDialog.xaml | 0 .../SolidVisualizationDialog.xaml.cs | 0 .../Visualization/XyzVisualizationDialog.xaml | 0 .../XyzVisualizationDialog.xaml.cs | 0 .../Views/Extensions/ContextMenuExtensions.cs | 0 .../FallbackValues/RevitConfigFallbacks.cs | 0 .../Views/Markup/MenusDictionary.cs | 0 .../Views/Markup/StylesDictionary.cs | 0 .../Views/Markup/ThemesDictionary.cs | 0 .../Abstraction/SnoopViewBase.ContextMenu.cs | 0 .../Abstraction/SnoopViewBase.Gestures.cs | 0 .../Abstraction/SnoopViewBase.Navigation.cs | 0 .../Pages/Abstraction/SnoopViewBase.Styles.cs | 0 .../Abstraction/SnoopViewBase.ToolTips.cs | 0 .../Pages/Abstraction/SnoopViewBase.xaml.cs | 0 .../Views/Pages/DashboardPage.xaml | 0 .../Views/Pages/DashboardPage.xaml.cs | 0 .../Views/Pages/EventsPage.xaml | 0 .../Views/Pages/EventsPage.xaml.cs | 0 .../Views/Pages/RevitSettingsPage.xaml | 0 .../Views/Pages/RevitSettingsPage.xaml.cs | 0 .../Views/Pages/SettingsPage.xaml | 0 .../Views/Pages/SnoopPage.xaml | 0 .../Views/Pages/SnoopPage.xaml.cs | 0 .../Views/Resources/Menus.xaml | 0 .../Views/Resources/RevitLookup.Ui.xaml | 8 +- .../Views/RevitLookupView.xaml | 0 .../Views/RevitLookupView.xaml.cs | 0 .../Views/Utils/HelpUtils.cs | 0 .../Views/Utils/VisualUtils.cs | 0 427 files changed, 4854 insertions(+), 1153 deletions(-) create mode 100644 source/RevitLookup.Abstractions/Services/Appearance/IThemeWatcherService.cs rename source/RevitLookup.Abstractions/Services/{ => Application}/IRevitLookupUiService.cs (94%) rename source/RevitLookup.Abstractions/Services/{ => Presentation}/INotificationService.cs (80%) rename source/RevitLookup.Abstractions/Services/{ => Presentation}/IWindowIntercomService.cs (82%) rename source/RevitLookup.Abstractions/Services/{ => Settings}/ISettingsService.cs (67%) rename source/RevitLookup.Abstractions/Services/{ => Settings}/ISoftwareUpdateService.cs (95%) rename source/RevitLookup.Abstractions/Services/{ => Summary}/IVisualDecompositionService.cs (91%) rename source/RevitLookup.UI.Framework/Services/{ => Presentation}/NotificationService.cs (97%) rename source/RevitLookup.UI.Framework/Services/{ => Presentation}/WindowIntercomService.cs (70%) rename source/RevitLookup.UI.Playground/Client/Services/{ViewModelsRegistrationExtensions.cs => ViewModelServices.cs} (92%) rename source/RevitLookup.UI.Playground/Client/Services/{ViewsRegistrationExtensions.cs => ViewServices.cs} (96%) create mode 100644 source/RevitLookup.UI.Playground/Services/Appearance/MockThemeWatcherService.cs rename source/RevitLookup.UI.Playground/Services/{ => Application}/MockRevitLookupUiService.cs (96%) rename source/RevitLookup.UI.Playground/Services/{ => Settings}/MockSettingsService.cs (96%) rename source/RevitLookup.UI.Playground/Services/{ => Settings}/MockSoftwareUpdateService.cs (91%) rename source/RevitLookup.UI.Playground/Services/{ => Summary}/MockVisualDecompositionService.cs (95%) rename source/{RevitLookup2 => RevitLookup}/Config/ApplicationOptions.cs (98%) rename source/{RevitLookup2 => RevitLookup}/Config/SerializerOptions.cs (95%) rename source/{RevitLookup2 => RevitLookup}/Core/RevitShell.API.cs (99%) rename source/{RevitLookup2 => RevitLookup}/Core/RevitShell.Handlers.cs (98%) rename source/{RevitLookup2/Core/Tools => RevitLookup/Core/Tools/RevitSettings}/RevitConfigurator.cs (99%) rename source/{RevitLookup2/Core/Tools => RevitLookup/Core/Tools/Search}/ElementsFinder.cs (98%) rename source/{RevitLookup2/Core/Tools => RevitLookup/Core/Tools/Units}/UnitsCollector.cs (99%) create mode 100644 source/RevitLookup/Core/Tools/Visualization/BoundingBoxVisualizationServer.cs create mode 100644 source/RevitLookup/Core/Tools/Visualization/Buffers/RenderingBufferStorage.cs create mode 100644 source/RevitLookup/Core/Tools/Visualization/Events/RenderFailedEventArgs.cs create mode 100644 source/RevitLookup/Core/Tools/Visualization/FaceVisualizationServer.cs create mode 100644 source/RevitLookup/Core/Tools/Visualization/Helpers/RenderGeometryHelper.cs create mode 100644 source/RevitLookup/Core/Tools/Visualization/Helpers/RenderHelper.cs create mode 100644 source/RevitLookup/Core/Tools/Visualization/MeshVisualizationServer.cs create mode 100644 source/RevitLookup/Core/Tools/Visualization/PolylineVisualizationServer.cs create mode 100644 source/RevitLookup/Core/Tools/Visualization/SolidVisualizationServer.cs create mode 100644 source/RevitLookup/Core/Tools/Visualization/XyzVisualizationServer.cs rename source/{RevitLookup2 => RevitLookup}/Mappers/DecompositionResultMapper.cs (93%) create mode 100644 source/RevitLookup/Services/Appearance/ThemeWatcherService.cs rename source/{RevitLookup2 => RevitLookup}/Services/Application/HostBackgroundService.cs (96%) rename source/{RevitLookup2 => RevitLookup}/Services/Application/RevitLookupUiService.cs (96%) rename source/{RevitLookup2 => RevitLookup}/Services/Application/RibbonController.cs (99%) rename source/{RevitLookup.UI.Playground => RevitLookup}/Services/RevitLookupUiService.cs (99%) rename source/{RevitLookup2 => RevitLookup}/Services/Settings/SettingsService.cs (95%) rename source/{RevitLookup2 => RevitLookup}/Services/Settings/SoftwareUpdateService.cs (97%) rename source/{RevitLookup2 => RevitLookup}/Services/Summary/EventsMonitoringService.cs (98%) rename source/{RevitLookup2 => RevitLookup}/Services/Summary/VisualDecompositionService.cs (94%) rename source/{RevitLookup2 => RevitLookup}/Services/ViewModelServices.cs (93%) rename source/{RevitLookup2 => RevitLookup}/Services/ViewServices.cs (96%) create mode 100644 source/RevitLookup/Styles/App.Resources.xaml rename source/{RevitLookup2 => RevitLookup}/Styles/ComponentStyles/MembersGrid/DataGridCellTemplate.xaml (97%) rename source/{RevitLookup2 => RevitLookup}/Styles/ComponentStyles/MembersGrid/DataGridCellTemplateSelector.cs (93%) rename source/{RevitLookup2 => RevitLookup}/Styles/ComponentStyles/MembersGrid/DataGridGroupStyles.xaml (96%) rename source/{RevitLookup2 => RevitLookup}/Styles/ComponentStyles/MembersGrid/DataGridRowStyle.xaml (90%) rename source/{RevitLookup2 => RevitLookup}/Styles/ComponentStyles/MembersGrid/DataGridRowStyleSelector.cs (95%) rename source/{RevitLookup2 => RevitLookup}/Styles/ComponentStyles/ObjectsTree/TreeGroupTemplates.xaml (95%) rename source/{RevitLookup2 => RevitLookup}/Styles/ComponentStyles/ObjectsTree/TreeViewItemTemplateSelector.cs (93%) rename source/{RevitLookup2 => RevitLookup}/Styles/Converters/ObjectColorConverter.cs (94%) rename source/{RevitLookup2 => RevitLookup}/Utils/RibbonExtensions.cs (99%) rename source/{RevitLookup2 => RevitLookup}/ViewModels/AboutProgram/AboutViewModel.cs (98%) rename source/{RevitLookup2 => RevitLookup}/ViewModels/AboutProgram/OpenSourceViewModel.cs (98%) rename source/{RevitLookup2 => RevitLookup}/ViewModels/Dashboard/DashboardViewModel.cs (99%) rename source/{RevitLookup2 => RevitLookup}/ViewModels/Settings/SettingsViewModel.cs (88%) rename source/{RevitLookup2 => RevitLookup}/ViewModels/Summary/MockDecompositionSummaryViewModel.cs (96%) rename source/{RevitLookup2 => RevitLookup}/ViewModels/Summary/MockEventsSummaryViewModel.cs (96%) rename source/{RevitLookup2 => RevitLookup}/ViewModels/Tools/ModulesViewModel.cs (98%) rename source/{RevitLookup2 => RevitLookup}/ViewModels/Tools/RevitSettingsViewModel.cs (92%) rename source/{RevitLookup2 => RevitLookup}/ViewModels/Tools/SearchElementsViewModel.cs (81%) rename source/{RevitLookup2 => RevitLookup}/ViewModels/Tools/UnitsViewModel.cs (95%) create mode 100644 source/RevitLookup/ViewModels/Visualization/BoundingBoxVisualizationViewModel.cs create mode 100644 source/RevitLookup/ViewModels/Visualization/FaceVisualizationViewModel.cs create mode 100644 source/RevitLookup/ViewModels/Visualization/MeshVisualizationViewModel.cs create mode 100644 source/RevitLookup/ViewModels/Visualization/PolylineVisualizationViewModel.cs create mode 100644 source/RevitLookup/ViewModels/Visualization/SolidVisualizationViewModel.cs create mode 100644 source/RevitLookup/ViewModels/Visualization/XyzVisualizationViewModel.cs delete mode 100644 source/RevitLookup2/App.xaml delete mode 100644 source/RevitLookup2/Host.cs delete mode 100644 source/RevitLookup2/RevitLookup2.csproj delete mode 100644 source/RevitLookup2/RevitLookup2.csproj.DotSettings delete mode 100644 source/RevitLookup2/Services/UserInterface/RevitThemeWatcherService.cs delete mode 100644 source/RevitLookup2/ViewModels/Visualization/MockBoundingBoxVisualizationViewModel.cs delete mode 100644 source/RevitLookup2/ViewModels/Visualization/MockFaceVisualizationViewModel.cs delete mode 100644 source/RevitLookup2/ViewModels/Visualization/MockMeshVisualizationViewModel.cs delete mode 100644 source/RevitLookup2/ViewModels/Visualization/MockPolylineVisualizationViewModel.cs delete mode 100644 source/RevitLookup2/ViewModels/Visualization/MockSolidVisualizationViewModel.cs delete mode 100644 source/RevitLookup2/ViewModels/Visualization/XyzVisualizationViewModel.cs rename source/{RevitLookup => RevitLookupObsolete}/.editorconfig (100%) rename source/{RevitLookup => RevitLookupObsolete}/App.xaml (100%) rename source/{RevitLookup2 => RevitLookupObsolete}/Application.cs (58%) rename source/{RevitLookup2 => RevitLookupObsolete}/Commands/DashboardCommand.cs (85%) rename source/{RevitLookup2 => RevitLookupObsolete}/Commands/EventMonitorCommand.cs (85%) rename source/{RevitLookup2 => RevitLookupObsolete}/Commands/SearchElementsCommand.cs (78%) rename source/{RevitLookup2 => RevitLookupObsolete}/Commands/SnoopDatabaseCommand.cs (78%) rename source/{RevitLookup2 => RevitLookupObsolete}/Commands/SnoopDocumentCommand.cs (78%) rename source/{RevitLookup2 => RevitLookupObsolete}/Commands/SnoopEdgeCommand.cs (78%) rename source/{RevitLookup2 => RevitLookupObsolete}/Commands/SnoopFaceCommand.cs (78%) rename source/{RevitLookup2 => RevitLookupObsolete}/Commands/SnoopLinkedElementCommand.cs (78%) rename source/{RevitLookup2 => RevitLookupObsolete}/Commands/SnoopPointCommand.cs (78%) rename source/{RevitLookup2 => RevitLookupObsolete}/Commands/SnoopSelectionCommand.cs (78%) rename source/{RevitLookup2 => RevitLookupObsolete}/Commands/SnoopSubElementCommand.cs (78%) rename source/{RevitLookup2 => RevitLookupObsolete}/Commands/SnoopViewCommand.cs (78%) rename source/{RevitLookup => RevitLookupObsolete}/Config/GeneralConfiguration.cs (100%) rename source/{RevitLookup2 => RevitLookupObsolete}/Config/LoggerConfigurator.cs (95%) rename source/{RevitLookup => RevitLookupObsolete}/Config/OptionsConfiguration.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Config/RenderConfiguration.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/DescriptorMap.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/APIObjectDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/AnalyticalLinkTypeDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/ApplicationDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/AreaVolumeSettingsDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/AssetPropertiesDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/AssetPropertyDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/BasePointDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/BoundarySegmentDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/BoundingBoxXyzDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/CategoryDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/CityDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/ColorDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/ColorMediaDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/CompoundStructureLayerDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/ConnectorManagerDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/CurtainGridDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/CurveDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/CurveElementDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/CylindricalFaceDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/DatumPlaneDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/DefinitionBindingMapIteratorDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/DefinitionDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/DefinitionGroupDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/DependencyObjectDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/DisposableDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/DocumentDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/EdgeDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/ElementDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/ElementIdDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/ElevationMarkerDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/EntityDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/EnumerableDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/EnumeratorDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/EvaluatedParameterDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/ExternalServiceDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/FaceDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/FailureMessageDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/FamilyDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/FamilyInstanceDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/FamilyManagerDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/FamilyParameterDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/FamilySizeTableColumnDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/FamilySizeTableDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/FamilySizeTableManagerDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/FieldDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/ForgeTypeIdDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/GuidEnumDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/HostObjectDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/IndependentTagDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/InternalOriginDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/LightFamilyDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/LocationCurveDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/MacroManagerDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/MepSectionDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/MepSystemDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/MeshDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/PanelDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/PaperSize.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/ParameterDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/PartDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/PartMakerDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/PerformanceAdviserDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/PipeDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/PlanViewRangeDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/PrintManagerDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/ReferenceDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/RevisionNumberingSequenceDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/RevitLinkTypeDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/RibbonItemDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/RibbonPanelDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/RibbonTabDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/SchedulableFieldDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/ScheduleDefinitionDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/SchemaDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/SolidDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/SpatialElementDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/StringDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/StructuralSettingsDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/SunAndShadowSettingsDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/TableDataDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/TableSectionDataDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/TableViewDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/UiApplicationDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/UiElementDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/UiObjectDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/UpdaterInfoDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/VariantDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/VariantsDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/ViewDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/ViewScheduleDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/WireDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/WorksetDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/WorksetTableDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/ComponentModel/Descriptors/XyzDescriptor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/Enums/SearchOption.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/Modules/Configuration/RevitConfigurator.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/Modules/Events/EventMonitor.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/Modules/Visualization/BoundingBoxVisualizationServer.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/Modules/Visualization/FaceVisualizationServer.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/Modules/Visualization/Helpers/RenderGeometryHelper.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/Modules/Visualization/Helpers/RenderHelper.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/Modules/Visualization/MeshVisualizationServer.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/Modules/Visualization/PolylineVisualizationServer.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/Modules/Visualization/SolidVisualizationServer.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/Modules/Visualization/XyzVisualizationServer.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/RevitShell.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/Selector.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Core/Utils/ContextUtils.cs (100%) create mode 100644 source/RevitLookupObsolete/Host.cs rename source/{RevitLookup => RevitLookupObsolete}/Models/GitHubResponse.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Models/OpenSourceSoftware.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Models/Options/AssemblyInfo.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Models/Options/FolderLocations.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Models/Render/RenderFailedEventArgs.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Models/Render/RenderingBufferStorage.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Models/Settings/GeneralSettings.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Models/Settings/RenderSettings.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Models/UnitInfo.cs (100%) rename source/{RevitLookup2 => RevitLookupObsolete}/Resources/Images/RibbonIcon16.png (100%) rename source/{RevitLookup2 => RevitLookupObsolete}/Resources/Images/RibbonIcon32.png (100%) rename source/{RevitLookup => RevitLookupObsolete}/Resources/Localization/Colors.Designer.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Resources/Localization/Colors.resx (100%) rename source/{RevitLookup => RevitLookupObsolete}/Resources/Localization/Colors.ru.resx (100%) rename source/{RevitLookup2 => RevitLookupObsolete}/RevitLookup.addin (69%) create mode 100644 source/RevitLookupObsolete/RevitLookupObsolete.csproj create mode 100644 source/RevitLookupObsolete/RevitLookupObsolete.csproj.DotSettings rename source/{RevitLookup => RevitLookupObsolete}/RibbonController.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Services/Contracts/ILookupService.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Services/Contracts/ISettings.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Services/Contracts/ISettingsService.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Services/Contracts/ISnoopVisualService.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Services/Contracts/IWindow.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Services/Enums/SnoopableType.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Services/HostedLifecycleService.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Services/LookupService.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Services/SettingsService.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Services/SnoopVisualService.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Services/SoftwareUpdateService.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Utils/AccessUtils.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Utils/ColorFormatUtils.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Utils/ColorRepresentationUtils.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Utils/ProcessTasks.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Utils/RibbonUtils.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Utils/TypeExtensions.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/ViewModels/Contracts/IDashboardViewModel.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/ViewModels/Contracts/IEventsViewModel.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/ViewModels/Contracts/ISnoopViewModel.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/ViewModels/Converters/BooleanVisibilityConverter.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/ViewModels/Converters/InverseBooleanConverter.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/ViewModels/Converters/InverseBooleanVisibilityConverter.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/ViewModels/Converters/UpdateConverter.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/ViewModels/Dialogs/EditParameterViewModel.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/ViewModels/Dialogs/FamilySizeTableEditDialogViewModel.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/ViewModels/Dialogs/FamilySizeTableSelectDialogViewModel.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/ViewModels/Dialogs/ModulesViewModel.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/ViewModels/Dialogs/OpenSourceViewModel.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/ViewModels/Dialogs/SearchElementsViewModel.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/ViewModels/Dialogs/UnitsViewModel.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/ViewModels/Dialogs/Visualization/BoundingBoxVisualizationViewModel.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/ViewModels/Dialogs/Visualization/FaceVisualizationViewModel.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/ViewModels/Dialogs/Visualization/MeshVisualizationViewModel.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/ViewModels/Dialogs/Visualization/PolylineVisualizationViewModel.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/ViewModels/Dialogs/Visualization/SolidVisualizationViewModel.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/ViewModels/Dialogs/Visualization/XyzVisualizationViewModel.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/ViewModels/ObservableObjects/ObservableRevitSettingsEntry.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/ViewModels/Pages/AboutViewModel.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/ViewModels/Pages/DashboardViewModel.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/ViewModels/Pages/EventsViewModel.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/ViewModels/Pages/RevitSettingsViewModel.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/ViewModels/Pages/SettingsViewModel.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/ViewModels/Pages/SnoopViewModel.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/ViewModels/Pages/SnoopViewModelBase.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/ViewModels/Utils/SearchEngine.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Appearance/RevitThemeWatcher.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Controls/DataGrid/DataGridCellTemplate.xaml (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Controls/DataGrid/DataGridGroupStyle.xaml (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Controls/DataGrid/DataGridGroupStyle.xaml.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Controls/DataGrid/DataGridRowStyle.xaml (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Controls/TreeView/TreeViewItemTemplate.xaml (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Converters/BytesToStringConverter.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Converters/CollectionEmptyAfterInitializationConverter.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Converters/CollectionEmptyVisibilityConverter.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Converters/DescriptorLabelConverters.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Converters/EmptySearchResultsVisibilityConverter.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Converters/IconDescriptorConverter.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Converters/InverseCollectionEmptyVisibilityConverter.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Converters/ObjectColorConverter.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Converters/TimeToStringConverter.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Converters/TreeViewSourceConverter.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Dialogs/EditParameterDialog.xaml (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Dialogs/EditParameterDialog.xaml.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Dialogs/EditSettingsEntryDialog.xaml (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Dialogs/EditSettingsEntryDialog.xaml.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Dialogs/FamilySizeTableEditDialog.xaml (92%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Dialogs/FamilySizeTableEditDialog.xaml.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Dialogs/FamilySizeTableSelectDialog.xaml (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Dialogs/FamilySizeTableSelectDialog.xaml.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Dialogs/ModulesDialog.xaml (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Dialogs/ModulesDialog.xaml.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Dialogs/OpenSourceDialog.xaml (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Dialogs/OpenSourceDialog.xaml.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Dialogs/ResetSettingsDialog.xaml (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Dialogs/ResetSettingsDialog.xaml.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Dialogs/SearchElementsDialog.xaml (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Dialogs/SearchElementsDialog.xaml.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Dialogs/UnitsDialog.xaml (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Dialogs/UnitsDialog.xaml.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Dialogs/Visualization/BoundingBoxVisualizationDialog.xaml (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Dialogs/Visualization/BoundingBoxVisualizationDialog.xaml.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Dialogs/Visualization/FaceVisualizationDialog.xaml (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Dialogs/Visualization/FaceVisualizationDialog.xaml.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Dialogs/Visualization/MeshVisualizationDialog.xaml (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Dialogs/Visualization/MeshVisualizationDialog.xaml.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Dialogs/Visualization/PolylineVisualizationDialog.xaml (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Dialogs/Visualization/PolylineVisualizationDialog.xaml.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Dialogs/Visualization/SolidVisualizationDialog.xaml (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Dialogs/Visualization/SolidVisualizationDialog.xaml.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Dialogs/Visualization/XyzVisualizationDialog.xaml (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Dialogs/Visualization/XyzVisualizationDialog.xaml.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Extensions/ContextMenuExtensions.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/FallbackValues/RevitConfigFallbacks.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Markup/MenusDictionary.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Markup/StylesDictionary.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Markup/ThemesDictionary.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Pages/Abstraction/SnoopViewBase.ContextMenu.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Pages/Abstraction/SnoopViewBase.Gestures.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Pages/Abstraction/SnoopViewBase.Navigation.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Pages/Abstraction/SnoopViewBase.Styles.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Pages/Abstraction/SnoopViewBase.ToolTips.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Pages/Abstraction/SnoopViewBase.xaml.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Pages/DashboardPage.xaml (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Pages/DashboardPage.xaml.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Pages/EventsPage.xaml (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Pages/EventsPage.xaml.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Pages/RevitSettingsPage.xaml (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Pages/RevitSettingsPage.xaml.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Pages/SettingsPage.xaml (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Pages/SnoopPage.xaml (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Pages/SnoopPage.xaml.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Resources/Menus.xaml (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Resources/RevitLookup.Ui.xaml (71%) rename source/{RevitLookup => RevitLookupObsolete}/Views/RevitLookupView.xaml (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/RevitLookupView.xaml.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Utils/HelpUtils.cs (100%) rename source/{RevitLookup => RevitLookupObsolete}/Views/Utils/VisualUtils.cs (100%) diff --git a/RevitLookup.slnx b/RevitLookup.slnx index 8a3b128f..4e344f89 100644 --- a/RevitLookup.slnx +++ b/RevitLookup.slnx @@ -159,14 +159,7 @@ - - - - - - - - + @@ -183,6 +176,13 @@ + + + + + + + diff --git a/source/RevitLookup.Abstractions/Services/Appearance/IThemeWatcherService.cs b/source/RevitLookup.Abstractions/Services/Appearance/IThemeWatcherService.cs new file mode 100644 index 00000000..fb33870e --- /dev/null +++ b/source/RevitLookup.Abstractions/Services/Appearance/IThemeWatcherService.cs @@ -0,0 +1,11 @@ +using System.Windows; + +namespace RevitLookup.Abstractions.Services.Appearance; + +public interface IThemeWatcherService +{ + void Initialize(); + void Watch(); + void Watch(FrameworkElement frameworkElement); + void Unwatch(); +} \ No newline at end of file diff --git a/source/RevitLookup.Abstractions/Services/IRevitLookupUiService.cs b/source/RevitLookup.Abstractions/Services/Application/IRevitLookupUiService.cs similarity index 94% rename from source/RevitLookup.Abstractions/Services/IRevitLookupUiService.cs rename to source/RevitLookup.Abstractions/Services/Application/IRevitLookupUiService.cs index e372166c..7183ae7e 100644 --- a/source/RevitLookup.Abstractions/Services/IRevitLookupUiService.cs +++ b/source/RevitLookup.Abstractions/Services/Application/IRevitLookupUiService.cs @@ -4,7 +4,7 @@ using RevitLookup.Abstractions.Models.Summary; using RevitLookup.Abstractions.ObservableModels.Decomposition; -namespace RevitLookup.Abstractions.Services; +namespace RevitLookup.Abstractions.Services.Application; public interface IRevitLookupUiService : ILookupServiceDependsStage, ILookupServiceRunStage { diff --git a/source/RevitLookup.Abstractions/Services/INotificationService.cs b/source/RevitLookup.Abstractions/Services/Presentation/INotificationService.cs similarity index 80% rename from source/RevitLookup.Abstractions/Services/INotificationService.cs rename to source/RevitLookup.Abstractions/Services/Presentation/INotificationService.cs index c3dc35e9..0d1b0316 100644 --- a/source/RevitLookup.Abstractions/Services/INotificationService.cs +++ b/source/RevitLookup.Abstractions/Services/Presentation/INotificationService.cs @@ -1,4 +1,4 @@ -namespace RevitLookup.Abstractions.Services; +namespace RevitLookup.Abstractions.Services.Presentation; public interface INotificationService { diff --git a/source/RevitLookup.Abstractions/Services/IWindowIntercomService.cs b/source/RevitLookup.Abstractions/Services/Presentation/IWindowIntercomService.cs similarity index 82% rename from source/RevitLookup.Abstractions/Services/IWindowIntercomService.cs rename to source/RevitLookup.Abstractions/Services/Presentation/IWindowIntercomService.cs index ec364792..4cb5dd1d 100644 --- a/source/RevitLookup.Abstractions/Services/IWindowIntercomService.cs +++ b/source/RevitLookup.Abstractions/Services/Presentation/IWindowIntercomService.cs @@ -1,7 +1,7 @@ using System.Windows; using System.Windows.Threading; -namespace RevitLookup.Abstractions.Services; +namespace RevitLookup.Abstractions.Services.Presentation; public interface IWindowIntercomService { diff --git a/source/RevitLookup.Abstractions/Services/ISettingsService.cs b/source/RevitLookup.Abstractions/Services/Settings/ISettingsService.cs similarity index 67% rename from source/RevitLookup.Abstractions/Services/ISettingsService.cs rename to source/RevitLookup.Abstractions/Services/Settings/ISettingsService.cs index af489427..54d24b69 100644 --- a/source/RevitLookup.Abstractions/Services/ISettingsService.cs +++ b/source/RevitLookup.Abstractions/Services/Settings/ISettingsService.cs @@ -1,6 +1,6 @@ using RevitLookup.Abstractions.Models.Settings; -namespace RevitLookup.Abstractions.Services; +namespace RevitLookup.Abstractions.Services.Settings; public interface ISettingsService { @@ -8,5 +8,6 @@ public interface ISettingsService public RenderSettings RenderSettings { get; } void SaveSettings(); void LoadSettings(); - void ResetSettings(); + void ResetGeneralSettings(); + void ResetRenderSettings(); } \ No newline at end of file diff --git a/source/RevitLookup.Abstractions/Services/ISoftwareUpdateService.cs b/source/RevitLookup.Abstractions/Services/Settings/ISoftwareUpdateService.cs similarity index 95% rename from source/RevitLookup.Abstractions/Services/ISoftwareUpdateService.cs rename to source/RevitLookup.Abstractions/Services/Settings/ISoftwareUpdateService.cs index 5106ae6e..f8336265 100644 --- a/source/RevitLookup.Abstractions/Services/ISoftwareUpdateService.cs +++ b/source/RevitLookup.Abstractions/Services/Settings/ISoftwareUpdateService.cs @@ -18,7 +18,7 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. -namespace RevitLookup.Abstractions.Services; +namespace RevitLookup.Abstractions.Services.Settings; public interface ISoftwareUpdateService { diff --git a/source/RevitLookup.Abstractions/Services/IVisualDecompositionService.cs b/source/RevitLookup.Abstractions/Services/Summary/IVisualDecompositionService.cs similarity index 91% rename from source/RevitLookup.Abstractions/Services/IVisualDecompositionService.cs rename to source/RevitLookup.Abstractions/Services/Summary/IVisualDecompositionService.cs index 7a2d14e1..858df6c2 100644 --- a/source/RevitLookup.Abstractions/Services/IVisualDecompositionService.cs +++ b/source/RevitLookup.Abstractions/Services/Summary/IVisualDecompositionService.cs @@ -2,7 +2,7 @@ using RevitLookup.Abstractions.Models.Summary; using RevitLookup.Abstractions.ObservableModels.Decomposition; -namespace RevitLookup.Abstractions.Services; +namespace RevitLookup.Abstractions.Services.Summary; public interface IVisualDecompositionService { diff --git a/source/RevitLookup.UI.Framework/Converters/BooleanCollapsedVisibilityConverter.cs b/source/RevitLookup.UI.Framework/Converters/BooleanCollapsedVisibilityConverter.cs index e96a1512..b81f5c1c 100644 --- a/source/RevitLookup.UI.Framework/Converters/BooleanCollapsedVisibilityConverter.cs +++ b/source/RevitLookup.UI.Framework/Converters/BooleanCollapsedVisibilityConverter.cs @@ -1,5 +1,4 @@ -using System; -using System.Globalization; +using System.Globalization; using System.Windows.Data; using System.Windows.Markup; using Visibility = System.Windows.Visibility; @@ -10,12 +9,12 @@ public sealed class BooleanCollapsedVisibilityConverter : MarkupExtension, IValu { public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { - return (bool)value! ? Visibility.Visible : Visibility.Collapsed; + return (bool) value! ? Visibility.Visible : Visibility.Collapsed; } public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { - return (Visibility)value! == Visibility.Visible; + return (Visibility) value! == Visibility.Visible; } public override object ProvideValue(IServiceProvider serviceProvider) diff --git a/source/RevitLookup.UI.Framework/Converters/BooleanHiddenVisibilityConverter.cs b/source/RevitLookup.UI.Framework/Converters/BooleanHiddenVisibilityConverter.cs index 99fcca44..f97c3f69 100644 --- a/source/RevitLookup.UI.Framework/Converters/BooleanHiddenVisibilityConverter.cs +++ b/source/RevitLookup.UI.Framework/Converters/BooleanHiddenVisibilityConverter.cs @@ -1,5 +1,4 @@ -using System; -using System.Globalization; +using System.Globalization; using System.Windows.Data; using System.Windows.Markup; using Visibility = System.Windows.Visibility; @@ -10,12 +9,12 @@ public sealed class BooleanHiddenVisibilityConverter : MarkupExtension, IValueCo { public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { - return (bool)value! ? Visibility.Visible : Visibility.Hidden; + return (bool) value! ? Visibility.Visible : Visibility.Hidden; } public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { - return (Visibility)value! == Visibility.Visible; + return (Visibility) value! == Visibility.Visible; } public override object ProvideValue(IServiceProvider serviceProvider) diff --git a/source/RevitLookup.UI.Framework/Converters/EnumCollapsedVisibilityConverter.cs b/source/RevitLookup.UI.Framework/Converters/EnumCollapsedVisibilityConverter.cs index 1fb8f41d..32986b8a 100644 --- a/source/RevitLookup.UI.Framework/Converters/EnumCollapsedVisibilityConverter.cs +++ b/source/RevitLookup.UI.Framework/Converters/EnumCollapsedVisibilityConverter.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using System.Globalization; using System.Windows.Data; using System.Windows.Markup; @@ -20,7 +18,7 @@ public object Convert(object? value, Type targetType, object? parameter, Culture { throw new ArgumentException($"{nameof(parameter)} is not type: {typeof(TEnum)}"); } - + return EqualityComparer.Default.Equals(valueEnum, parameterEnum) ? Visibility.Visible : Visibility.Collapsed; } diff --git a/source/RevitLookup.UI.Framework/Converters/EnumHiddenVisibilityConverter.cs b/source/RevitLookup.UI.Framework/Converters/EnumHiddenVisibilityConverter.cs index dae14dcc..9068b0b1 100644 --- a/source/RevitLookup.UI.Framework/Converters/EnumHiddenVisibilityConverter.cs +++ b/source/RevitLookup.UI.Framework/Converters/EnumHiddenVisibilityConverter.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using System.Globalization; using System.Windows.Data; using System.Windows.Markup; @@ -20,7 +18,7 @@ public object Convert(object? value, Type targetType, object? parameter, Culture { throw new ArgumentException($"{nameof(parameter)} is not type: {typeof(TEnum)}"); } - + return EqualityComparer.Default.Equals(valueEnum, parameterEnum) ? Visibility.Visible : Visibility.Hidden; } diff --git a/source/RevitLookup.UI.Framework/Converters/InverseBooleanCollapsedVisibilityConverter.cs b/source/RevitLookup.UI.Framework/Converters/InverseBooleanCollapsedVisibilityConverter.cs index fb087dac..26378692 100644 --- a/source/RevitLookup.UI.Framework/Converters/InverseBooleanCollapsedVisibilityConverter.cs +++ b/source/RevitLookup.UI.Framework/Converters/InverseBooleanCollapsedVisibilityConverter.cs @@ -1,4 +1,3 @@ -using System; using System.Globalization; using System.Windows.Data; using System.Windows.Markup; @@ -10,12 +9,12 @@ public sealed class InverseBooleanCollapsedVisibilityConverter : MarkupExtension { public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { - return (bool)value! == false ? Visibility.Visible : Visibility.Collapsed; + return (bool) value! == false ? Visibility.Visible : Visibility.Collapsed; } public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { - return (Visibility)value! != Visibility.Visible; + return (Visibility) value! != Visibility.Visible; } public override object ProvideValue(IServiceProvider serviceProvider) diff --git a/source/RevitLookup.UI.Framework/Converters/InverseBooleanHiddenVisibilityConverter.cs b/source/RevitLookup.UI.Framework/Converters/InverseBooleanHiddenVisibilityConverter.cs index dcf4cd0b..6e4cdca6 100644 --- a/source/RevitLookup.UI.Framework/Converters/InverseBooleanHiddenVisibilityConverter.cs +++ b/source/RevitLookup.UI.Framework/Converters/InverseBooleanHiddenVisibilityConverter.cs @@ -1,4 +1,3 @@ -using System; using System.Globalization; using System.Windows.Data; using System.Windows.Markup; @@ -10,12 +9,12 @@ public class InverseBooleanHiddenVisibilityConverter : MarkupExtension, IValueCo { public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { - return (bool)value! == false ? Visibility.Visible : Visibility.Hidden; + return (bool) value! == false ? Visibility.Visible : Visibility.Hidden; } public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { - return (Visibility)value! != Visibility.Visible; + return (Visibility) value! != Visibility.Visible; } public override object ProvideValue(IServiceProvider serviceProvider) diff --git a/source/RevitLookup.UI.Framework/Extensions/CollectionExtensions.cs b/source/RevitLookup.UI.Framework/Extensions/CollectionExtensions.cs index 1bcedb2f..4f7a12a7 100644 --- a/source/RevitLookup.UI.Framework/Extensions/CollectionExtensions.cs +++ b/source/RevitLookup.UI.Framework/Extensions/CollectionExtensions.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.Collections.ObjectModel; +using System.Collections.ObjectModel; namespace RevitLookup.UI.Framework.Extensions; diff --git a/source/RevitLookup.UI.Framework/Extensions/ContextMenuExtensions.cs b/source/RevitLookup.UI.Framework/Extensions/ContextMenuExtensions.cs index b8cc9ce7..e8fe532c 100644 --- a/source/RevitLookup.UI.Framework/Extensions/ContextMenuExtensions.cs +++ b/source/RevitLookup.UI.Framework/Extensions/ContextMenuExtensions.cs @@ -37,7 +37,7 @@ public static void AddSeparator(this ContextMenu menu) public static void AddLabel(this ContextMenu menu, string text) { - var label = (MenuItem?)menu.Resources["Label"]; + var label = (MenuItem?) menu.Resources["Label"]; if (label is null) throw new InvalidOperationException("Resource \"Label\" not found"); label.Header = text; @@ -54,7 +54,7 @@ public static MenuItem AddMenuItem(this ContextMenu menu) public static MenuItem AddMenuItem(this ContextMenu menu, string resource) { - var item = (MenuItem?)menu.Resources[resource]; + var item = (MenuItem?) menu.Resources[resource]; if (item is null) throw new InvalidOperationException($"Resource \"{resource}\" not found"); menu.Items.Add(item); @@ -76,6 +76,13 @@ public static MenuItem SetCommand(this MenuItem item, ICommand command) return item; } + public static MenuItem SetCommand(this MenuItem item, Func command) + { + item.Command = new AsyncRelayCommand(command); + + return item; + } + public static MenuItem SetCommand(this MenuItem item, T parameter, Action command) { item.CommandParameter = parameter; @@ -98,7 +105,7 @@ public static MenuItem SetShortcut(this MenuItem item, ModifierKeys modifiers, K var menu = item.FindLogicalParent(); if (menu is null) throw new InvalidOperationException("Unable to find context menu"); - menu.PlacementTarget.InputBindings.Add(new InputBinding(item.Command, inputGesture) { CommandParameter = item.CommandParameter }); + menu.PlacementTarget.InputBindings.Add(new InputBinding(item.Command, inputGesture) {CommandParameter = item.CommandParameter}); item.InputGestureText = inputGesture.GetDisplayStringForCulture(CultureInfo.InvariantCulture); return item; @@ -110,7 +117,7 @@ public static MenuItem SetShortcut(this MenuItem item, Key key) var menu = item.FindLogicalParent(); if (menu is null) throw new InvalidOperationException("Unable to find context menu"); - menu.PlacementTarget.InputBindings.Add(new InputBinding(item.Command, inputGesture) { CommandParameter = item.CommandParameter }); + menu.PlacementTarget.InputBindings.Add(new InputBinding(item.Command, inputGesture) {CommandParameter = item.CommandParameter}); item.InputGestureText = inputGesture.GetDisplayStringForCulture(CultureInfo.InvariantCulture); return item; diff --git a/source/RevitLookup.UI.Framework/Extensions/StringExtensions.cs b/source/RevitLookup.UI.Framework/Extensions/StringExtensions.cs index cec0e78d..6b99afb5 100644 --- a/source/RevitLookup.UI.Framework/Extensions/StringExtensions.cs +++ b/source/RevitLookup.UI.Framework/Extensions/StringExtensions.cs @@ -1,5 +1,4 @@ -using System; -using JetBrains.Annotations; +using JetBrains.Annotations; namespace RevitLookup.UI.Framework.Extensions; diff --git a/source/RevitLookup.UI.Framework/Services/NotificationService.cs b/source/RevitLookup.UI.Framework/Services/Presentation/NotificationService.cs similarity index 97% rename from source/RevitLookup.UI.Framework/Services/NotificationService.cs rename to source/RevitLookup.UI.Framework/Services/Presentation/NotificationService.cs index 7892fa8a..a27f6787 100644 --- a/source/RevitLookup.UI.Framework/Services/NotificationService.cs +++ b/source/RevitLookup.UI.Framework/Services/Presentation/NotificationService.cs @@ -19,11 +19,11 @@ // (Rights in Technical Data and Computer Software), as applicable. using System.Windows; -using RevitLookup.Abstractions.Services; +using RevitLookup.Abstractions.Services.Presentation; using Wpf.Ui; using Wpf.Ui.Controls; -namespace RevitLookup.UI.Framework.Services; +namespace RevitLookup.UI.Framework.Services.Presentation; public sealed class NotificationService(ISnackbarService snackbarService, IWindowIntercomService intercomService) : INotificationService { diff --git a/source/RevitLookup.UI.Framework/Services/WindowIntercomService.cs b/source/RevitLookup.UI.Framework/Services/Presentation/WindowIntercomService.cs similarity index 70% rename from source/RevitLookup.UI.Framework/Services/WindowIntercomService.cs rename to source/RevitLookup.UI.Framework/Services/Presentation/WindowIntercomService.cs index cb514a8d..00ab9760 100644 --- a/source/RevitLookup.UI.Framework/Services/WindowIntercomService.cs +++ b/source/RevitLookup.UI.Framework/Services/Presentation/WindowIntercomService.cs @@ -1,9 +1,9 @@ using System.Windows; using System.Windows.Threading; using JetBrains.Annotations; -using RevitLookup.Abstractions.Services; +using RevitLookup.Abstractions.Services.Presentation; -namespace RevitLookup.UI.Framework.Services; +namespace RevitLookup.UI.Framework.Services.Presentation; public sealed class WindowIntercomService : IWindowIntercomService { @@ -19,7 +19,15 @@ public void SetSharedHost(Window host) { SetHost(host); SharedWindows.Add(host); - host.Closed += (sender, _) => { SharedWindows.Remove((Window)sender!); }; + host.Closed += OnHostDisconnected; + } + + private static void OnHostDisconnected(object? sender, EventArgs args) + { + var self = (Window) sender!; + self.Closed += OnHostDisconnected; + + SharedWindows.Remove(self); } public List OpenedWindows => SharedWindows; diff --git a/source/RevitLookup.UI.Framework/Views/AboutProgram/AboutPage.xaml.cs b/source/RevitLookup.UI.Framework/Views/AboutProgram/AboutPage.xaml.cs index 525b17bf..f4e9a11d 100644 --- a/source/RevitLookup.UI.Framework/Views/AboutProgram/AboutPage.xaml.cs +++ b/source/RevitLookup.UI.Framework/Views/AboutProgram/AboutPage.xaml.cs @@ -18,6 +18,7 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. +using RevitLookup.Abstractions.Services.Appearance; using RevitLookup.Abstractions.ViewModels.AboutProgram; using Wpf.Ui.Abstractions.Controls; @@ -25,8 +26,10 @@ namespace RevitLookup.UI.Framework.Views.AboutProgram; public sealed partial class AboutPage : INavigableView { - public AboutPage(IAboutViewModel viewModel) + public AboutPage(IAboutViewModel viewModel, IThemeWatcherService themeWatcherService) { + themeWatcherService.Watch(this); + ViewModel = viewModel; DataContext = this; InitializeComponent(); diff --git a/source/RevitLookup.UI.Framework/Views/AboutProgram/OpenSourceDialog.xaml b/source/RevitLookup.UI.Framework/Views/AboutProgram/OpenSourceDialog.xaml index ab419fbe..87306f08 100644 --- a/source/RevitLookup.UI.Framework/Views/AboutProgram/OpenSourceDialog.xaml +++ b/source/RevitLookup.UI.Framework/Views/AboutProgram/OpenSourceDialog.xaml @@ -17,11 +17,13 @@ - + + + + - + + + - + - + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/RevitLookup.UI/Controls/GridView/GridViewHeaderRowIndicator.xaml b/source/RevitLookup.UI/Controls/GridView/GridViewHeaderRowIndicator.xaml new file mode 100644 index 00000000..77537437 --- /dev/null +++ b/source/RevitLookup.UI/Controls/GridView/GridViewHeaderRowIndicator.xaml @@ -0,0 +1,26 @@ + + + + + + + + + + \ No newline at end of file diff --git a/source/RevitLookup.UI/Controls/GridView/GridViewHeaderRowPresenter.cs b/source/RevitLookup.UI/Controls/GridView/GridViewHeaderRowPresenter.cs new file mode 100644 index 00000000..b57b34a1 --- /dev/null +++ b/source/RevitLookup.UI/Controls/GridView/GridViewHeaderRowPresenter.cs @@ -0,0 +1,91 @@ +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. +// Copyright (C) Leszek Pomianowski and WPF UI Contributors. +// All Rights Reserved. + +using System.Diagnostics; +using System.Reflection; +using System.Windows.Controls; + +namespace Wpf.Ui.Controls; + +/// +/// Extends , and adds layout support for , which can have and . +/// +public class GridViewHeaderRowPresenter : System.Windows.Controls.GridViewHeaderRowPresenter +{ + public GridViewHeaderRowPresenter() + { + Loaded += OnLoaded; + } + + protected override Size ArrangeOverride(Size arrangeSize) + { + // update the desired width of each column (clamps desiredwidth to MinWidth and MaxWidth) + if (Columns != null) + { + foreach (GridViewColumn column in Columns.OfType()) + { + column.UpdateDesiredWidth(); + } + } + + return base.ArrangeOverride(arrangeSize); + } + + protected override Size MeasureOverride(Size constraint) + { + if (Columns != null) + { + foreach (GridViewColumn column in Columns.OfType()) + { + column.UpdateDesiredWidth(); + } + } + + return base.MeasureOverride(constraint); + } + + private void OnLoaded(object sender, RoutedEventArgs e) + { + UpdateIndicatorStyle(); + } + + private void UpdateIndicatorStyle() + { + FieldInfo? indicatorField = typeof(System.Windows.Controls.GridViewHeaderRowPresenter).GetField( + "_indicator", + BindingFlags.NonPublic | BindingFlags.Instance + ); + + if (indicatorField == null) + { + Debug.WriteLine("Failed to get the _indicator field"); + return; + } + + if (indicatorField.GetValue(this) is Separator indicator) + { + indicator.Margin = new Thickness(0); + indicator.Width = 3.0; + + ResourceDictionary resourceDictionary = + new() + { + Source = new Uri( + "pack://application:,,,/RevitLookup.UI;component/Controls/GridView/GridViewHeaderRowIndicator.xaml", + UriKind.Absolute + ) + }; + + if (resourceDictionary["GridViewHeaderRowIndicatorTemplate"] is ControlTemplate template) + { + indicator.Template = template; + } + else + { + Debug.WriteLine("Failed to get the GridViewHeaderRowIndicatorTemplate"); + } + } + } +} diff --git a/source/RevitLookup.UI/Controls/GridView/GridViewRowPresenter.cs b/source/RevitLookup.UI/Controls/GridView/GridViewRowPresenter.cs new file mode 100644 index 00000000..f6be3129 --- /dev/null +++ b/source/RevitLookup.UI/Controls/GridView/GridViewRowPresenter.cs @@ -0,0 +1,42 @@ +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. +// Copyright (C) Leszek Pomianowski and WPF UI Contributors. +// All Rights Reserved. + +using System.Reflection; +using System.Windows.Controls; + +namespace Wpf.Ui.Controls; + +/// +/// Extends , and adds header row layout support for , which can have and . +/// +public class GridViewRowPresenter : System.Windows.Controls.GridViewRowPresenter +{ + protected override Size ArrangeOverride(Size arrangeSize) + { + // update the desired width of each column (clamps desiredwidth to MinWidth and MaxWidth) + if (Columns != null) + { + foreach (GridViewColumn column in Columns.OfType()) + { + column.UpdateDesiredWidth(); + } + } + + return base.ArrangeOverride(arrangeSize); + } + + protected override Size MeasureOverride(Size constraint) + { + if (Columns != null) + { + foreach (GridViewColumn column in Columns.OfType()) + { + column.UpdateDesiredWidth(); + } + } + + return base.MeasureOverride(constraint); + } +} diff --git a/source/RevitLookup.UI/Controls/HyperlinkButton/HyperlinkButton.cs b/source/RevitLookup.UI/Controls/HyperlinkButton/HyperlinkButton.cs index 1b109dba..9db3aee4 100644 --- a/source/RevitLookup.UI/Controls/HyperlinkButton/HyperlinkButton.cs +++ b/source/RevitLookup.UI/Controls/HyperlinkButton/HyperlinkButton.cs @@ -13,9 +13,7 @@ namespace Wpf.Ui.Controls; /// public class HyperlinkButton : Wpf.Ui.Controls.Button { - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty NavigateUriProperty = DependencyProperty.Register( nameof(NavigateUri), typeof(string), @@ -24,7 +22,7 @@ public class HyperlinkButton : Wpf.Ui.Controls.Button ); /// - /// The URL (or application shortcut) to open. + /// Gets or sets the URL (or application shortcut) to open. /// public string NavigateUri { @@ -49,7 +47,7 @@ protected override void OnClick() ProcessStartInfo sInfo = new(new Uri(NavigateUri).AbsoluteUri) { UseShellExecute = true }; - Process.Start(sInfo); + _ = Process.Start(sInfo); } catch (Exception e) { diff --git a/source/RevitLookup.UI/Controls/IThemeElement.cs b/source/RevitLookup.UI/Controls/IThemeControl.cs similarity index 92% rename from source/RevitLookup.UI/Controls/IThemeElement.cs rename to source/RevitLookup.UI/Controls/IThemeControl.cs index 1c6ec549..8f15dd43 100644 --- a/source/RevitLookup.UI/Controls/IThemeElement.cs +++ b/source/RevitLookup.UI/Controls/IThemeControl.cs @@ -11,7 +11,7 @@ namespace Wpf.Ui.Controls; public interface IThemeControl { /// - /// The theme is currently set. + /// Gets the theme that is currently set. /// public Appearance.ApplicationTheme ApplicationTheme { get; } } diff --git a/source/RevitLookup.UI/Controls/IconElement/FontIcon.cs b/source/RevitLookup.UI/Controls/IconElement/FontIcon.cs index 91399d68..c9df0c9e 100644 --- a/source/RevitLookup.UI/Controls/IconElement/FontIcon.cs +++ b/source/RevitLookup.UI/Controls/IconElement/FontIcon.cs @@ -15,39 +15,27 @@ namespace Wpf.Ui.Controls; /// /// Represents an icon that uses a glyph from the specified font. /// -//[ToolboxItem(true)] -//[ToolboxBitmap(typeof(FontIcon), "FontIcon.bmp")] public class FontIcon : IconElement { - #region Static properties - - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty FontFamilyProperty = DependencyProperty.Register( nameof(FontFamily), typeof(FontFamily), typeof(FontIcon), - new FrameworkPropertyMetadata(SystemFonts.MessageFontFamily, OnFontFamilyChanged) + new FrameworkPropertyMetadata(new FontFamily("Segoe MDL2 Assets"), OnFontFamilyChanged) ); - /// - /// Property for . - /// - public static readonly DependencyProperty FontSizeProperty = TextElement - .FontSizeProperty - .AddOwner( - typeof(FontIcon), - new FrameworkPropertyMetadata( - SystemFonts.MessageFontSize, - FrameworkPropertyMetadataOptions.Inherits, - OnFontSizeChanged - ) - ); + /// Identifies the dependency property. + public static readonly DependencyProperty FontSizeProperty = TextElement.FontSizeProperty.AddOwner( + typeof(FontIcon), + new FrameworkPropertyMetadata( + SystemFonts.MessageFontSize, + FrameworkPropertyMetadataOptions.Inherits, + OnFontSizeChanged + ) + ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty FontStyleProperty = DependencyProperty.Register( nameof(FontStyle), typeof(FontStyle), @@ -55,9 +43,7 @@ public class FontIcon : IconElement new FrameworkPropertyMetadata(FontStyles.Normal, OnFontStyleChanged) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty FontWeightProperty = DependencyProperty.Register( nameof(FontWeight), typeof(FontWeight), @@ -65,9 +51,7 @@ public class FontIcon : IconElement new FrameworkPropertyMetadata(FontWeights.Normal, OnFontWeightChanged) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty GlyphProperty = DependencyProperty.Register( nameof(Glyph), typeof(string), @@ -75,12 +59,9 @@ public class FontIcon : IconElement new FrameworkPropertyMetadata(string.Empty, OnGlyphChanged) ); - #endregion - - #region Properties - /// - [Bindable(true), Category("Appearance")] + [Bindable(true)] + [Category("Appearance")] [Localizability(LocalizationCategory.Font)] public FontFamily FontFamily { @@ -90,7 +71,8 @@ public FontFamily FontFamily /// [TypeConverter(typeof(FontSizeConverter))] - [Bindable(true), Category("Appearance")] + [Bindable(true)] + [Category("Appearance")] [Localizability(LocalizationCategory.None)] public double FontSize { @@ -99,7 +81,8 @@ public double FontSize } /// - [Bindable(true), Category("Appearance")] + [Bindable(true)] + [Category("Appearance")] public FontStyle FontStyle { get => (FontStyle)GetValue(FontStyleProperty); @@ -107,7 +90,8 @@ public FontStyle FontStyle } /// - [Bindable(true), Category("Appearance")] + [Bindable(true)] + [Category("Appearance")] public FontWeight FontWeight { get => (FontWeight)GetValue(FontWeightProperty); @@ -124,17 +108,15 @@ public string Glyph set => SetValue(GlyphProperty, value); } - #endregion - - protected TextBlock? TextBlock; + protected TextBlock? TextBlock { get; set; } protected override UIElement InitializeChildren() { // if (VisualParent is not null) // { - // FontSize = TextElement.GetFontSize(VisualParent); + // SetCurrentValue(FontSizeProperty, TextElement.GetFontSize(VisualParent)); // } - + // // if (FontSize.Equals(SystemFonts.MessageFontSize)) // { // SetResourceReference(FontSizeProperty, "DefaultIconFontSize"); @@ -155,57 +137,75 @@ protected override UIElement InitializeChildren() Focusable = false, }; - Focusable = false; + SetCurrentValue(FocusableProperty, false); return TextBlock; } - #region Static methods - private static void OnFontFamilyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var self = (FontIcon)d; if (self.TextBlock is null) + { return; + } - self.TextBlock.FontFamily = (FontFamily)e.NewValue; + self.TextBlock.SetCurrentValue( + System.Windows.Controls.TextBlock.FontFamilyProperty, + (FontFamily)e.NewValue + ); } private static void OnFontSizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var self = (FontIcon)d; if (self.TextBlock is null) + { return; + } - self.TextBlock.FontSize = (double)e.NewValue; + self.TextBlock.SetCurrentValue( + System.Windows.Controls.TextBlock.FontSizeProperty, + (double)e.NewValue + ); } private static void OnFontStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var self = (FontIcon)d; if (self.TextBlock is null) + { return; + } - self.TextBlock.FontStyle = (FontStyle)e.NewValue; + self.TextBlock.SetCurrentValue( + System.Windows.Controls.TextBlock.FontStyleProperty, + (FontStyle)e.NewValue + ); } private static void OnFontWeightChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var self = (FontIcon)d; if (self.TextBlock is null) + { return; + } - self.TextBlock.FontWeight = (FontWeight)e.NewValue; + self.TextBlock.SetCurrentValue( + System.Windows.Controls.TextBlock.FontWeightProperty, + (FontWeight)e.NewValue + ); } private static void OnGlyphChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var self = (FontIcon)d; if (self.TextBlock is null) + { return; + } - self.TextBlock.Text = (string)e.NewValue; + self.TextBlock.SetCurrentValue(System.Windows.Controls.TextBlock.TextProperty, (string)e.NewValue); } - - #endregion } diff --git a/source/RevitLookup.UI/Controls/IconElement/IconElement.cs b/source/RevitLookup.UI/Controls/IconElement/IconElement.cs index 3eb2ec92..d5d13e18 100644 --- a/source/RevitLookup.UI/Controls/IconElement/IconElement.cs +++ b/source/RevitLookup.UI/Controls/IconElement/IconElement.cs @@ -19,27 +19,25 @@ public abstract class IconElement : FrameworkElement static IconElement() { FocusableProperty.OverrideMetadata(typeof(IconElement), new FrameworkPropertyMetadata(false)); - KeyboardNavigation - .IsTabStopProperty - .OverrideMetadata(typeof(IconElement), new FrameworkPropertyMetadata(false)); - } - - /// - /// Property for . - /// - public static readonly DependencyProperty ForegroundProperty = TextElement - .ForegroundProperty - .AddOwner( + KeyboardNavigation.IsTabStopProperty.OverrideMetadata( typeof(IconElement), - new FrameworkPropertyMetadata( - SystemColors.ControlTextBrush, - FrameworkPropertyMetadataOptions.Inherits, - static (d, args) => ((IconElement)d).OnForegroundPropertyChanged(args) - ) + new FrameworkPropertyMetadata(false) ); + } + + /// Identifies the dependency property. + public static readonly DependencyProperty ForegroundProperty = TextElement.ForegroundProperty.AddOwner( + typeof(IconElement), + new FrameworkPropertyMetadata( + SystemColors.ControlTextBrush, + FrameworkPropertyMetadataOptions.Inherits, + static (d, args) => ((IconElement)d).OnForegroundChanged(args) + ) + ); /// - [Bindable(true), Category("Appearance")] + [Bindable(true)] + [Category("Appearance")] public Brush Foreground { get => (Brush)GetValue(ForegroundProperty); @@ -50,31 +48,30 @@ public Brush Foreground private Grid? _layoutRoot; - #region Protected methods - protected abstract UIElement InitializeChildren(); - protected virtual void OnForegroundPropertyChanged(DependencyPropertyChangedEventArgs args) { } - - #endregion - - #region Layout methods + protected virtual void OnForegroundChanged(DependencyPropertyChangedEventArgs args) { } private void EnsureLayoutRoot() { if (_layoutRoot != null) + { return; + } _layoutRoot = new Grid { Background = Brushes.Transparent, SnapsToDevicePixels = true, }; - _layoutRoot.Children.Add(InitializeChildren()); + _ = _layoutRoot.Children.Add(InitializeChildren()); + AddVisualChild(_layoutRoot); } protected override Visual GetVisualChild(int index) { if (index != 0) - throw new ArgumentOutOfRangeException(nameof(index)); + { + throw new ArgumentOutOfRangeException(nameof(index), "IconElement should have only 1 child"); + } EnsureLayoutRoot(); return _layoutRoot!; @@ -92,9 +89,27 @@ protected override Size ArrangeOverride(Size finalSize) { EnsureLayoutRoot(); - _layoutRoot!.Arrange(new Rect(new Point(), finalSize)); + _layoutRoot!.Arrange(new Rect(default, finalSize)); return finalSize; } - #endregion + /// + /// Coerces the value of an Icon dependency property, allowing the use of either IconElement or IconSourceElement. + /// + /// The dependency object (unused). + /// The value to be coerced. + /// An IconElement, either directly or derived from an IconSourceElement. + public static object? Coerce(DependencyObject _, object? baseValue) + { + return baseValue switch + { + IconSourceElement iconSourceElement => iconSourceElement.CreateIconElement(), + IconElement or null => baseValue, + _ + => throw new ArgumentException( + message: $"Expected either '{typeof(IconSourceElement)}' or '{typeof(IconElement)}' but got '{baseValue.GetType()}'.", + paramName: nameof(baseValue) + ) + }; + } } diff --git a/source/RevitLookup.UI/Controls/IconElement/IconElementConverter.cs b/source/RevitLookup.UI/Controls/IconElement/IconElementConverter.cs index 30b96296..119bd3a2 100644 --- a/source/RevitLookup.UI/Controls/IconElement/IconElementConverter.cs +++ b/source/RevitLookup.UI/Controls/IconElement/IconElementConverter.cs @@ -9,17 +9,21 @@ namespace Wpf.Ui.Controls; /// -/// Tries to convert and to . +/// Tries to convert and to . /// public class IconElementConverter : TypeConverter { public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) { if (sourceType == typeof(SymbolRegular)) + { return true; + } if (sourceType == typeof(SymbolFilled)) + { return true; + } return false; } diff --git a/source/RevitLookup.UI/Controls/IconElement/IconSourceElement.cs b/source/RevitLookup.UI/Controls/IconElement/IconSourceElement.cs index 425a92c1..90241627 100644 --- a/source/RevitLookup.UI/Controls/IconElement/IconSourceElement.cs +++ b/source/RevitLookup.UI/Controls/IconElement/IconSourceElement.cs @@ -15,9 +15,7 @@ namespace Wpf.Ui.Controls; [ContentProperty(nameof(IconSource))] public class IconSourceElement : IconElement { - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty IconSourceProperty = DependencyProperty.Register( nameof(IconSource), typeof(IconSource), @@ -30,13 +28,18 @@ public class IconSourceElement : IconElement /// public IconSource? IconSource { - get => (IconSource)GetValue(IconSourceProperty); + get => (IconSource?)GetValue(IconSourceProperty); set => SetValue(IconSourceProperty, value); } protected override UIElement InitializeChildren() { - //TODO come up with an elegant solution - throw new InvalidOperationException($"Use {nameof(IconSourceElementConverter)} class."); + // TODO: Come up with an elegant solution + throw new InvalidOperationException($"Use {nameof(CreateIconElement)}"); + } + + public IconElement? CreateIconElement() + { + return IconSource?.CreateIconElement(); } } diff --git a/source/RevitLookup.UI/Controls/IconElement/ImageIcon.cs b/source/RevitLookup.UI/Controls/IconElement/ImageIcon.cs index d6e0ab9b..970eeb69 100644 --- a/source/RevitLookup.UI/Controls/IconElement/ImageIcon.cs +++ b/source/RevitLookup.UI/Controls/IconElement/ImageIcon.cs @@ -11,9 +11,7 @@ namespace Wpf.Ui.Controls; /// public class ImageIcon : IconElement { - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty SourceProperty = DependencyProperty.Register( nameof(Source), typeof(ImageSource), @@ -21,7 +19,7 @@ public class ImageIcon : IconElement new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender, - OnSourcePropertyChanged + OnSourceChanged ) ); @@ -30,11 +28,11 @@ public class ImageIcon : IconElement /// public ImageSource? Source { - get => (ImageSource)GetValue(SourceProperty); + get => (ImageSource?)GetValue(SourceProperty); set => SetValue(SourceProperty, value); } - protected System.Windows.Controls.Image? Image; + protected System.Windows.Controls.Image? Image { get; set; } protected override UIElement InitializeChildren() { @@ -43,12 +41,15 @@ protected override UIElement InitializeChildren() return Image; } - private static void OnSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - var self = (ImageIcon)d; + ImageIcon self = (ImageIcon)d; + if (self.Image is null) + { return; + } - self.Image.Source = (ImageSource)e.NewValue; + self.Image.SetCurrentValue(System.Windows.Controls.Image.SourceProperty, (ImageSource?)e.NewValue); } } diff --git a/source/RevitLookup.UI/Controls/IconElement/SymbolIcon.cs b/source/RevitLookup.UI/Controls/IconElement/SymbolIcon.cs index 14a48f70..43f173f6 100644 --- a/source/RevitLookup.UI/Controls/IconElement/SymbolIcon.cs +++ b/source/RevitLookup.UI/Controls/IconElement/SymbolIcon.cs @@ -11,13 +11,9 @@ namespace Wpf.Ui.Controls; /// /// Represents a text element containing an icon glyph. /// -//[ToolboxItem(true)] -//[ToolboxBitmap(typeof(SymbolIcon), "SymbolIcon.bmp")] public class SymbolIcon : FontIcon { - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty SymbolProperty = DependencyProperty.Register( nameof(Symbol), typeof(SymbolRegular), @@ -25,9 +21,7 @@ public class SymbolIcon : FontIcon new PropertyMetadata(SymbolRegular.Empty, static (o, _) => ((SymbolIcon)o).OnGlyphChanged()) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty FilledProperty = DependencyProperty.Register( nameof(Filled), typeof(bool), @@ -45,7 +39,7 @@ public SymbolRegular Symbol } /// - /// Defines whether or not we should use the . + /// Gets or sets a value indicating whether or not we should use the . /// public bool Filled { @@ -72,9 +66,13 @@ protected override void OnInitialized(EventArgs e) private void OnGlyphChanged() { if (Filled) - Glyph = Symbol.Swap().GetString(); + { + SetCurrentValue(GlyphProperty, Symbol.Swap().GetString()); + } else - Glyph = Symbol.GetString(); + { + SetCurrentValue(GlyphProperty, Symbol.GetString()); + } } private void SetFontReference() diff --git a/source/RevitLookup.UI/Controls/IconSource/FontIconSource.cs b/source/RevitLookup.UI/Controls/IconSource/FontIconSource.cs index 46110ba1..74d9d14f 100644 --- a/source/RevitLookup.UI/Controls/IconSource/FontIconSource.cs +++ b/source/RevitLookup.UI/Controls/IconSource/FontIconSource.cs @@ -13,9 +13,7 @@ namespace Wpf.Ui.Controls; /// public class FontIconSource : IconSource { - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty FontFamilyProperty = DependencyProperty.Register( nameof(FontFamily), typeof(FontFamily), @@ -23,9 +21,7 @@ public class FontIconSource : IconSource new PropertyMetadata(SystemFonts.MessageFontFamily) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty FontSizeProperty = DependencyProperty.Register( nameof(FontSize), typeof(double), @@ -33,9 +29,7 @@ public class FontIconSource : IconSource new PropertyMetadata(SystemFonts.MessageFontSize) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty FontStyleProperty = DependencyProperty.Register( nameof(FontStyle), typeof(FontStyle), @@ -43,9 +37,7 @@ public class FontIconSource : IconSource new PropertyMetadata(FontStyles.Normal) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty FontWeightProperty = DependencyProperty.Register( nameof(FontWeight), typeof(FontWeight), @@ -53,9 +45,7 @@ public class FontIconSource : IconSource new PropertyMetadata(FontWeights.Normal) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty GlyphProperty = DependencyProperty.Register( nameof(Glyph), typeof(string), @@ -103,7 +93,7 @@ public string Glyph public override IconElement CreateIconElement() { - FontIcon fontIcon = new FontIcon() { Glyph = Glyph }; + var fontIcon = new FontIcon() { Glyph = Glyph }; if (!Equals(FontFamily, SystemFonts.MessageFontFamily)) { diff --git a/source/RevitLookup.UI/Controls/IconSource/IconSource.cs b/source/RevitLookup.UI/Controls/IconSource/IconSource.cs index fdf5b588..dd07efcd 100644 --- a/source/RevitLookup.UI/Controls/IconSource/IconSource.cs +++ b/source/RevitLookup.UI/Controls/IconSource/IconSource.cs @@ -13,9 +13,7 @@ namespace Wpf.Ui.Controls; /// public abstract class IconSource : DependencyObject { - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty ForegroundProperty = DependencyProperty.Register( nameof(Foreground), typeof(Brush), @@ -23,9 +21,7 @@ public abstract class IconSource : DependencyObject new FrameworkPropertyMetadata(SystemColors.ControlTextBrush) ); - /// /// - /// public Brush Foreground { get => (Brush)GetValue(ForegroundProperty); diff --git a/source/RevitLookup.UI/Controls/IconSource/SymbolIconSource.cs b/source/RevitLookup.UI/Controls/IconSource/SymbolIconSource.cs index 6a865d18..95a54850 100644 --- a/source/RevitLookup.UI/Controls/IconSource/SymbolIconSource.cs +++ b/source/RevitLookup.UI/Controls/IconSource/SymbolIconSource.cs @@ -13,9 +13,7 @@ namespace Wpf.Ui.Controls; /// public class SymbolIconSource : IconSource { - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty FontSizeProperty = DependencyProperty.Register( nameof(FontSize), typeof(double), @@ -23,9 +21,7 @@ public class SymbolIconSource : IconSource new PropertyMetadata(SystemFonts.MessageFontSize) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty FontStyleProperty = DependencyProperty.Register( nameof(FontStyle), typeof(FontStyle), @@ -33,9 +29,7 @@ public class SymbolIconSource : IconSource new PropertyMetadata(FontStyles.Normal) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty FontWeightProperty = DependencyProperty.Register( nameof(FontWeight), typeof(FontWeight), @@ -43,9 +37,7 @@ public class SymbolIconSource : IconSource new PropertyMetadata(FontWeights.Normal) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty SymbolProperty = DependencyProperty.Register( nameof(Symbol), typeof(SymbolRegular), @@ -53,9 +45,7 @@ public class SymbolIconSource : IconSource new PropertyMetadata(SymbolRegular.Empty) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty FilledProperty = DependencyProperty.Register( nameof(Filled), typeof(bool), @@ -94,7 +84,7 @@ public SymbolRegular Symbol } /// - /// Defines whether or not we should use the . + /// Gets or sets a value indicating whether or not we should use the . /// public bool Filled { diff --git a/source/RevitLookup.UI/Controls/Image/Image.cs b/source/RevitLookup.UI/Controls/Image/Image.cs index 5f62288f..23c49cd7 100644 --- a/source/RevitLookup.UI/Controls/Image/Image.cs +++ b/source/RevitLookup.UI/Controls/Image/Image.cs @@ -13,11 +13,7 @@ namespace Wpf.Ui.Controls; /// public class Image : Control { - #region DependencyPropreties - /// - /// Gets/Sets the Source on this Image. - /// The Source property is the ImageSource that holds the actual image drawn. - /// + /// Identifies the dependency property. public static readonly DependencyProperty SourceProperty = DependencyProperty.Register( nameof(Source), typeof(ImageSource), @@ -31,9 +27,7 @@ public class Image : Control null ); - /// - /// DependencyProperty for CornerRadius property. - /// + /// Identifies the dependency property. public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.Register( nameof(CornerRadius), typeof(CornerRadius), @@ -41,9 +35,7 @@ public class Image : Control new PropertyMetadata(new CornerRadius(0), new PropertyChangedCallback(OnCornerRadiusChanged)) ); - /// - /// DependencyProperty for StretchDirection property. - /// + /// Identifies the dependency property. /// public static readonly DependencyProperty StretchProperty = DependencyProperty.Register( nameof(Stretch), @@ -56,9 +48,7 @@ public class Image : Control null ); - /// - /// DependencyProperty for Stretch property. - /// + /// Identifies the dependency property. public static readonly DependencyProperty StretchDirectionProperty = DependencyProperty.Register( nameof(StretchDirection), typeof(StretchDirection), @@ -70,9 +60,7 @@ public class Image : Control null ); - /// - /// DependencyPropertyKey for InnerCornerRadius property. - /// + /// Identifies the dependency property. public static readonly DependencyPropertyKey InnerCornerRadiusPropertyKey = DependencyProperty.RegisterReadOnly( nameof(InnerCornerRadius), @@ -81,26 +69,22 @@ public class Image : Control new PropertyMetadata(new CornerRadius(0)) ); - /// - /// DependencyProperty for InnerCornerRadius property. - /// + /// Identifies the dependency property. public static readonly DependencyProperty InnerCornerRadiusProperty = InnerCornerRadiusPropertyKey.DependencyProperty; - #endregion - #region Propreties /// - /// Gets/Sets the Source on this Image. + /// Gets or sets the Source on this Image. /// The Source property is the ImageSource that holds the actual image drawn. /// - public ImageSource Source + public ImageSource? Source { - get => (ImageSource)GetValue(SourceProperty); + get => (ImageSource?)GetValue(SourceProperty); set => SetValue(SourceProperty, value); } /// - /// Gets/Sets the Stretch on this Image. + /// Gets or sets the Stretch on this Image. /// The Stretch property determines how large the Image will be drawn. /// public Stretch Stretch @@ -110,7 +94,7 @@ public Stretch Stretch } /// - /// Gets/Sets the stretch direction of the Viewbox, which determines the restrictions on + /// Gets or sets the stretch direction of the Viewbox, which determines the restrictions on /// scaling that are applied to the content inside the Viewbox. For instance, this property /// can be used to prevent the content from being smaller than its native size or larger than /// its native size. @@ -122,7 +106,7 @@ public StretchDirection StretchDirection } /// - /// The CornerRadius property allows users to control the roundness of the corners independently by + /// Gets or sets the CornerRadius property allows users to control the roundness of the corners independently by /// setting a radius value for each corner. Radius values that are too large are scaled so that they /// smoothly blend from corner to corner. /// @@ -133,27 +117,24 @@ public CornerRadius CornerRadius } /// - /// The CornerRadius for the inner image's Mask. + /// Gets the CornerRadius for the inner image's Mask. /// internal CornerRadius InnerCornerRadius => (CornerRadius)GetValue(InnerCornerRadiusProperty); - #endregion - #region Methods private static void OnCornerRadiusChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var thickness = (Thickness)d.GetValue(BorderThicknessProperty); var outerRarius = (CornerRadius)e.NewValue; - //Inner radius = Outer radius - thickenss/2 + // Inner radius = Outer radius - thickenss/2 d.SetValue( InnerCornerRadiusPropertyKey, new CornerRadius( - topLeft: Math.Max(0, (int)Math.Round(outerRarius.TopLeft - thickness.Left / 2, 0)), - topRight: Math.Max(0, (int)Math.Round(outerRarius.TopRight - thickness.Top / 2, 0)), - bottomRight: Math.Max(0, (int)Math.Round(outerRarius.BottomRight - thickness.Right / 2, 0)), - bottomLeft: Math.Max(0, (int)Math.Round(outerRarius.BottomLeft - thickness.Bottom / 2, 0)) + topLeft: Math.Max(0, (int)Math.Round(outerRarius.TopLeft - (thickness.Left / 2), 0)), + topRight: Math.Max(0, (int)Math.Round(outerRarius.TopRight - (thickness.Top / 2), 0)), + bottomRight: Math.Max(0, (int)Math.Round(outerRarius.BottomRight - (thickness.Right / 2), 0)), + bottomLeft: Math.Max(0, (int)Math.Round(outerRarius.BottomLeft - (thickness.Bottom / 2), 0)) ) ); } - #endregion } diff --git a/source/RevitLookup.UI/Controls/InfoBadge/InfoBadge.cs b/source/RevitLookup.UI/Controls/InfoBadge/InfoBadge.cs index b1432d12..df893a75 100644 --- a/source/RevitLookup.UI/Controls/InfoBadge/InfoBadge.cs +++ b/source/RevitLookup.UI/Controls/InfoBadge/InfoBadge.cs @@ -3,25 +3,19 @@ // Copyright (C) Leszek Pomianowski and WPF UI Contributors. // All Rights Reserved. -using Wpf.Ui.Converters; - namespace Wpf.Ui.Controls; public class InfoBadge : System.Windows.Controls.Control { - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty IconProperty = DependencyProperty.Register( nameof(Icon), typeof(IconElement), typeof(InfoBadge), - new PropertyMetadata(null, null, IconSourceElementConverter.ConvertToIconElement) + new PropertyMetadata(null, null, IconElement.Coerce) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty SeverityProperty = DependencyProperty.Register( nameof(Severity), typeof(InfoBadgeSeverity), @@ -29,9 +23,7 @@ public class InfoBadge : System.Windows.Controls.Control new PropertyMetadata(InfoBadgeSeverity.Informational) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty ValueProperty = DependencyProperty.Register( nameof(Value), typeof(string), @@ -39,18 +31,14 @@ public class InfoBadge : System.Windows.Controls.Control new PropertyMetadata(string.Empty) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.Register( nameof(CornerRadius), typeof(CornerRadius), typeof(InfoBadge), - (PropertyMetadata) new FrameworkPropertyMetadata( - (object)new CornerRadius(8), - FrameworkPropertyMetadataOptions.AffectsMeasure - | FrameworkPropertyMetadataOptions.AffectsRender + new CornerRadius(8), + FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender ) ); @@ -62,6 +50,7 @@ public InfoBadgeSeverity Severity get => (InfoBadgeSeverity)GetValue(SeverityProperty); set => SetValue(SeverityProperty, value); } + /// /// Gets or sets the title of the . /// @@ -76,17 +65,18 @@ public string Value /// public CornerRadius CornerRadius { - get => (CornerRadius)GetValue(ValueProperty); - set => SetValue(ValueProperty, value); + get => (CornerRadius)GetValue(CornerRadiusProperty); + set => SetValue(CornerRadiusProperty, value); } /// /// Gets or sets displayed . /// - [Bindable(true), Category("Appearance")] + [Bindable(true)] + [Category("Appearance")] public IconElement? Icon { - get => (IconElement)GetValue(IconProperty); + get => (IconElement?)GetValue(IconProperty); set => SetValue(IconProperty, value); } } diff --git a/source/RevitLookup.UI/Controls/InfoBadge/InfoBadge.xaml b/source/RevitLookup.UI/Controls/InfoBadge/InfoBadge.xaml index 6cfd5318..3a2d3f38 100644 --- a/source/RevitLookup.UI/Controls/InfoBadge/InfoBadge.xaml +++ b/source/RevitLookup.UI/Controls/InfoBadge/InfoBadge.xaml @@ -25,8 +25,8 @@ Critical = 4 -} \ No newline at end of file +} diff --git a/source/RevitLookup.UI/Controls/InfoBar/InfoBar.cs b/source/RevitLookup.UI/Controls/InfoBar/InfoBar.cs index 957edef9..80de344f 100644 --- a/source/RevitLookup.UI/Controls/InfoBar/InfoBar.cs +++ b/source/RevitLookup.UI/Controls/InfoBar/InfoBar.cs @@ -17,9 +17,7 @@ namespace Wpf.Ui.Controls; /// public class InfoBar : System.Windows.Controls.ContentControl { - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty IsClosableProperty = DependencyProperty.Register( nameof(IsClosable), typeof(bool), @@ -27,9 +25,7 @@ public class InfoBar : System.Windows.Controls.ContentControl new PropertyMetadata(true) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty IsOpenProperty = DependencyProperty.Register( nameof(IsOpen), typeof(bool), @@ -37,29 +33,23 @@ public class InfoBar : System.Windows.Controls.ContentControl new PropertyMetadata(false) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty TitleProperty = DependencyProperty.Register( nameof(Title), typeof(string), typeof(InfoBar), - new PropertyMetadata(String.Empty) + new PropertyMetadata(string.Empty) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty MessageProperty = DependencyProperty.Register( nameof(Message), typeof(string), typeof(InfoBar), - new PropertyMetadata(String.Empty) + new PropertyMetadata(string.Empty) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty SeverityProperty = DependencyProperty.Register( nameof(Severity), typeof(InfoBarSeverity), @@ -67,9 +57,7 @@ public class InfoBar : System.Windows.Controls.ContentControl new PropertyMetadata(InfoBarSeverity.Informational) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty TemplateButtonCommandProperty = DependencyProperty.Register( nameof(TemplateButtonCommand), typeof(IRelayCommand), @@ -78,8 +66,7 @@ public class InfoBar : System.Windows.Controls.ContentControl ); /// - /// Gets or sets a value that indicates whether the user can close the - /// . Defaults to true. + /// Gets or sets a value indicating whether the user can close the . Defaults to true. /// public bool IsClosable { @@ -88,8 +75,7 @@ public bool IsClosable } /// - /// Gets or sets a value that indicates whether the - /// is open. + /// Gets or sets a value indicating whether the is open. /// public bool IsOpen { @@ -132,9 +118,14 @@ public InfoBarSeverity Severity /// public IRelayCommand TemplateButtonCommand => (IRelayCommand)GetValue(TemplateButtonCommandProperty); - /// + /// + /// Initializes a new instance of the class. + /// public InfoBar() { - SetValue(TemplateButtonCommandProperty, new RelayCommand(o => IsOpen = false)); + SetValue( + TemplateButtonCommandProperty, + new RelayCommand(_ => SetCurrentValue(IsOpenProperty, false)) + ); } } diff --git a/source/RevitLookup.UI/Controls/InfoBar/InfoBar.xaml b/source/RevitLookup.UI/Controls/InfoBar/InfoBar.xaml index 44da6f3f..2c24495e 100644 --- a/source/RevitLookup.UI/Controls/InfoBar/InfoBar.xaml +++ b/source/RevitLookup.UI/Controls/InfoBar/InfoBar.xaml @@ -53,7 +53,7 @@ - + + + + + - + - + diff --git a/source/RevitLookup.UI/Controls/ItemRange.cs b/source/RevitLookup.UI/Controls/ItemRange.cs index 9c8f29b7..96f4e646 100644 --- a/source/RevitLookup.UI/Controls/ItemRange.cs +++ b/source/RevitLookup.UI/Controls/ItemRange.cs @@ -3,10 +3,11 @@ // Copyright (C) Leszek Pomianowski and WPF UI Contributors. // All Rights Reserved. -// Based on VirtualizingWrapPanel created by S. Bäumlisberger licensed under MIT license. -// https://github.com/sbaeumlisberger/VirtualizingWrapPanel -// Copyright (C) S. Bäumlisberger -// All Rights Reserved. +/* Based on VirtualizingWrapPanel created by S. Bäumlisberger licensed under MIT license. + https://github.com/sbaeumlisberger/VirtualizingWrapPanel + + Copyright (C) S. Bäumlisberger + All Rights Reserved. */ namespace Wpf.Ui.Controls; @@ -14,9 +15,10 @@ namespace Wpf.Ui.Controls; /// Items range. /// Based on . /// -public struct ItemRange +public readonly struct ItemRange { public int StartIndex { get; } + public int EndIndex { get; } public ItemRange(int startIndex, int endIndex) @@ -26,8 +28,5 @@ public ItemRange(int startIndex, int endIndex) EndIndex = endIndex; } - public bool Contains(int itemIndex) - { - return itemIndex >= StartIndex && itemIndex <= EndIndex; - } + public readonly bool Contains(int itemIndex) => itemIndex >= StartIndex && itemIndex <= EndIndex; } diff --git a/source/RevitLookup.UI/Controls/ListView/ListView.cs b/source/RevitLookup.UI/Controls/ListView/ListView.cs new file mode 100644 index 00000000..ef2b44a3 --- /dev/null +++ b/source/RevitLookup.UI/Controls/ListView/ListView.cs @@ -0,0 +1,114 @@ +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. +// Copyright (C) Leszek Pomianowski and WPF UI Contributors. +// All Rights Reserved. + +namespace Wpf.Ui.Controls; + +/// +/// Extends , and adds customized support or . +/// +/// +/// +/// <ui:ListView ItemsSource="{Binding ...}" > +/// <ui:ListView.View> +/// <ui:GridView> +/// <GridViewColumn +/// DisplayMemberBinding="{Binding FirstName}" +/// Header="First Name" /> +/// <GridViewColumn +/// DisplayMemberBinding="{Binding LastName}" +/// Header="Last Name" /> +/// </ui:GridView> +/// </ui:ListView.View> +/// </ui:ListView> +/// +/// +public class ListView : System.Windows.Controls.ListView +{ + /// Identifies the dependency property. + public static readonly DependencyProperty ViewStateProperty = DependencyProperty.Register( + nameof(ViewState), + typeof(ListViewViewState), + typeof(ListView), + new FrameworkPropertyMetadata(ListViewViewState.Default, OnViewStateChanged) + ); + + /// + /// Gets or sets the view state of the , enabling custom logic based on the current view. + /// + /// The current view state of the . + public ListViewViewState ViewState + { + get => (ListViewViewState)GetValue(ViewStateProperty); + set => SetValue(ViewStateProperty, value); + } + + private static void OnViewStateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is not ListView self) + { + return; + } + + self.OnViewStateChanged(e); + } + + protected virtual void OnViewStateChanged(DependencyPropertyChangedEventArgs e) + { + // Hook for derived classes to react to ViewState property changes + } + + public ListView() + { + Loaded += OnLoaded; + } + + private void OnLoaded(object sender, RoutedEventArgs e) + { + Loaded -= OnLoaded; // prevent memory leaks + + // Setup initial ViewState and hook into View property changes + var descriptor = DependencyPropertyDescriptor.FromProperty( + System.Windows.Controls.ListView.ViewProperty, + typeof(System.Windows.Controls.ListView) + ); + descriptor?.AddValueChanged(this, OnViewPropertyChanged); + UpdateViewState(); // set the initial state + } + + private void OnViewPropertyChanged(object? sender, EventArgs e) + { + UpdateViewState(); + } + + private void UpdateViewState() + { + ListViewViewState viewState = View switch + { + System.Windows.Controls.GridView => ListViewViewState.GridView, + null => ListViewViewState.Default, + _ => ListViewViewState.Default + }; + + SetCurrentValue(ViewStateProperty, viewState); + } + + static ListView() + { + DefaultStyleKeyProperty.OverrideMetadata( + typeof(ListView), + new FrameworkPropertyMetadata(typeof(ListView)) + ); + } + + protected override DependencyObject GetContainerForItemOverride() + { + return new ListViewItem(); + } + + protected override bool IsItemItsOwnContainerOverride(object item) + { + return item is ListViewItem; + } +} diff --git a/source/RevitLookup.UI/Controls/ListView/ListView.xaml b/source/RevitLookup.UI/Controls/ListView/ListView.xaml index 9001b6a9..6e6eb971 100644 --- a/source/RevitLookup.UI/Controls/ListView/ListView.xaml +++ b/source/RevitLookup.UI/Controls/ListView/ListView.xaml @@ -10,63 +10,160 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:controls="clr-namespace:Wpf.Ui.Controls"> - - - + + + FontSize="16" + KeyboardNavigation.IsTabStop="False" /> + + diff --git a/source/RevitLookup.UI/Controls/Menu/MenuLoader.xaml.cs b/source/RevitLookup.UI/Controls/Menu/MenuLoader.xaml.cs new file mode 100644 index 00000000..409c4f46 --- /dev/null +++ b/source/RevitLookup.UI/Controls/Menu/MenuLoader.xaml.cs @@ -0,0 +1,40 @@ +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. +// Copyright (C) Leszek Pomianowski and WPF UI Contributors. +// All Rights Reserved. + +using System.Reflection; + +namespace Wpf.Ui.Controls; + +/// +/// Changes readonly field value of to false. +/// +public partial class MenuLoader : ResourceDictionary +{ + /// + /// Initializes a new instance of the class. + /// + /// + /// Sets menu alignment on initialization. + /// + public MenuLoader() + { + MenuLoader.Initialize(); + } + + private static void Initialize() + { + if (!SystemParameters.MenuDropAlignment) + { + return; + } + + FieldInfo? fieldInfo = typeof(SystemParameters).GetField( + "_menuDropAlignment", + BindingFlags.NonPublic | BindingFlags.Static + ); + + fieldInfo?.SetValue(null, false); + } +} diff --git a/source/RevitLookup.UI/Controls/MessageBox/MessageBox.cs b/source/RevitLookup.UI/Controls/MessageBox/MessageBox.cs index 3978011c..1da1681f 100644 --- a/source/RevitLookup.UI/Controls/MessageBox/MessageBox.cs +++ b/source/RevitLookup.UI/Controls/MessageBox/MessageBox.cs @@ -3,9 +3,13 @@ // Copyright (C) Leszek Pomianowski and WPF UI Contributors. // All Rights Reserved. +using System.Reflection; using Wpf.Ui.Input; using Wpf.Ui.Interop; using Size = System.Windows.Size; +#if NET8_0_OR_GREATER +using System.Runtime.CompilerServices; +#endif // ReSharper disable once CheckNamespace namespace Wpf.Ui.Controls; @@ -13,15 +17,9 @@ namespace Wpf.Ui.Controls; /// /// Customized window for notifications. /// -//[ToolboxItem(true)] -//[ToolboxBitmap(typeof(MessageBox), "MessageBox.bmp")] public class MessageBox : System.Windows.Window { - #region Static properties - - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty ShowTitleProperty = DependencyProperty.Register( nameof(ShowTitle), typeof(bool), @@ -29,9 +27,7 @@ public class MessageBox : System.Windows.Window new PropertyMetadata(true) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty PrimaryButtonTextProperty = DependencyProperty.Register( nameof(PrimaryButtonText), typeof(string), @@ -39,9 +35,7 @@ public class MessageBox : System.Windows.Window new PropertyMetadata(string.Empty) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty SecondaryButtonTextProperty = DependencyProperty.Register( nameof(SecondaryButtonText), typeof(string), @@ -49,9 +43,7 @@ public class MessageBox : System.Windows.Window new PropertyMetadata(string.Empty) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty CloseButtonTextProperty = DependencyProperty.Register( nameof(CloseButtonText), typeof(string), @@ -59,39 +51,31 @@ public class MessageBox : System.Windows.Window new PropertyMetadata("Close") ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty PrimaryButtonIconProperty = DependencyProperty.Register( nameof(PrimaryButtonIcon), - typeof(SymbolRegular), + typeof(IconElement), typeof(MessageBox), - new PropertyMetadata(SymbolRegular.Empty) + new PropertyMetadata(null) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty SecondaryButtonIconProperty = DependencyProperty.Register( nameof(SecondaryButtonIcon), - typeof(SymbolRegular), + typeof(IconElement), typeof(MessageBox), - new PropertyMetadata(SymbolRegular.Empty) + new PropertyMetadata(null) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty CloseButtonIconProperty = DependencyProperty.Register( nameof(CloseButtonIcon), - typeof(SymbolRegular), + typeof(IconElement), typeof(MessageBox), - new PropertyMetadata(SymbolRegular.Empty) + new PropertyMetadata(null) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty PrimaryButtonAppearanceProperty = DependencyProperty.Register( nameof(PrimaryButtonAppearance), typeof(ControlAppearance), @@ -99,9 +83,7 @@ public class MessageBox : System.Windows.Window new PropertyMetadata(ControlAppearance.Primary) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty SecondaryButtonAppearanceProperty = DependencyProperty.Register( nameof(SecondaryButtonAppearance), typeof(ControlAppearance), @@ -109,9 +91,7 @@ public class MessageBox : System.Windows.Window new PropertyMetadata(ControlAppearance.Secondary) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty CloseButtonAppearanceProperty = DependencyProperty.Register( nameof(CloseButtonAppearance), typeof(ControlAppearance), @@ -119,9 +99,7 @@ public class MessageBox : System.Windows.Window new PropertyMetadata(ControlAppearance.Secondary) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty IsPrimaryButtonEnabledProperty = DependencyProperty.Register( nameof(IsPrimaryButtonEnabled), typeof(bool), @@ -129,9 +107,7 @@ public class MessageBox : System.Windows.Window new PropertyMetadata(true) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty IsSecondaryButtonEnabledProperty = DependencyProperty.Register( nameof(IsSecondaryButtonEnabled), typeof(bool), @@ -139,9 +115,7 @@ public class MessageBox : System.Windows.Window new PropertyMetadata(true) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty TemplateButtonCommandProperty = DependencyProperty.Register( nameof(TemplateButtonCommand), typeof(IRelayCommand), @@ -149,12 +123,8 @@ public class MessageBox : System.Windows.Window new PropertyMetadata(null) ); - #endregion - - #region Properties - /// - /// Gets or sets a value that determines whether to show the in . + /// Gets or sets a value indicating whether to show the in . /// public bool ShowTitle { @@ -192,27 +162,27 @@ public string CloseButtonText /// /// Gets or sets the on the primary button /// - public SymbolRegular PrimaryButtonIcon + public IconElement? PrimaryButtonIcon { - get => (SymbolRegular)GetValue(PrimaryButtonIconProperty); + get => (IconElement?)GetValue(PrimaryButtonIconProperty); set => SetValue(PrimaryButtonIconProperty, value); } /// /// Gets or sets the on the secondary button /// - public SymbolRegular SecondaryButtonIcon + public IconElement? SecondaryButtonIcon { - get => (SymbolRegular)GetValue(SecondaryButtonIconProperty); + get => (IconElement?)GetValue(SecondaryButtonIconProperty); set => SetValue(SecondaryButtonIconProperty, value); } /// /// Gets or sets the on the close button /// - public SymbolRegular CloseButtonIcon + public IconElement? CloseButtonIcon { - get => (SymbolRegular)GetValue(CloseButtonIconProperty); + get => (IconElement?)GetValue(CloseButtonIconProperty); set => SetValue(CloseButtonIconProperty, value); } @@ -244,7 +214,7 @@ public ControlAppearance CloseButtonAppearance } /// - /// Gets or sets whether the primary button is enabled. + /// Gets or sets a value indicating whether the primary button is enabled. /// public bool IsSecondaryButtonEnabled { @@ -253,7 +223,7 @@ public bool IsSecondaryButtonEnabled } /// - /// Gets or sets whether the secondary button is enabled. + /// Gets or sets a value indicating whether the secondary button is enabled. /// public bool IsPrimaryButtonEnabled { @@ -262,11 +232,16 @@ public bool IsPrimaryButtonEnabled } /// - /// Command triggered after clicking the button on the Footer. + /// Gets the command triggered after clicking the button on the Footer. /// public IRelayCommand TemplateButtonCommand => (IRelayCommand)GetValue(TemplateButtonCommandProperty); - #endregion +#if !NET8_0_OR_GREATER + private static readonly PropertyInfo CanCenterOverWPFOwnerPropertyInfo = typeof(Window).GetProperty( + "CanCenterOverWPFOwner", + BindingFlags.NonPublic | BindingFlags.Instance + )!; +#endif /// /// Initializes a new instance of the class. @@ -285,7 +260,7 @@ public MessageBox() }; } - protected TaskCompletionSource? Tcs; + protected TaskCompletionSource? Tcs { get; set; } [Obsolete($"Use {nameof(ShowDialogAsync)} instead")] public new void Show() @@ -309,7 +284,7 @@ public MessageBox() /// Displays a message box /// /// - /// + /// Thrown if the operation is canceled. public async Task ShowDialogAsync( bool showAsDialog = true, CancellationToken cancellationToken = default @@ -326,9 +301,13 @@ public async Task ShowDialogAsync( RemoveTitleBarAndApplyMica(); if (showAsDialog) + { base.ShowDialog(); + } else + { base.Show(); + } return await Tcs.Task; } @@ -350,22 +329,60 @@ protected virtual void OnLoaded() var rootElement = (UIElement)GetVisualChild(0)!; ResizeToContentSize(rootElement); - CenterWindowOnScreen(); + + switch (WindowStartupLocation) + { + case WindowStartupLocation.Manual: + case WindowStartupLocation.CenterScreen: + CenterWindowOnScreen(); + break; + case WindowStartupLocation.CenterOwner: + if ( + !CanCenterOverWPFOwner() + || Owner.WindowState is WindowState.Maximized or WindowState.Minimized + ) + { + CenterWindowOnScreen(); + } + else + { + CenterWindowOnOwner(); + } + + break; + default: + throw new InvalidOperationException(); + } } + // CanCenterOverWPFOwner property see https://source.dot.net/#PresentationFramework/System/Windows/Window.cs,e679e433777b21b8 + private bool CanCenterOverWPFOwner() + { +#if NET8_0_OR_GREATER + return CanCenterOverWPFOwnerAccessor(this); +#else + return (bool)CanCenterOverWPFOwnerPropertyInfo.GetValue(this)!; +#endif + } + +#if NET8_0_OR_GREATER + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "get_CanCenterOverWPFOwner")] + private static extern bool CanCenterOverWPFOwnerAccessor(Window w); +#endif + /// - /// Sets Width and Height + /// Resizes the MessageBox to fit the content's size, including margins. /// - /// + /// The root element of the MessageBox protected virtual void ResizeToContentSize(UIElement rootElement) { Size desiredSize = rootElement.DesiredSize; - //left and right margin + // left and right margin const double margin = 12.0 * 2; - Width = desiredSize.Width + margin; - Height = desiredSize.Height; + SetCurrentValue(WidthProperty, desiredSize.Width + margin); + SetCurrentValue(HeightProperty, desiredSize.Height); ResizeWidth(rootElement); ResizeHeight(rootElement); @@ -376,26 +393,35 @@ protected override void OnClosing(CancelEventArgs e) base.OnClosing(e); if (e.Cancel) + { return; + } - Tcs?.TrySetResult(MessageBoxResult.None); + _ = Tcs?.TrySetResult(MessageBoxResult.None); } protected virtual void CenterWindowOnScreen() { - //TODO MessageBox should be displayed on the window on which the application - double screenWidth = SystemParameters.PrimaryScreenWidth; double screenHeight = SystemParameters.PrimaryScreenHeight; - Left = (screenWidth / 2) - (Width / 2); - Top = (screenHeight / 2) - (Height / 2); + SetCurrentValue(LeftProperty, (screenWidth / 2) - (Width / 2)); + SetCurrentValue(TopProperty, (screenHeight / 2) - (Height / 2)); + } + + private void CenterWindowOnOwner() + { + double left = Owner.Left + ((Owner.Width - Width) / 2); + double top = Owner.Top + ((Owner.Height - Height) / 2); + + SetCurrentValue(LeftProperty, left); + SetCurrentValue(TopProperty, top); } /// /// Occurs after the is clicked /// - /// + /// The MessageBox button protected virtual void OnButtonClick(MessageBoxButton button) { MessageBoxResult result = button switch @@ -405,49 +431,49 @@ protected virtual void OnButtonClick(MessageBoxButton button) _ => MessageBoxResult.None }; - Tcs?.TrySetResult(result); + _ = Tcs?.TrySetResult(result); base.Close(); } private void RemoveTitleBarAndApplyMica() { - UnsafeNativeMethods.RemoveWindowTitlebarContents(this); - WindowBackdrop.ApplyBackdrop(this, WindowBackdropType.Mica); + _ = UnsafeNativeMethods.RemoveWindowTitlebarContents(this); + _ = WindowBackdrop.ApplyBackdrop(this, WindowBackdropType.Mica); } - #region Resize private methods - private void ResizeWidth(UIElement element) { if (Width <= MaxWidth) + { return; + } - Width = MaxWidth; + SetCurrentValue(WidthProperty, MaxWidth); element.UpdateLayout(); - Height = element.DesiredSize.Height; + SetCurrentValue(HeightProperty, element.DesiredSize.Height); if (Height > MaxHeight) { - MaxHeight = Height; + SetCurrentValue(MaxHeightProperty, Height); } } private void ResizeHeight(UIElement element) { if (Height <= MaxHeight) + { return; + } - Height = MaxHeight; + SetCurrentValue(HeightProperty, MaxHeight); element.UpdateLayout(); - Width = element.DesiredSize.Width; + SetCurrentValue(WidthProperty, element.DesiredSize.Width); if (Width > MaxWidth) { - MaxWidth = Width; + SetCurrentValue(MaxWidthProperty, Width); } } - - #endregion } diff --git a/source/RevitLookup.UI/Controls/MessageBox/MessageBox.xaml b/source/RevitLookup.UI/Controls/MessageBox/MessageBox.xaml index aaeb848d..de5f9245 100644 --- a/source/RevitLookup.UI/Controls/MessageBox/MessageBox.xaml +++ b/source/RevitLookup.UI/Controls/MessageBox/MessageBox.xaml @@ -106,6 +106,7 @@ Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type controls:MessageBox}}, Path=TemplateButtonCommand, Mode=OneTime}" CommandParameter="{x:Static controls:MessageBoxButton.Primary}" Content="{TemplateBinding PrimaryButtonText}" + Icon="{TemplateBinding PrimaryButtonIcon}" IsDefault="True" /> + Content="{TemplateBinding SecondaryButtonText}" + Icon="{TemplateBinding SecondaryButtonIcon}" /> diff --git a/source/RevitLookup.UI/Controls/NavigationView/INavigableView.cs b/source/RevitLookup.UI/Controls/NavigationView/INavigableView.cs deleted file mode 100644 index 9cd23d8b..00000000 --- a/source/RevitLookup.UI/Controls/NavigationView/INavigableView.cs +++ /dev/null @@ -1,19 +0,0 @@ -// This Source Code Form is subject to the terms of the MIT License. -// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. -// Copyright (C) Leszek Pomianowski and WPF UI Contributors. -// All Rights Reserved. - -// ReSharper disable once CheckNamespace -namespace Wpf.Ui.Controls; - -/// -/// A component whose ViewModel is separate from the DataContext and can be navigated by . -/// -public interface INavigableView -{ - /// - /// ViewModel used by the view. - /// Optionally, it may implement and be navigated by . - /// - T ViewModel { get; } -} diff --git a/source/RevitLookup.UI/Controls/NavigationView/INavigationAware.cs b/source/RevitLookup.UI/Controls/NavigationView/INavigationAware.cs deleted file mode 100644 index 586aedbd..00000000 --- a/source/RevitLookup.UI/Controls/NavigationView/INavigationAware.cs +++ /dev/null @@ -1,23 +0,0 @@ -// This Source Code Form is subject to the terms of the MIT License. -// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. -// Copyright (C) Leszek Pomianowski and WPF UI Contributors. -// All Rights Reserved. - -// ReSharper disable once CheckNamespace -namespace Wpf.Ui.Controls; - -/// -/// Notifies class about being navigated. -/// -public interface INavigationAware -{ - /// - /// Method triggered when the class is navigated. - /// - void OnNavigatedTo(); - - /// - /// Method triggered when the navigation leaves the current class. - /// - void OnNavigatedFrom(); -} diff --git a/source/RevitLookup.UI/Controls/NavigationView/INavigationView.cs b/source/RevitLookup.UI/Controls/NavigationView/INavigationView.cs index 2ef6e5b2..3f8e9aa7 100644 --- a/source/RevitLookup.UI/Controls/NavigationView/INavigationView.cs +++ b/source/RevitLookup.UI/Controls/NavigationView/INavigationView.cs @@ -3,11 +3,12 @@ // Copyright (C) Leszek Pomianowski and WPF UI Contributors. // All Rights Reserved. -// Based on Windows UI Library -// Copyright(c) Microsoft Corporation.All rights reserved. +/* Based on Windows UI Library + Copyright(c) Microsoft Corporation.All rights reserved. */ using System.Collections; using System.Windows.Controls; +using Wpf.Ui.Abstractions; using Wpf.Ui.Animations; // ReSharper disable once CheckNamespace @@ -29,14 +30,14 @@ public interface INavigationView Visibility HeaderVisibility { get; set; } /// - /// Gets or sets a value that indicates whether the header is always visible. + /// Gets or sets a value indicating whether the header is always visible. /// bool AlwaysShowHeader { get; set; } /// /// Gets the collection of menu items displayed in the NavigationView. /// - IList MenuItems { get; set; } + IList MenuItems { get; } /// /// Gets or sets an object source used to generate the content of the NavigationView menu. @@ -46,7 +47,7 @@ public interface INavigationView /// /// Gets the list of objects to be used as navigation items in the footer menu. /// - IList FooterMenuItems { get; set; } + IList FooterMenuItems { get; } /// /// Gets or sets the object that represents the navigation items to be used in the footer menu. @@ -64,7 +65,7 @@ public interface INavigationView object? ContentOverlay { get; set; } /// - /// Gets a value that indicates whether the back button is enabled or disabled. + /// Gets a value indicating whether the back button is enabled or disabled. /// bool IsBackEnabled { get; } @@ -75,17 +76,17 @@ public interface INavigationView NavigationViewBackButtonVisible IsBackButtonVisible { get; set; } /// - /// Gets or sets a value that indicates whether the toggle button is visible. + /// Gets or sets a value indicating whether the toggle button is visible. /// bool IsPaneToggleVisible { get; set; } /// - /// Gets or sets a value that specifies whether the NavigationView pane is expanded to its full width. + /// Gets or sets a value indicating whether the NavigationView pane is expanded to its full width. /// bool IsPaneOpen { get; set; } /// - /// Gets or sets a value that determines whether the pane is shown. + /// Gets or sets a value indicating whether the pane is shown. /// bool IsPaneVisible { get; set; } @@ -115,7 +116,7 @@ public interface INavigationView object? PaneFooter { get; set; } /// - /// Gets a value that specifies how the pane and content areas of a NavigationView are being shown. + /// Gets or sets a value that specifies how the pane and content areas of a NavigationView are being shown. /// It is not the same PaneDisplayMode as in WinUi. /// NavigationViewPaneDisplayMode PaneDisplayMode { get; set; } @@ -136,7 +137,7 @@ public interface INavigationView BreadcrumbBar? BreadcrumbBar { get; set; } /// - /// Template Property for and . + /// Gets or sets the template property for and . /// ControlTemplate? ItemTemplate { get; set; } @@ -191,7 +192,7 @@ public interface INavigationView event TypedEventHandler Navigated; /// - /// Gets a value that indicates whether there is at least one entry in back navigation history. + /// Gets a value indicating whether there is at least one entry in back navigation history. /// bool CanGoBack { get; } @@ -210,9 +211,6 @@ public interface INavigationView /// /// Synchronously adds an element to the navigation stack and navigates current navigation Frame to the /// - /// - /// - /// bool NavigateWithHierarchy(Type pageType, object? dataContext = null); /// @@ -245,7 +243,7 @@ public interface INavigationView /// /// Allows you to assign to the NavigationView a special service responsible for retrieving the page instances. /// - void SetPageService(IPageService pageService); + void SetPageProviderService(INavigationViewPageProvider navigationViewPageProvider); /// /// Allows you to assign a general to the NavigationView that will be used to retrieve page instances and view models. diff --git a/source/RevitLookup.UI/Controls/NavigationView/INavigationViewItem.cs b/source/RevitLookup.UI/Controls/NavigationView/INavigationViewItem.cs index cbaa06fe..1bdddbee 100644 --- a/source/RevitLookup.UI/Controls/NavigationView/INavigationViewItem.cs +++ b/source/RevitLookup.UI/Controls/NavigationView/INavigationViewItem.cs @@ -3,8 +3,8 @@ // Copyright (C) Leszek Pomianowski and WPF UI Contributors. // All Rights Reserved. -// Based on Windows UI Library -// Copyright(c) Microsoft Corporation.All rights reserved. +/* Based on Windows UI Library + Copyright(c) Microsoft Corporation.All rights reserved. */ using System.Collections; using System.Windows.Controls; @@ -18,12 +18,12 @@ namespace Wpf.Ui.Controls; public interface INavigationViewItem { /// - /// Unique identifier that allows the item to be located in the navigation. + /// Gets the unique identifier that allows the item to be located in the navigation. /// string Id { get; } /// - /// Get or sets content + /// Gets or sets the content /// object Content { get; set; } @@ -35,7 +35,7 @@ public interface INavigationViewItem /// /// Gets the collection of menu items displayed in the NavigationView. /// - IList MenuItems { get; set; } + IList MenuItems { get; } /// /// Gets or sets an object source used to generate the content of the NavigationView menu. @@ -43,40 +43,39 @@ public interface INavigationViewItem object? MenuItemsSource { get; set; } /// - /// Gets information whether the current element is active. + /// Gets a value indicating whether the current element is active. /// bool IsActive { get; } /// - /// Gets information whether the sub- are expanded. + /// Gets or sets a value indicating whether the sub- are expanded. /// bool IsExpanded { get; internal set; } /// - /// A unique tag used by the parent navigation system for the purpose of searching and navigating. + /// Gets or sets the unique tag used by the parent navigation system for the purpose of searching and navigating. /// string TargetPageTag { get; set; } /// - /// The type of the page to be navigated. (Should be derived from ). + /// Gets or sets the type of the page to be navigated. (Should be derived from ). /// Type? TargetPageType { get; set; } - InfoBadge? InfoBadge { get; set; } /// - /// Specifies caching characteristics for a page involved in a navigation. + /// Gets or sets the caching characteristics for a page involved in a navigation. /// NavigationCacheMode NavigationCacheMode { get; set; } /// - /// Template Property + /// Gets or sets the template property /// ControlTemplate? Template { get; set; } /// - /// Gets parent if in collection + /// Gets or sets the parent if it's in collection /// INavigationViewItem? NavigationViewItemParent { get; internal set; } diff --git a/source/RevitLookup.UI/Controls/NavigationView/NavigatedEventArgs.cs b/source/RevitLookup.UI/Controls/NavigationView/NavigatedEventArgs.cs new file mode 100644 index 00000000..5521c782 --- /dev/null +++ b/source/RevitLookup.UI/Controls/NavigationView/NavigatedEventArgs.cs @@ -0,0 +1,18 @@ +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. +// Copyright (C) Leszek Pomianowski and WPF UI Contributors. +// All Rights Reserved. + +/* Based on Windows UI Library + Copyright(c) Microsoft Corporation.All rights reserved. */ + +// ReSharper disable once CheckNamespace +namespace Wpf.Ui.Controls; + +public class NavigatedEventArgs : RoutedEventArgs +{ + public NavigatedEventArgs(RoutedEvent routedEvent, object source) + : base(routedEvent, source) { } + + public required object Page { get; init; } +} diff --git a/source/RevitLookup.UI/Controls/NavigationView/NavigationViewEventArgs.cs b/source/RevitLookup.UI/Controls/NavigationView/NavigatingCancelEventArgs.cs similarity index 76% rename from source/RevitLookup.UI/Controls/NavigationView/NavigationViewEventArgs.cs rename to source/RevitLookup.UI/Controls/NavigationView/NavigatingCancelEventArgs.cs index 20655241..7ec4b4cd 100644 --- a/source/RevitLookup.UI/Controls/NavigationView/NavigationViewEventArgs.cs +++ b/source/RevitLookup.UI/Controls/NavigationView/NavigatingCancelEventArgs.cs @@ -15,13 +15,6 @@ public NavigatingCancelEventArgs(RoutedEvent routedEvent, object source) : base(routedEvent, source) { } public required object Page { get; init; } - public bool Cancel { get; set; } -} -public class NavigatedEventArgs : RoutedEventArgs -{ - public NavigatedEventArgs(RoutedEvent routedEvent, object source) - : base(routedEvent, source) { } - - public required object Page { get; init; } + public bool Cancel { get; set; } } diff --git a/source/RevitLookup.UI/Controls/NavigationView/NavigationCache.cs b/source/RevitLookup.UI/Controls/NavigationView/NavigationCache.cs index 529a26d3..98c20c7d 100644 --- a/source/RevitLookup.UI/Controls/NavigationView/NavigationCache.cs +++ b/source/RevitLookup.UI/Controls/NavigationView/NavigationCache.cs @@ -11,7 +11,7 @@ namespace Wpf.Ui.Controls; internal class NavigationCache { - private IDictionary _entires = new Dictionary(); + private readonly Dictionary _entires = []; public object? Remember(Type? entryType, NavigationCacheMode cacheMode, Func generate) { @@ -22,33 +22,25 @@ internal class NavigationCache if (cacheMode == NavigationCacheMode.Disabled) { -#if DEBUG - System - .Diagnostics - .Debug - .WriteLine($"Cache for {entryType} is disabled. Generating instance using action..."); -#endif + System.Diagnostics.Debug.WriteLine( + $"Cache for {entryType} is disabled. Generating instance using action..." + ); return generate.Invoke(); } if (!_entires.TryGetValue(entryType, out var value)) { -#if DEBUG - System - .Diagnostics - .Debug - .WriteLine($"{entryType} not found in cache, generating instance using action..."); -#endif + System.Diagnostics.Debug.WriteLine( + $"{entryType} not found in cache, generating instance using action..." + ); value = generate.Invoke(); _entires.Add(entryType, value); } -#if DEBUG System.Diagnostics.Debug.WriteLine($"{entryType} found in cache."); -#endif return value; } diff --git a/source/RevitLookup.UI/Controls/NavigationView/NavigationView.AttachedProperties.cs b/source/RevitLookup.UI/Controls/NavigationView/NavigationView.AttachedProperties.cs index bb1c06a8..f6c65ead 100644 --- a/source/RevitLookup.UI/Controls/NavigationView/NavigationView.AttachedProperties.cs +++ b/source/RevitLookup.UI/Controls/NavigationView/NavigationView.AttachedProperties.cs @@ -6,17 +6,61 @@ // ReSharper disable once CheckNamespace namespace Wpf.Ui.Controls; +/// +/// Defines attached properties for . +/// public partial class NavigationView { + // ============================================================ + // HeaderContent Attached Property + // ============================================================ + + /// Registers attached property NavigationView.HeaderContent public static readonly DependencyProperty HeaderContentProperty = DependencyProperty.RegisterAttached( "HeaderContent", typeof(object), - typeof(FrameworkElement), + typeof(NavigationView), new FrameworkPropertyMetadata(null) ); + /// Helper for getting from . + /// to read from. + /// HeaderContent property value. + [AttachedPropertyBrowsableForType(typeof(FrameworkElement))] public static object? GetHeaderContent(FrameworkElement target) => target.GetValue(HeaderContentProperty); - public static void SetHeaderContent(FrameworkElement target, object headerContent) => + /// Helper for setting on . + /// to set on. + /// HeaderContent property value. + public static void SetHeaderContent(FrameworkElement target, object? headerContent) => target.SetValue(HeaderContentProperty, headerContent); + + // ============================================================ + // NavigationParent Attached Property + // ============================================================ + + /// Identifies the dependency property. + internal static readonly DependencyProperty NavigationParentProperty = + DependencyProperty.RegisterAttached( + nameof(NavigationParent), + typeof(NavigationView), + typeof(NavigationView), + new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits) + ); + + /// + /// Gets the parent for its children. + /// + internal NavigationView? NavigationParent + { + get => (NavigationView?)GetValue(NavigationParentProperty); + private set => SetValue(NavigationParentProperty, value); + } + + /// Helper for getting from . + /// to read from. + /// NavigationParent property value. + [AttachedPropertyBrowsableForType(typeof(DependencyObject))] + internal static NavigationView? GetNavigationParent(DependencyObject navigationItem) => + navigationItem.GetValue(NavigationParentProperty) as NavigationView; } diff --git a/source/RevitLookup.UI/Controls/NavigationView/NavigationView.Base.cs b/source/RevitLookup.UI/Controls/NavigationView/NavigationView.Base.cs index 6a81a2fb..3b693905 100644 --- a/source/RevitLookup.UI/Controls/NavigationView/NavigationView.Base.cs +++ b/source/RevitLookup.UI/Controls/NavigationView/NavigationView.Base.cs @@ -3,8 +3,7 @@ // Copyright (C) Leszek Pomianowski and WPF UI Contributors. // All Rights Reserved. -// Based on Windows UI Library -// Copyright(c) Microsoft Corporation.All rights reserved. +/* Based on Windows UI Library https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.navigationview?view=winrt-22621 */ using System.Collections; using System.Collections.ObjectModel; @@ -15,17 +14,13 @@ // ReSharper disable once CheckNamespace namespace Wpf.Ui.Controls; -// https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.navigationview?view=winrt-22621 - /// /// Represents a container that enables navigation of app content. It has a header, a view for the main content, and a menu pane for navigation commands. /// -//[ToolboxItem(true)] -//[System.Drawing.ToolboxBitmap(typeof(NavigationView), "NavigationView.bmp")] public partial class NavigationView : System.Windows.Controls.Control, INavigationView { /// - /// Static constructor which overrides default property metadata. + /// Initializes static members of the class and overrides default property metadata. /// static NavigationView() { @@ -39,32 +34,62 @@ static NavigationView() ); } + /// + /// Initializes a new instance of the class. + /// public NavigationView() { NavigationParent = this; - //It really should be here - MenuItems = new ObservableCollection(); - FooterMenuItems = new ObservableCollection(); - Loaded += OnLoaded; Unloaded += OnUnloaded; SizeChanged += OnSizeChanged; + + // Initialize MenuItems collection + var menuItems = new ObservableCollection(); + menuItems.CollectionChanged += OnMenuItems_CollectionChanged; + SetValue(MenuItemsPropertyKey, menuItems); + + var footerMenuItems = new ObservableCollection(); + footerMenuItems.CollectionChanged += OnMenuItems_CollectionChanged; + SetValue(FooterMenuItemsPropertyKey, footerMenuItems); } /// public INavigationViewItem? SelectedItem { get; protected set; } - protected Dictionary PageIdOrTargetTagNavigationViewsDictionary = new(); - protected Dictionary PageTypeNavigationViewsDictionary = new(); + protected Dictionary PageIdOrTargetTagNavigationViewsDictionary { get; } = + []; + + protected Dictionary PageTypeNavigationViewsDictionary { get; } = []; + + private readonly ObservableCollection _autoSuggestBoxItems = []; + private readonly ObservableCollection _breadcrumbBarItems = []; + + private static readonly Thickness TitleBarPaneOpenMarginDefault = new(35, 0, 0, 0); + private static readonly Thickness TitleBarPaneCompactMarginDefault = new(35, 0, 0, 0); + private static readonly Thickness AutoSuggestBoxMarginDefault = new(8, 8, 8, 16); + private static readonly Thickness FrameMarginDefault = new(0, 50, 0, 0); - private readonly ObservableCollection _autoSuggestBoxItems = new(); - private readonly ObservableCollection _breadcrumbBarItems = new(); + protected static void UpdateVisualState(NavigationView navigationView) + { + // Skip display modes that don't have multiple states + if ( + navigationView.PaneDisplayMode + is NavigationViewPaneDisplayMode.LeftFluent + or NavigationViewPaneDisplayMode.Top + or NavigationViewPaneDisplayMode.Bottom + ) + { + return; + } - private static readonly Thickness s_titleBarPaneOpenMargin = new(35, 0, 0, 0); - private static readonly Thickness s_titleBarPaneCompactMargin = new(35, 0, 0, 0); - private static readonly Thickness s_autoSuggestBoxMargin = new(8, 8, 8, 16); - private static readonly Thickness s_frameMargin = new(0, 50, 0, 0); + _ = VisualStateManager.GoToState( + navigationView, + navigationView.IsPaneOpen ? "PaneOpen" : "PaneCompact", + true + ); + } /// protected override void OnInitialized(EventArgs e) @@ -85,6 +110,7 @@ protected override void OnInitialized(EventArgs e) private void OnLoaded(object sender, RoutedEventArgs e) { // TODO: Refresh + UpdateVisualState((NavigationView)sender); } /// @@ -110,24 +136,32 @@ protected virtual void OnUnloaded(object sender, RoutedEventArgs e) } if (Header is BreadcrumbBar breadcrumbBar) + { breadcrumbBar.ItemClicked -= BreadcrumbBarOnItemClicked; + } if (ToggleButton is not null) + { ToggleButton.Click -= OnToggleButtonClick; + } if (BackButton is not null) + { BackButton.Click -= OnToggleButtonClick; + } if (AutoSuggestBoxSymbolButton is not null) + { AutoSuggestBoxSymbolButton.Click -= AutoSuggestBoxSymbolButtonOnClick; + } } protected override void OnMouseDown(MouseButtonEventArgs e) { - //Back button + // Back button if (e.ChangedButton is MouseButton.XButton1) { - GoBack(); + _ = GoBack(); e.Handled = true; } @@ -147,7 +181,7 @@ protected virtual void OnSizeChanged(object sender, SizeChangedEventArgs e) /// protected virtual void OnBackButtonClick(object sender, RoutedEventArgs e) { - GoBack(); + _ = GoBack(); } /// @@ -155,7 +189,7 @@ protected virtual void OnBackButtonClick(object sender, RoutedEventArgs e) /// protected virtual void OnToggleButtonClick(object sender, RoutedEventArgs e) { - IsPaneOpen = !IsPaneOpen; + SetCurrentValue(IsPaneOpenProperty, !IsPaneOpen); } /// @@ -163,8 +197,8 @@ protected virtual void OnToggleButtonClick(object sender, RoutedEventArgs e) /// protected virtual void AutoSuggestBoxSymbolButtonOnClick(object sender, RoutedEventArgs e) { - IsPaneOpen = !IsPaneOpen; - AutoSuggestBox?.Focus(); + SetCurrentValue(IsPaneOpenProperty, !IsPaneOpen); + _ = AutoSuggestBox?.Focus(); } /// @@ -175,8 +209,8 @@ protected virtual void OnPaneDisplayModeChanged() switch (PaneDisplayMode) { case NavigationViewPaneDisplayMode.LeftFluent: - IsBackButtonVisible = NavigationViewBackButtonVisible.Collapsed; - IsPaneToggleVisible = false; + SetCurrentValue(IsBackButtonVisibleProperty, NavigationViewBackButtonVisible.Collapsed); + SetCurrentValue(IsPaneToggleVisibleProperty, false); break; } } @@ -198,7 +232,7 @@ internal void OnNavigationViewItemClick(NavigationViewItem navigationViewItem) { OnItemInvoked(); - NavigateInternal(navigationViewItem); + _ = NavigateInternal(navigationViewItem); } protected virtual void BreadcrumbBarOnItemClicked( @@ -207,13 +241,15 @@ BreadcrumbBarItemClickedEventArgs e ) { var item = (NavigationViewBreadcrumbItem)e.Item; - Navigate(item.PageId); + _ = Navigate(item.PageId); } private void UpdateAutoSuggestBoxSuggestions() { if (AutoSuggestBox == null) + { return; + } _autoSuggestBoxItems.Clear(); @@ -229,15 +265,21 @@ AutoSuggestBoxSuggestionChosenEventArgs args ) { if (sender.IsSuggestionListOpen) + { return; + } if (args.SelectedItem is not string selectedSuggestBoxItem) + { return; + } if (NavigateToMenuItemFromAutoSuggestBox(MenuItems, selectedSuggestBoxItem)) + { return; + } - NavigateToMenuItemFromAutoSuggestBox(FooterMenuItems, selectedSuggestBoxItem); + _ = NavigateToMenuItemFromAutoSuggestBox(FooterMenuItems, selectedSuggestBoxItem); } private void AutoSuggestBoxOnQuerySubmitted( @@ -255,33 +297,36 @@ AutoSuggestBoxQuerySubmittedEventArgs args foreach (string queryToken in querySplit) { if (item.IndexOf(queryToken, StringComparison.CurrentCultureIgnoreCase) < 0) + { isMatch = false; + } } if (isMatch) + { suggestions.Add(item); + } } if (suggestions.Count <= 0) + { return; + } var element = suggestions.First(); if (NavigateToMenuItemFromAutoSuggestBox(MenuItems, element)) + { return; + } - NavigateToMenuItemFromAutoSuggestBox(FooterMenuItems, element); + _ = NavigateToMenuItemFromAutoSuggestBox(FooterMenuItems, element); } - protected virtual void AddItemsToDictionaries(IList list) + protected virtual void AddItemsToDictionaries(IEnumerable list) { - for (var i = 0; i < list.Count; i++) + foreach (NavigationViewItem singleNavigationViewItem in list.OfType()) { - var singleMenuItem = list[i]; - - if (singleMenuItem is not INavigationViewItem singleNavigationViewItem) - continue; - if (!PageIdOrTargetTagNavigationViewsDictionary.ContainsKey(singleNavigationViewItem.Id)) { PageIdOrTargetTagNavigationViewsDictionary.Add( @@ -303,7 +348,7 @@ protected virtual void AddItemsToDictionaries(IList list) } if ( - singleNavigationViewItem.TargetPageType is not null + singleNavigationViewItem.TargetPageType != null && !PageTypeNavigationViewsDictionary.ContainsKey(singleNavigationViewItem.TargetPageType) ) { @@ -315,10 +360,10 @@ singleNavigationViewItem.TargetPageType is not null singleNavigationViewItem.IsMenuElement = true; - if (singleNavigationViewItem.MenuItems.Count <= 0) - continue; - - AddItemsToDictionaries(singleNavigationViewItem.MenuItems); + if (singleNavigationViewItem.HasMenuItems) + { + AddItemsToDictionaries(singleNavigationViewItem.MenuItems); + } } } @@ -328,25 +373,22 @@ protected virtual void AddItemsToDictionaries() AddItemsToDictionaries(FooterMenuItems); } - protected virtual void AddItemsToAutoSuggestBoxItems(IList list) + protected virtual void AddItemsToAutoSuggestBoxItems(IEnumerable list) { - for (var i = 0; i < list.Count; i++) + foreach (NavigationViewItem singleNavigationViewItem in list.OfType()) { - var singleMenuItem = list[i]; - - if (singleMenuItem is not NavigationViewItem singleNavigationViewItem) - continue; - if ( singleNavigationViewItem is { Content: string content, TargetPageType: { } } && !string.IsNullOrWhiteSpace(content) ) + { _autoSuggestBoxItems.Add(content); + } - if (singleNavigationViewItem.MenuItems.Count <= 0) - continue; - - AddItemsToAutoSuggestBoxItems(singleNavigationViewItem.MenuItems); + if (singleNavigationViewItem.HasMenuItems) + { + AddItemsToAutoSuggestBoxItems(singleNavigationViewItem.MenuItems); + } } } @@ -356,44 +398,49 @@ protected virtual void AddItemsToAutoSuggestBoxItems() AddItemsToAutoSuggestBoxItems(FooterMenuItems); } - protected virtual bool NavigateToMenuItemFromAutoSuggestBox(IList list, string selectedSuggestBoxItem) + protected virtual bool NavigateToMenuItemFromAutoSuggestBox( + IEnumerable list, + string selectedSuggestBoxItem + ) { - for (var i = 0; i < list.Count; i++) + foreach (NavigationViewItem singleNavigationViewItem in list.OfType()) { - var singleMenuItem = list[i]; - - if (singleMenuItem is not NavigationViewItem singleNavigationViewItem) - continue; - if (singleNavigationViewItem.Content is string content && content == selectedSuggestBoxItem) { - NavigateInternal(singleNavigationViewItem); + _ = NavigateInternal(singleNavigationViewItem); singleNavigationViewItem.BringIntoView(); - singleNavigationViewItem.Focus(); // TODO: Element or content? + _ = singleNavigationViewItem.Focus(); // TODO: Element or content? return true; } - if (singleNavigationViewItem.MenuItems.Count <= 0) - continue; - - NavigateToMenuItemFromAutoSuggestBox(singleNavigationViewItem.MenuItems, selectedSuggestBoxItem); + if ( + NavigateToMenuItemFromAutoSuggestBox( + singleNavigationViewItem.MenuItems, + selectedSuggestBoxItem + ) + ) + { + return true; + } } return false; } - protected virtual void UpdateMenuItemsTemplate(IList list) + protected virtual void UpdateMenuItemsTemplate(IEnumerable list) { - for (var i = 0; i < list.Count; i++) + if (ItemTemplate == null) { - var singleMenuItem = list[i]; - - if (singleMenuItem is not NavigationViewItem singleNavigationViewItem) - continue; + return; + } - if (ItemTemplate is not null && singleNavigationViewItem.Template != ItemTemplate) + foreach (var item in list) + { + if (item is NavigationViewItem singleNavigationViewItem) + { singleNavigationViewItem.Template = ItemTemplate; + } } } @@ -406,12 +453,14 @@ protected virtual void UpdateMenuItemsTemplate() protected virtual void CloseNavigationViewItemMenus() { if (Journal.Count <= 0 || IsPaneOpen) + { return; + } DeactivateMenuItems(MenuItems); DeactivateMenuItems(FooterMenuItems); - var currentItem = PageIdOrTargetTagNavigationViewsDictionary[Journal[^1]]; + INavigationViewItem currentItem = PageIdOrTargetTagNavigationViewsDictionary[Journal[^1]]; if (currentItem.NavigationViewItemParent is null) { currentItem.Activate(this); @@ -422,16 +471,14 @@ protected virtual void CloseNavigationViewItemMenus() currentItem.NavigationViewItemParent?.Activate(this); } - protected void DeactivateMenuItems(IList list) + protected void DeactivateMenuItems(IEnumerable list) { - for (var i = 0; i < list.Count; i++) + foreach (var item in list) { - var singleMenuItem = list[i]; - - if (singleMenuItem is not NavigationViewItem singleNavigationViewItem) - continue; - - singleNavigationViewItem.Deactivate(this); + if (item is NavigationViewItem singleNavigationViewItem) + { + singleNavigationViewItem.Deactivate(this); + } } } @@ -459,7 +506,7 @@ private void NavigationStackOnCollectionChanged(object? sender, NotifyCollection _breadcrumbBarItems.Clear(); break; default: - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(e), e.Action, $"Unsupported action: {e.Action}"); } } } diff --git a/source/RevitLookup.UI/Controls/NavigationView/NavigationView.Events.cs b/source/RevitLookup.UI/Controls/NavigationView/NavigationView.Events.cs index 5b5b9754..ef2af06f 100644 --- a/source/RevitLookup.UI/Controls/NavigationView/NavigationView.Events.cs +++ b/source/RevitLookup.UI/Controls/NavigationView/NavigationView.Events.cs @@ -6,15 +6,15 @@ // Based on Windows UI Library // Copyright(c) Microsoft Corporation.All rights reserved. - // ReSharper disable once CheckNamespace namespace Wpf.Ui.Controls; +/// +/// Defines events for . +/// public partial class NavigationView { - /// - /// Property for . - /// + /// Identifies the routed event. public static readonly RoutedEvent PaneOpenedEvent = EventManager.RegisterRoutedEvent( nameof(PaneOpened), RoutingStrategy.Bubble, @@ -22,9 +22,7 @@ public partial class NavigationView typeof(NavigationView) ); - /// - /// Property for . - /// + /// Identifies the routed event. public static readonly RoutedEvent PaneClosedEvent = EventManager.RegisterRoutedEvent( nameof(PaneClosed), RoutingStrategy.Bubble, @@ -32,9 +30,7 @@ public partial class NavigationView typeof(NavigationView) ); - /// - /// Property for . - /// + /// Identifies the routed event. public static readonly RoutedEvent SelectionChangedEvent = EventManager.RegisterRoutedEvent( nameof(SelectionChanged), RoutingStrategy.Bubble, @@ -42,9 +38,7 @@ public partial class NavigationView typeof(NavigationView) ); - /// - /// Property for . - /// + /// Identifies the routed event. public static readonly RoutedEvent ItemInvokedEvent = EventManager.RegisterRoutedEvent( nameof(ItemInvoked), RoutingStrategy.Bubble, @@ -52,9 +46,7 @@ public partial class NavigationView typeof(NavigationView) ); - /// - /// Property for . - /// + /// Identifies the routed event. public static readonly RoutedEvent BackRequestedEvent = EventManager.RegisterRoutedEvent( nameof(BackRequested), RoutingStrategy.Bubble, @@ -62,9 +54,7 @@ public partial class NavigationView typeof(NavigationView) ); - /// - /// Property for . - /// + /// Identifies the routed event. public static readonly RoutedEvent NavigatingEvent = EventManager.RegisterRoutedEvent( nameof(Navigating), RoutingStrategy.Bubble, @@ -72,9 +62,7 @@ public partial class NavigationView typeof(NavigationView) ); - /// - /// Property for . - /// + /// Identifies the routed event. public static readonly RoutedEvent NavigatedEvent = EventManager.RegisterRoutedEvent( nameof(Navigated), RoutingStrategy.Bubble, @@ -174,8 +162,6 @@ protected virtual void OnBackRequested() /// /// Raises the navigating requested event. /// - /// - /// protected virtual bool OnNavigating(object sourcePage) { var eventArgs = new NavigatingCancelEventArgs(NavigatingEvent, this) { Page = sourcePage }; @@ -188,7 +174,6 @@ protected virtual bool OnNavigating(object sourcePage) /// /// Raises the navigated requested event. /// - /// protected virtual void OnNavigated(object page) { var eventArgs = new NavigatedEventArgs(NavigatedEvent, this) { Page = page }; diff --git a/source/RevitLookup.UI/Controls/NavigationView/NavigationView.Navigation.cs b/source/RevitLookup.UI/Controls/NavigationView/NavigationView.Navigation.cs index c8a0ffd6..638eb407 100644 --- a/source/RevitLookup.UI/Controls/NavigationView/NavigationView.Navigation.cs +++ b/source/RevitLookup.UI/Controls/NavigationView/NavigationView.Navigation.cs @@ -3,30 +3,34 @@ // Copyright (C) Leszek Pomianowski and WPF UI Contributors. // All Rights Reserved. -// Based on Windows UI Library -// Copyright(c) Microsoft Corporation.All rights reserved. +/* Based on Windows UI Library */ using System.Collections.ObjectModel; using System.Diagnostics; +using Wpf.Ui.Abstractions; // ReSharper disable once CheckNamespace namespace Wpf.Ui.Controls; +/// +/// Defines navigation logic and state management for . +/// public partial class NavigationView { - protected readonly List Journal = new(50); + protected List Journal { get; } = new(50); - protected readonly ObservableCollection NavigationStack = new(); + protected ObservableCollection NavigationStack { get; } = []; private readonly NavigationCache _cache = new(); private readonly Dictionary< INavigationViewItem, List - > _complexNavigationStackHistory = new(); + > _complexNavigationStackHistory = []; private IServiceProvider? _serviceProvider; - private IPageService? _pageService; + + private INavigationViewPageProvider? _pageService; private int _currentIndexInJournal; @@ -34,7 +38,8 @@ private readonly Dictionary< public bool CanGoBack => Journal.Count > 1 && _currentIndexInJournal >= 0; /// - public void SetPageService(IPageService pageService) => _pageService = pageService; + public void SetPageProviderService(INavigationViewPageProvider navigationViewPageProvider) => + _pageService = navigationViewPageProvider; /// public void SetServiceProvider(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; @@ -42,39 +47,49 @@ private readonly Dictionary< /// public virtual bool Navigate(Type pageType, object? dataContext = null) { - if (!PageTypeNavigationViewsDictionary.TryGetValue(pageType, out var navigationViewItem)) + if ( + PageTypeNavigationViewsDictionary.TryGetValue( + pageType, + out INavigationViewItem? navigationViewItem + ) + ) { - return TryToNavigateWithoutINavigationViewItem(pageType, false, dataContext); + return NavigateInternal(navigationViewItem, dataContext); } - return NavigateInternal(navigationViewItem, dataContext); + return TryToNavigateWithoutINavigationViewItem(pageType, false, dataContext); } /// public virtual bool Navigate(string pageIdOrTargetTag, object? dataContext = null) { if ( - !PageIdOrTargetTagNavigationViewsDictionary.TryGetValue( + PageIdOrTargetTagNavigationViewsDictionary.TryGetValue( pageIdOrTargetTag, out INavigationViewItem? navigationViewItem ) ) { - return false; + return NavigateInternal(navigationViewItem, dataContext); } - return NavigateInternal(navigationViewItem, dataContext); + return false; } /// public virtual bool NavigateWithHierarchy(Type pageType, object? dataContext = null) { - if (!PageTypeNavigationViewsDictionary.TryGetValue(pageType, out var navigationViewItem)) + if ( + PageTypeNavigationViewsDictionary.TryGetValue( + pageType, + out INavigationViewItem? navigationViewItem + ) + ) { - return TryToNavigateWithoutINavigationViewItem(pageType, true, dataContext); + return NavigateInternal(navigationViewItem, dataContext, true); } - return NavigateInternal(navigationViewItem, dataContext, true); + return TryToNavigateWithoutINavigationViewItem(pageType, true, dataContext); } /// @@ -115,19 +130,19 @@ public virtual bool GoForward() { throw new NotImplementedException(); - //if (Journal.Count <= 1) - //{ - // return false; - //} + /*if (Journal.Count <= 1) + { + return false; + } - //_currentIndexInJournal += 1; + _currentIndexInJournal += 1; - //if (_currentIndexInJournal > Journal.Count - 1) - //{ - // return false; - //} + if (_currentIndexInJournal > Journal.Count - 1) + { + return false; + } - //return Navigate(Journal[_currentIndexInJournal]); + return Navigate(Journal[_currentIndexInJournal]);*/ } /// @@ -188,27 +203,19 @@ private bool NavigateInternal( if (OnNavigating(pageInstance)) { -#if DEBUG System.Diagnostics.Debug.WriteLineIf(EnableDebugMessages, "Navigation canceled"); -#endif return false; } -#if DEBUG - System - .Diagnostics - .Debug - .WriteLineIf( - EnableDebugMessages, - $"DEBUG | {viewItem.Id} - {(String.IsNullOrEmpty(viewItem.TargetPageTag) ? "NO_TAG" : viewItem.TargetPageTag)} - {viewItem.TargetPageType} | NAVIGATED" - ); -#endif + System.Diagnostics.Debug.WriteLineIf( + EnableDebugMessages, + $"DEBUG | {viewItem.Id} - {(string.IsNullOrEmpty(viewItem.TargetPageTag) ? "NO_TAG" : viewItem.TargetPageTag)} - {viewItem.TargetPageType} | NAVIGATED" + ); OnNavigated(pageInstance); ApplyAttachedProperties(viewItem, pageInstance); - UpdateDictionary(pageInstance); UpdateContent(pageInstance, dataContext); AddToNavigationStack(viewItem, addToNavigationStack, isBackwardsNavigated); @@ -236,45 +243,49 @@ private void AddToJournal(INavigationViewItem viewItem, bool isBackwardsNavigate Journal.Add(viewItem.Id); _currentIndexInJournal++; - IsBackEnabled = CanGoBack; + SetCurrentValue(IsBackEnabledProperty, CanGoBack); -#if DEBUG Debug.WriteLineIf(EnableDebugMessages, $"JOURNAL INDEX {_currentIndexInJournal}"); if (Journal.Count > 0) { Debug.WriteLineIf(EnableDebugMessages, $"JOURNAL LAST ELEMENT {Journal[^1]}"); } -#endif } private object GetNavigationItemInstance(INavigationViewItem viewItem) { if (viewItem.TargetPageType is null) { - throw new ArgumentNullException(nameof(viewItem.TargetPageType)); + throw new InvalidOperationException( + $"The {nameof(viewItem)}.{nameof(viewItem.TargetPageType)} property cannot be null." + ); } if (_serviceProvider is not null) { return _serviceProvider.GetService(viewItem.TargetPageType) - ?? new ArgumentNullException($"{nameof(_serviceProvider.GetService)} returned null"); + ?? throw new InvalidOperationException( + $"{nameof(_serviceProvider)}.{nameof(_serviceProvider.GetService)} returned null for type {viewItem.TargetPageType}." + ); } if (_pageService is not null) { return _pageService.GetPage(viewItem.TargetPageType) - ?? throw new ArgumentNullException($"{nameof(_pageService.GetPage)} returned null"); + ?? throw new InvalidOperationException( + $"{nameof(_pageService)}.{nameof(_pageService.GetPage)} returned null for type {viewItem.TargetPageType}." + ); } return _cache.Remember( - viewItem.TargetPageType, - viewItem.NavigationCacheMode, - ComputeCachedNavigationInstance - ) - ?? throw new ArgumentNullException( - $"Unable to get or create instance of {viewItem.TargetPageType} from cache." - ); + viewItem.TargetPageType, + viewItem.NavigationCacheMode, + ComputeCachedNavigationInstance + ) + ?? throw new InvalidOperationException( + $"Unable to get or create instance of {viewItem.TargetPageType} from cache." + ); object? ComputeCachedNavigationInstance() => GetPageInstanceFromCache(viewItem.TargetPageType); } @@ -288,33 +299,30 @@ private object GetNavigationItemInstance(INavigationViewItem viewItem) if (_serviceProvider is not null) { -#if DEBUG - System - .Diagnostics - .Debug - .WriteLine($"Getting {targetPageType} from cache using IServiceProvider."); -#endif + System.Diagnostics.Debug.WriteLine( + $"Getting {targetPageType} from cache using IServiceProvider." + ); return _serviceProvider.GetService(targetPageType) - ?? new ArgumentNullException($"{nameof(_serviceProvider.GetService)} returned null"); + ?? throw new InvalidOperationException( + $"{nameof(_serviceProvider.GetService)} returned null" + ); } if (_pageService is not null) { -#if DEBUG - System.Diagnostics.Debug.WriteLine($"Getting {targetPageType} from cache using IPageService."); -#endif + System.Diagnostics.Debug.WriteLine( + $"Getting {targetPageType} from cache using INavigationViewPageProvider." + ); return _pageService.GetPage(targetPageType) - ?? throw new ArgumentNullException($"{nameof(_pageService.GetPage)} returned null"); + ?? throw new InvalidOperationException($"{nameof(_pageService.GetPage)} returned null"); } -#if DEBUG System.Diagnostics.Debug.WriteLine($"Getting {targetPageType} from cache using reflection."); -#endif return NavigationViewActivator.CreateInstance(targetPageType) - ?? throw new ArgumentException("Failed to create instance of the page"); + ?? throw new InvalidOperationException("Failed to create instance of the page"); } private static void ApplyAttachedProperties(INavigationViewItem viewItem, object pageInstance) @@ -328,16 +336,6 @@ pageInstance is FrameworkElement frameworkElement } } - private void UpdateDictionary(object? content) - { - if (content is FrameworkElement frameworkViewContent) - { - var window = Window.GetWindow(this); - if (window is null) return; - frameworkViewContent.Resources = window.Resources; - } - } - private void UpdateContent(object? content, object? dataContext = null) { if (dataContext is not null && content is FrameworkElement frameworkViewContent) @@ -345,7 +343,7 @@ private void UpdateContent(object? content, object? dataContext = null) frameworkViewContent.DataContext = dataContext; } - NavigationViewContentPresenter.Navigate(content); + _ = NavigationViewContentPresenter.Navigate(content); } private void OnNavigationViewContentPresenterNavigated( @@ -360,12 +358,10 @@ System.Windows.Navigation.NavigationEventArgs e _ = frame.RemoveBackEntry(); - //var replaced = 1; - //((NavigationViewContentPresenter)sender).JournalOwnership = + /*var replaced = 1; + ((NavigationViewContentPresenter)sender).JournalOwnership =*/ } - #region Navigation stack methods - private void AddToNavigationStack( INavigationViewItem viewItem, bool addToNavigationStack, @@ -418,12 +414,13 @@ private void UpdateCurrentNavigationStackItem(INavigationViewItem viewItem) private void RecreateNavigationStackFromHistory(INavigationViewItem item) { - if (!_complexNavigationStackHistory.TryGetValue(item, out var historyList) || historyList.Count == 0) + List? historyList; + if (!_complexNavigationStackHistory.TryGetValue(item, out historyList) || historyList.Count == 0) { return; } - var latestHistory = historyList[^1]; + INavigationViewItem?[] latestHistory = historyList[^1]; var startIndex = 0; if (latestHistory[0]!.IsMenuElement) @@ -456,7 +453,7 @@ private void RecreateNavigationStackFromHistory(INavigationViewItem item) private void AddToNavigationStackHistory(INavigationViewItem viewItem) { - var lastItem = NavigationStack[^1]; + INavigationViewItem lastItem = NavigationStack[^1]; var startIndex = NavigationStack.IndexOf(viewItem); if (startIndex < 0) @@ -464,7 +461,8 @@ private void AddToNavigationStackHistory(INavigationViewItem viewItem) startIndex = 0; } - if (!_complexNavigationStackHistory.TryGetValue(lastItem, out var historyList)) + List? historyList; + if (!_complexNavigationStackHistory.TryGetValue(lastItem, out historyList)) { historyList = new List(5); _complexNavigationStackHistory.Add(lastItem, historyList); @@ -473,8 +471,7 @@ private void AddToNavigationStackHistory(INavigationViewItem viewItem) int arrayLength = NavigationStack.Count - 1 - startIndex; INavigationViewItem[] array; - //Initializing an array every time well... not an ideal - + // OPTIMIZATION: Initializing an array every time well... not an ideal #if NET6_0_OR_GREATER array = System.Buffers.ArrayPool.Shared.Rent(arrayLength); #else @@ -483,7 +480,7 @@ private void AddToNavigationStackHistory(INavigationViewItem viewItem) historyList.Add(array); - var latestHistory = historyList[^1]; + INavigationViewItem?[] latestHistory = historyList[^1]; int i = 0; for (int j = startIndex; j < NavigationStack.Count - 1; j++) @@ -505,7 +502,7 @@ private void ClearNavigationStack(int navigationStackItemIndex) for (int j = navigationStackCount - 1; j >= navigationStackCount - length; j--) { - NavigationStack.Remove(NavigationStack[j]); + _ = NavigationStack.Remove(NavigationStack[j]); } } @@ -533,6 +530,4 @@ private void ReplaceThirstElementInNavigationStack(INavigationViewItem newItem) NavigationStack[0] = newItem; NavigationStack[0].Activate(this); } - - #endregion -} \ No newline at end of file +} diff --git a/source/RevitLookup.UI/Controls/NavigationView/NavigationView.Parent.cs b/source/RevitLookup.UI/Controls/NavigationView/NavigationView.Parent.cs deleted file mode 100644 index 8daa5a86..00000000 --- a/source/RevitLookup.UI/Controls/NavigationView/NavigationView.Parent.cs +++ /dev/null @@ -1,47 +0,0 @@ -// This Source Code Form is subject to the terms of the MIT License. -// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. -// Copyright (C) Leszek Pomianowski and WPF UI Contributors. -// All Rights Reserved. - -// Based on Windows UI Library -// Copyright(c) Microsoft Corporation.All rights reserved. - -// ReSharper disable once CheckNamespace -namespace Wpf.Ui.Controls; - -public partial class NavigationView -{ - /// - /// Attached property for 's to get its parent. - /// - internal static readonly DependencyProperty NavigationParentProperty = - DependencyProperty.RegisterAttached( - nameof(NavigationParent), - typeof(INavigationView), - typeof(INavigationView), - new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits) - ); - - /// - /// - /// - internal INavigationView NavigationParent - { - get => (INavigationView)GetValue(NavigationParentProperty); - private set => SetValue(NavigationParentProperty, value); - } - - /// - /// Gets the parent view for its children. - /// - /// - /// Instance of the or . - internal static NavigationView? GetNavigationParent(T navigationItem) - where T : DependencyObject, INavigationViewItem - { - if (navigationItem.GetValue(NavigationParentProperty) is NavigationView navigationView) - return navigationView; - - return null; - } -} diff --git a/source/RevitLookup.UI/Controls/NavigationView/NavigationView.Properties.cs b/source/RevitLookup.UI/Controls/NavigationView/NavigationView.Properties.cs index fd062259..ea3716c8 100644 --- a/source/RevitLookup.UI/Controls/NavigationView/NavigationView.Properties.cs +++ b/source/RevitLookup.UI/Controls/NavigationView/NavigationView.Properties.cs @@ -3,21 +3,21 @@ // Copyright (C) Leszek Pomianowski and WPF UI Contributors. // All Rights Reserved. -// Based on Windows UI Library -// Copyright(c) Microsoft Corporation.All rights reserved. - using System.Collections; +using System.Collections.ObjectModel; +using System.Collections.Specialized; using System.Windows.Controls; using Wpf.Ui.Animations; // ReSharper disable once CheckNamespace namespace Wpf.Ui.Controls; +/// +/// Defines the dependency properties and dp callbacks for control +/// public partial class NavigationView { - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty EnableDebugMessagesProperty = DependencyProperty.Register( nameof(EnableDebugMessages), typeof(bool), @@ -25,9 +25,7 @@ public partial class NavigationView new FrameworkPropertyMetadata(false) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register( nameof(Header), typeof(object), @@ -35,9 +33,7 @@ public partial class NavigationView new FrameworkPropertyMetadata(null) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty HeaderVisibilityProperty = DependencyProperty.Register( nameof(HeaderVisibility), typeof(Visibility), @@ -45,9 +41,7 @@ public partial class NavigationView new FrameworkPropertyMetadata(Visibility.Visible) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty AlwaysShowHeaderProperty = DependencyProperty.Register( nameof(AlwaysShowHeader), typeof(bool), @@ -55,49 +49,45 @@ public partial class NavigationView new FrameworkPropertyMetadata(false) ); - /// - /// Property for . - /// - public static readonly DependencyProperty MenuItemsProperty = DependencyProperty.Register( + private static readonly DependencyPropertyKey MenuItemsPropertyKey = DependencyProperty.RegisterReadOnly( nameof(MenuItems), - typeof(IList), + typeof(ObservableCollection), typeof(NavigationView), - new FrameworkPropertyMetadata(null, OnMenuItemsPropertyChanged) + new PropertyMetadata(null) ); - /// - /// Property for . - /// + /// Identifies the dependency property. + public static readonly DependencyProperty MenuItemsProperty = MenuItemsPropertyKey.DependencyProperty; + + /// Identifies the dependency property. public static readonly DependencyProperty MenuItemsSourceProperty = DependencyProperty.Register( nameof(MenuItemsSource), typeof(object), typeof(NavigationView), - new FrameworkPropertyMetadata(null, OnMenuItemsSourcePropertyChanged) + new FrameworkPropertyMetadata(null, OnMenuItemsSourceChanged) ); - /// - /// Property for . - /// - public static readonly DependencyProperty FooterMenuItemsProperty = DependencyProperty.Register( - nameof(FooterMenuItemsProperty), - typeof(IList), - typeof(NavigationView), - new FrameworkPropertyMetadata(null) - ); + private static readonly DependencyPropertyKey FooterMenuItemsPropertyKey = + DependencyProperty.RegisterReadOnly( + nameof(FooterMenuItems), + typeof(ObservableCollection), + typeof(NavigationView), + new PropertyMetadata(null) + ); - /// - /// Property for . - /// + /// Identifies the dependency property. + public static readonly DependencyProperty FooterMenuItemsProperty = + FooterMenuItemsPropertyKey.DependencyProperty; + + /// Identifies the dependency property. public static readonly DependencyProperty FooterMenuItemsSourceProperty = DependencyProperty.Register( nameof(FooterMenuItemsSource), typeof(object), typeof(NavigationView), - new FrameworkPropertyMetadata(null, OnFooterMenuItemsSourcePropertyChanged) + new FrameworkPropertyMetadata(null, OnFooterMenuItemsSourceChanged) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty ContentOverlayProperty = DependencyProperty.Register( nameof(ContentOverlay), typeof(object), @@ -105,9 +95,7 @@ public partial class NavigationView new FrameworkPropertyMetadata(null) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty IsBackEnabledProperty = DependencyProperty.Register( nameof(IsBackEnabled), typeof(bool), @@ -115,9 +103,7 @@ public partial class NavigationView new FrameworkPropertyMetadata(false) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty IsBackButtonVisibleProperty = DependencyProperty.Register( nameof(IsBackButtonVisible), typeof(NavigationViewBackButtonVisible), @@ -125,9 +111,7 @@ public partial class NavigationView new FrameworkPropertyMetadata(NavigationViewBackButtonVisible.Auto) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty IsPaneToggleVisibleProperty = DependencyProperty.Register( nameof(IsPaneToggleVisible), typeof(bool), @@ -135,19 +119,15 @@ public partial class NavigationView new FrameworkPropertyMetadata(true) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty IsPaneOpenProperty = DependencyProperty.Register( nameof(IsPaneOpen), typeof(bool), typeof(NavigationView), - new FrameworkPropertyMetadata(false, IsPaneOpenChangedCallback) + new FrameworkPropertyMetadata(true, OnIsPaneOpenChanged) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty IsPaneVisibleProperty = DependencyProperty.Register( nameof(IsPaneVisible), typeof(bool), @@ -155,9 +135,7 @@ public partial class NavigationView new FrameworkPropertyMetadata(false) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty OpenPaneLengthProperty = DependencyProperty.Register( nameof(OpenPaneLength), typeof(double), @@ -165,9 +143,7 @@ public partial class NavigationView new FrameworkPropertyMetadata(0D) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty CompactPaneLengthProperty = DependencyProperty.Register( nameof(CompactPaneLength), typeof(double), @@ -175,9 +151,7 @@ public partial class NavigationView new FrameworkPropertyMetadata(0D) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty PaneHeaderProperty = DependencyProperty.Register( nameof(PaneHeader), typeof(object), @@ -185,9 +159,7 @@ public partial class NavigationView new FrameworkPropertyMetadata(null) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty PaneTitleProperty = DependencyProperty.Register( nameof(PaneTitle), typeof(string), @@ -195,9 +167,7 @@ public partial class NavigationView new FrameworkPropertyMetadata(null) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty PaneFooterProperty = DependencyProperty.Register( nameof(PaneFooter), typeof(object), @@ -205,49 +175,39 @@ public partial class NavigationView new FrameworkPropertyMetadata(null) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty PaneDisplayModeProperty = DependencyProperty.Register( nameof(PaneDisplayMode), typeof(NavigationViewPaneDisplayMode), typeof(NavigationView), - new FrameworkPropertyMetadata(NavigationViewPaneDisplayMode.Left, OnPaneDisplayModePropertyChanged) + new FrameworkPropertyMetadata(NavigationViewPaneDisplayMode.Left, OnPaneDisplayModeChanged) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty AutoSuggestBoxProperty = DependencyProperty.Register( nameof(AutoSuggestBox), typeof(AutoSuggestBox), typeof(NavigationView), - new FrameworkPropertyMetadata(null, OnAutoSuggestBoxPropertyChangedCallback) + new FrameworkPropertyMetadata(null, OnAutoSuggestBoxChanged) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty TitleBarProperty = DependencyProperty.Register( nameof(TitleBar), typeof(TitleBar), typeof(NavigationView), - new FrameworkPropertyMetadata(null, OnTitleBarPropertyChangedCallback) + new FrameworkPropertyMetadata(null, OnTitleBarChanged) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty BreadcrumbBarProperty = DependencyProperty.Register( nameof(BreadcrumbBar), typeof(BreadcrumbBar), typeof(NavigationView), - new FrameworkPropertyMetadata(null, OnBreadcrumbBarPropertyChangedCallback) + new FrameworkPropertyMetadata(null, OnBreadcrumbBarChanged) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register( nameof(ItemTemplate), typeof(ControlTemplate), @@ -255,13 +215,11 @@ public partial class NavigationView new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.AffectsMeasure, - OnItemTemplatePropertyChanged + OnItemTemplateChanged ) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty TransitionDurationProperty = DependencyProperty.Register( nameof(TransitionDuration), typeof(int), @@ -269,9 +227,7 @@ public partial class NavigationView new FrameworkPropertyMetadata(200) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty TransitionProperty = DependencyProperty.Register( nameof(Transition), typeof(Transition), @@ -279,18 +235,16 @@ public partial class NavigationView new FrameworkPropertyMetadata(Transition.FadeInWithSlide) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty FrameMarginProperty = DependencyProperty.Register( nameof(FrameMargin), typeof(Thickness), typeof(NavigationView), - new FrameworkPropertyMetadata(new Thickness()) + new FrameworkPropertyMetadata(default(Thickness)) ); /// - /// Enables or disables debugging messages for this control + /// Gets or sets a value indicating whether debugging messages for this control are enabled /// public bool EnableDebugMessages { @@ -320,11 +274,7 @@ public bool AlwaysShowHeader } /// - public IList MenuItems - { - get => (IList)GetValue(MenuItemsProperty); - set => SetValue(MenuItemsProperty, value); - } + public IList MenuItems => (ObservableCollection)GetValue(MenuItemsProperty); /// [Bindable(true)] @@ -345,11 +295,7 @@ public object? MenuItemsSource } /// - public IList FooterMenuItems - { - get => (IList)GetValue(FooterMenuItemsProperty); - set => SetValue(FooterMenuItemsProperty, value); - } + public IList FooterMenuItems => (ObservableCollection)GetValue(FooterMenuItemsProperty); /// [Bindable(true)] @@ -504,57 +450,135 @@ public Thickness FrameMargin set => SetValue(FrameMarginProperty, value); } - private static void OnMenuItemsPropertyChanged(DependencyObject? d, DependencyPropertyChangedEventArgs e) + private void OnMenuItemsSource_CollectionChanged( + object? sender, + IList collection, + NotifyCollectionChangedEventArgs e + ) { - if (d is not NavigationView navigationView || e.NewValue is not IList enumerableNewValue) + if (ReferenceEquals(sender, collection)) { return; } - if (navigationView.MenuItemsItemsControl is null) + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + foreach (var item in e.NewItems) + { + collection.Add(item); + } + break; + + case NotifyCollectionChangedAction.Remove: + foreach (var item in e.OldItems) + { + if (!e.NewItems.Contains(item)) + { + collection.Remove(item); + } + } + break; + + case NotifyCollectionChangedAction.Move: + var moveItem = MenuItems[e.OldStartingIndex]; + collection.RemoveAt(e.OldStartingIndex); + collection.Insert(e.NewStartingIndex, moveItem); + break; + + case NotifyCollectionChangedAction.Replace: + collection.RemoveAt(e.OldStartingIndex); + collection.Insert(e.OldStartingIndex, e.NewItems[0]); + break; + + case NotifyCollectionChangedAction.Reset: + collection.Clear(); + break; + } + } + + private void OnMenuItems_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + if (e.NewItems is null) { return; } - if (navigationView.MenuItemsItemsControl.ItemsSource.Equals(enumerableNewValue)) + UpdateMenuItemsTemplate(e.NewItems); + AddItemsToDictionaries(e.NewItems); + } + + private static void OnMenuItemsSourceChanged(DependencyObject? d, DependencyPropertyChangedEventArgs e) + { + if (d is not NavigationView navigationView) { return; } - navigationView.MenuItemsItemsControl.ItemsSource = null; - navigationView.MenuItemsItemsControl.ItemsSource = enumerableNewValue; + navigationView.MenuItems.Clear(); + + if (e.NewValue is IEnumerable newItemsSource and not string) + { + foreach (var item in newItemsSource) + { + navigationView.MenuItems.Add(item); + } + } + else if (e.NewValue != null) + { + navigationView.MenuItems.Add(e.NewValue); + } + + if (e.NewValue is INotifyCollectionChanged oc) + { + oc.CollectionChanged += (s, e) => + navigationView.OnMenuItemsSource_CollectionChanged(oc, navigationView.MenuItems, e); + } } - private static void OnMenuItemsSourcePropertyChanged( - DependencyObject? d, - DependencyPropertyChangedEventArgs e - ) + private void OnFooterMenuItems_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { - if (d is not NavigationView navigationView || e.NewValue is not IList enumerableNewValue) + if (e.NewItems is null) { return; } - navigationView.MenuItems = enumerableNewValue; + UpdateMenuItemsTemplate(e.NewItems); + AddItemsToDictionaries(e.NewItems); } - private static void OnFooterMenuItemsSourcePropertyChanged( + private static void OnFooterMenuItemsSourceChanged( DependencyObject? d, DependencyPropertyChangedEventArgs e ) { - if (d is not NavigationView navigationView || e.NewValue is not IList enumerableNewValue) + if (d is not NavigationView navigationView) { return; } - navigationView.FooterMenuItems = enumerableNewValue; + navigationView.FooterMenuItems.Clear(); + + if (e.NewValue is IEnumerable newItemsSource and not string) + { + foreach (var item in newItemsSource) + { + navigationView.FooterMenuItems.Add(item); + } + } + else if (e.NewValue != null) + { + navigationView.FooterMenuItems.Add(e.NewValue); + } + + if (e.NewValue is INotifyCollectionChanged oc) + { + oc.CollectionChanged += (s, e) => + navigationView.OnMenuItemsSource_CollectionChanged(oc, navigationView.FooterMenuItems, e); + } } - private static void OnPaneDisplayModePropertyChanged( - DependencyObject? d, - DependencyPropertyChangedEventArgs e - ) + private static void OnPaneDisplayModeChanged(DependencyObject? d, DependencyPropertyChangedEventArgs e) { if (d is not NavigationView navigationView) { @@ -564,10 +588,7 @@ DependencyPropertyChangedEventArgs e navigationView.OnPaneDisplayModeChanged(); } - private static void OnItemTemplatePropertyChanged( - DependencyObject? d, - DependencyPropertyChangedEventArgs e - ) + private static void OnItemTemplateChanged(DependencyObject? d, DependencyPropertyChangedEventArgs e) { if (d is not NavigationView navigationView) { @@ -577,7 +598,7 @@ DependencyPropertyChangedEventArgs e navigationView.OnItemTemplateChanged(); } - private static void IsPaneOpenChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) + private static void OnIsPaneOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is not NavigationView navigationView) { @@ -600,27 +621,15 @@ private static void IsPaneOpenChangedCallback(DependencyObject d, DependencyProp navigationView.CloseNavigationViewItemMenus(); - if (navigationView.TitleBar is not null) - { - navigationView - .TitleBar - .SetCurrentValue( - MarginProperty, - navigationView.IsPaneOpen ? s_titleBarPaneOpenMargin : s_titleBarPaneCompactMargin - ); - } - - _ = VisualStateManager.GoToState( - navigationView, - navigationView.IsPaneOpen ? "PaneOpen" : "PaneCompact", - true + navigationView.TitleBar?.SetCurrentValue( + MarginProperty, + navigationView.IsPaneOpen ? TitleBarPaneOpenMarginDefault : TitleBarPaneCompactMarginDefault ); + + UpdateVisualState(navigationView); } - private static void OnTitleBarPropertyChangedCallback( - DependencyObject d, - DependencyPropertyChangedEventArgs e - ) + private static void OnTitleBarChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is not NavigationView navigationView) { @@ -632,7 +641,7 @@ DependencyPropertyChangedEventArgs e navigationView.FrameMargin = new Thickness(0); oldValue.Margin = new Thickness(0); - if (navigationView.AutoSuggestBox?.Margin == s_autoSuggestBoxMargin) + if (navigationView.AutoSuggestBox?.Margin == AutoSuggestBoxMarginDefault) { navigationView.AutoSuggestBox.SetCurrentValue(MarginProperty, new Thickness(0)); } @@ -645,19 +654,16 @@ DependencyPropertyChangedEventArgs e return; } - navigationView.FrameMargin = s_frameMargin; - titleBar.Margin = s_titleBarPaneOpenMargin; + navigationView.FrameMargin = FrameMarginDefault; + titleBar.Margin = TitleBarPaneOpenMarginDefault; if (navigationView.AutoSuggestBox?.Margin is { Bottom: 0, Left: 0, Right: 0, Top: 0 }) { - navigationView.AutoSuggestBox.SetCurrentValue(MarginProperty, s_autoSuggestBoxMargin); + navigationView.AutoSuggestBox.SetCurrentValue(MarginProperty, AutoSuggestBoxMarginDefault); } } - private static void OnAutoSuggestBoxPropertyChangedCallback( - DependencyObject d, - DependencyPropertyChangedEventArgs e - ) + private static void OnAutoSuggestBoxChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is not NavigationView navigationView) { @@ -681,18 +687,15 @@ DependencyPropertyChangedEventArgs e autoSuggestBox.QuerySubmitted += navigationView.AutoSuggestBoxOnQuerySubmitted; if ( - navigationView.TitleBar?.Margin == s_titleBarPaneOpenMargin + navigationView.TitleBar?.Margin == TitleBarPaneOpenMarginDefault && autoSuggestBox.Margin is { Bottom: 0, Left: 0, Right: 0, Top: 0 } ) { - autoSuggestBox.Margin = s_autoSuggestBoxMargin; + autoSuggestBox.Margin = AutoSuggestBoxMarginDefault; } } - private static void OnBreadcrumbBarPropertyChangedCallback( - DependencyObject d, - DependencyPropertyChangedEventArgs e - ) + private static void OnBreadcrumbBarChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is not NavigationView navigationView) { @@ -714,7 +717,7 @@ DependencyPropertyChangedEventArgs e breadcrumbBar.ItemsSource = navigationView._breadcrumbBarItems; breadcrumbBar.ItemTemplate ??= - Application.MainWindow.TryFindResource("NavigationViewItemDataTemplate") as DataTemplate; + UiApplication.Current.TryFindResource("NavigationViewItemDataTemplate") as DataTemplate; breadcrumbBar.ItemClicked += navigationView.BreadcrumbBarOnItemClicked; } } diff --git a/source/RevitLookup.UI/Controls/NavigationView/NavigationView.TemplateParts.cs b/source/RevitLookup.UI/Controls/NavigationView/NavigationView.TemplateParts.cs index d5a99d71..46309752 100644 --- a/source/RevitLookup.UI/Controls/NavigationView/NavigationView.TemplateParts.cs +++ b/source/RevitLookup.UI/Controls/NavigationView/NavigationView.TemplateParts.cs @@ -7,8 +7,14 @@ // Copyright(c) Microsoft Corporation.All rights reserved. // ReSharper disable once CheckNamespace + +using Wpf.Ui.Appearance; + namespace Wpf.Ui.Controls; +/// +/// Defines the template parts for the control +/// [TemplatePart( Name = TemplateElementNavigationViewContentPresenter, Type = typeof(NavigationViewContentPresenter) @@ -61,34 +67,34 @@ public partial class NavigationView private const string TemplateElementAutoSuggestBoxSymbolButton = "PART_AutoSuggestBoxSymbolButton"; /// - /// Control responsible for rendering the content. + /// Gets or sets the control responsible for rendering the content. /// - protected NavigationViewContentPresenter NavigationViewContentPresenter = null!; + protected NavigationViewContentPresenter NavigationViewContentPresenter { get; set; } = null!; /// - /// Control located at the top of the pane with left arrow icon. + /// Gets or sets the control located at the top of the pane with left arrow icon. /// - protected System.Windows.Controls.ItemsControl MenuItemsItemsControl = null!; + protected System.Windows.Controls.ItemsControl MenuItemsItemsControl { get; set; } = null!; /// - /// Control located at the top of the pane with hamburger icon. + /// Gets or sets the control located at the top of the pane with hamburger icon. /// - protected System.Windows.Controls.ItemsControl FooterMenuItemsItemsControl = null!; + protected System.Windows.Controls.ItemsControl FooterMenuItemsItemsControl { get; set; } = null!; /// - /// Control located at the top of the pane with left arrow icon. + /// Gets or sets the control located at the top of the pane with left arrow icon. /// - protected System.Windows.Controls.Button? BackButton; + protected System.Windows.Controls.Button? BackButton { get; set; } /// - /// Control located at the top of the pane with hamburger icon. + /// Gets or sets the control located at the top of the pane with hamburger icon. /// - protected System.Windows.Controls.Button? ToggleButton; + protected System.Windows.Controls.Button? ToggleButton { get; set; } /// - /// Control that is visitable if PaneDisplayMode="Left" and in compact state + /// Gets or sets the control that is visitable if PaneDisplayMode="Left" and in compact state /// - protected System.Windows.Controls.Button? AutoSuggestBoxSymbolButton; + protected System.Windows.Controls.Button? AutoSuggestBoxSymbolButton { get; set; } /// public override void OnApplyTemplate() @@ -105,8 +111,14 @@ public override void OnApplyTemplate() TemplateElementFooterMenuItemsItemsControl ); - MenuItemsItemsControl.ItemsSource = MenuItems; - FooterMenuItemsItemsControl.ItemsSource = FooterMenuItems; + MenuItemsItemsControl.SetCurrentValue( + System.Windows.Controls.ItemsControl.ItemsSourceProperty, + MenuItems + ); + FooterMenuItemsItemsControl.SetCurrentValue( + System.Windows.Controls.ItemsControl.ItemsSourceProperty, + FooterMenuItems + ); if (NavigationViewContentPresenter is not null) { @@ -146,7 +158,9 @@ protected T GetTemplateChild(string name) where T : DependencyObject { if (GetTemplateChild(name) is not T dependencyObject) + { throw new ArgumentNullException(name); + } return dependencyObject; } diff --git a/source/RevitLookup.UI/Controls/NavigationView/NavigationViewActivator.cs b/source/RevitLookup.UI/Controls/NavigationView/NavigationViewActivator.cs index 3ca08e37..c826358d 100644 --- a/source/RevitLookup.UI/Controls/NavigationView/NavigationViewActivator.cs +++ b/source/RevitLookup.UI/Controls/NavigationView/NavigationViewActivator.cs @@ -5,6 +5,7 @@ using System.Reflection; using System.Windows.Controls; +using Wpf.Ui.Abstractions; using Wpf.Ui.Designer; // ReSharper disable once CheckNamespace @@ -24,11 +25,14 @@ internal static class NavigationViewActivator public static FrameworkElement? CreateInstance(Type pageType, object? dataContext = null) { if (!typeof(FrameworkElement).IsAssignableFrom(pageType)) + { throw new InvalidCastException( $"PageType of the ${typeof(INavigationViewItem)} must be derived from {typeof(FrameworkElement)}. {pageType} is not." ); + } if (DesignerHelper.IsInDesignMode) + { return new Page { Content = new TextBlock @@ -36,13 +40,14 @@ internal static class NavigationViewActivator Text = "Pages are not rendered while using the Designer. Edit the page template directly." } }; + } FrameworkElement? instance; #if NET48_OR_GREATER || NETCOREAPP3_0_OR_GREATER if (ControlsServices.ControlsServiceProvider != null) { - var pageConstructors = pageType.GetConstructors(); + ConstructorInfo[] pageConstructors = pageType.GetConstructors(); var parameterlessCount = pageConstructors.Count(ctor => ctor.GetParameters().Length == 0); var parameterfullCount = pageConstructors.Length - parameterlessCount; @@ -52,15 +57,11 @@ internal static class NavigationViewActivator } else if (parameterlessCount == 0 && parameterfullCount > 0) { - ConstructorInfo? selectedCtor = FitBestConstructor(pageConstructors, dataContext); - - if (selectedCtor == null) - { - throw new InvalidOperationException( - $"The {pageType} page does not have a parameterless constructor or the required services have not been configured for dependency injection. Use the static {nameof(ControlsServices)} class to initialize the GUI library with your service provider. If you are using {typeof(IPageService)} do not navigate initially and don't use Cache or Precache." + ConstructorInfo? selectedCtor = + FitBestConstructor(pageConstructors, dataContext) + ?? throw new InvalidOperationException( + $"The {pageType} page does not have a parameterless constructor or the required services have not been configured for dependency injection. Use the static {nameof(ControlsServices)} class to initialize the GUI library with your service provider. If you are using {typeof(INavigationViewPageProvider)} do not navigate initially and don't use Cache or Precache." ); - } - instance = InvokeElementConstructor(selectedCtor, dataContext); SetDataContext(instance, dataContext); @@ -80,15 +81,11 @@ internal static class NavigationViewActivator } } - ConstructorInfo? emptyConstructor = FindParameterlessConstructor(pageType); - - if (emptyConstructor == null) - { - throw new InvalidOperationException( - $"The {pageType} page does not have a parameterless constructor. If you are using {typeof(IPageService)} do not navigate initially and don't use Cache or Precache." + ConstructorInfo emptyConstructor = + FindParameterlessConstructor(pageType) + ?? throw new InvalidOperationException( + $"The {pageType} page does not have a parameterless constructor. If you are using {typeof(INavigationViewPageProvider)} do not navigate initially and don't use Cache or Precache." ); - } - instance = emptyConstructor.Invoke(null) as FrameworkElement; SetDataContext(instance, dataContext); @@ -107,11 +104,11 @@ internal static class NavigationViewActivator } /// - /// Picks a constructor which has the most satisfiable arguments count. + /// Picks the constructor with the highest number of satisfiable parameters based on the provided context. /// - /// - /// - /// + /// Array of constructors to evaluate. + /// Context used to determine parameter satisfaction. + /// The constructor with the most satisfiable arguments, or null if none are fully satisfiable. private static ConstructorInfo? FitBestConstructor( ConstructorInfo[] parameterfullCtors, object? dataContext @@ -120,26 +117,24 @@ internal static class NavigationViewActivator return parameterfullCtors .Select(ctor => { - var parameters = ctor.GetParameters(); - var argumentResolution = parameters.Select(prm => - { - var resolved = ResolveConstructorParameter(prm.ParameterType, dataContext); - return resolved != null; - }); - var fullyResolved = argumentResolution.All(resolved => resolved == true); - var score = fullyResolved ? parameters.Length : 0; - - return score == 0 ? null : new { Constructor = ctor, Score = score }; + ParameterInfo[] parameters = ctor.GetParameters(); + int score = parameters.Aggregate( + 0, + (acc, prm) => + acc + (ResolveConstructorParameter(prm.ParameterType, dataContext) != null ? 1 : 0) + ); + score = score != parameters.Length ? 0 : score; + return new { Constructor = ctor, Score = score }; }) - .Where(cs => cs != null) - .OrderBy(cs => cs.Score) + .Where(cs => cs.Score != 0) + .OrderByDescending(cs => cs.Score) .FirstOrDefault() ?.Constructor; } private static FrameworkElement? InvokeElementConstructor(ConstructorInfo ctor, object? dataContext) { - var args = ctor.GetParameters() + IEnumerable args = ctor.GetParameters() .Select(prm => ResolveConstructorParameter(prm.ParameterType, dataContext)); return ctor.Invoke(args.ToArray()) as FrameworkElement; @@ -148,14 +143,11 @@ internal static class NavigationViewActivator private static FrameworkElement? InvokeElementConstructor(Type tPage, object? dataContext) { - var ctor = dataContext is null + ConstructorInfo? ctor = dataContext is null ? tPage.GetConstructor(Type.EmptyTypes) - : tPage.GetConstructor(new[] { dataContext!.GetType() }); - - if (ctor != null) - return ctor.Invoke(new[] { dataContext }) as FrameworkElement; + : tPage.GetConstructor(new[] { dataContext.GetType() }); - return null; + return ctor?.Invoke(new[] { dataContext }) as FrameworkElement; } private static ConstructorInfo? FindParameterlessConstructor(Type? tPage) @@ -171,6 +163,8 @@ internal static class NavigationViewActivator private static void SetDataContext(FrameworkElement? element, object? dataContext) { if (element != null && dataContext != null) + { element.DataContext = dataContext; + } } } diff --git a/source/RevitLookup.UI/Controls/NavigationView/NavigationViewBreadcrumbItem.cs b/source/RevitLookup.UI/Controls/NavigationView/NavigationViewBreadcrumbItem.cs index 52da8819..0c9e8781 100644 --- a/source/RevitLookup.UI/Controls/NavigationView/NavigationViewBreadcrumbItem.cs +++ b/source/RevitLookup.UI/Controls/NavigationView/NavigationViewBreadcrumbItem.cs @@ -9,7 +9,7 @@ // ReSharper disable once CheckNamespace namespace Wpf.Ui.Controls; -internal class NavigationViewBreadcrumbItem +public class NavigationViewBreadcrumbItem { public NavigationViewBreadcrumbItem(INavigationViewItem item) { @@ -18,5 +18,6 @@ public NavigationViewBreadcrumbItem(INavigationViewItem item) } public object Content { get; } + public string PageId { get; } } diff --git a/source/RevitLookup.UI/Controls/NavigationView/NavigationViewCompact.xaml b/source/RevitLookup.UI/Controls/NavigationView/NavigationViewCompact.xaml index 86cd8eff..f8d2f075 100644 --- a/source/RevitLookup.UI/Controls/NavigationView/NavigationViewCompact.xaml +++ b/source/RevitLookup.UI/Controls/NavigationView/NavigationViewCompact.xaml @@ -17,6 +17,7 @@ - + diff --git a/source/RevitLookup.UI/Controls/NavigationView/NavigationViewContentPresenter.cs b/source/RevitLookup.UI/Controls/NavigationView/NavigationViewContentPresenter.cs index e2fc899f..204082a0 100644 --- a/source/RevitLookup.UI/Controls/NavigationView/NavigationViewContentPresenter.cs +++ b/source/RevitLookup.UI/Controls/NavigationView/NavigationViewContentPresenter.cs @@ -3,22 +3,21 @@ // Copyright (C) Leszek Pomianowski and WPF UI Contributors. // All Rights Reserved. -// Based on Windows UI Library -// Copyright(c) Microsoft Corporation.All rights reserved. +/* Based on Windows UI Library */ using System.Windows.Controls; using System.Windows.Input; using System.Windows.Navigation; +using Wpf.Ui.Abstractions.Controls; using Wpf.Ui.Animations; +using Wpf.Ui.Appearance; // ReSharper disable once CheckNamespace namespace Wpf.Ui.Controls; public class NavigationViewContentPresenter : Frame { - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty TransitionDurationProperty = DependencyProperty.Register( nameof(TransitionDuration), typeof(int), @@ -26,9 +25,7 @@ public class NavigationViewContentPresenter : Frame new FrameworkPropertyMetadata(200) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty TransitionProperty = DependencyProperty.Register( nameof(Transition), typeof(Transition), @@ -36,9 +33,7 @@ public class NavigationViewContentPresenter : Frame new FrameworkPropertyMetadata(Transition.FadeInWithSlide) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty IsDynamicScrollViewerEnabledProperty = DependencyProperty.Register( nameof(IsDynamicScrollViewerEnabled), @@ -47,7 +42,8 @@ public class NavigationViewContentPresenter : Frame new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.AffectsMeasure) ); - [Bindable(true), Category("Appearance")] + [Bindable(true)] + [Category("Appearance")] public int TransitionDuration { get => (int)GetValue(TransitionDurationProperty); @@ -64,7 +60,7 @@ public Transition Transition } /// - /// Gets a value indicating whether the dynamic scroll viewer is enabled. + /// Gets or sets a value indicating whether the dynamic scroll viewer is enabled. /// public bool IsDynamicScrollViewerEnabled { @@ -78,25 +74,26 @@ static NavigationViewContentPresenter() typeof(NavigationViewContentPresenter), new FrameworkPropertyMetadata(typeof(NavigationViewContentPresenter)) ); - + NavigationUIVisibilityProperty.OverrideMetadata( typeof(NavigationViewContentPresenter), new FrameworkPropertyMetadata(NavigationUIVisibility.Hidden) ); - + SandboxExternalContentProperty.OverrideMetadata( typeof(NavigationViewContentPresenter), new FrameworkPropertyMetadata(true) ); - + JournalOwnershipProperty.OverrideMetadata( typeof(NavigationViewContentPresenter), new FrameworkPropertyMetadata(JournalOwnership.UsesParentJournal) ); - // - // ScrollViewer - // .CanContentScrollProperty - // .OverrideMetadata(typeof(Page), new FrameworkPropertyMetadata(true)); + + ScrollViewer.CanContentScrollProperty.OverrideMetadata( + typeof(Page), + new FrameworkPropertyMetadata(true) + ); } public NavigationViewContentPresenter() @@ -129,7 +126,7 @@ protected override void OnInitialized(EventArgs e) { base.OnInitialized(e); - //I didn't understand something, but why is it necessary? + // REVIEW: I didn't understand something, but why is it necessary? Unloaded += static (sender, _) => { if (sender is NavigationViewContentPresenter navigator) @@ -170,7 +167,10 @@ protected virtual void OnNavigated(NavigationEventArgs eventArgs) return; } - IsDynamicScrollViewerEnabled = ScrollViewer.GetCanContentScroll(dependencyObject); + SetCurrentValue( + IsDynamicScrollViewerEnabledProperty, + ScrollViewer.GetCanContentScroll(dependencyObject) + ); } private void ApplyTransitionEffectToNavigatedPage(object content) @@ -180,50 +180,49 @@ private void ApplyTransitionEffectToNavigatedPage(object content) return; } - TransitionAnimationProvider.ApplyTransition(content, Transition, TransitionDuration); + _ = TransitionAnimationProvider.ApplyTransition(content, Transition, TransitionDuration); } private static void NotifyContentAboutNavigatingTo(object content) { - if (content is INavigationAware navigationAwareNavigationContent) - { - navigationAwareNavigationContent.OnNavigatedTo(); - } - - if ( - content is INavigableView - { - ViewModel: INavigationAware navigationAwareNavigableViewViewModel - } - ) - { - navigationAwareNavigableViewViewModel.OnNavigatedTo(); - } - - if (content is FrameworkElement { DataContext: INavigationAware navigationAwareCurrentContent }) - { - navigationAwareCurrentContent.OnNavigatedTo(); - } + NotifyContentAboutNavigating(content, navigationAware => navigationAware.OnNavigatedToAsync()); } private static void NotifyContentAboutNavigatingFrom(object content) { - if (content is INavigationAware navigationAwareNavigationContent) - navigationAwareNavigationContent.OnNavigatedFrom(); + NotifyContentAboutNavigating(content, navigationAware => navigationAware.OnNavigatedFromAsync()); + } - if ( - content is INavigableView + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "ReSharper", + "SuspiciousTypeConversion.Global", + Justification = "The library user might make a class inherit from both FrameworkElement and INavigationAware at the same time." + )] + private static void NotifyContentAboutNavigating(object content, Func function) + { + // The order in which the OnNavigatedToAsync/OnNavigatedFromAsync methods of View and ViewModel are called + // is not guaranteed + if (content is INavigationAware navigationAwareNavigationContent) + { + function(navigationAwareNavigationContent); + if (navigationAwareNavigationContent is FrameworkElement { DataContext: INavigationAware viewModel } && + !ReferenceEquals(viewModel, navigationAwareNavigationContent)) { - ViewModel: INavigationAware navigationAwareNavigableViewViewModel + function(viewModel); + return; } - ) + } + + if (content is INavigableView { ViewModel: INavigationAware navigationAwareNavigableViewViewModel }) { - navigationAwareNavigableViewViewModel.OnNavigatedFrom(); + function(navigationAwareNavigableViewViewModel); + return; } - + if (content is FrameworkElement { DataContext: INavigationAware navigationAwareCurrentContent }) { - navigationAwareCurrentContent.OnNavigatedFrom(); + function(navigationAwareCurrentContent); + return; } } } diff --git a/source/RevitLookup.UI/Controls/NavigationView/NavigationViewContentPresenter.xaml b/source/RevitLookup.UI/Controls/NavigationView/NavigationViewContentPresenter.xaml index 46a6cc73..712e36e6 100644 --- a/source/RevitLookup.UI/Controls/NavigationView/NavigationViewContentPresenter.xaml +++ b/source/RevitLookup.UI/Controls/NavigationView/NavigationViewContentPresenter.xaml @@ -32,16 +32,15 @@ - - - - - - - - - + + + + + + + + + + + + /// Rotating loading ring. /// -//[ToolboxItem(true)] -//[ToolboxBitmap(typeof(ProgressRing), "ProgressRing.bmp")] public class ProgressRing : System.Windows.Controls.Control { - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty ProgressProperty = DependencyProperty.Register( nameof(Progress), typeof(double), typeof(ProgressRing), - new PropertyMetadata(50d, PropertyChangedCallback) + new PropertyMetadata(50d, OnProgressChanged) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty IsIndeterminateProperty = DependencyProperty.Register( nameof(IsIndeterminate), typeof(bool), @@ -38,9 +32,7 @@ public class ProgressRing : System.Windows.Controls.Control new PropertyMetadata(false) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty EngAngleProperty = DependencyProperty.Register( nameof(EngAngle), typeof(double), @@ -48,9 +40,7 @@ public class ProgressRing : System.Windows.Controls.Control new PropertyMetadata(180.0d) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty IndeterminateAngleProperty = DependencyProperty.Register( nameof(IndeterminateAngle), typeof(double), @@ -58,9 +48,7 @@ public class ProgressRing : System.Windows.Controls.Control new PropertyMetadata(180.0d) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty CoverRingStrokeProperty = DependencyProperty.RegisterAttached( nameof(CoverRingStroke), typeof(Brush), @@ -73,9 +61,7 @@ public class ProgressRing : System.Windows.Controls.Control ) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty CoverRingVisibilityProperty = DependencyProperty.Register( nameof(CoverRingVisibility), typeof(System.Windows.Visibility), @@ -93,8 +79,8 @@ public double Progress } /// - /// Determines if shows actual values () - /// or generic, continuous progress feedback (). + /// Gets or sets a value indicating whether shows actual values () + /// or generic, continuous progress feedback. /// public bool IsIndeterminate { @@ -121,7 +107,7 @@ public double IndeterminateAngle } /// - /// Background ring fill. + /// Gets background ring fill. /// public Brush CoverRingStroke { @@ -130,7 +116,7 @@ public Brush CoverRingStroke } /// - /// Background ring visibility. + /// Gets background ring visibility. /// public System.Windows.Visibility CoverRingVisibility { @@ -146,27 +132,35 @@ protected void UpdateProgressAngle() var percentage = Progress; if (percentage > 100) + { percentage = 100; + } if (percentage < 0) + { percentage = 0; + } // (360 / 100) * percentage var endAngle = 3.6d * percentage; if (endAngle >= 360) + { endAngle = 359; + } - EngAngle = endAngle; + SetCurrentValue(EngAngleProperty, endAngle); } /// /// Validates the entered and redraws the . /// - protected static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) + protected static void OnProgressChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is not ProgressRing control) + { return; + } control.UpdateProgressAngle(); } diff --git a/source/RevitLookup.UI/Controls/RadioButton/RadioButton.xaml b/source/RevitLookup.UI/Controls/RadioButton/RadioButton.xaml index 5dec5478..ffef00b4 100644 --- a/source/RevitLookup.UI/Controls/RadioButton/RadioButton.xaml +++ b/source/RevitLookup.UI/Controls/RadioButton/RadioButton.xaml @@ -62,7 +62,7 @@ Stroke="{DynamicResource RadioButtonOuterEllipseStroke}" StrokeThickness="{StaticResource RadioButtonStrokeThickness}" UseLayoutRounding="False" /> - + - + /// Displays the rating scale with interactions. /// -//[ToolboxItem(true)] -//[ToolboxBitmap(typeof(RatingControl), "RatingControl.bmp")] [TemplatePart(Name = "PART_Star1", Type = typeof(SymbolIcon))] [TemplatePart(Name = "PART_Star2", Type = typeof(SymbolIcon))] [TemplatePart(Name = "PART_Star3", Type = typeof(SymbolIcon))] @@ -28,34 +26,25 @@ private enum StarValue } private const double MaxValue = 5.0D; - private const double MinValue = 0.0D; - private const int OffsetTolerance = 8; - private static readonly SymbolRegular StarSymbol = SymbolRegular.Star28; - private static readonly SymbolRegular StarHalfSymbol = SymbolRegular.StarHalf28; + private SymbolIcon? _symbolIconStarOne; + private SymbolIcon? _symbolIconStarTwo; + private SymbolIcon? _symbolIconStarThree; + private SymbolIcon? _symbolIconStarFour; + private SymbolIcon? _symbolIconStarFive; - private SymbolIcon? _symbolIconStarOne, - _symbolIconStarTwo, - _symbolIconStarThree, - _symbolIconStarFour, - _symbolIconStarFive; - - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty ValueProperty = DependencyProperty.Register( nameof(Value), typeof(double), typeof(RatingControl), - new PropertyMetadata(0.0D, OnValuePropertyChanged) + new PropertyMetadata(0.0D, OnValueChanged) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty MaxRatingProperty = DependencyProperty.Register( nameof(MaxRating), typeof(int), @@ -63,9 +52,7 @@ private enum StarValue new PropertyMetadata(5) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty HalfStarEnabledProperty = DependencyProperty.Register( nameof(HalfStarEnabled), typeof(bool), @@ -73,9 +60,7 @@ private enum StarValue new PropertyMetadata(true) ); - /// - /// Routed event for . - /// + /// Identifies the routed event. public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent( nameof(ValueChanged), RoutingStrategy.Bubble, @@ -102,7 +87,7 @@ public int MaxRating } /// - /// Gets or sets the value deciding whether half of the star can be selected. + /// Gets or sets a value indicating whether half of the star can be selected. /// public bool HalfStarEnabled { @@ -126,20 +111,22 @@ protected virtual void OnValueChanged(double oldValue) { if (Value > MaxValue) { - Value = MaxValue; + SetCurrentValue(ValueProperty, MaxValue); return; } if (Value < MinValue) { - Value = MinValue; + SetCurrentValue(ValueProperty, MinValue); return; } if (!Value.Equals(oldValue)) + { RaiseEvent(new RoutedEventArgs(ValueChangedEvent)); + } UpdateStarsFromValue(); } @@ -161,11 +148,13 @@ protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); - var currentPossition = e.GetPosition(this); + Point currentPossition = e.GetPosition(this); var mouseOffset = currentPossition.X * 100 / ActualWidth; if (e.LeftButton != MouseButtonState.Pressed) + { UpdateStarsOnMousePreview(mouseOffset); + } } /// @@ -175,26 +164,32 @@ protected override void OnMouseDown(MouseButtonEventArgs e) { base.OnMouseDown(e); - var currentPossition = e.GetPosition(this); + Point currentPossition = e.GetPosition(this); var mouseOffset = currentPossition.X * 100 / ActualWidth; if (e.LeftButton == MouseButtonState.Pressed) + { UpdateStarsOnMouseClick(mouseOffset); + } } /// - /// Is called after lifting a keyboard key. + /// Adjusts the control's in response to keyboard input, incrementing or decrementing based on the key pressed. /// - /// + /// Key event arguments containing details about the key press. protected override void OnKeyUp(KeyEventArgs e) { base.OnKeyUp(e); if ((e.Key == Key.Right || e.Key == Key.Up) && Value < MaxValue) + { Value += HalfStarEnabled ? 0.5D : 1; + } if ((e.Key == Key.Left || e.Key == Key.Down) && Value > MinValue) + { Value -= HalfStarEnabled ? 0.5D : 1; + } } /// @@ -205,19 +200,29 @@ public override void OnApplyTemplate() base.OnApplyTemplate(); if (GetTemplateChild("PART_Star1") is SymbolIcon starOne) + { _symbolIconStarOne = starOne; + } if (GetTemplateChild("PART_Star2") is SymbolIcon starTwo) + { _symbolIconStarTwo = starTwo; + } if (GetTemplateChild("PART_Star3") is SymbolIcon starThree) + { _symbolIconStarThree = starThree; + } if (GetTemplateChild("PART_Star4") is SymbolIcon starFour) + { _symbolIconStarFour = starFour; + } if (GetTemplateChild("PART_Star5") is SymbolIcon starFive) + { _symbolIconStarFive = starFive; + } UpdateStarsFromValue(); } @@ -231,7 +236,7 @@ private void UpdateStarsOnMouseClick(double offsetPercentage) { var currentValue = ExtractValueFromOffset(offsetPercentage); - Value = currentValue / 2D; + SetCurrentValue(ValueProperty, currentValue / 2.0); } private void UpdateStarsFromValue() @@ -335,7 +340,7 @@ private void SetStarsPresence(int index) private void UpdateStar(int starIndex, StarValue starValue) { - var _selectedIcon = starIndex switch + SymbolIcon? selectedIcon = starIndex switch { 1 => _symbolIconStarTwo, 2 => _symbolIconStarThree, @@ -344,24 +349,26 @@ private void UpdateStar(int starIndex, StarValue starValue) _ => _symbolIconStarOne, }; - if (_selectedIcon is null) + if (selectedIcon is null) + { return; + } switch (starValue) { case StarValue.HalfFilled: - _selectedIcon.Filled = false; - _selectedIcon.Symbol = StarHalfSymbol; + selectedIcon.Filled = false; + selectedIcon.Symbol = StarHalfSymbol; break; case StarValue.Filled: - _selectedIcon.Filled = true; - _selectedIcon.Symbol = StarSymbol; + selectedIcon.Filled = true; + selectedIcon.Symbol = StarSymbol; break; default: - _selectedIcon.Filled = false; - _selectedIcon.Symbol = StarSymbol; + selectedIcon.Filled = false; + selectedIcon.Symbol = StarSymbol; break; } } @@ -373,19 +380,25 @@ private int ExtractValueFromOffset(double offset) if (!HalfStarEnabled) { if (starValue < 2) + { return 0; + } if (starValue % 2 != 0) + { starValue += 1; + } } return starValue; } - private static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is not RatingControl ratingControl) + { return; + } ratingControl.OnValueChanged((double)e.OldValue); } diff --git a/source/RevitLookup.UI/Controls/RichTextBox/RichTextBox.cs b/source/RevitLookup.UI/Controls/RichTextBox/RichTextBox.cs index 28918e35..9a68889e 100644 --- a/source/RevitLookup.UI/Controls/RichTextBox/RichTextBox.cs +++ b/source/RevitLookup.UI/Controls/RichTextBox/RichTextBox.cs @@ -7,13 +7,11 @@ namespace Wpf.Ui.Controls; /// -/// TODO +/// Extends the control with additional properties. /// public class RichTextBox : System.Windows.Controls.RichTextBox { - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty IsTextSelectionEnabledProperty = DependencyProperty.Register( nameof(IsTextSelectionEnabled), typeof(bool), @@ -22,7 +20,7 @@ public class RichTextBox : System.Windows.Controls.RichTextBox ); /// - /// TODO + /// Gets or sets a value indicating whether the user can select text in the control. /// public bool IsTextSelectionEnabled { diff --git a/source/RevitLookup.UI/Controls/ScrollDirection.cs b/source/RevitLookup.UI/Controls/ScrollDirection.cs index d8dcacb3..c9008cbe 100644 --- a/source/RevitLookup.UI/Controls/ScrollDirection.cs +++ b/source/RevitLookup.UI/Controls/ScrollDirection.cs @@ -3,10 +3,11 @@ // Copyright (C) Leszek Pomianowski and WPF UI Contributors. // All Rights Reserved. -// Based on VirtualizingWrapPanel created by S. Bäumlisberger licensed under MIT license. -// https://github.com/sbaeumlisberger/VirtualizingWrapPanel -// Copyright (C) S. Bäumlisberger -// All Rights Reserved. +/* Based on VirtualizingWrapPanel created by S. Bäumlisberger licensed under MIT license. + https://github.com/sbaeumlisberger/VirtualizingWrapPanel + + Copyright (C) S. Bäumlisberger + All Rights Reserved. */ namespace Wpf.Ui.Controls; diff --git a/source/RevitLookup.UI/Controls/ScrollViewer/ScrollViewer.xaml b/source/RevitLookup.UI/Controls/ScrollViewer/ScrollViewer.xaml index 998b6299..8fad518c 100644 --- a/source/RevitLookup.UI/Controls/ScrollViewer/ScrollViewer.xaml +++ b/source/RevitLookup.UI/Controls/ScrollViewer/ScrollViewer.xaml @@ -1,4 +1,4 @@ - - - /// Snackbar inform user of a process that an app has performed or will perform. It appears temporarily, towards the bottom of the window. /// public class Snackbar : ContentControl, IAppearanceControl, IIconControl { - #region Static properties - - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty IsCloseButtonEnabledProperty = DependencyProperty.Register( nameof(IsCloseButtonEnabled), typeof(bool), @@ -29,29 +24,23 @@ public class Snackbar : ContentControl, IAppearanceControl, IIconControl new PropertyMetadata(true) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty SlideTransformProperty = DependencyProperty.Register( nameof(SlideTransform), typeof(TranslateTransform), typeof(Snackbar), - new PropertyMetadata(new TranslateTransform()) + new PropertyMetadata(null) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty IsShownProperty = DependencyProperty.Register( nameof(IsShown), typeof(bool), typeof(Snackbar), - new PropertyMetadata(false) + new PropertyMetadata(false, (d, e) => (d as Snackbar)?.OnIsShownChanged(e)) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty TimeoutProperty = DependencyProperty.Register( nameof(Timeout), typeof(TimeSpan), @@ -59,9 +48,7 @@ public class Snackbar : ContentControl, IAppearanceControl, IIconControl new PropertyMetadata(TimeSpan.FromSeconds(2)) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty TitleProperty = DependencyProperty.Register( nameof(Title), typeof(object), @@ -69,9 +56,7 @@ public class Snackbar : ContentControl, IAppearanceControl, IIconControl new PropertyMetadata(null) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty TitleTemplateProperty = DependencyProperty.Register( nameof(TitleTemplate), typeof(DataTemplate), @@ -79,19 +64,15 @@ public class Snackbar : ContentControl, IAppearanceControl, IIconControl new PropertyMetadata(null) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty IconProperty = DependencyProperty.Register( nameof(Icon), typeof(IconElement), typeof(Snackbar), - new PropertyMetadata(null, null, IconSourceElementConverter.ConvertToIconElement) + new PropertyMetadata(null, null, IconElement.Coerce) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty AppearanceProperty = DependencyProperty.Register( nameof(Appearance), typeof(ControlAppearance), @@ -99,9 +80,7 @@ public class Snackbar : ContentControl, IAppearanceControl, IIconControl new PropertyMetadata(ControlAppearance.Secondary) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty TemplateButtonCommandProperty = DependencyProperty.Register( nameof(TemplateButtonCommand), typeof(IRelayCommand), @@ -109,9 +88,7 @@ public class Snackbar : ContentControl, IAppearanceControl, IIconControl new PropertyMetadata(null) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty ContentForegroundProperty = DependencyProperty.Register( nameof(ContentForeground), typeof(Brush), @@ -122,9 +99,7 @@ public class Snackbar : ContentControl, IAppearanceControl, IIconControl ) ); - /// - /// Property for . - /// + /// Identifies the routed event. public static readonly RoutedEvent OpenedEvent = EventManager.RegisterRoutedEvent( nameof(Opened), RoutingStrategy.Bubble, @@ -132,9 +107,7 @@ public class Snackbar : ContentControl, IAppearanceControl, IIconControl typeof(Snackbar) ); - /// - /// Property for . - /// + /// Identifies the routed event. public static readonly RoutedEvent ClosedEvent = EventManager.RegisterRoutedEvent( nameof(Closed), RoutingStrategy.Bubble, @@ -142,10 +115,6 @@ public class Snackbar : ContentControl, IAppearanceControl, IIconControl typeof(Snackbar) ); - #endregion - - #region Properties - /// /// Gets or sets a value indicating whether the close button should be visible. /// @@ -158,26 +127,32 @@ public bool IsCloseButtonEnabled /// /// Gets or sets the transform. /// - public TranslateTransform SlideTransform + public TranslateTransform? SlideTransform { - get => (TranslateTransform)GetValue(SlideTransformProperty); + get => (TranslateTransform?)GetValue(SlideTransformProperty); set => SetValue(SlideTransformProperty, value); } /// - /// Gets the information whether the is visible. + /// Gets or sets a value indicating whether the is visible. /// public bool IsShown { get => (bool)GetValue(IsShownProperty); - set - { - SetValue(IsShownProperty, value); + set => SetValue(IsShownProperty, value); + } - if (value) - OnOpened(); - else - OnClosed(); + protected void OnIsShownChanged(DependencyPropertyChangedEventArgs e) + { + bool newValue = (bool)e.NewValue; + + if (newValue) + { + OnOpened(); + } + else + { + OnClosed(); } } @@ -193,7 +168,7 @@ public TimeSpan Timeout /// /// Gets or sets the title of the . /// - public object Title + public object? Title { get => GetValue(TitleProperty); set => SetValue(TitleProperty, value); @@ -202,24 +177,26 @@ public object Title /// /// Gets or sets the title template of the . /// - public DataTemplate TitleTemplate + public DataTemplate? TitleTemplate { - get => (DataTemplate)GetValue(TitleTemplateProperty); + get => (DataTemplate?)GetValue(TitleTemplateProperty); set => SetValue(TitleTemplateProperty, value); } /// - /// TODO + /// Gets or sets the icon /// - [Bindable(true), Category("Appearance")] + [Bindable(true)] + [Category("Appearance")] public IconElement? Icon { - get => (IconElement)GetValue(IconProperty); + get => (IconElement?)GetValue(IconProperty); set => SetValue(IconProperty, value); } /// - [Bindable(true), Category("Appearance")] + [Bindable(true)] + [Category("Appearance")] public ControlAppearance Appearance { get => (ControlAppearance)GetValue(AppearanceProperty); @@ -227,9 +204,10 @@ public ControlAppearance Appearance } /// - /// Foreground of the . + /// Gets or sets the foreground of the . /// - [Bindable(true), Category("Appearance")] + [Bindable(true)] + [Category("Appearance")] public Brush ContentForeground { get => (Brush)GetValue(ContentForegroundProperty); @@ -237,7 +215,7 @@ public Brush ContentForeground } /// - /// Command triggered after clicking the button in the template. + /// Gets the command triggered after clicking the button in the template. /// public IRelayCommand TemplateButtonCommand => (IRelayCommand)GetValue(TemplateButtonCommandProperty); @@ -259,12 +237,10 @@ public event TypedEventHandler Closed remove => RemoveHandler(ClosedEvent, value); } - #endregion - /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class with a specified presenter. /// - /// + /// The to manage the snackbar's display and interactions. public Snackbar(SnackbarPresenter presenter) { Presenter = presenter; @@ -272,7 +248,7 @@ public Snackbar(SnackbarPresenter presenter) SetValue(TemplateButtonCommandProperty, new RelayCommand(_ => Hide())); } - protected readonly SnackbarPresenter Presenter; + protected SnackbarPresenter Presenter { get; } /// /// Shows the @@ -289,9 +265,7 @@ public virtual void Show(bool immediately) { if (immediately) { -#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed _ = Presenter.ImmediatelyDisplay(this); -#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed } else { diff --git a/source/RevitLookup.UI/Controls/Snackbar/Snackbar.xaml b/source/RevitLookup.UI/Controls/Snackbar/Snackbar.xaml index 66608288..9139cff6 100644 --- a/source/RevitLookup.UI/Controls/Snackbar/Snackbar.xaml +++ b/source/RevitLookup.UI/Controls/Snackbar/Snackbar.xaml @@ -77,28 +77,28 @@ Margin="0" Content="{TemplateBinding Title}" ContentTemplate="{TemplateBinding TitleTemplate}" - TextElement.FontWeight="SemiBold" TextElement.Foreground="{TemplateBinding Foreground}"> diff --git a/source/RevitLookup.UI/Controls/Snackbar/SnackbarPresenter.cs b/source/RevitLookup.UI/Controls/Snackbar/SnackbarPresenter.cs index 05a63222..aba5a764 100644 --- a/source/RevitLookup.UI/Controls/Snackbar/SnackbarPresenter.cs +++ b/source/RevitLookup.UI/Controls/Snackbar/SnackbarPresenter.cs @@ -8,9 +8,14 @@ namespace Wpf.Ui.Controls; public class SnackbarPresenter : System.Windows.Controls.ContentPresenter { + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "WpfAnalyzers.DependencyProperty", + "WPF0012:CLR property type should match registered type", + Justification = "seems harmless" + )] public new Snackbar? Content { - get => (Snackbar)GetValue(ContentProperty); + get => (Snackbar?)GetValue(ContentProperty); protected set => SetValue(ContentProperty, value); } @@ -23,14 +28,41 @@ public SnackbarPresenter() }; } - protected readonly Queue Queue = new(); + protected Queue Queue { get; } = new(); - protected CancellationTokenSource CancellationTokenSource = new(); + protected CancellationTokenSource CancellationTokenSource { get; set; } = new(); protected virtual void OnUnloaded() { + if (CancellationTokenSource.IsCancellationRequested) + { + return; + } + + ImmediatelyHideCurrent(); + ResetCancellationTokenSource(); + } + + private void ImmediatelyHideCurrent() + { + if (Content is null) + { + return; + } + CancellationTokenSource.Cancel(); - CancellationTokenSource.Dispose(); + ImmediatelyHidSnackbar(Content); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "WpfAnalyzers.DependencyProperty", + "WPF0041:Set mutable dependency properties using SetCurrentValue", + Justification = "SetCurrentValue(ContentProperty, ...) will not work" + )] + private void ImmediatelyHidSnackbar(Snackbar snackbar) + { + snackbar.SetCurrentValue(Snackbar.IsShownProperty, false); + Content = null; } protected void ResetCancellationTokenSource() @@ -79,11 +111,16 @@ private async Task ShowQueuedSnackbars() } } + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "WpfAnalyzers.DependencyProperty", + "WPF0041:Set mutable dependency properties using SetCurrentValue", + Justification = "SetCurrentValue(ContentProperty, ...) will not work" + )] private async Task ShowSnackbar(Snackbar snackbar) { Content = snackbar; - snackbar.IsShown = true; + snackbar.SetCurrentValue(Snackbar.IsShownProperty, true); try { @@ -97,12 +134,27 @@ private async Task ShowSnackbar(Snackbar snackbar) await HidSnackbar(snackbar); } + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "WpfAnalyzers.DependencyProperty", + "WPF0041:Set mutable dependency properties using SetCurrentValue", + Justification = "SetCurrentValue(ContentProperty, ...) will not work" + )] private async Task HidSnackbar(Snackbar snackbar) { - snackbar.IsShown = false; + snackbar.SetCurrentValue(Snackbar.IsShownProperty, false); await Task.Delay(300); Content = null; } + + ~SnackbarPresenter() + { + if (!CancellationTokenSource.IsCancellationRequested) + { + CancellationTokenSource.Cancel(); + } + + CancellationTokenSource.Dispose(); + } } diff --git a/source/RevitLookup.UI/Controls/SpacingMode.cs b/source/RevitLookup.UI/Controls/SpacingMode.cs index 73a7f8af..154af360 100644 --- a/source/RevitLookup.UI/Controls/SpacingMode.cs +++ b/source/RevitLookup.UI/Controls/SpacingMode.cs @@ -3,10 +3,10 @@ // Copyright (C) Leszek Pomianowski and WPF UI Contributors. // All Rights Reserved. -// Based on VirtualizingWrapPanel created by S. Bäumlisberger licensed under MIT license. -// https://github.com/sbaeumlisberger/VirtualizingWrapPanel -// Copyright (C) S. Bäumlisberger -// All Rights Reserved. +/* Based on VirtualizingWrapPanel created by S. Bäumlisberger licensed under MIT license. + https://github.com/sbaeumlisberger/VirtualizingWrapPanel + Copyright (C) S. Bäumlisberger + All Rights Reserved. */ namespace Wpf.Ui.Controls; diff --git a/source/RevitLookup.UI/Controls/SplitButton/SplitButton.cs b/source/RevitLookup.UI/Controls/SplitButton/SplitButton.cs index c168fd27..8d2e4aac 100644 --- a/source/RevitLookup.UI/Controls/SplitButton/SplitButton.cs +++ b/source/RevitLookup.UI/Controls/SplitButton/SplitButton.cs @@ -15,31 +15,27 @@ namespace Wpf.Ui.Controls; [TemplatePart(Name = TemplateElementToggleButton, Type = typeof(ToggleButton))] public class SplitButton : Wpf.Ui.Controls.Button { - private ContextMenu? _contextMenu; - /// /// Template element represented by the ToggleButton name. /// - private const string TemplateElementToggleButton = "ToggleButton"; + private const string TemplateElementToggleButton = "PART_ToggleButton"; - /// - /// Control responsible for toggling the drop-down button. - /// - protected ToggleButton SplitButtonToggleButton = null!; + private ContextMenu? _contextMenu; /// - /// Property for . + /// Gets or sets control responsible for toggling the drop-down button. /// + protected ToggleButton SplitButtonToggleButton { get; set; } = null!; + + /// Identifies the dependency property. public static readonly DependencyProperty FlyoutProperty = DependencyProperty.Register( nameof(Flyout), typeof(object), typeof(SplitButton), - new PropertyMetadata(null, OnFlyoutChangedCallback) + new PropertyMetadata(null, OnFlyoutChanged) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty IsDropDownOpenProperty = DependencyProperty.Register( nameof(IsDropDownOpen), typeof(bool), @@ -81,15 +77,17 @@ public SplitButton() }; } - private static void OnFlyoutChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) + private static void OnFlyoutChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is SplitButton dropDownButton) { - dropDownButton.OnFlyoutChangedCallback(e.NewValue); + dropDownButton.OnFlyoutChanged(e.NewValue); } } - protected virtual void OnFlyoutChangedCallback(object value) + /// This method is invoked when the changes. + /// The new value of . + protected virtual void OnFlyoutChanged(object value) { if (value is ContextMenu contextMenu) { @@ -103,7 +101,7 @@ private static void OnIsDropDownOpenChanged(DependencyObject d, DependencyProper { if (d is SplitButton dropDownButton) { - dropDownButton.OnIsDropDownOpenChanged(e.NewValue is bool ? (bool)e.NewValue : false); + dropDownButton.OnIsDropDownOpenChanged(e.NewValue is bool boolVal && boolVal); } } @@ -117,6 +115,8 @@ protected virtual void OnContextMenuOpened(object sender, RoutedEventArgs e) SetCurrentValue(IsDropDownOpenProperty, true); } + /// This method is invoked when the changes. + /// The new value of . protected virtual void OnIsDropDownOpenChanged(bool currentValue) { } /// @@ -149,12 +149,7 @@ protected virtual void ReleaseTemplateResources() private void OnSplitButtonToggleButtonOnClick(object sender, RoutedEventArgs e) { - if (sender is not ToggleButton toggleButton) - { - return; - } - - if (_contextMenu is null) + if (sender is not ToggleButton || _contextMenu is null) { return; } diff --git a/source/RevitLookup.UI/Controls/SplitButton/SplitButton.xaml b/source/RevitLookup.UI/Controls/SplitButton/SplitButton.xaml index c70b241d..dd3925b2 100644 --- a/source/RevitLookup.UI/Controls/SplitButton/SplitButton.xaml +++ b/source/RevitLookup.UI/Controls/SplitButton/SplitButton.xaml @@ -126,7 +126,7 @@ CornerRadius="{TemplateBinding CornerRadius, Converter={StaticResource RightSplitCornerRadiusConverter}}"> -/// Represents a list of filled Fluent System Icons v.1.1.209. +/// Represents a list of filled Fluent System Icons v.1.1.233. /// May be converted to using GetGlyph() or to using GetString() /// -#pragma warning disable CS1591 public enum SymbolFilled { /// @@ -18,7 +17,6 @@ public enum SymbolFilled Empty = 0x0, // Automatically generated, may contain bugs. - AccessTime20 = 0xE000, Accessibility32 = 0xE001, Accessibility48 = 0xE002, @@ -7110,6 +7108,720 @@ public enum SymbolFilled VoicemailShield24 = 0xF02E0, VoicemailShield32 = 0xF02E1, WindowDatabase32 = 0xF02E2, + CastMultiple20 = 0xF02E3, + CastMultiple24 = 0xF02E4, + CastMultiple28 = 0xF02E5, + CircleHintHalfVertical16 = 0xF02E6, + CircleHintHalfVertical20 = 0xF02E7, + CircleHintHalfVertical24 = 0xF02E8, + FlashSparkle20 = 0xF02E9, + FlashSparkle24 = 0xF02EA, + Hexagon12 = 0xF02EB, + Hexagon24 = 0xF02EC, + HexagonThree12 = 0xF02ED, + HexagonThree24 = 0xF02EE, + NextFrame20 = 0xF02EF, + NextFrame24 = 0xF02F0, + PreviousFrame20 = 0xF02F1, + PreviousFrame24 = 0xF02F2, + TextboxAlignBottomCenter16 = 0xF02F3, + TextboxAlignBottomCenter20 = 0xF02F4, + TextboxAlignBottomCenter24 = 0xF02F5, + TextboxAlignBottomLeft16 = 0xF02F6, + TextboxAlignBottomLeft20 = 0xF02F7, + TextboxAlignBottomLeft24 = 0xF02F8, + TextboxAlignBottomRight16 = 0xF02F9, + TextboxAlignBottomRight20 = 0xF02FA, + TextboxAlignBottomRight24 = 0xF02FB, + TextboxAlignCenter16 = 0xF02FC, + TextboxAlignMiddleLeft16 = 0xF02FD, + TextboxAlignMiddleLeft20 = 0xF02FE, + TextboxAlignMiddleLeft24 = 0xF02FF, + TextboxAlignMiddleRight16 = 0xF0300, + TextboxAlignMiddleRight20 = 0xF0301, + TextboxAlignMiddleRight24 = 0xF0302, + TextboxAlignTopCenter16 = 0xF0303, + TextboxAlignTopCenter20 = 0xF0304, + TextboxAlignTopCenter24 = 0xF0305, + TextboxAlignTopLeft16 = 0xF0306, + TextboxAlignTopLeft20 = 0xF0307, + TextboxAlignTopLeft24 = 0xF0308, + TextboxAlignTopRight16 = 0xF0309, + TextboxAlignTopRight20 = 0xF030A, + TextboxAlignTopRight24 = 0xF030B, + TriangleDown24 = 0xF030C, + CallEnd12 = 0xF030D, + CallEnd32 = 0xF030E, + CallEnd48 = 0xF030F, + ContentViewGallery16 = 0xF0310, + ContentViewGalleryLightning16 = 0xF0311, + ContentViewGalleryLightning20 = 0xF0312, + ContentViewGalleryLightning24 = 0xF0313, + ContentViewGalleryLightning28 = 0xF0314, + GlobeArrowForward16 = 0xF0315, + GlobeArrowForward20 = 0xF0316, + GlobeArrowForward24 = 0xF0317, + GlobeArrowForward32 = 0xF0318, + HardDrive24 = 0xF0319, + HardDrive32 = 0xF031A, + HardDriveCall24 = 0xF031B, + HardDriveCall32 = 0xF031C, + MailRewind16 = 0xF031D, + MailRewind20 = 0xF031E, + MailRewind24 = 0xF031F, + PanelRightGallery16 = 0xF0320, + PanelRightGallery20 = 0xF0321, + PanelRightGallery24 = 0xF0322, + PanelRightGallery28 = 0xF0323, + PanelTopGallery16 = 0xF0324, + PanelTopGallery20 = 0xF0325, + PanelTopGallery24 = 0xF0326, + PanelTopGallery28 = 0xF0327, + RectangleLandscapeSparkle16 = 0xF0328, + RectangleLandscapeSparkle20 = 0xF0329, + RectangleLandscapeSparkle24 = 0xF032A, + RectangleLandscapeSparkle28 = 0xF032B, + RectangleLandscapeSparkle32 = 0xF032C, + ScanPerson16 = 0xF032D, + ScanPerson20 = 0xF032E, + ScanPerson24 = 0xF032F, + ScanPerson28 = 0xF0330, + ScanPerson48 = 0xF0331, + VoicemailShield16 = 0xF0332, + ChevronDown32 = 0xF0333, + ChevronLeft32 = 0xF0334, + ChevronRight32 = 0xF0335, + ChevronUp32 = 0xF0336, + DocumentLightning16 = 0xF0337, + DocumentLightning20 = 0xF0338, + DocumentLightning24 = 0xF0339, + DocumentLightning28 = 0xF033A, + DocumentLightning32 = 0xF033B, + DocumentLightning48 = 0xF033C, + Edit12 = 0xF033D, + ServerLink16 = 0xF033E, + ServerLink20 = 0xF033F, + Step20 = 0xF0340, + Step24 = 0xF0341, + TabDesktopMultipleAdd20 = 0xF0342, + TextDescription16 = 0xF0343, + TextDescription28 = 0xF0344, + TextDescription32 = 0xF0345, + TextGrammarLightning16 = 0xF0346, + TextGrammarLightning20 = 0xF0347, + TextGrammarLightning24 = 0xF0348, + TextGrammarLightning28 = 0xF0349, + TextGrammarLightning32 = 0xF034A, + BeakerAdd20 = 0xF034B, + BeakerAdd24 = 0xF034C, + BeakerDismiss20 = 0xF034D, + BeakerDismiss24 = 0xF034E, + DocumentCube20 = 0xF034F, + DocumentCube24 = 0xF0350, + Drawer20 = 0xF0351, + Drawer24 = 0xF0352, + FilmstripImage20 = 0xF0353, + FilmstripImage24 = 0xF0354, + NumberCircle016 = 0xF0355, + NumberCircle020 = 0xF0356, + NumberCircle024 = 0xF0357, + NumberCircle028 = 0xF0358, + NumberCircle032 = 0xF0359, + NumberCircle048 = 0xF035A, + NumberCircle616 = 0xF035B, + NumberCircle620 = 0xF035C, + NumberCircle624 = 0xF035D, + NumberCircle628 = 0xF035E, + NumberCircle632 = 0xF035F, + NumberCircle648 = 0xF0360, + NumberCircle716 = 0xF0361, + NumberCircle720 = 0xF0362, + NumberCircle724 = 0xF0363, + NumberCircle728 = 0xF0364, + NumberCircle732 = 0xF0365, + NumberCircle748 = 0xF0366, + NumberCircle816 = 0xF0367, + NumberCircle820 = 0xF0368, + NumberCircle824 = 0xF0369, + NumberCircle828 = 0xF036A, + NumberCircle832 = 0xF036B, + NumberCircle848 = 0xF036C, + NumberCircle916 = 0xF036D, + NumberCircle920 = 0xF036E, + NumberCircle924 = 0xF036F, + NumberCircle928 = 0xF0370, + NumberCircle932 = 0xF0371, + NumberCircle948 = 0xF0372, + Server12 = 0xF0373, + SquareHintHexagon12 = 0xF0374, + SquareHintHexagon16 = 0xF0375, + SquareHintHexagon20 = 0xF0376, + SquareHintHexagon24 = 0xF0377, + SquareHintHexagon28 = 0xF0378, + SquareHintHexagon32 = 0xF0379, + SquareHintHexagon48 = 0xF037A, + TabDesktopMultiple16 = 0xF037B, + TabDesktopMultipleAdd16 = 0xF037C, + TargetAdd20 = 0xF037D, + TargetAdd24 = 0xF037E, + TargetDismiss20 = 0xF037F, + TargetDismiss24 = 0xF0380, + TextHeader1Lines16 = 0xF0381, + TextHeader1Lines20 = 0xF0382, + TextHeader1Lines24 = 0xF0383, + TextHeader1LinesCaret16 = 0xF0384, + TextHeader1LinesCaret20 = 0xF0385, + TextHeader1LinesCaret24 = 0xF0386, + TextHeader2Lines16 = 0xF0387, + TextHeader2Lines20 = 0xF0388, + TextHeader2Lines24 = 0xF0389, + TextHeader2LinesCaret16 = 0xF038A, + TextHeader2LinesCaret20 = 0xF038B, + TextHeader2LinesCaret24 = 0xF038C, + TextHeader3Lines16 = 0xF038D, + TextHeader3Lines20 = 0xF038E, + TextHeader3Lines24 = 0xF038F, + TextHeader3LinesCaret16 = 0xF0390, + TextHeader3LinesCaret20 = 0xF0391, + TextHeader3LinesCaret24 = 0xF0392, + ArrowDownload28 = 0xF0393, + ArrowDownload32 = 0xF0394, + ArrowExpand16 = 0xF0395, + ArrowExportUp16 = 0xF0396, + ArrowImport16 = 0xF0397, + ArrowUpRightDashes16 = 0xF0398, + Battery1016 = 0xF0399, + BeakerEmpty16 = 0xF039A, + Book16 = 0xF039B, + BorderNone16 = 0xF039C, + BranchRequest16 = 0xF039D, + ClipboardTaskList16 = 0xF039E, + Cut16 = 0xF039F, + FolderSearch16 = 0xF03A0, + FolderSearch20 = 0xF03A1, + FolderSearch24 = 0xF03A2, + Hexagon28 = 0xF03A3, + Hexagon32 = 0xF03A4, + Hexagon48 = 0xF03A5, + PlugConnected16 = 0xF03A6, + PlugDisconnected16 = 0xF03A7, + ProjectionScreenText20 = 0xF03A8, + Rss16 = 0xF03A9, + ShapeOrganic16 = 0xF03AA, + ShapeOrganic20 = 0xF03AB, + ShapeOrganic24 = 0xF03AC, + ShapeOrganic28 = 0xF03AD, + ShapeOrganic32 = 0xF03AE, + ShapeOrganic48 = 0xF03AF, + TeardropBottomRight16 = 0xF03B0, + TeardropBottomRight20 = 0xF03B1, + TeardropBottomRight24 = 0xF03B2, + TeardropBottomRight28 = 0xF03B3, + TeardropBottomRight32 = 0xF03B4, + TeardropBottomRight48 = 0xF03B5, + TextEditStyle16 = 0xF03B6, + TextWholeWord16 = 0xF03B7, + Triangle24 = 0xF03B8, + Triangle28 = 0xF03B9, + TextAsterisk16 = 0xF03BA, + ArrowDownloadOff16 = 0xF03BB, + ArrowDownloadOff20 = 0xF03BC, + ArrowDownloadOff24 = 0xF03BD, + ArrowDownloadOff28 = 0xF03BE, + ArrowDownloadOff32 = 0xF03BF, + ArrowDownloadOff48 = 0xF03C0, + BorderInside16 = 0xF03C1, + BorderInside20 = 0xF03C2, + BorderInside24 = 0xF03C3, + ChatLock16 = 0xF03C4, + ChatLock20 = 0xF03C5, + ChatLock24 = 0xF03C6, + ChatLock28 = 0xF03C7, + ErrorCircle48 = 0xF03C8, + FullScreenMaximize28 = 0xF03C9, + FullScreenMaximize32 = 0xF03CA, + FullScreenMinimize28 = 0xF03CB, + FullScreenMinimize32 = 0xF03CC, + LinkPerson16 = 0xF03CD, + LinkPerson20 = 0xF03CE, + LinkPerson24 = 0xF03CF, + LinkPerson32 = 0xF03D0, + LinkPerson48 = 0xF03D1, + PeopleChat16 = 0xF03D2, + PeopleChat20 = 0xF03D3, + PeopleChat24 = 0xF03D4, + PersonSupport28 = 0xF03D5, + Shapes32 = 0xF03D6, + SlideTextEdit16 = 0xF03D7, + SlideTextEdit20 = 0xF03D8, + SlideTextEdit24 = 0xF03D9, + SlideTextEdit28 = 0xF03DA, + SubtractCircle48 = 0xF03DB, + SubtractParentheses16 = 0xF03DC, + SubtractParentheses20 = 0xF03DD, + SubtractParentheses24 = 0xF03DE, + SubtractParentheses28 = 0xF03DF, + SubtractParentheses32 = 0xF03E0, + SubtractParentheses48 = 0xF03E1, + Warning48 = 0xF03E2, + AlertOn16 = 0xF03E3, + ArrowDownExclamation16 = 0xF03E4, + ArrowDownExclamation20 = 0xF03E5, + ArrowFit24 = 0xF03E6, + ArrowFitIn24 = 0xF03E7, + Book32 = 0xF03E8, + BookDatabase16 = 0xF03E9, + BookDatabase32 = 0xF03EA, + BookToolbox16 = 0xF03EB, + BuildingDesktop32 = 0xF03EC, + BuildingGovernment16 = 0xF03ED, + BuildingGovernmentSearch16 = 0xF03EE, + BuildingGovernmentSearch20 = 0xF03EF, + BuildingGovernmentSearch24 = 0xF03F0, + BuildingGovernmentSearch32 = 0xF03F1, + CalendarRecord16 = 0xF03F2, + CalendarRecord20 = 0xF03F3, + CalendarRecord24 = 0xF03F4, + CalendarRecord28 = 0xF03F5, + CalendarRecord32 = 0xF03F6, + CalendarRecord48 = 0xF03F7, + Clipboard28 = 0xF03F8, + ClipboardMathFormula16 = 0xF03F9, + ClipboardMathFormula20 = 0xF03FA, + ClipboardMathFormula24 = 0xF03FB, + ClipboardMathFormula28 = 0xF03FC, + ClipboardMathFormula32 = 0xF03FD, + ClipboardNumber12316 = 0xF03FE, + ClipboardNumber12320 = 0xF03FF, + ClipboardNumber12324 = 0xF0400, + ClipboardNumber12328 = 0xF0401, + ClipboardNumber12332 = 0xF0402, + Collections16 = 0xF0403, + CommunicationShield16 = 0xF0404, + CommunicationShield20 = 0xF0405, + CommunicationShield24 = 0xF0406, + DialpadQuestionMark20 = 0xF0407, + DialpadQuestionMark24 = 0xF0408, + DocumentBriefcase16 = 0xF0409, + DocumentBriefcase32 = 0xF040A, + DocumentSearch32 = 0xF040B, + Fingerprint16 = 0xF040C, + Fingerprint32 = 0xF040D, + FolderPerson24 = 0xF040E, + FolderPerson28 = 0xF040F, + FolderPerson32 = 0xF0410, + FolderPerson48 = 0xF0411, + HatGraduationAdd16 = 0xF0412, + HatGraduationAdd20 = 0xF0413, + HatGraduationAdd24 = 0xF0414, + LayerDiagonalAdd20 = 0xF0415, + Library32 = 0xF0416, + LightbulbFilament32 = 0xF0417, + LinkAdd16 = 0xF0418, + LinkAdd20 = 0xF0419, + LockShield16 = 0xF041A, + LockShield28 = 0xF041B, + LockShield32 = 0xF041C, + PersonVoice16 = 0xF041D, + PersonWarning16 = 0xF041E, + PersonWarning20 = 0xF041F, + PersonWarning24 = 0xF0420, + PersonWarning28 = 0xF0421, + PersonWarning32 = 0xF0422, + PersonWarning48 = 0xF0423, + ScanTypeOff24 = 0xF0424, + Screenshot16 = 0xF0425, + ScreenshotRecord16 = 0xF0426, + ScreenshotRecord20 = 0xF0427, + ScreenshotRecord24 = 0xF0428, + SlideSearch16 = 0xF0429, + SlideSearch32 = 0xF042A, + VehicleSubwayClock16 = 0xF042B, + VehicleSubwayClock20 = 0xF042C, + VehicleSubwayClock24 = 0xF042D, + VideoClipOptimize16 = 0xF042E, + VideoClipOptimize20 = 0xF042F, + VideoClipOptimize24 = 0xF0430, + VideoClipOptimize28 = 0xF0431, + VideoPersonPulse16 = 0xF0432, + VideoPersonPulse20 = 0xF0433, + VideoPersonPulse24 = 0xF0434, + VideoPersonPulse28 = 0xF0435, + ArchiveSettings32 = 0xF0436, + ArrowForward32 = 0xF0437, + ArrowReply32 = 0xF0438, + ArrowReplyAll32 = 0xF0439, + Attach32 = 0xF043A, + Autocorrect32 = 0xF043B, + Broom32 = 0xF043C, + CalendarNote16 = 0xF043D, + CalendarNote20 = 0xF043E, + CalendarNote24 = 0xF043F, + CalendarNote32 = 0xF0440, + CheckmarkUnderlineCircle24 = 0xF0441, + DataBarVerticalAscending20 = 0xF0442, + DataBarVerticalAscending24 = 0xF0443, + Diversity16 = 0xF0444, + Filter32 = 0xF0445, + FolderMail32 = 0xF0446, + GlanceHorizontal32 = 0xF0447, + GlanceHorizontalSparkle32 = 0xF0448, + GlobeArrowUp16 = 0xF0449, + GlobeArrowUp20 = 0xF044A, + GlobeArrowUp24 = 0xF044B, + GlobeError16 = 0xF044C, + GlobeError20 = 0xF044D, + GlobeError24 = 0xF044E, + GlobeProhibited16 = 0xF044F, + GlobeProhibited24 = 0xF0450, + GlobeSync16 = 0xF0451, + GlobeSync20 = 0xF0452, + GlobeSync24 = 0xF0453, + GlobeWarning16 = 0xF0454, + GlobeWarning20 = 0xF0455, + GlobeWarning24 = 0xF0456, + Important32 = 0xF0457, + LayerDiagonal16 = 0xF0458, + LayerDiagonalPerson16 = 0xF0459, + MailMultiple32 = 0xF045A, + MailRead32 = 0xF045B, + MailUnread32 = 0xF045C, + Mailbox16 = 0xF045D, + Mailbox20 = 0xF045E, + OrganizationHorizontal16 = 0xF045F, + OrganizationHorizontal24 = 0xF0460, + PeopleList32 = 0xF0461, + PersonAdd32 = 0xF0462, + PersonSquare16 = 0xF0463, + PersonSquare32 = 0xF0464, + PersonSquareCheckmark16 = 0xF0465, + PersonSquareCheckmark20 = 0xF0466, + PersonSquareCheckmark24 = 0xF0467, + PersonSquareCheckmark32 = 0xF0468, + PhoneFooterArrowDown20 = 0xF0469, + PhoneFooterArrowDown24 = 0xF046A, + PhoneHeaderArrowUp20 = 0xF046B, + PhoneHeaderArrowUp24 = 0xF046C, + Poll32 = 0xF046D, + Question32 = 0xF046E, + Screenshot28 = 0xF046F, + ScreenshotRecord28 = 0xF0470, + Star32 = 0xF0471, + TextDensity32 = 0xF0472, + TextEditStyleCharacterA32 = 0xF0473, + WrenchScrewdriver32 = 0xF0474, + ArrowClockwiseDashes16 = 0xF0475, + ArrowClockwiseDashes32 = 0xF0476, + BuildingSwap16 = 0xF0477, + BuildingSwap20 = 0xF0478, + BuildingSwap24 = 0xF0479, + BuildingSwap32 = 0xF047A, + BuildingSwap48 = 0xF047B, + Certificate32 = 0xF047C, + ClipboardBrush16 = 0xF047D, + ClipboardBrush20 = 0xF047E, + ClipboardBrush24 = 0xF047F, + ClipboardBrush28 = 0xF0480, + ClipboardBrush32 = 0xF0481, + CloudBeaker16 = 0xF0482, + CloudBeaker20 = 0xF0483, + CloudBeaker24 = 0xF0484, + CloudBeaker28 = 0xF0485, + CloudBeaker32 = 0xF0486, + CloudBeaker48 = 0xF0487, + CloudCube16 = 0xF0488, + CloudCube20 = 0xF0489, + CloudCube24 = 0xF048A, + CloudCube28 = 0xF048B, + CloudCube32 = 0xF048C, + CloudCube48 = 0xF048D, + ContractUpRight16 = 0xF048E, + ContractUpRight20 = 0xF048F, + ContractUpRight24 = 0xF0490, + ContractUpRight28 = 0xF0491, + ContractUpRight32 = 0xF0492, + ContractUpRight48 = 0xF0493, + DocumentDataLock16 = 0xF0494, + DocumentDataLock20 = 0xF0495, + DocumentDataLock24 = 0xF0496, + DocumentDataLock32 = 0xF0497, + GlanceHorizontalSparkles20 = 0xF0498, + LayoutCellFour16 = 0xF0499, + LayoutCellFour20 = 0xF049A, + LayoutCellFour24 = 0xF049B, + LayoutColumnFour16 = 0xF04A8, + LayoutColumnFour20 = 0xF04A9, + LayoutColumnFour24 = 0xF04AA, + LayoutColumnOneThirdLeft16 = 0xF04B7, + LayoutColumnOneThirdLeft20 = 0xF04B8, + LayoutColumnOneThirdLeft24 = 0xF04B9, + LayoutColumnOneThirdRight16 = 0xF04BA, + LayoutColumnOneThirdRight20 = 0xF04BB, + LayoutColumnOneThirdRight24 = 0xF04BC, + LayoutColumnOneThirdRightHint16 = 0xF04BD, + LayoutColumnOneThirdRightHint20 = 0xF04BE, + LayoutColumnOneThirdRightHint24 = 0xF04BF, + LayoutColumnThree16 = 0xF04C0, + LayoutColumnThree20 = 0xF04C1, + LayoutColumnThree24 = 0xF04C2, + LayoutColumnTwo16 = 0xF04CC, + LayoutColumnTwo20 = 0xF04CD, + LayoutColumnTwo24 = 0xF04CE, + LayoutColumnTwoSplitLeft16 = 0xF04D5, + LayoutColumnTwoSplitLeft20 = 0xF04D6, + LayoutColumnTwoSplitLeft24 = 0xF04D7, + LayoutColumnTwoSplitRight16 = 0xF04E1, + LayoutColumnTwoSplitRight20 = 0xF04E2, + LayoutColumnTwoSplitRight24 = 0xF04E3, + LayoutRowFour16 = 0xF04ED, + LayoutRowFour20 = 0xF04EE, + LayoutRowFour24 = 0xF04EF, + LayoutRowThree16 = 0xF04FC, + LayoutRowThree20 = 0xF04FD, + LayoutRowThree24 = 0xF04FE, + LayoutRowTwo16 = 0xF0508, + LayoutRowTwo20 = 0xF0509, + LayoutRowTwo24 = 0xF050A, + LayoutRowTwoSplitBottom16 = 0xF0511, + LayoutRowTwoSplitBottom20 = 0xF0512, + LayoutRowTwoSplitBottom24 = 0xF0513, + LayoutRowTwoSplitTop16 = 0xF051D, + LayoutRowTwoSplitTop20 = 0xF051E, + LayoutRowTwoSplitTop24 = 0xF051F, + LocationTargetSquare16 = 0xF0529, + LocationTargetSquare20 = 0xF052A, + LocationTargetSquare24 = 0xF052B, + LocationTargetSquare32 = 0xF052C, + Resize16 = 0xF052D, + Resize28 = 0xF052E, + Resize32 = 0xF052F, + Resize48 = 0xF0530, + SelectAllOff16 = 0xF0531, + SelectAllOn16 = 0xF0532, + ShareAndroid16 = 0xF0533, + ShareAndroid32 = 0xF0534, + TextArrowDownRightColumn16 = 0xF0535, + TextArrowDownRightColumn20 = 0xF0536, + TextArrowDownRightColumn24 = 0xF0537, + TextArrowDownRightColumn28 = 0xF0538, + TextArrowDownRightColumn32 = 0xF0539, + TextArrowDownRightColumn48 = 0xF053A, + TextEffectsSparkle20 = 0xF053B, + TextEffectsSparkle24 = 0xF053C, + Whiteboard16 = 0xF053D, + WhiteboardOff16 = 0xF053E, + WhiteboardOff20 = 0xF053F, + WhiteboardOff24 = 0xF0540, + Flowchart16 = 0xF0541, + Flowchart32 = 0xF0542, + LayerDiagonal24 = 0xF0543, + LayerDiagonalPerson24 = 0xF0544, + PollOff16 = 0xF0545, + PollOff20 = 0xF0546, + PollOff24 = 0xF0547, + PollOff32 = 0xF0548, + RectangleLandscapeSparkle48 = 0xF0549, + RectangleLandscapeSync16 = 0xF054A, + RectangleLandscapeSync20 = 0xF054B, + RectangleLandscapeSync24 = 0xF054C, + RectangleLandscapeSync28 = 0xF054D, + RectangleLandscapeSyncOff16 = 0xF054E, + RectangleLandscapeSyncOff20 = 0xF054F, + RectangleLandscapeSyncOff24 = 0xF0550, + RectangleLandscapeSyncOff28 = 0xF0551, + Seat16 = 0xF0552, + Seat20 = 0xF0553, + Seat24 = 0xF0554, + SeatAdd16 = 0xF0555, + SeatAdd20 = 0xF0556, + SeatAdd24 = 0xF0557, + SpeakerBox16 = 0xF0558, + SpeakerBox20 = 0xF0559, + SpeakerBox24 = 0xF055A, + TextEditStyleCharacterGa32 = 0xF055B, + WindowAd24 = 0xF055C, + WrenchSettings20 = 0xF055D, + WrenchSettings24 = 0xF055E, + BuildingLighthouse24 = 0xF055F, + BuildingLighthouse32 = 0xF0560, + BuildingLighthouse48 = 0xF0561, + CalendarLink24 = 0xF0562, + CalendarLink28 = 0xF0563, + CalendarVideo24 = 0xF0564, + CalendarVideo28 = 0xF0565, + Cookies16 = 0xF0566, + Cookies28 = 0xF0567, + Cookies32 = 0xF0568, + Cookies48 = 0xF0569, + HardDrive28 = 0xF056A, + HardDrive48 = 0xF056B, + Laptop32 = 0xF056C, + LaptopSettings20 = 0xF056D, + LaptopSettings24 = 0xF056E, + LaptopSettings32 = 0xF056F, + PeopleAudience32 = 0xF0570, + ShoppingBagAdd20 = 0xF0571, + ShoppingBagAdd24 = 0xF0572, + StreetSign20 = 0xF0573, + StreetSign24 = 0xF0574, + VideoLink24 = 0xF0575, + VideoLink28 = 0xF0576, + BuildingLighthouse16 = 0xF0577, + CalendarSparkle16 = 0xF0578, + CalendarSparkle20 = 0xF0579, + CalendarSparkle24 = 0xF057A, + CalendarSparkle28 = 0xF057B, + CalendarSparkle32 = 0xF057C, + CalendarSparkle48 = 0xF057D, + CalendarTemplate20 = 0xF057E, + CalendarTemplate24 = 0xF057F, + CalendarTemplate32 = 0xF0580, + Clipboard12 = 0xF0581, + Clipboard48 = 0xF0582, + Compose12 = 0xF0583, + Compose32 = 0xF0584, + Compose48 = 0xF0585, + Globe28 = 0xF0586, + Guest12 = 0xF0587, + Guest32 = 0xF0588, + Guest48 = 0xF0589, + LaptopBriefcase20 = 0xF058A, + LaptopBriefcase24 = 0xF058B, + LaptopBriefcase32 = 0xF058C, + LayerDiagonalSparkle16 = 0xF058D, + LayerDiagonalSparkle20 = 0xF058E, + LayerDiagonalSparkle24 = 0xF058F, + PaymentWireless16 = 0xF0590, + PaymentWireless20 = 0xF0591, + PaymentWireless24 = 0xF0592, + PaymentWireless28 = 0xF0593, + PaymentWireless32 = 0xF0594, + PaymentWireless48 = 0xF0595, + Status28 = 0xF0596, + Status32 = 0xF0597, + Status48 = 0xF0598, + VideoOff16 = 0xF0599, + CheckmarkCircleWarning16 = 0xF059A, + CheckmarkCircleWarning20 = 0xF059B, + CheckmarkCircleWarning24 = 0xF059C, + CloudArrowRight16 = 0xF059D, + CloudArrowRight20 = 0xF059E, + CloudArrowRight24 = 0xF059F, + DocumentArrowDown24 = 0xF05A0, + DocumentSignature16 = 0xF05A1, + DocumentSignature20 = 0xF05A2, + DocumentSignature24 = 0xF05A3, + DocumentSignature28 = 0xF05A4, + DocumentSignature32 = 0xF05A5, + DocumentSignature48 = 0xF05A6, + HomeGarage20 = 0xF05A7, + HomeGarage24 = 0xF05A8, + ImageSplit20 = 0xF05A9, + ImageSplit24 = 0xF05AA, + Laptop48 = 0xF05AB, + LineFlowDiagonalUpRight16 = 0xF05AC, + LineFlowDiagonalUpRight20 = 0xF05AD, + LineFlowDiagonalUpRight24 = 0xF05AE, + LineFlowDiagonalUpRight32 = 0xF05AF, + MailArrowClockwise16 = 0xF05B0, + MailArrowClockwise20 = 0xF05B1, + MailArrowClockwise24 = 0xF05B2, + PersonPasskey16 = 0xF05B3, + PersonPasskey20 = 0xF05B4, + PersonPasskey24 = 0xF05B5, + PersonPasskey28 = 0xF05B6, + PersonPasskey32 = 0xF05B7, + PersonPasskey48 = 0xF05B8, + PersonProhibited32 = 0xF05B9, + PersonRibbon24 = 0xF05BA, + PlantCattail20 = 0xF05BB, + PlantCattail24 = 0xF05BC, + Storage16 = 0xF05BD, + Storage28 = 0xF05BE, + Storage32 = 0xF05BF, + Storage48 = 0xF05C0, + VideoClipWand16 = 0xF05C1, + VideoClipWand20 = 0xF05C2, + VideoClipWand24 = 0xF05C3, + WindowFingerprint16 = 0xF05C4, + WindowFingerprint20 = 0xF05C5, + WindowFingerprint24 = 0xF05C6, + WindowFingerprint28 = 0xF05C7, + WindowFingerprint32 = 0xF05C8, + WindowFingerprint48 = 0xF05C9, + AccessibilityError20 = 0xF05CA, + AccessibilityError24 = 0xF05CB, + AccessibilityQuestionMark20 = 0xF05CC, + AccessibilityQuestionMark24 = 0xF05CD, + ArrowDownExclamation24 = 0xF05CE, + ArrowSortUpLines16 = 0xF05CF, + ArrowSortUpLines20 = 0xF05D0, + ArrowSortUpLines24 = 0xF05D1, + ArrowUpExclamation16 = 0xF05D2, + ArrowUpExclamation20 = 0xF05D3, + ArrowUpExclamation24 = 0xF05D4, + Bench20 = 0xF05D5, + Bench24 = 0xF05D6, + BuildingLighthouse28 = 0xF05D7, + CalendarVideo20 = 0xF05D8, + ClockBill16 = 0xF05D9, + ClockBill20 = 0xF05DA, + ClockBill24 = 0xF05DB, + ClockBill32 = 0xF05DC, + DataUsage16 = 0xF05DD, + DataUsageSettings16 = 0xF05DE, + DataUsageSettings24 = 0xF05DF, + EditPerson20 = 0xF05E0, + EditPerson24 = 0xF05E1, + Highway20 = 0xF05E2, + Highway24 = 0xF05E3, + LaptopPerson20 = 0xF05E4, + LaptopPerson24 = 0xF05E5, + LaptopPerson48 = 0xF05E6, + LocationRipple16 = 0xF05E7, + LocationRipple20 = 0xF05E8, + LocationRipple24 = 0xF05E9, + MailArrowDoubleBack32 = 0xF05EA, + MailBriefcase48 = 0xF05EB, + Options28 = 0xF05EC, + Options32 = 0xF05ED, + PeopleAdd32 = 0xF05EE, + PersonAlert32 = 0xF05EF, + Road20 = 0xF05F0, + Road24 = 0xF05F1, + Save32 = 0xF05F2, + TabDesktopMultiple24 = 0xF05F3, + TabDesktopMultipleSparkle16 = 0xF05F4, + TabDesktopMultipleSparkle20 = 0xF05F5, + TabDesktopMultipleSparkle24 = 0xF05F6, + VehicleTractor20 = 0xF05F7, + VehicleTractor24 = 0xF05F8, + Classification32 = 0xF05F9, + DocumentTarget20 = 0xF05FA, + DocumentTarget24 = 0xF05FB, + DocumentTarget32 = 0xF05FC, + EmojiMeme16 = 0xF05FD, + EmojiMeme20 = 0xF05FE, + EmojiMeme24 = 0xF05FF, + HandPoint16 = 0xF0600, + HandPoint20 = 0xF0601, + HandPoint24 = 0xF0602, + HandPoint28 = 0xF0603, + HandPoint32 = 0xF0604, + HandPoint48 = 0xF0605, + MailReadBriefcase48 = 0xF0606, + PeopleSubtract20 = 0xF0607, + PeopleSubtract24 = 0xF0608, + PeopleSubtract32 = 0xF0609, + PersonAlertOff16 = 0xF060A, + PersonAlertOff20 = 0xF060B, + PersonAlertOff24 = 0xF060C, + PersonAlertOff32 = 0xF060D, + ShoppingBagAdd16 = 0xF060E, + SpatulaSpoon16 = 0xF060F, + SpatulaSpoon20 = 0xF0610, + SpatulaSpoon24 = 0xF0611, + SpatulaSpoon28 = 0xF0612, + SpatulaSpoon32 = 0xF0613, + SpatulaSpoon48 = 0xF0614, } - -#pragma warning restore CS1591 diff --git a/source/RevitLookup.UI/Controls/SymbolGlyph.cs b/source/RevitLookup.UI/Controls/SymbolGlyph.cs index 172fd729..227cd2ba 100644 --- a/source/RevitLookup.UI/Controls/SymbolGlyph.cs +++ b/source/RevitLookup.UI/Controls/SymbolGlyph.cs @@ -26,14 +26,16 @@ public static class SymbolGlyph /// Name of the icon. public static SymbolRegular Parse(string name) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) + { return DefaultIcon; + } try { return (SymbolRegular)Enum.Parse(typeof(SymbolRegular), name); } - catch (Exception _) + catch (Exception) { #if DEBUG throw; @@ -49,14 +51,16 @@ public static SymbolRegular Parse(string name) /// Name of the icon. public static SymbolFilled ParseFilled(string name) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) + { return DefaultFilledIcon; + } try { return (SymbolFilled)Enum.Parse(typeof(SymbolFilled), name); } - catch (Exception e) + catch (Exception) { #if DEBUG throw; diff --git a/source/RevitLookup.UI/Controls/SymbolRegular.cs b/source/RevitLookup.UI/Controls/SymbolRegular.cs index 1661da93..a60740fa 100644 --- a/source/RevitLookup.UI/Controls/SymbolRegular.cs +++ b/source/RevitLookup.UI/Controls/SymbolRegular.cs @@ -6,10 +6,9 @@ namespace Wpf.Ui.Controls; /// -/// Represents a list of regular Fluent System Icons v.1.1.209. +/// Represents a list of regular Fluent System Icons v.1.1.233. /// May be converted to using GetGlyph() or to using GetString() /// -#pragma warning disable CS1591 public enum SymbolRegular { /// @@ -18,7 +17,6 @@ public enum SymbolRegular Empty = 0x0, // Automatically generated, may contain bugs. - AccessTime20 = 0xE000, Accessibility32 = 0xE001, Accessibility48 = 0xE002, @@ -7110,6 +7108,720 @@ public enum SymbolRegular VoicemailShield24 = 0xF02CD, VoicemailShield32 = 0xF02CE, WindowDatabase32 = 0xF02CF, + CastMultiple20 = 0xF02D0, + CastMultiple24 = 0xF02D1, + CastMultiple28 = 0xF02D2, + CircleHintHalfVertical16 = 0xF02D3, + CircleHintHalfVertical20 = 0xF02D4, + CircleHintHalfVertical24 = 0xF02D5, + FlashSparkle20 = 0xF02D6, + FlashSparkle24 = 0xF02D7, + Hexagon12 = 0xF02D8, + Hexagon24 = 0xF02D9, + HexagonThree12 = 0xF02DA, + HexagonThree24 = 0xF02DB, + NextFrame20 = 0xF02DC, + NextFrame24 = 0xF02DD, + PreviousFrame20 = 0xF02DE, + PreviousFrame24 = 0xF02DF, + TextboxAlignBottomCenter16 = 0xF02E0, + TextboxAlignBottomCenter20 = 0xF02E1, + TextboxAlignBottomCenter24 = 0xF02E2, + TextboxAlignBottomLeft16 = 0xF02E3, + TextboxAlignBottomLeft20 = 0xF02E4, + TextboxAlignBottomLeft24 = 0xF02E5, + TextboxAlignBottomRight16 = 0xF02E6, + TextboxAlignBottomRight20 = 0xF02E7, + TextboxAlignBottomRight24 = 0xF02E8, + TextboxAlignCenter16 = 0xF02E9, + TextboxAlignMiddleLeft16 = 0xF02EA, + TextboxAlignMiddleLeft20 = 0xF02EB, + TextboxAlignMiddleLeft24 = 0xF02EC, + TextboxAlignMiddleRight16 = 0xF02ED, + TextboxAlignMiddleRight20 = 0xF02EE, + TextboxAlignMiddleRight24 = 0xF02EF, + TextboxAlignTopCenter16 = 0xF02F0, + TextboxAlignTopCenter20 = 0xF02F1, + TextboxAlignTopCenter24 = 0xF02F2, + TextboxAlignTopLeft16 = 0xF02F3, + TextboxAlignTopLeft20 = 0xF02F4, + TextboxAlignTopLeft24 = 0xF02F5, + TextboxAlignTopRight16 = 0xF02F6, + TextboxAlignTopRight20 = 0xF02F7, + TextboxAlignTopRight24 = 0xF02F8, + TriangleDown24 = 0xF02F9, + CallEnd12 = 0xF02FA, + CallEnd32 = 0xF02FB, + CallEnd48 = 0xF02FC, + ContentViewGallery16 = 0xF02FD, + ContentViewGalleryLightning16 = 0xF02FE, + ContentViewGalleryLightning20 = 0xF02FF, + ContentViewGalleryLightning24 = 0xF0300, + ContentViewGalleryLightning28 = 0xF0301, + GlobeArrowForward16 = 0xF0302, + GlobeArrowForward20 = 0xF0303, + GlobeArrowForward24 = 0xF0304, + GlobeArrowForward32 = 0xF0305, + HardDrive24 = 0xF0306, + HardDrive32 = 0xF0307, + HardDriveCall24 = 0xF0308, + HardDriveCall32 = 0xF0309, + MailRewind16 = 0xF030A, + MailRewind20 = 0xF030B, + MailRewind24 = 0xF030C, + PanelRightGallery16 = 0xF030D, + PanelRightGallery20 = 0xF030E, + PanelRightGallery24 = 0xF030F, + PanelRightGallery28 = 0xF0310, + PanelTopGallery16 = 0xF0311, + PanelTopGallery20 = 0xF0312, + PanelTopGallery24 = 0xF0313, + PanelTopGallery28 = 0xF0314, + RectangleLandscapeSparkle16 = 0xF0315, + RectangleLandscapeSparkle20 = 0xF0316, + RectangleLandscapeSparkle24 = 0xF0317, + RectangleLandscapeSparkle28 = 0xF0318, + RectangleLandscapeSparkle32 = 0xF0319, + ScanPerson16 = 0xF031A, + ScanPerson20 = 0xF031B, + ScanPerson24 = 0xF031C, + ScanPerson28 = 0xF031D, + ScanPerson48 = 0xF031E, + VoicemailShield16 = 0xF031F, + ChevronDown32 = 0xF0320, + ChevronLeft32 = 0xF0321, + ChevronRight32 = 0xF0322, + ChevronUp32 = 0xF0323, + DocumentLightning16 = 0xF0324, + DocumentLightning20 = 0xF0325, + DocumentLightning24 = 0xF0326, + DocumentLightning28 = 0xF0327, + DocumentLightning32 = 0xF0328, + DocumentLightning48 = 0xF0329, + Edit12 = 0xF032A, + ServerLink16 = 0xF032B, + ServerLink20 = 0xF032C, + Step20 = 0xF032D, + Step24 = 0xF032E, + TabDesktopMultipleAdd20 = 0xF032F, + TextDescription16 = 0xF0330, + TextDescription28 = 0xF0331, + TextDescription32 = 0xF0332, + TextGrammarLightning16 = 0xF0333, + TextGrammarLightning20 = 0xF0334, + TextGrammarLightning24 = 0xF0335, + TextGrammarLightning28 = 0xF0336, + TextGrammarLightning32 = 0xF0337, + BeakerAdd20 = 0xF0338, + BeakerAdd24 = 0xF0339, + BeakerDismiss20 = 0xF033A, + BeakerDismiss24 = 0xF033B, + DocumentCube20 = 0xF033C, + DocumentCube24 = 0xF033D, + Drawer20 = 0xF033E, + Drawer24 = 0xF033F, + FilmstripImage20 = 0xF0340, + FilmstripImage24 = 0xF0341, + NumberCircle016 = 0xF0342, + NumberCircle020 = 0xF0343, + NumberCircle024 = 0xF0344, + NumberCircle028 = 0xF0345, + NumberCircle032 = 0xF0346, + NumberCircle048 = 0xF0347, + NumberCircle616 = 0xF0348, + NumberCircle620 = 0xF0349, + NumberCircle624 = 0xF034A, + NumberCircle628 = 0xF034B, + NumberCircle632 = 0xF034C, + NumberCircle648 = 0xF034D, + NumberCircle716 = 0xF034E, + NumberCircle720 = 0xF034F, + NumberCircle724 = 0xF0350, + NumberCircle728 = 0xF0351, + NumberCircle732 = 0xF0352, + NumberCircle748 = 0xF0353, + NumberCircle816 = 0xF0354, + NumberCircle820 = 0xF0355, + NumberCircle824 = 0xF0356, + NumberCircle828 = 0xF0357, + NumberCircle832 = 0xF0358, + NumberCircle848 = 0xF0359, + NumberCircle916 = 0xF035A, + NumberCircle920 = 0xF035B, + NumberCircle924 = 0xF035C, + NumberCircle928 = 0xF035D, + NumberCircle932 = 0xF035E, + NumberCircle948 = 0xF035F, + Server12 = 0xF0360, + SquareHintHexagon12 = 0xF0361, + SquareHintHexagon16 = 0xF0362, + SquareHintHexagon20 = 0xF0363, + SquareHintHexagon24 = 0xF0364, + SquareHintHexagon28 = 0xF0365, + SquareHintHexagon32 = 0xF0366, + SquareHintHexagon48 = 0xF0367, + TabDesktopMultiple16 = 0xF0368, + TabDesktopMultipleAdd16 = 0xF0369, + TargetAdd20 = 0xF036A, + TargetAdd24 = 0xF036B, + TargetDismiss20 = 0xF036C, + TargetDismiss24 = 0xF036D, + TextHeader1Lines16 = 0xF036E, + TextHeader1Lines20 = 0xF036F, + TextHeader1Lines24 = 0xF0370, + TextHeader1LinesCaret16 = 0xF0371, + TextHeader1LinesCaret20 = 0xF0372, + TextHeader1LinesCaret24 = 0xF0373, + TextHeader2Lines16 = 0xF0374, + TextHeader2Lines20 = 0xF0375, + TextHeader2Lines24 = 0xF0376, + TextHeader2LinesCaret16 = 0xF0377, + TextHeader2LinesCaret20 = 0xF0378, + TextHeader2LinesCaret24 = 0xF0379, + TextHeader3Lines16 = 0xF037A, + TextHeader3Lines20 = 0xF037B, + TextHeader3Lines24 = 0xF037C, + TextHeader3LinesCaret16 = 0xF037D, + TextHeader3LinesCaret20 = 0xF037E, + TextHeader3LinesCaret24 = 0xF037F, + ArrowDownload28 = 0xF0380, + ArrowDownload32 = 0xF0381, + ArrowExpand16 = 0xF0382, + ArrowExportUp16 = 0xF0383, + ArrowImport16 = 0xF0384, + ArrowUpRightDashes16 = 0xF0385, + Battery1016 = 0xF0386, + BeakerEmpty16 = 0xF0387, + Book16 = 0xF0388, + BorderNone16 = 0xF0389, + BranchRequest16 = 0xF038A, + ClipboardTaskList16 = 0xF038B, + Cut16 = 0xF038C, + FolderSearch16 = 0xF038D, + FolderSearch20 = 0xF038E, + FolderSearch24 = 0xF038F, + Hexagon28 = 0xF0390, + Hexagon32 = 0xF0391, + Hexagon48 = 0xF0392, + PlugConnected16 = 0xF0393, + PlugDisconnected16 = 0xF0394, + ProjectionScreenText20 = 0xF0395, + Rss16 = 0xF0396, + ShapeOrganic16 = 0xF0397, + ShapeOrganic20 = 0xF0398, + ShapeOrganic24 = 0xF0399, + ShapeOrganic28 = 0xF039A, + ShapeOrganic32 = 0xF039B, + ShapeOrganic48 = 0xF039C, + TeardropBottomRight16 = 0xF039D, + TeardropBottomRight20 = 0xF039E, + TeardropBottomRight24 = 0xF039F, + TeardropBottomRight28 = 0xF03A0, + TeardropBottomRight32 = 0xF03A1, + TeardropBottomRight48 = 0xF03A2, + TextEditStyle16 = 0xF03A3, + TextWholeWord16 = 0xF03A4, + Triangle24 = 0xF03A5, + Triangle28 = 0xF03A6, + TextAsterisk16 = 0xF03A7, + ArrowDownloadOff16 = 0xF03A8, + ArrowDownloadOff20 = 0xF03A9, + ArrowDownloadOff24 = 0xF03AA, + ArrowDownloadOff28 = 0xF03AB, + ArrowDownloadOff32 = 0xF03AC, + ArrowDownloadOff48 = 0xF03AD, + BorderInside16 = 0xF03AE, + BorderInside20 = 0xF03AF, + BorderInside24 = 0xF03B0, + ChatLock16 = 0xF03B1, + ChatLock20 = 0xF03B2, + ChatLock24 = 0xF03B3, + ChatLock28 = 0xF03B4, + ErrorCircle48 = 0xF03B5, + FullScreenMaximize28 = 0xF03B6, + FullScreenMaximize32 = 0xF03B7, + FullScreenMinimize28 = 0xF03B8, + FullScreenMinimize32 = 0xF03B9, + LinkPerson16 = 0xF03BA, + LinkPerson20 = 0xF03BB, + LinkPerson24 = 0xF03BC, + LinkPerson32 = 0xF03BD, + LinkPerson48 = 0xF03BE, + PeopleChat16 = 0xF03BF, + PeopleChat20 = 0xF03C0, + PeopleChat24 = 0xF03C1, + PersonSupport28 = 0xF03C2, + Shapes32 = 0xF03C3, + SlideTextEdit16 = 0xF03C4, + SlideTextEdit20 = 0xF03C5, + SlideTextEdit24 = 0xF03C6, + SlideTextEdit28 = 0xF03C7, + SubtractCircle48 = 0xF03C8, + SubtractParentheses16 = 0xF03C9, + SubtractParentheses20 = 0xF03CA, + SubtractParentheses24 = 0xF03CB, + SubtractParentheses28 = 0xF03CC, + SubtractParentheses32 = 0xF03CD, + SubtractParentheses48 = 0xF03CE, + Warning48 = 0xF03CF, + AlertOn16 = 0xF03D0, + ArrowDownExclamation16 = 0xF03D1, + ArrowDownExclamation20 = 0xF03D2, + ArrowFit24 = 0xF03D3, + ArrowFitIn24 = 0xF03D4, + Book32 = 0xF03D5, + BookDatabase16 = 0xF03D6, + BookDatabase32 = 0xF03D7, + BookToolbox16 = 0xF03D8, + BuildingDesktop32 = 0xF03D9, + BuildingGovernment16 = 0xF03DA, + BuildingGovernmentSearch16 = 0xF03DB, + BuildingGovernmentSearch20 = 0xF03DC, + BuildingGovernmentSearch24 = 0xF03DD, + BuildingGovernmentSearch32 = 0xF03DE, + CalendarRecord16 = 0xF03DF, + CalendarRecord20 = 0xF03E0, + CalendarRecord24 = 0xF03E1, + CalendarRecord28 = 0xF03E2, + CalendarRecord32 = 0xF03E3, + CalendarRecord48 = 0xF03E4, + Clipboard28 = 0xF03E5, + ClipboardMathFormula16 = 0xF03E6, + ClipboardMathFormula20 = 0xF03E7, + ClipboardMathFormula24 = 0xF03E8, + ClipboardMathFormula28 = 0xF03E9, + ClipboardMathFormula32 = 0xF03EA, + ClipboardNumber12316 = 0xF03EB, + ClipboardNumber12320 = 0xF03EC, + ClipboardNumber12324 = 0xF03ED, + ClipboardNumber12328 = 0xF03EE, + ClipboardNumber12332 = 0xF03EF, + Collections16 = 0xF03F0, + CommunicationShield16 = 0xF03F1, + CommunicationShield20 = 0xF03F2, + CommunicationShield24 = 0xF03F3, + DialpadQuestionMark20 = 0xF03F4, + DialpadQuestionMark24 = 0xF03F5, + DocumentBriefcase16 = 0xF03F6, + DocumentBriefcase32 = 0xF03F7, + DocumentSearch32 = 0xF03F8, + Fingerprint16 = 0xF03F9, + Fingerprint32 = 0xF03FA, + FolderPerson24 = 0xF03FB, + FolderPerson28 = 0xF03FC, + FolderPerson32 = 0xF03FD, + FolderPerson48 = 0xF03FE, + HatGraduationAdd16 = 0xF03FF, + HatGraduationAdd20 = 0xF0400, + HatGraduationAdd24 = 0xF0401, + LayerDiagonalAdd20 = 0xF0402, + Library32 = 0xF0403, + LightbulbFilament32 = 0xF0404, + LinkAdd16 = 0xF0405, + LinkAdd20 = 0xF0406, + LockShield16 = 0xF0407, + LockShield28 = 0xF0408, + LockShield32 = 0xF0409, + PersonVoice16 = 0xF040A, + PersonWarning16 = 0xF040B, + PersonWarning20 = 0xF040C, + PersonWarning24 = 0xF040D, + PersonWarning28 = 0xF040E, + PersonWarning32 = 0xF040F, + PersonWarning48 = 0xF0410, + ScanTypeOff24 = 0xF0411, + Screenshot16 = 0xF0412, + ScreenshotRecord16 = 0xF0413, + ScreenshotRecord20 = 0xF0414, + ScreenshotRecord24 = 0xF0415, + SlideSearch16 = 0xF0416, + SlideSearch32 = 0xF0417, + VehicleSubwayClock16 = 0xF0418, + VehicleSubwayClock20 = 0xF0419, + VehicleSubwayClock24 = 0xF041A, + VideoClipOptimize16 = 0xF041B, + VideoClipOptimize20 = 0xF041C, + VideoClipOptimize24 = 0xF041D, + VideoClipOptimize28 = 0xF041E, + VideoPersonPulse16 = 0xF041F, + VideoPersonPulse20 = 0xF0420, + VideoPersonPulse24 = 0xF0421, + VideoPersonPulse28 = 0xF0422, + ArchiveSettings32 = 0xF0423, + ArrowForward32 = 0xF0424, + ArrowReply32 = 0xF0425, + ArrowReplyAll32 = 0xF0426, + Attach32 = 0xF0427, + Autocorrect32 = 0xF0428, + Broom32 = 0xF0429, + CalendarNote16 = 0xF042A, + CalendarNote20 = 0xF042B, + CalendarNote24 = 0xF042C, + CalendarNote32 = 0xF042D, + CheckmarkUnderlineCircle24 = 0xF042E, + DataBarVerticalAscending20 = 0xF042F, + DataBarVerticalAscending24 = 0xF0430, + Diversity16 = 0xF0431, + Filter32 = 0xF0432, + FolderMail32 = 0xF0433, + GlanceHorizontal32 = 0xF0434, + GlanceHorizontalSparkle32 = 0xF0435, + GlobeArrowUp16 = 0xF0436, + GlobeArrowUp20 = 0xF0437, + GlobeArrowUp24 = 0xF0438, + GlobeError16 = 0xF0439, + GlobeError20 = 0xF043A, + GlobeError24 = 0xF043B, + GlobeProhibited16 = 0xF043C, + GlobeProhibited24 = 0xF043D, + GlobeSync16 = 0xF043E, + GlobeSync20 = 0xF043F, + GlobeSync24 = 0xF0440, + GlobeWarning16 = 0xF0441, + GlobeWarning20 = 0xF0442, + GlobeWarning24 = 0xF0443, + Important32 = 0xF0444, + LayerDiagonal16 = 0xF0445, + LayerDiagonalPerson16 = 0xF0446, + MailMultiple32 = 0xF0447, + MailRead32 = 0xF0448, + MailUnread32 = 0xF0449, + Mailbox16 = 0xF044A, + Mailbox20 = 0xF044B, + OrganizationHorizontal16 = 0xF044C, + OrganizationHorizontal24 = 0xF044D, + PeopleList32 = 0xF044E, + PersonAdd32 = 0xF044F, + PersonSquare16 = 0xF0450, + PersonSquare32 = 0xF0451, + PersonSquareCheckmark16 = 0xF0452, + PersonSquareCheckmark20 = 0xF0453, + PersonSquareCheckmark24 = 0xF0454, + PersonSquareCheckmark32 = 0xF0455, + PhoneFooterArrowDown20 = 0xF0456, + PhoneFooterArrowDown24 = 0xF0457, + PhoneHeaderArrowUp20 = 0xF0458, + PhoneHeaderArrowUp24 = 0xF0459, + Poll32 = 0xF045A, + Question32 = 0xF045B, + Screenshot28 = 0xF045C, + ScreenshotRecord28 = 0xF045D, + Star32 = 0xF045E, + TextDensity32 = 0xF045F, + TextEditStyleCharacterA32 = 0xF0460, + WrenchScrewdriver32 = 0xF0461, + ArrowClockwiseDashes16 = 0xF0462, + ArrowClockwiseDashes32 = 0xF0463, + BuildingSwap16 = 0xF0464, + BuildingSwap20 = 0xF0465, + BuildingSwap24 = 0xF0466, + BuildingSwap32 = 0xF0467, + BuildingSwap48 = 0xF0468, + Certificate32 = 0xF0469, + ClipboardBrush16 = 0xF046A, + ClipboardBrush20 = 0xF046B, + ClipboardBrush24 = 0xF046C, + ClipboardBrush28 = 0xF046D, + ClipboardBrush32 = 0xF046E, + CloudBeaker16 = 0xF046F, + CloudBeaker20 = 0xF0470, + CloudBeaker24 = 0xF0471, + CloudBeaker28 = 0xF0472, + CloudBeaker32 = 0xF0473, + CloudBeaker48 = 0xF0474, + CloudCube16 = 0xF0475, + CloudCube20 = 0xF0476, + CloudCube24 = 0xF0477, + CloudCube28 = 0xF0478, + CloudCube32 = 0xF0479, + CloudCube48 = 0xF047A, + ContractUpRight16 = 0xF047B, + ContractUpRight20 = 0xF047C, + ContractUpRight24 = 0xF047D, + ContractUpRight28 = 0xF047E, + ContractUpRight32 = 0xF047F, + ContractUpRight48 = 0xF0480, + DocumentDataLock16 = 0xF0481, + DocumentDataLock20 = 0xF0482, + DocumentDataLock24 = 0xF0483, + DocumentDataLock32 = 0xF0484, + GlanceHorizontalSparkles20 = 0xF0485, + LayoutCellFour16 = 0xF0486, + LayoutCellFour20 = 0xF0487, + LayoutCellFour24 = 0xF0488, + LayoutColumnFour16 = 0xF0489, + LayoutColumnFour20 = 0xF048A, + LayoutColumnFour24 = 0xF048B, + LayoutColumnOneThirdLeft16 = 0xF048C, + LayoutColumnOneThirdLeft20 = 0xF048D, + LayoutColumnOneThirdLeft24 = 0xF048E, + LayoutColumnOneThirdRight16 = 0xF048F, + LayoutColumnOneThirdRight20 = 0xF0490, + LayoutColumnOneThirdRight24 = 0xF0491, + LayoutColumnOneThirdRightHint16 = 0xF0492, + LayoutColumnOneThirdRightHint20 = 0xF0493, + LayoutColumnOneThirdRightHint24 = 0xF0494, + LayoutColumnThree16 = 0xF0495, + LayoutColumnThree20 = 0xF0496, + LayoutColumnThree24 = 0xF0497, + LayoutColumnTwo16 = 0xF0498, + LayoutColumnTwo20 = 0xF0499, + LayoutColumnTwo24 = 0xF049A, + LayoutColumnTwoSplitLeft16 = 0xF049B, + LayoutColumnTwoSplitLeft20 = 0xF049C, + LayoutColumnTwoSplitLeft24 = 0xF049D, + LayoutColumnTwoSplitRight16 = 0xF049E, + LayoutColumnTwoSplitRight20 = 0xF049F, + LayoutColumnTwoSplitRight24 = 0xF04A0, + LayoutRowFour16 = 0xF04A1, + LayoutRowFour20 = 0xF04A2, + LayoutRowFour24 = 0xF04A3, + LayoutRowThree16 = 0xF04A4, + LayoutRowThree20 = 0xF04A5, + LayoutRowThree24 = 0xF04A6, + LayoutRowTwo16 = 0xF04A7, + LayoutRowTwo20 = 0xF04A8, + LayoutRowTwo24 = 0xF04A9, + LayoutRowTwoSplitBottom16 = 0xF04AA, + LayoutRowTwoSplitBottom20 = 0xF04AB, + LayoutRowTwoSplitBottom24 = 0xF04AC, + LayoutRowTwoSplitTop16 = 0xF04AD, + LayoutRowTwoSplitTop20 = 0xF04AE, + LayoutRowTwoSplitTop24 = 0xF04AF, + LocationTargetSquare16 = 0xF04B0, + LocationTargetSquare20 = 0xF04B1, + LocationTargetSquare24 = 0xF04B2, + LocationTargetSquare32 = 0xF04B3, + Resize16 = 0xF04B4, + Resize28 = 0xF04B5, + Resize32 = 0xF04B6, + Resize48 = 0xF04B7, + SelectAllOff16 = 0xF04B8, + SelectAllOn16 = 0xF04B9, + ShareAndroid16 = 0xF04BA, + ShareAndroid32 = 0xF04BB, + TextArrowDownRightColumn16 = 0xF04BC, + TextArrowDownRightColumn20 = 0xF04BD, + TextArrowDownRightColumn24 = 0xF04BE, + TextArrowDownRightColumn28 = 0xF04BF, + TextArrowDownRightColumn32 = 0xF04C0, + TextArrowDownRightColumn48 = 0xF04C1, + TextEffectsSparkle20 = 0xF04C2, + TextEffectsSparkle24 = 0xF04C3, + Whiteboard16 = 0xF04C4, + WhiteboardOff16 = 0xF04C5, + WhiteboardOff20 = 0xF04C6, + WhiteboardOff24 = 0xF04C7, + Flowchart16 = 0xF04C8, + Flowchart32 = 0xF04C9, + LayerDiagonal24 = 0xF04CA, + LayerDiagonalPerson24 = 0xF04CB, + PollOff16 = 0xF04CC, + PollOff20 = 0xF04CD, + PollOff24 = 0xF04CE, + PollOff32 = 0xF04CF, + RectangleLandscapeSparkle48 = 0xF04D0, + RectangleLandscapeSync16 = 0xF04D1, + RectangleLandscapeSync20 = 0xF04D2, + RectangleLandscapeSync24 = 0xF04D3, + RectangleLandscapeSync28 = 0xF04D4, + RectangleLandscapeSyncOff16 = 0xF04D5, + RectangleLandscapeSyncOff20 = 0xF04D6, + RectangleLandscapeSyncOff24 = 0xF04D7, + RectangleLandscapeSyncOff28 = 0xF04D8, + Seat16 = 0xF04D9, + Seat20 = 0xF04DA, + Seat24 = 0xF04DB, + SeatAdd16 = 0xF04DC, + SeatAdd20 = 0xF04DD, + SeatAdd24 = 0xF04DE, + SpeakerBox16 = 0xF04DF, + SpeakerBox20 = 0xF04E0, + SpeakerBox24 = 0xF04E1, + TextEditStyleCharacterGa32 = 0xF04E2, + WindowAd24 = 0xF04E3, + WrenchSettings20 = 0xF04E4, + WrenchSettings24 = 0xF04E5, + BuildingLighthouse24 = 0xF04E6, + BuildingLighthouse32 = 0xF04E7, + BuildingLighthouse48 = 0xF04E8, + CalendarLink24 = 0xF04E9, + CalendarLink28 = 0xF04EA, + CalendarVideo24 = 0xF04EB, + CalendarVideo28 = 0xF04EC, + Cookies16 = 0xF04ED, + Cookies28 = 0xF04EE, + Cookies32 = 0xF04EF, + Cookies48 = 0xF04F0, + HardDrive28 = 0xF04F1, + HardDrive48 = 0xF04F2, + Laptop32 = 0xF04F3, + LaptopSettings20 = 0xF04F4, + LaptopSettings24 = 0xF04F5, + LaptopSettings32 = 0xF04F6, + PeopleAudience32 = 0xF04F7, + ShoppingBagAdd20 = 0xF04F8, + ShoppingBagAdd24 = 0xF04F9, + StreetSign20 = 0xF04FA, + StreetSign24 = 0xF04FB, + VideoLink24 = 0xF04FC, + VideoLink28 = 0xF04FD, + BuildingLighthouse16 = 0xF04FE, + CalendarSparkle16 = 0xF04FF, + CalendarSparkle20 = 0xF0500, + CalendarSparkle24 = 0xF0501, + CalendarSparkle28 = 0xF0502, + CalendarSparkle32 = 0xF0503, + CalendarSparkle48 = 0xF0504, + CalendarTemplate20 = 0xF0505, + CalendarTemplate24 = 0xF0506, + CalendarTemplate32 = 0xF0507, + Clipboard12 = 0xF0508, + Clipboard48 = 0xF0509, + Compose12 = 0xF050A, + Compose32 = 0xF050B, + Compose48 = 0xF050C, + Globe28 = 0xF050D, + Guest12 = 0xF050E, + Guest32 = 0xF050F, + Guest48 = 0xF0510, + LaptopBriefcase20 = 0xF0511, + LaptopBriefcase24 = 0xF0512, + LaptopBriefcase32 = 0xF0513, + LayerDiagonalSparkle16 = 0xF0514, + LayerDiagonalSparkle20 = 0xF0515, + LayerDiagonalSparkle24 = 0xF0516, + PaymentWireless16 = 0xF0517, + PaymentWireless20 = 0xF0518, + PaymentWireless24 = 0xF0519, + PaymentWireless28 = 0xF051A, + PaymentWireless32 = 0xF051B, + PaymentWireless48 = 0xF051C, + Status28 = 0xF051D, + Status32 = 0xF051E, + Status48 = 0xF051F, + VideoOff16 = 0xF0520, + CheckmarkCircleWarning16 = 0xF0521, + CheckmarkCircleWarning20 = 0xF0522, + CheckmarkCircleWarning24 = 0xF0523, + CloudArrowRight16 = 0xF0524, + CloudArrowRight20 = 0xF0525, + CloudArrowRight24 = 0xF0526, + DocumentArrowDown24 = 0xF0527, + DocumentSignature16 = 0xF0528, + DocumentSignature20 = 0xF0529, + DocumentSignature24 = 0xF052A, + DocumentSignature28 = 0xF052B, + DocumentSignature32 = 0xF052C, + DocumentSignature48 = 0xF052D, + HomeGarage20 = 0xF052E, + HomeGarage24 = 0xF052F, + ImageSplit20 = 0xF0530, + ImageSplit24 = 0xF0531, + Laptop48 = 0xF0532, + LineFlowDiagonalUpRight16 = 0xF0533, + LineFlowDiagonalUpRight20 = 0xF0534, + LineFlowDiagonalUpRight24 = 0xF0535, + LineFlowDiagonalUpRight32 = 0xF0536, + MailArrowClockwise16 = 0xF0537, + MailArrowClockwise20 = 0xF0538, + MailArrowClockwise24 = 0xF0539, + PersonPasskey16 = 0xF053A, + PersonPasskey20 = 0xF053B, + PersonPasskey24 = 0xF053C, + PersonPasskey28 = 0xF053D, + PersonPasskey32 = 0xF053E, + PersonPasskey48 = 0xF053F, + PersonProhibited32 = 0xF0540, + PersonRibbon24 = 0xF0541, + PlantCattail20 = 0xF0542, + PlantCattail24 = 0xF0543, + Storage16 = 0xF0544, + Storage28 = 0xF0545, + Storage32 = 0xF0546, + Storage48 = 0xF0547, + VideoClipWand16 = 0xF0548, + VideoClipWand20 = 0xF0549, + VideoClipWand24 = 0xF054A, + WindowFingerprint16 = 0xF054B, + WindowFingerprint20 = 0xF054C, + WindowFingerprint24 = 0xF054D, + WindowFingerprint28 = 0xF054E, + WindowFingerprint32 = 0xF054F, + WindowFingerprint48 = 0xF0550, + AccessibilityError20 = 0xF0551, + AccessibilityError24 = 0xF0552, + AccessibilityQuestionMark20 = 0xF0553, + AccessibilityQuestionMark24 = 0xF0554, + ArrowDownExclamation24 = 0xF0555, + ArrowSortUpLines16 = 0xF0556, + ArrowSortUpLines20 = 0xF0557, + ArrowSortUpLines24 = 0xF0558, + ArrowUpExclamation16 = 0xF0559, + ArrowUpExclamation20 = 0xF055A, + ArrowUpExclamation24 = 0xF055B, + Bench20 = 0xF055C, + Bench24 = 0xF055D, + BuildingLighthouse28 = 0xF055E, + CalendarVideo20 = 0xF055F, + ClockBill16 = 0xF0560, + ClockBill20 = 0xF0561, + ClockBill24 = 0xF0562, + ClockBill32 = 0xF0563, + DataUsage16 = 0xF0564, + DataUsageSettings16 = 0xF0565, + DataUsageSettings24 = 0xF0566, + EditPerson20 = 0xF0567, + EditPerson24 = 0xF0568, + Highway20 = 0xF0569, + Highway24 = 0xF056A, + LaptopPerson20 = 0xF056B, + LaptopPerson24 = 0xF056C, + LaptopPerson48 = 0xF056D, + LocationRipple16 = 0xF056E, + LocationRipple20 = 0xF056F, + LocationRipple24 = 0xF0570, + MailArrowDoubleBack32 = 0xF0571, + MailBriefcase48 = 0xF0572, + Options28 = 0xF0573, + Options32 = 0xF0574, + PeopleAdd32 = 0xF0575, + PersonAlert32 = 0xF0576, + Road20 = 0xF0577, + Road24 = 0xF0578, + Save32 = 0xF0579, + TabDesktopMultiple24 = 0xF057A, + TabDesktopMultipleSparkle16 = 0xF057B, + TabDesktopMultipleSparkle20 = 0xF057C, + TabDesktopMultipleSparkle24 = 0xF057D, + VehicleTractor20 = 0xF057E, + VehicleTractor24 = 0xF057F, + Classification32 = 0xF0580, + DocumentTarget20 = 0xF0581, + DocumentTarget24 = 0xF0582, + DocumentTarget32 = 0xF0583, + EmojiMeme16 = 0xF0584, + EmojiMeme20 = 0xF0585, + EmojiMeme24 = 0xF0586, + HandPoint16 = 0xF0587, + HandPoint20 = 0xF0588, + HandPoint24 = 0xF0589, + HandPoint28 = 0xF058A, + HandPoint32 = 0xF058B, + HandPoint48 = 0xF058C, + MailReadBriefcase48 = 0xF058D, + PeopleSubtract20 = 0xF058E, + PeopleSubtract24 = 0xF058F, + PeopleSubtract32 = 0xF0590, + PersonAlertOff16 = 0xF0591, + PersonAlertOff20 = 0xF0592, + PersonAlertOff24 = 0xF0593, + PersonAlertOff32 = 0xF0594, + ShoppingBagAdd16 = 0xF0595, + SpatulaSpoon16 = 0xF0596, + SpatulaSpoon20 = 0xF0597, + SpatulaSpoon24 = 0xF0598, + SpatulaSpoon28 = 0xF0599, + SpatulaSpoon32 = 0xF059A, + SpatulaSpoon48 = 0xF059B, } - -#pragma warning restore CS1591 diff --git a/source/RevitLookup.UI/Controls/TextBlock/TextBlock.cs b/source/RevitLookup.UI/Controls/TextBlock/TextBlock.cs index 86c6a2fd..862064b5 100644 --- a/source/RevitLookup.UI/Controls/TextBlock/TextBlock.cs +++ b/source/RevitLookup.UI/Controls/TextBlock/TextBlock.cs @@ -3,7 +3,6 @@ // Copyright (C) Leszek Pomianowski and WPF UI Contributors. // All Rights Reserved. -using System.Windows.Documents; using Wpf.Ui.Extensions; // ReSharper disable once CheckNamespace @@ -14,34 +13,36 @@ namespace Wpf.Ui.Controls; /// public class TextBlock : System.Windows.Controls.TextBlock { - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty FontTypographyProperty = DependencyProperty.Register( nameof(FontTypography), typeof(FontTypography), typeof(TextBlock), new PropertyMetadata( FontTypography.Body, - static (o, args) => ((TextBlock)o).OnFontTypographyChanged((FontTypography)args.NewValue) + static (o, args) => + { + ((TextBlock)o).OnFontTypographyChanged((FontTypography)args.NewValue); + } ) ); - /// - /// Property for . - /// + /// Identifies the dependency property. public static readonly DependencyProperty AppearanceProperty = DependencyProperty.Register( nameof(Appearance), typeof(TextColor), typeof(TextBlock), new PropertyMetadata( TextColor.Primary, - static (o, args) => ((TextBlock)o).OnAppearanceChanged((TextColor)args.NewValue) + static (o, args) => + { + ((TextBlock)o).OnAppearanceChanged((TextColor)args.NewValue); + } ) ); /// - /// TODO + /// Gets or sets the of the text. /// public FontTypography FontTypography { @@ -50,7 +51,7 @@ public FontTypography FontTypography } /// - /// TODO + /// Gets or sets the color of the text. /// public TextColor Appearance { diff --git a/source/RevitLookup.UI/Controls/TextBlock/TextBlock.xaml b/source/RevitLookup.UI/Controls/TextBlock/TextBlock.xaml index 3212231c..d07d15bd 100644 --- a/source/RevitLookup.UI/Controls/TextBlock/TextBlock.xaml +++ b/source/RevitLookup.UI/Controls/TextBlock/TextBlock.xaml @@ -9,12 +9,12 @@ - + + \ No newline at end of file diff --git a/source/RevitLookup.UI/Resources/Fonts/FluentSystemIcons-Filled.ttf b/source/RevitLookup.UI/Resources/Fonts/FluentSystemIcons-Filled.ttf index 8726d355c0564c22b225829f9cf6db6200dba24d..21e767087db8120d9569cdc4aa069735d6e5eae5 100644 GIT binary patch delta 332503 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
    `ds+MostHE? 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

    Yp;faK;yNjmI;0T4zY*d_ zPKfVN3Z+f)Z8tLtEp-%1&xLkaP-s~NV9U3WhL*P~v?lGEY-g0ioI-0cwlYcs(0VCQ#g`DQ$Hw|DAh3RqLRmO$K+p{s-w@-C(iO^Peq$UqNmFRk zOoei?71}Icq0M275`}UR-?CaEUaUe}cPNyX1H%e!Q?Jmr*lz2$qY7=82Qv!gcPX?z zakg*h=FfscJ0P$ld%q(_1&HjF4(!#=H45$GLWe?y%ooz`N)o%aD70G^Ahdf5)WMWO zMI|u68`YTGqZJl-qZ-CKZaC}?%~hzJK!;`XX&UB^ zK&ygSM>2OLK1VTsR5$fMI)k^&%_vmaqtG#k9)tL?E|AD^Q@jmlTA}0NM8>Lf{LgvA z7@zU(;IrN3Fv;h-D`1Gv_+|lneJY8aM$pq3JDsu9TY#}z>}rSkC^utgV0XqW^*^%& z`V=}V73yJHp*qIudKEf54H|*5bBbU

    xUn+r=*E~^9lFRuaG7HqE=Q|QWJg|4FC+N#jiXk3HOHKPh$TMCS~q1T4Zby*~MU5i53 z6ZConZx~Rhy9ZzeFWj-gwN6uPZep-#p-5xzYW8i7RbXjX_bSv~s zze3NBEA(6;^eHr$0nOC^`AUUe$W`dYG=+u?!1yJMUg}clWyW6~ROl5}Vt81gS4&|= zq1Q;{wRweJuTf~k1q9zHhAD;KoK@&8csoy_cbI>-K%p@Q#(2{Sn(w3eKAIn3|3L$T zqnpfzn3SjK(Lcr!5=BCqNj{1K~0^cF>J%MIi=uzkg9DgiP=%);rQD~M# zer6AT?ojBLY*7;U1H`7d$uOa?Tc@yxt;cp^ zSm7jgDd?=?Lbt-J)+@ZIRN>St7*}{Twih=myn2hmY3(qk@RAhZ^*_9Xi6w}yfheDk z3opeW-2j}@39zgL`W0TD2?(s223-ngu$|GV@LK5#uT8@1;G3DR@VW&GuZPz9jS6o- zLK|X}jnBq83U7kXCJPF0I-u}oQh4)*B89iWcndPhMI@I!$(>V}E1xh|J>fjE+{S?^ zg}3cic)KQr^Gg-po*+9QzC*vlJJu^)(55i27vWvX6fVSe*D-~68&!CB^miXrxF`>L z72X5(guODLgIhDjDGC=8Xm6bM!D-(Cg-hBM-mgmG{mT_T0D%MM6)t6LMW@0Cb}3wD zD1304!iV%LT#o-?*d5OJ5i<%OnGLi@p>uSj!j%L&2Hj(a6h01(s(OWw$Nq$Bg->jx z{?$01G^OwA z+Q@sg$?$TvTMU>`_=-M-uWVEJsxF0F5xScGHIoXr6)1cifvzV&JIUPGq3}%v?_m5E zY;L9BnXmBe^zTRqeDBNze7abpi)p3PiC-_K?!f&t#ZbI1#OzSS z;!PkC&Tu16pCVBvVi?7z6mc2j>xv?YY?x7mw?afx3V^v)niW}<@m1#)S=6scDt4=7 zz=R@;TNGJ62Syc1t5IZ0y&`KgDY6uwbmo>3bU9jUmMfBx0u2OQt4@)%5nOvxk#*V> z$t+T2T?3d~59jp@VL_3sPDM7LzrnO38zQz*77Qwqjo8MSfd3|4ifme?NKP)`v)Pa$ zn-{~3B3slelA8*xifl>nEomFJ98+YgG(co40&F#<$kqsNJ*r3^4%;BS4FR|9Q)IhZ zMe=JD*&f>+5ZVFX9orNsC{tu7G++q+}8`>-M$j3Pzd zitLf9hR7Zm?a9EN(~9iXu1IkSuqS(0$^X<+WS|I zlnyGg0^b9RfM5sFFDn2v4{lN95Ht@>g>gm7(K`&^!!ux#`XAA$NCm=2ro*HnN7X5E zG{#3GUWvmo1Bx7r({W9TRFTy24v@?Vti*}*PwZBtx=@jmniV;DSdkhMJe7c_5%}~m zMQZUqqg;_Q(-b)?Q<1tHMb2jaZ1m4*XjSA~2I|ulS!n=+=S@%-0yo0>RlwKM zfh&6zxeDi2#9Dh4xjGG6VL_2=2y_ji*On^MmIY&qTvxBi_4&a34fwX#Dsp2sV0TlO zA~)mHkqOM-GOoz21B%>+UMDebN9PVfzan?mQU9(2MY`q{xvN`|yDJpAClwItZd2sm zVnyyVphuDWYZU3pgE>VwKa4zx;6nuJtyJV;G#(+~BeRM;I;corIbiqLm?Do8{D}!g z`qA#6QRGQ_UPfZ;Q3iad+>vy+NEhwvc%K@6Tp?1dIZUMx_A^TNo>If}e8 zs>pDcBCmET@>)J1{(3r8KnD<%bHd0-A=E(+Oe^vR3BG~N8|{F`o7lX`>wo0UMi^A& zEdvSwr&0Q&B=vR{j4Sd^lOpdDd@L1a6?tz&k@u?=`2dj*2s%#t;jkhfqct(F$fwv( zW-IbJfxaN%mnDGT6yskBYGH)>e@*6JGx;@+-_UYm82L6+k?(S$SCQ`#`Mz6`8OCNv z20oFadxDzcEF z$UkWQTcF5)rD`x#CnMA>Bit^7b3elvkzp>#uv29?IWR3F+9M-YFC$(o!_9zs86M*a zY?FgBQkY+*Q^u-QG8W~@NG*}ET8)gwjIGXmS`lEog!v_CuhA%DsR5k-8|f}!xGW1u zV0k_a$yl>VMn;8<^GBT@WtlKJMJ;c_Zm625oB(p(_j139A;h2n#2(nQ( zOv%X31rpCDiH!-kagmHoTo{(IDf&4Lh;Ehvb22u^d5cOJxfte>$d&}%lJTwjWo%7= zylNTSklePZFv*t|bjrx@;7baa+hJTrLAH#Y5Z#%%!W2Ov$()6WG3>TgHXy&?2MB0CtSm z4x_0PeBFy285iY44a~^6nD!DuF|-3>%{X03wwKekxhi2UBLJ178%!Ie+>y=i{7o%PqF=Up^Skv zn3nNOyNqW`WjvQHV-T&uVHwXi%6K79#*0-lhDhS2E*Y<2Kin+iRT6)_PR0oB8w~_| z6Nk5YWsKtVHo|XD%6NxB?;`wOjf@X+Wqjz!_=xcd>^>p$PqF(fN5-TfP2MJNnsqX*5}7tX0%$s!;_X#1E;EJ$$9L16lj*UY=#< z-`ohhY&5c`Wp0e$CMCe!rp)D_w;6MrF}``D%q`f?6>$H@+_FpNRt#>P4t+B75ZWdS z5Zbm!=6208^9htcER*Ygb9-!dKzv68c4VH@Z*!*%7?rs*0e0z@S=b_T*IJpo)xea@ z-SOu>fLW9c{WABU-P4fSu;+-(y@q5Kv)p^n$lTYFS&}1jzap5Hxqq9?19AcJ(pH%( zm|HO{^FWe3C=(`Sma&}N4=@jAZx88^c__YzPRJ~;k$D(F4rA=_JQ$OCM6=8a<~h8Z zM=ntRqtj(p65yCZna9!|hv9JqtHSVj1Wtex^JP}MGEefEm3i`%%u~i?*0jq!mHE@q zIBi1a>3uRe-8Ro?k$GmN%(JRw)>X(nyG!Od_|>P%TuJcr>e=%FnT{~tJ9!lvt%EYJu9taDLmq$TWnPPDTdmCNascP+n`PdR3+!ooo6H+=yvcNcpux36$3txXG5n< zj*@17zRV|GK$z2Ob3ihmVf)!sAmFnTGM~fdIqU};Wj|r zgvd*|&@1!hOlXt&iUSA@6Lfe==Bv!T#`f#QGDnyj8Ik!0c5hb7d@BbQWWG%T@1)Co z7oRc4-b3^K8kry9JKje9KV<1XBBPIoWKK-Vf3uMJ8BUXhGCyyT`9-VDFRNuvHOc%6 z@vm!Tev>bA+JG^c-{SinOM`|% znZH%Z{JmM`AKfzN3HB#Wf0fDnI}_$)E)2>1hXDUp0QUc70QUdQ$Wp(oP>ZZ^rL0J< zEW?pyHp{YI=$7Ra!KAEcqpVma49JR?!JI6&T~>paCCeLt*F(lh-6`8`^CeWo^s&wzS)kz;@{7rvQPsFP60fb32SR z$l9?*RzV3cQ7|oQr$XqGwKK*$Hv@aLOASoQDy)VHS-TF%+O1dC?j*50enlCwxSL_^ ziRhjSvi2I6Ra^x9vi2tU-c>LpYag`t!FON$_9bo!=XF*I!S>IQb-=K!Qi7IN$XbEW z3WN{LgHc%r)yOJiuB=Da!H66@E$a}(4{efFj{jl!9zG+BGiU3_YGC`QOj$?g$f`u6 zvJK{B9a9SU9h)!fIG6e#Hz}*CSJv^3vQ8+Gbz+*V>U3Er;dC;4bn=3%QwC(!Fn=nX zRv_zi5;?t9R&9%{Gs<8_)|uGVIkL{i{~Q9JGa~EUT3PiOKr$<9WSvL)+Nw9DC<(3FB_6|`J}8XNbE`iUR4FywH5=(U7Zb` zvaU&iI+&1kZ4vazYDK9)kDG$%*c8u zRaS4VtcN>fJxafiz>oFIdLlMX9)Z(0iMf(F0_!7YH<&1q7b#l=XQIVDm+ltS|Fr zP2o2+E9*szGm(lbiP4*nz`v37?$;IF<|#ytE}(&5-98YURg7_FedAVa+s3! zW4o-MFq}<;aalk2$ohq`U&;7a`oB)dnsb4`bJfrx>$fslzo*Fh1LhqdvH1~Me`Z4q z%*gtyRMy{(vKG2!{WB`--*oE#FNXj2$@&kc|0Z%spRwha%ZKvge#|+B;Rt-kD@~$$&Z8h4}A^&u#?Rty4BfM!P6i_8yrq zA$!jj*?Z;7E>4%dH+uW{jH|)kw_SD#gC!lZ_rrLK1+wx^!H!S;x zCfV&(vTrPweN&F?o6~?~8agIq--6h!aNCINP7=7iO*WU)_MM1z<;lJ)UH09QeGg*Y zjk51W@P4*?-~j?Z&@KDHLfH?|e`rv4Z@=t^`(!_Y)}zqJJm-t{V{@_}pOyW@lNQ56VDRhxpLWqO|qXKko`g#Ov`?;QT9+aOv-+V{wp1_U+t33QP6&! z@z+OXk1+lQ{WoT0zd0fMtzp@t^|Ie)2i|7v9qiw&kUf?r`#tR5&zJoH|NpheYh{0k z;72%ojN=4Zewr$qqo4g5{Ym!V^D5b2RLlO7cB)+VS4FbFCb@5FWKYxoHeL32%zaOg znQ7TS%**~MMfPkKjLH7FN%k)U{gts_NoKA`_HX$9-azLMjQ`A+{TD`mx5!>-m;KLx z?0-jP|2H8glp`lxDkstiqjHQ4sF7nbZqCWE`uOJaS~*T0%*%<^18t1C_<|f45)KUW z^Q~=iQp)(b)=U`S15B%y$XRtpPHMWG)jH%X9+#8G9&ykbm%Gn@Q&W5xbWy{Gfma{Q&HbEoD zRfCh$!}q_G`QQ5j^+48JG|S0lFLV3lY}q1*1Cz5Ac3aKL**Xsx+j>$?UKS8=n-V@N zR48Y=QaSm>a<-@4fp$mQf?PQ}p|ulsJCoEd4ot}@gxz@mp|e{)v;qUWV^GBQ9<+Om z$l0?J=H=|wCWmu5XYUy~`_Sj^s8iA-Xa7uSmU95fA229~D`aOyDqwqHnVf@C^hIlKX}BXZxIPMqV*DhmLelWO^b$`U!JWC6+5(AIRxIhFQQ+S6!xN0f7VwVYaF)=pFZ zGnhD&Y|e6Fm~S{3u2P&Ps!nQ z%;^}Fb1UMv)ynB4P$y%zSHi5EJL=@zSuUrmM9y7sH-emoIo;_nE$7}AzWYA|(73-! zPEU#)4p7cR_&l5~=TSpWA8{UUmGeX@^?#yWPCo)qRsc><6#~JZE&;M0D1$*c&*Z?6 zoM*9nc3jSLr9eAa34L;&Pl0Awkn=(f5adNPh6?1ogzZa=a|-9YQVer)hCAiF+EB!w zSvjwf#OoNoJ}qaYP0kznfYF=1a^Aw`tw}kfIF9zoc^lEUC*-_C;CEACK+agB9IkAg z_XzTSznl*$_-;5?&WAN}J{plTQ7GpVlKre!&gTwb^942yUn1}&fu@G#d{qwgziyH9 zO}dR?*YNHvTqYA|oQFsZ0Dq^RAc zs57Q$j4`)F(L}wXDea1`%G_%BuhydI;!NmKboClV)9_!C1~ZDT(WdB9wwE?F@Ml=j z^b{xs1kwqxtQe*gU5@CQIIY>MXhsn*w-$42^(ne`26QO8jsvurIe^x>PUA?63^bd5Qiw_|lM5YJfP=;t(GfXLZ zFk=U|0Ro3$d?+I2C5j%F4kU7TucAi~xS~kWBh!GfqZm7yu}bWY!R{Ewj_p(QxOzpa ziWTJu8s*e3dIEYUwkTR%s_01>)c+(5PhL>8#(_aaPpwk)v{Xe;huRcH&&X8tOq|a` zw9aQv(X+8Tr&7^#4d~{#00~e(tLRErY9)H-mB9$hn-4vTHsXIidKb8W?}gYkF?Uge z_|vZ_=Xudf@?b{MW*jahiOX=hY(mk?+Z1hKudYDgig`t^Wb7(LS_yQGqv*ATingKC zHlXNr2wzW9*S9Kq1NJv`D%#$y=#9+Xg#FEPigrx#ZEE9ui`pc~-8QCZCr-C#0)uy8 zcxRWQU8RcNRi)_NjNi@pJ%!M#Xg6c`W&nQo)hl{`xuQM!Fs0}N?TS8Fr07HV^riwk z^YDnGj|?gLD7t;bd8|g!$Ey{60{W|{|C2a8g~L+|iatH6=rb?KFhSF#j+HCxfwMid=k@7_dUG)K|5*^76p_#JoxzCWwzc!#1N6(~AUuIQ&|d`ADX z2}LIf{yDl|vp_McPBEKpXGXCY^YISFJXf)VU`{b^ z*~M0AR&3RA#TMa{icRW-Vyoe|I2#yWo%uBEmQ*XY2Jx28Dz=QdhGm0_Ew50Fdvvii zyA{hQgc-%w>fzgqY7|>1119;#BIedbd_9~vuZv|dzCoH|8>TC^5oF`PahYP9bSSnd z$!?aS*yiYNF``)RjAC2mE5^YqmWSpxXl={*cJurQ{r0Jf?U1F|j@614FtHO8JCkvt zqu8!E?N+4N?sbY4^(eLniR?M6SaGIedz0us{fh02?SA<0U#ZvuY?n?c#;I5ApasPa z#^;bx#SWcO>@aK&FISA?My#TX?}DCI>}an4V@DIPvR1KUGJ*cFr9gY!fMQh>iXA_! z*a;nqok-@KX2nio`;;`rYU&g_6~EJ36+4}vXA~*MaUymm!OkMlx*^5R#)jiW?A&6- z>T?xaNsRMSU{0|{w$E?C;DRi`;lfVEn#k~?3dJriQS1`>&GawLfJuJauU)ar3DQC$ zSCG(^By!cPVpnG?b`4|Kki@m6FsN7?V{O=9XTXqR*EcG50|9TCR;+zgu^ampyQ!7> z-(0C!2V%D%cAFII>`{z6U9qkl#qMJ4?gAK4>>iwX{>Qoz?(SCXUJ}2LVE2tHc0cy_ z&nnhK5UvMf56mg{;G|*?!NX;WJu;|RUx8weq4RjHVo#v`#EfE3iuym5rP$N?iVYw( zK)`1(e5OyaX9@Idr((|$Xs}4J=hFbE7Z7=|0_GJPYFF$f`Y#di?1N#&K1A?C z#y_fuImJF+P;3G|q0a#(HrcM&=hKRPkq0d>q1cz~<(Kq1(8Q+bf8_xEulp7IhH?IX z5SwnO<`0A4ra>9BEB0LnAEzbD860K?75kx9u^%&GRI#6$6`L(m>}ULb!S>f7#pX!j z_iSi`dBy%f=Z^u!=F_1OCKdaWfPdvew_<;%LX%<(dDMRahktq%`Qp?nUGddmF(RuIB#nS;q$|EOTk&)P zq$9{(viR~G7*%}D8pShGfM6N(imz1%bBeEB4fw9pqBy5(@yyW%#n)|9e7$nT*Uwcv z3&RaY6yLB`@r_cUU-4|_vKJKJxI^(x5ZE*y#uU#X$Yw5dDZV*7u?2#;nTl`ez>MNs zVY4;K=2a@bjiLCqO^R?+KG<U~>ZP2?RcoxYd0ABVIkD_(>Cr zpWLDNDFm(|=&9L?pO&w9Eyiauepa{QbvU0rt@yc!))QzYK^qDbZxr+^e*S{u7nUpD zgzZI>ieHQ_XH@Y^nqXA%X7rlTxHKJF6u&GL>Zt!^2whIl%j;lF@s=FG=?WaKm{t5r zoUX#5wGyTjznZygGGR#ZYx@*$>s0)@R>iL`Rs4oD#oO__kwkB1{FW-kZ)NT_66&l~ z{C4JVA6ER1O2zM_-_<}icV#GkcdFv|XGr2%?4BJ}{J9)xfoa7D znIFV=koo8Hp$_nW!G&_@rT#A(ioaM21m&0>AHr#<4d}mw(MyAhznl(a{W1Yw$pz-Q zgpLnm%ke%wOmeT%f3-{T*Bq#X0mWZWgL+_og!z$nAh9;L$hjXygkD2%whlvy*p$SAkLG%+uKVkk;Y(FL8Pe&C0EDPv!b{n58 z00ciL(C3vfr1%%i^I{bL(tui+QGAN=uh^Nd(f%f#`hUZ~bd}=YvhgjV-y!mSCCn*4 z)2#RpjQ`N9_>YCqrT9+{VE@yw;eF(U7;@J@Xeqww*>ab;Fe7()pWHR;4fhH>&{VJ|#0!NNe|D$T<9-Ri2&@ZVN&i{4A#}iJv*P@ z@FdVV2%pPXeZAb3*)SpZyf$i80t<2*d*z;A1&m)%F89JLV7v*xi@N1roC`y8FKLw9 zTqyTaG%szIdl~Iz^+0l$kH~H5LGOw>xmT9Ty(%3@rnOJ*)opUGVf@-+xosqHodF1P zQ^CD~B-+yeksGJv-o#$sOwf)_xwqhZYpvYd@_>XohvnW*0=Eyyy@Rnkn7B0R_=4%atBHJd6Id)3Wnsqm@0RuMD9y{a$m;x6@t8K$bGFw?(5BR zM_8RVNbt>3xxB`>qcd{fk=%D1$o5@=zB?v&EEif~Uhexie?V|vG~5r%<$hEq_hW*7 zjKBnT69|5S4X+C>|EzL9#pknexs!czKW~%!MXlT~QvkcEZ0MK!Rj=Hy8|8kJ2P8So zSi`prd{-p*`#QNZ!*YMX@kbng?2-FZ4j{i~Oe3jfkbH$I4z<}JpJLPiM!~LgC?!T>a|7(<|8hIh!|L=th0s>t&$^eRmNAHmA7a@-fE7##U=7qZ;_WaC~wJ} zyfvy}O5RfJmyXFxXFh#Y-ZFHS_sUzdt3lpcWU~&zGdtw1J1uYhVtH9@@-{$VBZ6lm zym6DfO=jigRKTRX&1g5HoOjHEad`#U?35vIXZB*3E_sDB@^;IWw>yD~NP3T9 zd3%y9hg5HGeD@&$M^Ue2P~Lu1@(wVdQ(kF4OvqbNC-1;Cn3vaZ(6qcV1h`e=9nvcA z&?TEb6lIe zs&qj2_yTz+xIjWD;$NKuz4A`t^MBsSrSeW8!&7jm$(46%p1jj4>jCfDRG@9E1?H|penb0q9up9{fJlZcX_5wC9GB!klLyWz|m%w^2 z4a||TzPM%%X~<>Z?*D%N8}F|nqWcRJY#<%^cU^lvYjF5>Qj5+3#*jvk^3L=){Wt3;CVWVaG222?1qiVMR^tlFf+A~aG3 z{YtEc_TnlfR?kr)&EQ9<(#Dlo(x${3g+TD7h@_V*u`C4!l~_*jH3^nM@Uc zTqV{mQ)0b(CD!jyB5Od24ba&T|BZ^E9j2AY&WBc*QetCtH_3xxB{n6&O(&JescukW zGXpANNQuoep;?J7$aaf9C34Gvc1r?pSpzt2MM7I6l1I>O29?;hUy1FQ%P&-7dk4_k zp7|ZJpc$r>*pb!Qu}6u5bRZ%AC70N#PKlixSf-uZmDr_2iNY3`RbtmtKy){ZcWVL! zb|;A<`gXDX1$UcE{bV^d6!;u$6OE`uQ@_Q`_8iAurpdApc zY*pfzLM1p$P8>@D$6-@dB!0veW|TN#T!|AIuVyDs!sp~>B~BquO*!>HHD8I-T$oql z^eH83$CNk&!!r>*t6GUVgwD=W;+!lc&dpS!9@~`&ubd-$l4wNm{AndF=uzUr3MHD- zU_ptC(7Jd;iAxx7?oi^=MkOw*QG!Ewq6NJxGL*QI&;KN@DpjI2TZyY3KIlyqg5D+V=PT{2|YKnK^Uj%o*!I8f!~2oR|L!-@=@U7dI~%ODZ95);4z}W@4;kq zF9o*Xxd%-D5Tw8ppm`D{cyb#Bdhy=d3P9MW3W0V289d!bfoC>RU>le}i|}U;P~bUa z_&oUaK_GZl0`RB=UIb5Q+(6`yV6eT70)MKcz)KM1rG5(RK*l=}_VNM>yi!VmS6u+Y zcWtJ?Ye@Kd0|ox90Q)JhyNd#QS}5=a=-$Bln;R&ww;nh|fw$Tz@OBk|=XVgk5B&a8 zNP)ldz)lLhyAkc*-%J6h+Q56Wfn5}MzZU4Gzy~1S?*q0{;KLRQd;~@xCjosFIM4(f zpupc&0Rt5HWE}-?m^AR|b_yI^M1ju;Xs5v6L%>lA`~#UC>Y%{qYbo#r=)TxZfiIU* z;4t=o17Eo)@HHg*CP;w+g985qjxZq6w;L#M6oMS>r@+5fQeY6YgJASsCCLPErNDlY z9h*pYE}+;CLb0z>Z6{f5Bw4~2K1%pzQWw6%)m;l5CfT!rWN#IKugUa9Hj)gLDhGlj z%lP(5`4Gv)L063Dk`|IngTQW*%hr)x-b8W*P>BpqVmM0j z$>2X0ysHcV1sk`D-3NuIL^0P{1zq$!W&Gav?&ZLh^EC zvK)+;Lzs&})B=fHz;Hz=fcqtDNxpP0FhKH3B)+VjWN1kFa%6V-Mv`0WfW0I`Q_5Ft zBKb=2yt13*pEi-aiU5RNRSz5_`RYZ$VYEGXMpg$&Ufl}pCi$9Lpo`>d!Sq^$VN+86 zSu=2m0NVLucEC9gt4j%xC@7PLmbS25Z43hk- zoh08mo8*mo0G{t^A^b`Tu#x0nuOqo*70LIY#J>UW-*%9^3F+?*0TALo(A-}S945JQ z5!%0VFUb!e^9TA!ey|kiB>8tqz&4V*kPzxwerPkv4;w%$06`u;O7bJMz#0H?kL)M8 zdnqtL@}syviucEm{`bgib2G`0gYV;@+foPYCb?%j$y>Wf{zD{)O9#nM>j_le`T~;8vHPGzlC&@1~01*B~Wd29cMz${{`A-P^69}RI<(C!#y(I52fEHjE$vYw3&YdK`4BA&f z^9pERZ3Mt*7w|eVc)g$GKkp+M&I|dCBw!V=pX4_i02E*^!uGZShe>`5ac{eT4z&L} zr6glJRmS8X??Xxck_W6K`LD?QT_o5YJ4}keVH_4bM~*A^4xXoXM(|sVwE&o&(M3vAD=BBzk#ZIoowbjY zvk^9T6)E%DNSVKZlyi`90iMrYi=R`)ec>AXdg^NYYAWs*Y$RndGHk~4g#>_Ri4Q=! zB}YlQs2SKv%F-}^=cV01WPp@qRREYQ+eXUrAkYGU@L~{N3??lo!HOhc6DgNe0==YM zS_|yL*Fz)2%SwU$q+mW!E=R_fBmL!gZ$-M+K2k6zC|7PEWff1#RjWw38hKp36>X3B zNaQdntGAPK%_dT=%>y=*^0P&xtTBLnq+HiT%JpD+eFrHw)B*cRS-Xam8^P%3D8)_4 z^k#*WU#uhLmO=o$+L8WNq`7q?DYqfx+kthJzyVV3=q6?TT2i8|q-+SI{WtWJ@=FB% z3Pg7vB;_t7ygNk7ufeQiB`NoGlJc8oe5E>s`z_)(A)|XC$h}~8-zNO94AONX{sDwP z2wq)C_fQKdP}2%jv(nu~%A){09?I|ANx?3t^7v{}wg5eA(EeL{Nx|->@}!HD-X{D6 zCm22r$zet-+q&`7o5=L}JW~4Bkn#fFUp$O&h)(Ug;ub7kIq3 zl9bmEkg_{O%AQ72-T=*;1EjpwOv>BH=N(`lc)U9sh~TBa1vpB|dmwrr5zuVP{)41^ zzx+@xV#|y#9_1|Gtfse<0mIdhxyTc>cT< zI7-SFt4aA1d=7V#^3_&SzMhTt{~DQnvzC+rME-LXDMuLglk#mH0K%hHz;06hwTP5K z;JX(5EI=ixTn?#@dQzQjr1Gms71jW|NfpDuN}!KaDG9){3t_H(q`Esu^~@#Ji?mR3 zst@{K^*53l2$CujFhHtWN~#7T?J%i2m>VEAwvlRXCDrODHRuCykMAZm0r82rC)EK6 zOKv4KWf7^VO{9h(Xxds*(>qDcK<1ec0tT*{Q%UM*7XZOV?~Ra}TLmC--fB|EAoDR@ zq~?c!4Wt$rKs%|0Jb?H@2vr2eMcYU%h9JersJNHZk|baiu#eQzDxd>DTsfE2a-=H< zZ3WU)<^Y>WJt+iiB=zJV5CQYCAR3DVRges(u{v%6aFo>Qb)?pSSuFvZNgcnE)CqOK zeo{|aO=?{T*hVU>VRce7si)SGT3-sF0Fx0n8R<`hkf-53B?;&twIK|2k~$UlsSqkM zbu%82*)%Yiwi7?42?o=VK_i&Xm`mzR5YF67>a12$XXAM`o=@LI>KweEQ3ybQCeWQZ z8$jGyO#tp^_mMhx7pe0QKYtN`uyYzoT>ya=;Q3teIBy-=f8i=p7xBO$QqPB^7gUkD zm;ls7^IB3bMB)qkNnL^rmOwzLQFSTemm+*=AF0c7fHqQ>?183{D5*c$N9yGpNNsH*^$PI25}*H9f9eAu>8f^8uLAMah>QTML2%7n-~g%6it5ks zzNU`U>%jbaWPZaMQr9BQjay0mIRv?BKdEiNFA7P$WdXjeeoz~TuSO)ZKU4aN$Rf=31@4-q5aecTS@)hB2v4WNPP(J5BHPWZIJrtDpDWYMJlwPy15ZR9-AS+ zOQ3TO9R(rr z3bm^C=aBlI0qiFA{SBmkFqhQ*p!tvh2>Hfbv_Jp=}y zgXqh40C9)wfxV=D)lBNwKA?}(Z$R@6!UkGM{bvKIM`}s^mf)O%DMJfNlU`Mgy=B*iV|dnly`mRRH4RT1g9nKHdP3SHc?75^G6| zBqA~y%#x9DN*igZV3G=fLX80KX`Q5{uOuy_3II*GnY2t7fRbb(ENeGu*$_4dG^0v^ z0n$c8fLx@_>n3ds1kXoC`3Nfj|AGUg71onh)C8dYH8>HpVn|brh!SL8f<&bT&1N?H^6oQaIj4wE*wgS7d1q@4qy&#fozJkY^z z)GmnRkOobsLCa|uA)}>8uzV?iu#2kzFojmrS~iik0`V)_NV|jp&|Jy`>quJ}0zh}! zR?>a~T;4}o>rT?HK-iV-r2Vv+G}wOHst(ewGJv%J1iX3?+W%@0M#2CXuWlsmnjGLT zY1cxKpRL31jI1K?Ca?m~X5D21vWBm$bV<^XoOFb*u(Q#f5StH^_IN*OTXvGxgZm$nNP8j-M7l_O z5>@tOFKN9Eq&=kodr5n`owR4_N!tcS+YrBP0f4Y=+kiu)JsSdA0A&6gm_3K*=kWYI zGJ75Z^wpB~0-j$uNZN}C|0BY-8^BilrqEo{UgCicH2X^fr0r+~I!W7^2XvG6GJrL{ z_G%rlkF;Ho{&ghya|>y^!E8?>aDcQoR+9Fn57NDVeuNtM7{nh#$OF4c z`x`QXy4F5HCZFK`X)|dDk?}#qf3}UZzi%b&AKj$E>eW8S^B0u>?qA}5IES>ag1`pS zzD^?Tn>C~j96~X#we69qEorppSHC z1F(;Dz8M%GU1%d+oJ+dYM7j%cZqRxFZxw*_zCxgzbbknFCq1B$E^i`TjUYnZK)MzL zI!T94*Wv5cjaJgl9H5_cs~*@)dR!Pl;-Cw_Js!02dr41NM0(;%(v#Mbo(x_o9i*q? z9zvS5ZKS7fCp`n1g}X@4>?S=6;n@%}hbKLfgUC^k9wxCq8uwfufO{SR5F+mo>0_FK zgQVxT0C+DzW`)SKaDeopwWJpZflks(N&#e8+6bU_$`Dq*hV%-&S0<5u65>y8B7JN- z=~aju*G3V&dNt`aU|749^zqH4Lr?3c)RSIUNczMu>63V1Kk29LB)xts>615*ep)N( zQx=fk0J^C;q)+n!he)5km-I%^%mA+$c%F&)S>Q42DCx5ie>#MkbAa?S@O&l;c;?{< z>1QMHTx2${igf5`{hV6T7c3(ET-?tC7B-W&EynX=2-l1Z zn-7zI;VROXAbd$XaFlcytNKN|NnZ*+OS?#476dv-hr3r_4nZ%DRN>M`dP_a9i}V$< z0VKR+E$NpUz$VgHLb#P6zN`)a%}?e6`$@+-U%wo*t)RUEv{!)fm0)}&Xn)#G`l?>i zui8%f)u6#zUx%*ISMvbUuii}hH9mx0gGARtx}U8geGL*^*9i2Je*H>dC+Roj0IPw$ zq^~UnHjsYfQqq48VQvZm`$%uwMEcDL`vv&ig0NeTlHQK+Tk`-2cPoUw4RN>aCLQyD ze)|CF>sFI~hYRh0#{tsUA0#~rBKQLIUm~MlLFzl#kiM~s^t+NszuN!~;TO<$k>1ft z`aNNw55I!hLOOK2{#%4^LPqyil73$!>GwmJ&U(@x0NsN@(tn5WE)?jYCek0SBmI#= z(!1M9e+=LMqW@kYeKQCj2eyE)X94M3*O2}PNchBD(w{_FF9diB!aN-W4w3#01bh}5 zKi5F|^R1-!bpU%we*wH+K*ldXm>0GI1Ejy$Mfx9S19;xvM*5$?=TAFHe`yf_`%m8i zCOi5_-&qUvlKyfffJ|Q5MEa``U>BI~LW0*;0|!Wd9n4+_^W6|?_kPm%ECmkYm(5lJ z$mC7L?JWg5Nq;K|XeS-+R{iZ(0DRwR29WQ*b!h*;)RX>K0=AO=ZVT!CApjBYA;Eh{ z03EHrzW~@p`UiEuX43cP0Ue}&7zEl#{|M7V$3 z)xcrWp|h`taw&vF5MWMoHHlaaHC zj8PyS9V8={fP-Y@?IL4L2O0S*fkR{zEC9f~FbS9qtRy9V0zy`9B?D^L7>hzxL4a|a$*A5(MlF#M84tqo$Z!G}odO1R2C$8ciL1$&)ByC8 zaq3Dk>T>|lOs)g=l5tu)8B>;$(EysMVKS!i00f-gOGaZG8Sot#GYiR>MF29JjZ9BR z_?%`k&Zs4$DG9*-uW@FGj59mPI12%1?Iz>w7Bc37*}O(FaOl7|2MHH+l5y?^GS15b zddXObxP^zwScI_i>i|4o03M51lF?j6#)U|KAu?M6o=dvOxCnsyHI{86V>x(TjC%`s zuh@w8zhn&=mx6d@I~kX)CgUe{WL)k8`pIbBNX8Y5$hdMH89(hKW7PpNt|nAl9jYtb?*|-VMH~Yx=MHLyhL>7?I zj?&y(Nycrn$+&$L8S6U8xC2B{glz!RUxt7_GJb{UJMnzyZZbBuka1TS*hvN~VB^<0 zz+N&s){+4W*tlmq8NXRW2DGE`ThMO;{k@>S7xXZAjr&1!|9%vrvxAIIl;8nm{y--g z590lIsEMvp00|$dC*x5h`u$om9>;T!i;S(?$#|lTjNUpjo+dKj2QZ#pNyhUzWb_>% z<3;e;zJQFEQ1Tr}yA%9go(=Sq@d`43Wq^!VA>1zLe*>D$cx@vYuOq>qLjc})qeOdl zk?|(p_ZnopMZiHa;663pLHxe0Wc&rs?-r8LUr)w+$ozdCKt}sP|KSEQJ_-Y%{TT5F z@O%J;{2O?F(nZFn$o$}3GCsrSKa9@~k%4u;0mWk+sw3lb9{|EHz~swjU^f|utAGwN zzCzqr`^fmZ5diaVDgmS&2m))#IFdxhx7}pGhBO94WPG=aOs<7YM;O>grn8+)9`8aU znPLtQ*+!<+1PqYrLWJueneIgZh&>4R>?G4$2Y}933Urd`2Q&W$GGRoTfwjO*pT3UF3?4wba66fq5H9l& znOV4J?ISaL0Y%L0U1a9elR0WLnYp;b;575WuwW6Hg$9{Lg=7{t0td(}Sxsi?8Zyfu zc{#$%cad2EX3%iv$;kL*+{c2p3Yk~!Cv#jS&<6CASzQIRlUW0vHAr8J^tJH+o3*>i z9G?S#@puqUK;$W4Qs)Dd3qu;p@QUj-_O- z-#})xkIW5+$^2ClnRlX;cPV7v-9=`{Trz*-0}hh;+kIr-8zl3-Dl+d!x=#3i%m;X2 zJDCrH!S8mG39V;7w291zLGVZ=ncaKId~7zEzu!gX<_y<6vj+lf%_9?@Qu7Z3 zWIln~=!M`P?K1^21NhsY8TCU%kKBC^~qWO)vd!+_k+rm!tYtgNTE3U8i<&kkvei{T2whHuDEg;JKhzxEVAnWJ-WZeWI+K}iMwPf9b%-bQ*Z3P02Tq8$$H!c%mup1+ENH0 ztOs#h!@xeW{?Ja=6AffN3A!i2KhnFFtfy)LNd0sdS+7F!^gMS??m@yGYZI z=l;E9y@zz~Z6@peb!2@oo2>m1>_dgDkMe+a;4oPqZzAge;{L`1o5}iQ0a>4d?x3%d ztj`em*-^6oeu%6?NcednSzpwX^(BZ7uOaIz7qE`3uakgPKtEaE)b(^ObX+8PcW^t% zOO-T{&WNU5-a}xaY`4B~(jOk=L)z zccesXmpC$`)5DS+?LS8lxsvED7dWhF+Y(1c^oh%5CAw{i!*r(k1JS)p9O3Aj3j}?L z*DJA*=3yUs3h>d`{*>DZr&?WoKpUrPW+<3TNOopNliB;_}k0bTQko z%0XRh88R2(O#6)`En|^(<&68W$~ncZ7Q2ELy8UkaC3rnP)$ek-B`*r;g z7w)7Jqk5LFbo|pw{fg@$AEiKtO7kk~Kba@X7?! zOyJfy9bO-w$~!%N{vy%iLEXf$3;U>G&p9_aI_7%&xfgLjdo<0adC|gs4l_Ev-w}+a z{Y=Pp8De@CYuD&){f;pAUR1wcD2l$_??~jXik^Yzo_~JqSgI^;qI6UTYyFaJMxTlw zC$=l0hRcYvSOsO3)^O#;y^=KM&3q}KOLIhVjwey_=D#_{D$;F;Pz%vgQ zJrHV~H7VDXTs8glQwv1Sr^^l@cYJ+S#*w`GR5JvEfP22j&jp}Gg8st#S&d_p+<6m6 zdt|529X=^V%&M`6mwnmFJxTdQ73lm{Tt>QRSYx?Jarrp3dw@%3S6i5D*4<4D(r}9k zXPl9i;_`Z3Q(Ruoqj=M1&6$+LW!BA}Ga>BoDjrAn#F?j#I+8af>`y`T5S=SIIZiH{ zdPZZJVOCDhb9sEysQO$kXA*I?AWv{lP+m-sh%E^OVRDqB7Hl~jDb8v~Hr`O}5TiL~ z;`-65o%GGp@SBiR$PIKwpkt{Rt*^Siu3 zZ$j0KIj4>BB#)bs>vzk(oYM+~tck~J@EaK%i!#0ol;?flX8qf;u(EH*wSW3)h%)Mk{-DK4IK z!JHB&rv^C3==$09qmO{j(%BdQna3lB;^H~31Vo@LL9dX!s4CEmuE!LDf868TR*a8S zjF38-7NPmnOe+zb1)T_0S5Y3vlwX!p%+3<=9%oh9ijQ@cVoMSwF-t84O)5 zmCkBeHBiSxWAoV1L`{m><(9!H(z8H~8csn?muKT83*>0LQp{@@xN%k{8^6$77UC<) zxl~b-J@N4#SrRj2*Gy50AZUu_PB%rbB`R?qk0c#G%)LIh_NZI)#3`cX70vXSlEisL zx6i@z4xd}}%s3Xuoq}=Cadt50;2t}Hj*|u-ip7jD@Ol};NQ?m|dqtQM$0-iCOGHpi zC?;QPCZrvbfBTTI2)~StHIbFc%{q6ihi@6`;dyilO{a6{BD&&7s$Y!!NcC5ssaQ8< z{ThvviWWk5H>Kn1{y$cIc@|O!I9s1VNl0$hBg-DuEhWUR33kY2BjO2JqA%zQnBK`g zcQsROiePv+D7Sxl46hk*1$}5hr<3za=#vfy=l4k7na85I@#w)$$?NdCapkzpClGSZ z!N=?r+z1C~FwqLd7LlbI6PS*UqtU2r%2C!@k)#s8p9;s2gV5m3GUm_-2x*3g93 z6SF9OK#&v|6vKj4Fu6<-qHtXMaTbN=|7cJ|{@tSZuQlQbIP)Et(l9sKaP1q^xHrognrObY!6Y>MdFiO%*RJK|8HGxvN;GGp^QclZCFhQvtK$7~5?l^lKQ z0-rY$E8B3M>wmDXws2g8H`^E`9Lb|31YDb6cF zX<)$HHnc05O>vm??0FYc*}@;5;jEi}Xo}l9GR4(VBQ2n%bS0K7sQJh-6YjrMeKxE+ zW^R{as$v$O99!R-asOk@XR^r=MxEVs+5JOPT;$*9I81Rvvj?U)SmZvp{Qoq?4LamQx9V^dsDDC8^>B2M&q;LJ^7)T&9bJ+%`-#!gV)4`LiwQ(7rCCT0tVYN1h*g5EP-3dU^2x7d!Bn9 z->PG=MV)TzSYyb+5%=HZnvz$Ym&(Vd#Pg}~$)l6;$LW$+DtCG%eu~rUoPvqTSsvjo zPtD6qtzB$Ow%cTM$=qT=l7zwg%NWTNo{>v@+3`Z{Ad@m!ZH?4Q04@f*QkWOUo~F!W zqS~$)*j=c_%TYD?__Z8yGOA{>G>O-w{K1zBPASY1u)Yu4Rq5s5z#)-Cp_KK!BN4pw^Z zZg}e~3faYuu9)T2XCVhW1Mv50+EgLn6)qA3;zfctAWXf-gMVC#eSL^sznqG2L^2w$ zF!fwbRkb13_rcUUPV~qLq6d76uU=5S;)S9DcvYdki;2q>+SkXJ9{HkP^sz`r;}h$b zU^NRnW<>NTw{3|35BK8}^7D8iKF>_n6_*J<0DW`jU935{1pC@;ciMqjyxY$&=ke!v z^RrH8?kqoy^;pl{!@UX1F^|e&#h}kJ&kA!xm1Sdr7zekDm|g&%gp>yNTzN%W0f*U* zIjN*PqT{(DZk+gtAn4JF%>otQ*SW%KC7~gupab;Sf$ib({H$$bK1w#+ z5-EjpB2-9K@L$+oqG54mM=qB&T#$-vvm$#$XeKjsw9d5VP?vhnLOc8hpST@Npj53(pJBio)&SUPjet!hpn}tuT*MS#fE@ zBcg&$z@^z-78|%y8H&R6oRp2#KhtxO4@ZVd{_MfO42=^XC$pv=yqeYOLGSEjKO0YN zquH9KW_al2&&H7Px@6WlxNBLn9rT!dil2?FmeIGc9y=`PYuukHjlz@*4>(S+GEs)4 zjo8YBiE9t9n6w2D)D}=Q1}X;8J+6i~qep9M-fI2?>Ji z5xAWqFN@o1hKFI^=^7@r6yD|KZw@53gxgr!x8f57ZzLe1CS%pP2b04{b!HCNn>`Ku zVBKaPSDon0tUkF-mf*t4OKa!UC1&ZeZ7^rVnd{$-zQ@ zf|Y=7@W=I`pFm>_ZXD7OJ>n?m%g!^HeH(|FtsVv@&N9cvbb>uijKj)eEC-WOlqztA zyLQ+dVKG>0WX9Hzb|Va}Evq?LnOPax(a*lK;vyxR%rfS`E%(FM=l3wg&vQxpoHk(QpNiz#q%!G2?&p*X9W>CJR$zTd;a zBkuPMe(ix57T)GbkZ%%mI4_e7slxVV1-Mc;sy&lF$D8}}NsMZ_#Mbh$xhu-Oin$B< zfgxN+MoLep*!dk7*~!gEr(xqSR?iS57dacEt?8IEJdTaap-RR&-NI%ITiY|q){C4t z>O3s3+~=oh9* zJyxkRm5aoL-vJ>Lm^OnU{A04fW@G*21o^S5U?sQ$0_B_wFym_XmMi1XMG z{9eScq?yB_4oUSRvKEy&pBCr6#61YtASxKP8LT0;vw&R*Y%RcFWOr-kY8uONCktLh zTqfdA@d^!X`BTThXHa=gWgEAa&D{tFFv&NJnVQe0_LI)RXzn>M)>+|=yvRk-Tw&D2 zu{`q8Ww3~1u3F4j6|o7Pb)mBNMMGL9;ZOH-(!@iohaZ|KeZsu1sL2)aAM<~*%PwaVytLZ(Vg5?m^XzMQ2`j$48*Yt zMcjzLzHO)=u~G5w1+g=E=>)Uz#-j9-(lN>9O&xP12TRc}HcQNE!~{1HF>E-DnF{aC za7muw?u?c%cZMU+2tvaz3uhLpTK4$tUp5FHSzyMXAai#Jvd~aCv&iH1dJs32%~lA9 zp*X^YwLFGzZ1)Q4ka_6Sv+OOE*novW72Caf8riHXZ7PBNH)_(Tbq&MaEg~x1t>|10 zrA?(0>-5s-NuwHAms>6ZFHF@R#!VgGtcTQ7}Cp>b-n^MF)4i;B)hr z2!7!b-t7~b@~0Otd$D3(1l@SJPqES{*r1G+=lH!h=$@Fq|6T!)?auW{(!6;dF%im- z^`Of{ani(`x*T-r#Dc_$!dLyEw6?POC z6%=97LKl3rAQVB5_ZjG!tb>Ah#<7@Vg6-hiAXw4=ESPO&$2!I1qIm9^XS&6pE4IA3 zSQIB>8+Hu(G5YM3iNeUjhr1pNq%+TQ1w}VHw*+#$*P%zVjt$z0Y;6>a8L6??xI5t~ zE{=ZPf^H5od1x|^W&MyS&Yyp*vo{OE)Uz=PG}X!}OFDbl7{i*#9tQKzIoA2boTFCQF6DsDH zV=Q6ZOlY!W1ObDL+lbxx=Ecl{w8xep;JpdEkZf>a^feue8CE=7P&ZK1L#~QQOi^PS zk7?1NLl10EGUJwsCo1ZqnME*gppuVZN(eCZjd8|7mrpF51$)WwK@^&PSWn-|GGSI3 zc%3jDQXCk`Fk;HG%DG2e6}C>E;SzBs`g`h{YqynhqstQ5Xu!~6>bjA$^>t6;rOt+6 zfPqF!P@Ub|Sq(+iznEe&URm5h~`LWw& zs2;-)D=)TocnS+jwyd{TleW;3n$4DqBT{1@53kV~cSu(BU$=ODW4X(*>&}G*85UBV zs0tGWOlLxP@D-*QcC*Vyuj@v3$7MddSF;N$*c(kNHO*6U*fu^i4vWJ>A-gD8CB!7! zHsm8IqH)JnqiskZb81EJc~_2)e)^!{KbN~4zVS>pd@_?wiIYYAA$cZO2yY#jHDl_*gR7c3-Bn-D&u(^lI3p$< zTWc{ZHJ`>}0TbX zv3V#~h$9iX+<$HE`9Iv=)f4SjKRFxwd$Kr1#2;J$V)o!$cI$iQ!Ixlngd*ED%{stw zJ%H75!T0OP0VCd&q!Yw+!gB!7b24N zh4NTmpq{p0IQ=z4UfHeoh!3-kFb>-ZP@k$K9#_ZNSOxyDuaQe z0YB}p2iuwQ`yuwA&O6I`;)E{HkX*r2wsDqzO*NbbjK%8_mFrTi}*S%HJeHgRy{4|!zo7~EvL z|K^VPWMSxWg8qXpS*%#1FFQYSjCk1DVn2hmB~~76++v&V*ix76s>hY*W4?gaF0PQU ze86K0SMdd#ZdPVP<$AK%;vwMa3fWUp0nUy{)7hD<0;Z4Q!bfx#TLEXUWNErQ{yw(G zP;dqfbJ#Ry=jF5Lo>jY?4~dc3DWgV#2#dXkeQ7}u zomQIg2D3HLZaq0~IzN+!mbZJba7@Foo!Fv|Ef-U`*jmRPgP1Tx$%)#4Xv1#*&oE1t zdNL~1gsO>!ZcSvnFRn8tcr!A*5AsfYVe=!IrQvXCCU=D-HI6=|Hr-uZ2bmS$`3epq z3=T>PP8Cd*5;7SX_2}<9n+@-a+~pKcqtJ;mqK~d{8B>`@1(PH@FTu9(pcE{V+oEJ9 ze3B3jOA1MDi+Z76!qVP{&;x-Xqy zn%-Mc;yAXX)U%@9-KsgcjJ5pGD%yyw}=mdE@aI6g--z9}t4vUD%-T zN|#y2s8NP>nIwER#$~7;k7~HaL=U?8JtxOzlXs9yJaQ?fX)GsOepVXOfKNlpsDO_J z>*g}Ov?2ow1D1PMMYQNo%BYl)?75^d{_|`BnuPOh5iXFAL{}uZQj&*+5G7YQAtuJ) z-*wHFBso3$>;WZI5fh^yj;@%(oeVKdw!A5?K!xJ;4x13LD`%BRB{-9XV?p^`X+>uA z=ts`1S-oQXyx=IO$!hREc1c_ynHS@{3C}r>9k?A`%1aVo>hYHHqR5weJ>2?heV+VG z#U)(}Ey&^g*oTIdo$gU}*Y(lL>m13LvW8}Y|5N>>b;voX$J*zID@ciRr>6`z(~s29 zG|)tbBso?^Q~$Rrf+3Az65D^z#wbnYDgaBy$^$(Ko?;-Y2L6zW|4@x9QzK>LaNwC8 zUA~WV2m(%}h`42C;S`i8W@Y1z=h@qTs8l`GJ~GV?_Riusr%jH;Hmg|TAyy;WdLOza z^Wngs6*-|-!V`b2Q;zgW9Ne>=^C%boP^j{XW7Y5VSX=Dw**f|`yL-+Pg!I-xc4DB_ zD`)yuCp(@#GF)_PsH6UEwB1ilhvkTVx6z*vJ>Sc(8`6olp}WGM&qM`|^wF3x&ARTe zg`LgaSfb;27W(Bzc2T?&-C}~eQ^gk1b#u}*^HLn$Md!QJ)Gn9PEGtbOUK6nyG>L5< z>ao34dqBit1P&>HXo`=IYn0+jPGuV)S9q-D_)1zpZ!>Wy6qV*mRCNPF;|EUoRXnj zhI3=*I}zO)Oy^p;wBX?LKHclpy=X$5`1X77h(GS0^x(2(!Sr6Q9;bV4zfM8yY6noy z+Y0~B_(&9%+t5V9HqDP6N5Fx|G@3ddS0psUuDc0a4R-w(ZbQUNAqv0v7q~gMX6Au z9cS>tso3C0zHH&Z&Ld}KOv_Ao8xxs#@O@YdZpLu%n}p1>yo%!8#4hu>N;aJhe&AL7 z8fqhxo2lTC5MprYc_f#=#N&yT7T&Ci=*C7q7%}7Q!m%T9_QY9I5j#XsT8`X?Gx6(O ziWhS!a{s^`Fs(_J8E{u<@$upK`1y8Gbw3VXCbQhH@_StInyzc{E|32LMiGu5vi-h7 z0Y_s^d>Wg!W3?S&XLi}lR#H|3S3UmYWq1Nl=8D;-R9seeRz|=P)1Y`|qT=8P4nDAr z6quTJa};>^G{08kz?V6C{VRrABN!L1gY(A9s?Ex(om!if?kc>lFyOPFy{x6?v!)8D zy8pI#Q}c_0-9~6FenLX#gA%qq&5YczX@*B-+?bf?^2{~ai#gXbG#TteD<;EIthT4^ znAwqxCB1!oRw^>1<4)!iqYqTO($W_to7>G~T`SMheSXIx+0)>3H<`)y+UPfIfiyq* zS+y%+IoFP(4d!B}=?$pHWggk@OE$SY9E@Z%xZHqGD5%M4$*P(hN^@vF<=8J~=TGs@ z`UMU_l@#A#rly)#doOkQZgAifU`C;)xZNq%uKSp`ja>#5MYk+n4k@|g#^oxqB705G zxZF^Q-{tmY6e`?HDc0b2D+NE!;a;i+d@jw&Ic~O6BGo>($yQ~i+qcdsxU6^+=buw@ zQ-A6Zc#mtAm7)eb-dU2Q4psYy=tv3ZN_MqZ;4C;Z_w0HXr3^EJ4H?GLHb(Q=FsewJ*SC~-QUSc!aCaR_i zA)eWL(bv`qdL+ASSlPwyW!X5UjduG1)fBS`k0ct^Mxvr8)j;9Q2dcRwT}hp9o3vNe zB860wg#W0})~lS;|h;XN6La%a;5T1Gdb$n4`!=xJNPNLyW~l ziw-!GB5QF1U-2YlIvqYkaRzcdxOf6yEno+6OL5Rbamt>6M@7h493$mpVTSJ#=5NWj zFE_{bVEsQE34+^=xzD1w)UZ`t&Bz0HJ*OBxhch$5qePrOBk(cumN`d)<=|OZ0zMGq z9SPu0!r@+^ZHH>N4#^ zkuB7QiXV2GNE!JY#!N*C8}n?zir9HwrUWdh2yIm)W|i9? z#Ytw0n=L;uB{S_iv@qB{%CCijH5w7jU)gmT=|)ru>Fd;2;TmLV1S@%y#G9Shg_VWepVZTpYH) z0aHw|eE=TEGNf3Ch;o&uuW2IQdF_(5Shl71g@3KAwmcvyFRdpAKo*Z$G41zISqU9Xv;#{G}$FYH6j{>KX zdBSjb_E;Zpvj;-p=3^nf2w?{rxO>hT)UbFLJG2y=Td-+gwz1IjAS zw2vFJecr6>G8`;lpC76Wv%{1%JpTM1AzW!BCWpl_Re55@05^JEp(~t{7|tJ`A5KgR zKk#?ha%$`vdppbUUAGzgq0?IG29c(#duqSkm~!bFLh4((-YvOc~VsPWNsHb z_jFtOoK)X$d?`{wPBO0`#wkspp0D5KKTfW&x7Ru5?q*Z&X&7KXE6tRDxV>KYI_6GFv?navd7~s+$;s9_GbQEX)YKiA5b>w@FmvI_ zvR|VG@4DjV{{|FhuINcLq`cJAbv4C2%j-U!al~aKc3oMDP_lJ8V~0y>Di;smk5GF-6~~NVSYg;A8dh2`fbY1)9gIk^L5O}>UUmd~odtT5y}sr7e4e+TlJtVI zQEst@dA!)d7vk-Yi9U_Jc|5am;Sj>DPX&Y2B>gHj!B27uf^!mkx=K$<&8_qBj9kJ3 z=7?JiF~b@cIc4aR_uM)8TfN_KK-}saW z^wJ6C)>%=bp}Y9R$VeG);c+O}8S)6pMajO=@k0fic0$RX%S>)$oBfS(j-n}bA#H3H z#?JWs{NqdNaj7LvA!%G`rWltdjudk6n~_2sSF*RGx8B7E7h<;*1$qu{fifD855k{= zaWFiwvfd4c-4C}g4lvqlIM$AsUQtfF%f}iOMxL!u*^nrUcC;FaMej*M13TJWgNBJW zlVwi}7R|y$T(F`JWLmDWOq|4IFH>T-XyXn(aoiS`CSH`{Su(7+*@x1K1xd@%c&UWN zrKqMev)uJMdoBhyasBX*Tly5p((y$ByU~!@@p|=D<>2fnttyTQkLBj=b15 z7i&IS`C(lyN?8)hhdvxTQ5Y-Z@kKm7T(EFrk{upH^E0g)edcc7bJADSn2WF`t2zwJ z6yFHo;6=VnRAiUW!zHHp#*fAzwFfA(Z1J}?b{qUS`r1WWF!GYfkLE?wnKM2s(STNP zI;sUx)#Gv$Lv&-33vi<=^*`;v!LAtP%J(i*qT|9$S;c zh2W-!7ka3MSifd_lasN^T1+j+YG@j|!SPiT>)!Zm?MN5MVm*M>&~Xi)0kYv?etftP z-S&+fA2~9aRmn-|C#R*_Bi${X%qpTfrj4$O-3C8Oi7#d2pj78a z*PfIpmxN&cqYT_0Q5c0|0etp2%r$M28~$&fH#dzJZ5K(J$`wr%Yb19ss|)ryd2zHI z!5%A_K4HpFoTQer*{U?Lw#c7?`bo_!$;4kzGcVNe5mr6P*eqa9?P^pKbE2^s$tuBg z3GbZ6{2J_Y{@G=))NmBF#FT2dxQZc@(w-J7Mn3e;KB^Nti-3u3&~3S*{{@GPsz5caw40TLS}4?-0T7qW&qHtfT0 zY{xfZrm)EuDj^+?P}bb|BxgmOSyG01xn|gKW~D5Nm2zkc?zm$98Rs^pcpL$2gE$O) zJaxNQ<_rgRMFI{__M)`ki9JOa+_8Zyr1N|{b2;MTWG=->yxNN%_UakufEOLrP=;62-Gr@%*oBCPKhz>$+Gex7hxK40z{ChQ6m^s41}y z_Z?*X#2DQHklOcknQZ9J^;9^zgbYRdwgsINb+Eq}Nv zWv(JS!z`%~+3sL$8_+&p7%OAs_(DD_`o)=Er{eQ#4#N@Dp45U46YfD)#6SE&`eW`z z7tI!USx6LlMM&iNM4V^l*tM=&bh$)!;a+w+E2KERQm_J>)x<>g|D)_p0OTmkyzzPK zKDxTPtGfEWr;nM*Oi$*{Op=-0Hz61Zgpf%f0m2b(1ccaz4-^o*rQ8J%JWv!x#AQ_Q zTt!yIo7DvrSkG1eySj_6=39sV?|G|xju2(PFjQYv-EY0`^FH_Udp;eG%gfw^`rzQy z$fG0YQ+jzuwk7%Xl8Gy|CLKpK6R;@o3kB==82cCavy1RQ5Z@wdt^JK1DPlL0KLTmL zZ9Sjr-@v7?d$F!#2}-0S{S6B{_{&h}RhATu*t8|R$oHEOPe=(d+Xghp*VNCdZ=OaX z+!V1AC?pe99Z?7t73<0u$70`8l%-lWu@qbKYqW@8vy;-&L{^*q-q_&qTN;f5t_GLe zjl^!LNSpC))O-~9SG}BAoA7_Nsg*JvcuvjB77|XPU{j7EXwO67C@qjx6dANAlqcx4 z_as7^-$KpI$)*5u4fFepJb@dg`;;y0%Et61B2xL&SiW7-?7wM`tIH`gIIgHD{au>I z4#Q_auV|6$u;_(9V*aoz#)mYC1M%L6#JX!3yC{M_hmPN3^tmlOxCc9aRAc0!d#gMv&8&xedkxw z6dz+Q7~{csw~6Qd9Q!fuizz^@PGFfJ0E{HMH()PM^?#MJCq8G zIHh1#{IC2H8felqtFas^1*(H@y({x=3qw|OH8@R=Ud9M&^&Mb63s zkSDTkI_q8=82foL^xJ=%=bp_yIY|6#SeA#05vH$Cla?|<91zuN>Fe1yGO^kBD0+{P zH-`4d2b6+E8?M0G9=;pVyF|jF{^y# zH&Wm9M!K*cV!NM8QerVmHpG|Ve|)irE(PQNJjQ=w1l_9QWaehGgN z*vZ@3dz^IIxh(CRBr3M6hZ2^cH;k-NK_1N#zx%gRcVQ_NxZrer2`){g7QIY%%$(OH zKeI$8AJ~ZhrEBD>Q*Z67z{5vIm2X}v=SQxP6sX1szstY{xFXxtm5m(Q{N-#H>zZC? zq^_Kg5Ff%hicVU%q|AKtRQc_e{UX?niOj}4w$50`I)8G7oM5Vd*}vH3CKUg2JIc+@ zpZ!r^X?0BOKe_khM(OENgI#+L5;~Bo+8K!4yLkkLACBb*mk;G~L(5?}jo)8tbe(xp zFY7Ng8YTM`L@kN(qZXi^fx zeW~~JN&18T6mXh>x48;N<>%csHsx=Esxl}+82D)hiRSHHo zHw}LDpxhmsVqLW{FTI@>skK&j_`PB*9UUL53rZ=zx36@6gRh7GzCS2Pi#aHNSzH|m zpvO=Ps-rzc77$+jt)B~ugfIhS_)$sgN+xs82?-4F4Ll_jlZ3zGH=%U1zdOf_T=(pS zt;grMSDl5S*^?8TJnMME4|A_rGuPdnOBTLQ58pow=QvItYh7q0g4gHL>u|UfylSVB zkWHduyg>?6sWEfWA^B;g9?V;tyaR&;i^^SD3zpP>?k3l32J;uFuXS4BK#%QY4}*F! zQLMYvn7y^Hv^^7hgu*5`JYy)_wQ@u;?jV&FkA}j=i;d##nA{^Eb-UE~4V`>99Vk`| zJqO8Qc;xQ4V1Sg3VFH%4Wm5gWkMO=*)78_{RaQ(@XWw5%?qlCL*Va6gTY;eFT}_#Q~61|y^mXqH+doT81< zp2M+``E8N#FMm;t`@0_zy~x|r)dSdQY$)4ByYc=08y^u1E7xwWrf;ZczkDUwQzYB< z-DLELlT12CqRGwRTi{9);9+=Z|L%`D9a0>Bq+^Z$+wt+V#@*-M1LxOI0uE{NG!v~S$=5L6Z?SS zGEYs<_yY%(?9A^klcCAb7Uf0out~t6N^n=tb3@f{Ugu=i96O9(aVmq4wwS=Xi;3+h z{ig$m^W&bsDf|E%?7WGygRA4kV*GwOr_Q(N zS`uQb1_Jdu^l3@aR6I|2BDVVVSIb{z{(JYy760R#op$%bdfmZ3U`?}7U3dhcy-=65!iaJS=Se>R5Q{jIQ~u z`8b`qdFb)hi8%XmBxArGlWy%yKoYiv_Y(h;SIMa+5v8inH(l%tT?aO*`M1wY(Ai{n5`Wd<;cAi6+@?kgh0*C& zYU!5Ud3ED~u6cp_T(Wy)7ve=MY;$EkU}mMM{P0zqu**Mvl`UuJ)g7%9Ze96~Sk(lmD%Y2O}t0Orz@}9t;I> zFfSl~kB9DB+9C5xh&QZwcy^{bRhJ?ITZq|l9TxALIIM8T{hgDWN=y6xFXxN!LQ}B7 zKXO!lzxn&t?@e`VYU|5y{f;=8JrOUuOHHScCt&#ZSW-{qxLLZ`f8S)df9B6O%GXHE zmtI0tB@ia4hh_mm*2trbbQUCG&gpn6#q^5P>Qs2-%U`GBAw$v6%=Nmn#eYm%2kM zEBg#fQ{*K%O#D-(1n3-i2$K?iQ@T40NEOT8Q)>82dqR_=m#RXx0=ZNIa)tnax|pmX zZ73D`rYt4130a1Ag+aBb^BF#<6&-tMP?bAxa;jY<|4%Tc`bdLH+L4yaN5%H|m*3<} z<)2Sif5t>ZR>Y9uBHLD#_J}C(1c9;Y}5OmS79Vk?DV|pHAD#DQ13Y_4jJCxOZ{0D~+7o%B8@H8bp68^>*6n$Pz zMDs>)k^qK8_81$|C0(=yU3RRDre#Aee~AHvvZ|U5`49ewo6*z*ecwKfIGp^Gm4VI zooFJwOj0zIR@HRqt}qY|?J#~Er6y9?ZoSY1Nxt_!D42@Ga24zlb9DAU0(%D)T%aMQ z3bt-a;iCV|Tb+r~JD^x94C|tc(YPbNn%g;7NTv>)89^59sA8C{TM9^LvSUxj#Y8%T zO@Uy@$m5PbIA^i>2gmqE;~xOu(+eUJE7=%00-cP%1VT@vLu|DX!2)GM4lR(VNv@Sq zRk};QeBwL8VSt!wOon4#l5EKogI8hP!n>IiiD`n-)WWzONd!VFgbResmk@C=!da}6 z$o^Q%uvc4(Mqe)T)z4Utj2Weu{I~Q~vP@ss{v|;%!xE;H|1MmhAw`A#{1I zN5bjxOC&jzmvvE=#H8e8P$biuoq0`kHSu0xmESJwe(E;c<4UOi#jIBJA6y})eKV(J zW>UAyH?jrl>#C0FHp{;JBjJpH?c41>??in28GqG&Wg_^dd-f~KWvXuhqk?_B~L35iu_3Sg*u9wJcos2NHBU|5z>;f`ikyEg< zoP0yFbnI9O4`ud-Y`vZZ=7{d4-=Jw-+3y7Y`kQED+J)W8QYqOQ582@<_zad(YR#M% z5oxnveKZC*EL!6sJFY#Tz%YUm;E`j8Pg$Wm|La`$JDkV?8qsQFd%hdV)~#f0W4f5Q zJ5ikdBQy9D_9m1$<+npd=#KY}?%Y2H5!U0?#|(v=ieg#T^oGu zMR>7wC=Y}|GC!EooRK>+)P5J~H(c&=hS*--!D{N0u)Kf3dT*@`h0;}+)mYIXHHtji zNK_pPR`w>WUpkZ;9I8xJ)AU^I1VpfAYq#NlCU=Th*YsRQ_0#ZYcv#o=fIOKir-2cf zK=Mo+B^j-u^ur4jNIPuzbXWEf*xaA*%G$%#BbJrU)PCPdW)~eSJtyvW$P0_`~@2DL_N#yDrLI5GMRT}>wSg3bFSha z*R$QK=!D)Df^hB1)@QD{Q~s*e{5HE5mM-l!(s)R{AXh$^;Sdi2f!PsfgeKHr$GW%W ziV;_lq^@MiD~OU8Pa(0S;FXeHlBBqiVs0C|23gFBOd*phK$zscxGcvJ+84z_GF!+b z@L{X8{DlWcx6aiCz@Z*&dG!wFE!xd zHwD)MF6C0oh5So<14x$-XIi`Yt9-LiA@n+oVQh59yjNECtPWg4q^CM?8DlSXtihnj z5y^uqNFfQlGBUzH2&!~)Z6_d{U~~KS|IxjR>m#r7@|j&5o%pvYWoz~~PZKYq!&N#w zP{ zbzrXLWL;l9gGJfZn-9|JzQ{G~UW_>}goV0F1x*wX@_+wLIWu-_`Pp6hJ+B_xtcS&T z0$8SNkaNNF4JZdAc!Dvkz4Xd$Mm{B4`u5%frwv}>@4rfj&0z(71$&o#LGXQtff$3I zr~}{(WC0&3_6xB>4j=>lG~j|fM@03GO`g}6JL~!}aSuEp35GoS(X&eAzuU$TFx*NK zW2-`p7s=+QLs)+h>D%g1aVB;c|w8LBSb-B>CXZ z2@)u{_ru4=_bpqtZ+xh++XxHE($k=jBva$C{zK|16kI)J?g{(O_<^eqjHi;48#-<2 z2(Gwx@d-Gy^^+ukTq;7Qsb6%L{(G(zN|!FD#udxQ`cJoFQYww7N~@^_M%`+2!Jgu+ zEz&6|X6+gn#W|NO?0ylP`2RAkn_AO4(%6Oc)>LZQz71HX?W7BApL^1p)>H~v?YkNy znAWQopMW!Gel~zo-fp_bB<_)7!(avoW=8#-dXap8fA}*0`WY`% zI0m}JpHnM~dt`W56D+Hp6U|6Msta{6J^qe|+~Q!nNc9Qu+7W0~8(_i*fhQ<1!Oc;% zD%~EuFGyfl{rewwyI$3rv}BTKaT(GYT$Il?6n4b09LG9B2Y~P`DHIVH?e3;T>u9L8 z7{h%;`FjWBMBZ(>H4QQ4_N?d@)bRsx|9?J>EKCsg#Ats&`b^Vz zyLlH+mk<=?DB`H+Qr=CsWIMKWiRwh3L3wT~ zJBms{z)9It{ih7rS~T_)JDRK5xH=nDsPq0nidv zTw-^SdlExV{RyfS0k{+V$a+3zL4}Z3Y=8e{O2R+?=2*FTta0kN1FYoEioR=f-#8oD zdm9Ac-X#*M?8FgOJs-;>hU}1dx@w0X2;1swg7}1m9fD{D`%3rbJ$-Rx-8lCP!)G0y z9=k(i;#oZ+Qw(uLmd}^;eJcJv5K1OPP0mAUYfZI-9JSV1{|pYuV^e zfu0760)K)+WIJBWms~X6xmQ&6Y6cD{uvRrh zMrgD%r(UX5wHB}earO{)PIo|~t$I3S_;Y0yk`HJ^aB8x|JdFGBXs`{y$d~QgxfiR+ z#0#a6G%7JIBujVDTHZnVR{^ucKau3aq5VUSJ*_}LrZC0c$zeV|}u;L_kFI~?; z>*n%upwkT=e?EKPabJk(SpG)k9c=ti@D*c}=fPo9p%zlTa2XIw3xDzW5p~Qwe!KEU zhFds9Ll`4Nn*2JwMvB3 z1Yb8iB~S|g*rWj6gI8v-(-Bh&d0p|zc)zCVou+vkvRgVe)l-+o<6WI@*wLKo(B4kT zkZzTv&To0lOzNf;b8T6%B38|1pVXYNTdv3BOL39biOX+8>IE7?^~Wch@h%VFuew9k z*-v&#_zFYn%zJ`c!?ggna$}aMC#mWtQ1*$K>d;*%$cK?bgCk{A#fk)k)V>XxU|O95 z8^s4aQ*27W1q~rMf0QkCLf*=rrWYzXiAF4jY(-V?4cEfAT2?Q{Hs~&kHA46I${3O* z_nr+m7fY5y$mzG@DRxUK&p*^999`RyCSH<9fDAKBA%?8ax@ZxIbja6heE^oKF=G#*|@ZXYjLTf z40;o>hF1%@LlrjrKt&MlaBzJk7cUK(Fit{|sBBheuKJ+TDIN%5qbnHc;{5B;<=D4m z)T2OW-e6{1l4L7>hYm@#80(1@uWXkXp@it3YWf&e10G9PodjZdRLNv@_;32K@`92; zj$jA#zx5Y0dFrCCJgMqtv+nQcP!%%HCMR>Vu5Kd6jsv?V>(2g?Lf9L!A@-K3iA<0vbC+RJ7= zRzo&?3<)-d<1l8a_bkwNnGAGQK$2`2fVNNqkluL^?qS?hIP1PzQW|o^wVj9{C^Ec@2}jSmY#eEPL9W5q^t8S_ z-WN^V$$}aYAV)O#kZmJk=*#tsEKAj8Q;vsHVmdtUW}|Wl>fpAmWADO=abGqw5Q23{zX++XnHu( z+vu?sWCj;CRo3k;cZKHdHuI(ggjvCeJDyW80h^F@uNSo;5*etsfP>p{mE-sGSHW6W zxLqjl7M+L^6%BOFGSH-Ig?b2+gD;1Bmm>MV6`GqJGV%!aX-h|QcYAu~_YGzzNKxg* z+>2(1vj@4gm$1W*pBu~vmnM8KaW*2a^m-ylyU$H!#Fi1-YhFnEaQyoN?=Q&Ch1hci{Ow0)UAH=E11sdQ`K%;~PFi!@s zt7@IxS3*v_N%ptrgo-)?ro=8yw^&P>Je_AW0jDaOx})lv8EGVjRK55J=P5WKbO8~% zLeuPQZJ=Kf)TF7an%x~+p(VbU9Yt=m4DO5Sd^q@tso2H%><<$~Lvh^*^wOwhM#J); zDjkiN0*>+nOv;K_kDU?4ZW%ekPBawP!a9E9L$2&-F(gjQnyfidC}*EgeujQXP>m)! zR{GCBNx33lQ|xAfD_7*W8$vvTvg;F_|J)Wi`pFPM0vAf>YwB*vi91rB%cfOQOxi6P zj+8wMRzXM>&o8cGA0u&O>4ju8kW{w)m=OvYt3O2I>Z`i`m>w3iRjbelETsJ1MpB_W zW}bRXIV7S{GXAlDh7hPh2<+=P3SPHxwa7CxMk@w~qd?FK01m;Dw)2cy9{$9nh^X2`WY*J7<84TfJc`BCCY(W}ZsG+mpszm>h>A>XNReVWOLjaV z9c5zoimW0*Qeedow?qY&^?COSf zs~J;92NT(;QPOP5Rx?d{1HZzaB0)0ybvJ9}JXKjKWh+Uz*ZV?YQCj6@nH{ClAWX9Q zAsfML6*G@;kZo5n>j&*LbXhP?32&T(H_o6`-BN=1DYe|p)yU*5B|-@1SSAw@hYV&N z3UmFHrhRCHPrF}yR!KA`g~1a{vw-X>1XTa#JRd*JPlaMqog~d3S&Sr1F&q`Vuuxi> zkfgZX1^IM(Oa4F-UCJyk33e(q4cYXVkRuzifKHis7oAD*mg17Uir-45Hi#K}y2NBd z4(I1zBjhE8*;hzF%{~>08_B3F4~U6uOiNb?DBxT>ZicI^P|rVvVxVi?K#)0v;$1`A z!QZf(nus@`*r1pb8OCXcf@Ov#h>1e*8Y>_gu;mi%8Ge$h^TSAHJS~uUOql4F+GCPu*Ev;>{FC%9}gR{Dw(DvM6*P^ ziLfcrYf*?^YQ<4s<{G+Tq9=)^pUz)921<%itVn5`0Wp6Mf^Z4aTJ~^|0Jrp`Hv_cu`Xmz7G2`@Q3+J57{Lvs3 zf}fxTS|ooMqSa_=nV()PI@^--F$G2`nRNC_)%G|#s~U}7b3qD?s~WM>3xhU!Ml>*O{F zftJG_iF7p6wR%to(%K*=*dKLv8pN}#N%2MzYALf!$%xsiaEbrGzoN^@9=iaJVuFH^ zP--K`gSfdwZ>n*!2VAgmQA3BpSJ9408wxQZ1x0j-j9YTb!17>2;5A|r#rXeGsk2xa zj`EZzPl}S|B0|c#&iGrpkB4^{nh>rKLGv%YS zDN+$EgBZdlh~cOY=_I0~pDs+}GyLtG+x5z#&}*YPc30r894u>ahzWv{7@qRkuG zX!tLOjnTH0`H$A<-8{P=Vihsaj5{bs(hyT%+_6x~KyhWkEn9%{;iC1DmLGFk)u$BM zP*fRU!$?C=tPsT7_rex=%A|NU6t`tV(_voN@zD9%xCBfH3!NR&PzMn&ut?0J`hk#z z)DEL7B7^RApyOhVheNrfz{-hq41FBtWiDJbUNNW2hz}sgLXzVG!*5AnSSWb^pr@sa>X zU=%DXSh^o`pVe5$c^$WOy!(VpN1dbRmo1RPSO_%EBNG^P5crQ{F2SpdwtDc46MzQ& zUn9IMWUAD$0cY;-)6VY|SiYeGM5yNoxMAj`g zL%{xu)E7(XuUXK`6;Y03Rirq?h8BugD6bQ}Owz{G;-9P{=8=Y+j?Nrjt%OW7VT!8@|OZ)Ib z`TajY9)*x%s-}WDDXMNGq0E#LQ5~>es^{s^gk-2LSSU()QU6hhp}N7fkOqTM>*=@( z(WpvrV#{$I>nbYx9-=C2wP4YjFF>sKVZ&`l0slSNfG@K#5S+l2lbM0V%LCY!uOVL1 zwhLqfiE3!EST#QHBxV+Fx1bFobw`|c8@Cs%$!dvLB7u6Xjvpl=;QNnV(Q+#VI{lDj z#dY07lO_NF*-|1`NI)Hv5VfVn1)iM`UZK+_T zl2+J)w*~EP!IC{$6_WD%-K>Y7#^29C4x-{fqeTrimg!Xb^#f=y1vZS+3tl1<#%Z8H zMc2T6QO#{(4K}VoONT-Xf<1Ujf>eAO(-_!30WpxYkUnTW}VI>EV~gis5vS zNQ-np9K>%~=QyiV&l5`T0FM7W&9Dnwem#+U`Z|Gu6;jdBIbk}ovt&s~$adyFqMC5} z(V?=H3ZIu*1(ek~;bq zMx<$>LwPt1CYk!NMb%TP84@|E;1q*>9Hy)tI2IMO8&tiNWgFNMTwFEtGY%)#-bnv;f(c7;hjivAlQ}D~OM{$Q;FUfd2-$GC|UC{X|TF zt1S-j86`Pc$R+fs8M0l^P(4d52dYaMonk^vui1foj2!;ztz&EDd@@ z3=CL9W-XF^A)@SLCPwAQAdDRT#-Gao1yl0*C4G3PrDd@l!a}$G(CZN5qLtS zw3SRpYMKVErnyM1nSH`!rXhO?Xf;62r7y5j3BFH|wE2HRYM+Jh~-mDo``HbOl*3Gik!BJWeJf#$8E(-C|JTHD)r;vAZ@grAX8uPoSjehe#?2Bi9O9L$>v=;C8~yn zd&ZLF3K=bPFQ@tIX%6r)hws55K-wN-GW5BJI16_k*9&KlHK5IqnkQB8mDetMbtkk3 zoOUF5n6ZAM#Jr~b(o5h_FR~vYBpu+T?80lny$AsD6d-9R9g?E<K)-Y-$=Ft*f;lb z1y1xuPr;`7l(F&tEj4X;=hE)2&_k}LHsd_YXgjl=VkLbyMN{rhSDpst;ID-t(ZZpD zJ&KJ?H~VuQfv0G8a}&T1(FTN|4Z%ZsW*)%waJK*h2{NC`Fb|HqW4ht?jV)a2h0oc2 zTd%u*yS(9A^iSO^ya0b5o01^EPJ(`N3}Cwe0K#E?Y2HbGf@jQKb*QI$?&~gYM5YV9 zq1VzETuEQ0NS!$kkiPuzfd9a2>}>Nz_B_&NDsX_Uf}5MLiIL_J#J~CYl{(c9EzDJ- zRSfPe0DPg<{NLQ0w`~9A`^JTnvh_l7+5Tl;-bqGIa~)0kAE!C&z%-~lwUgNXPAX_V z!82y#O8l8dSN0NvE!&sIbbLXh7$<*BOVxP@eGldR%_pcj$v|K^5S)Qv4acVgn)YYr|y91X(|;54Ofaz2f+ z{Y;DwyNbI=H`zsgr4u|G6qg^ohQ4CCrYibjnlV(G#?WfYy@S5u4mb0N3b91EIJ`U& z2tV{{CjVc`QD^jf=!_HzRC}_ju=hygMxtc@2DLsSj(7d4Yg`y7Rhr@1 zp9?KXsT*>dVQ63Fhp!gmXT}S!-y|xENC)=u;EpD0a#VM0FOd}*lMfl1L#l|Q85?nS zAwCMb34b6Af1Rm*yZJo3A3ZDy6JcuMtuJQ_MocsCw};G`Ty1`qZq!4L^-pGRnkFhv zr2SnwJoZR3ddNv0nNskN4y^DoK8eF6DPj~XCz-=kKB7tmI=djLn`iC|sd=IFeU#UE zJUwZ_oCfJpKwN7u*B^-5u5WFUwd&9S)edmq^V|%cA`S1}1yu7?)l)ZoxG*~DZ#T_M ziG5)uCxXVdY_T5eDaC}w%Gsw2RMR#|hc17gt$O~|w))RQKSU;F2Eo(Si>HaU5KR0A zx^OjwP8Vr-bNj09U$#^9^4L}L6LX+4xoL$fnyJnhO-=C`|Cb>%*JKYDhKs03t4>x* zS+|yt6o>P(KMsHlM)LRyMY^sGMO+*f@dv%Te4PX?9i%Z(c4`#qYm^p0M{wlI2caeA zFsG3x$5q`q|9m@Z#2bh;)nok|mF_WNMR(pxtUj|>U*EgEwz^hZy^Ri)0RQoH(hp8= zV?wfrr~1W}uNv>$y*3$IGR|&b)!=Mg@~$Zo)D_6CP7<<6=~1QDVE+-Bb`+QgwUIUm z-3@!qf4Zz%VHF#1E}MP!Cpe)eVhl5M+L2lBzL_M3e_<86;W;iIxG1+wwkX+1BC4_I#ns zt>he`Z~46iTo7D!2>iHAXAYypXBg&^Se}`UsHy)ANnaf^Yt6q-!bG5`_&fr~O$~`2 z;)p1T;-~;GdF_?x`h4OUuc_0yqk;gdK^71SZ&TA_&~f^B(j6+Z{>4?lQfbrvaV%mH z?fa`AQru}UFYG?}G@$YX4RQYl?axosMX(t$JDS*Om}SHl#S?D`|K_+^>}n14&sbZJ zbFKwsPfbS17eI{G|MFJ7xOR@R@8}dGQS?k$Vy=CQY92+|7=;QI4r&l-rZmU7JL>AG zHeIh(w0=2Xj&~3L(%-jD&sLs8BY^}a25lARDVHGM8>%U=;Sa%Rs@0kBQ-0T zv})}&dY*j%WZlaPPbj+zUUm-QilhXH^(NwQ3p6hP`4M!|*yxBlhN?wyD1=@i`*?7E9rn|m+Q(>?^al-wswT-HkK6!(n@)(7|SMviYzZ3 z-a5WM&>+W6JsQ=`ebuTO4L3C{9G#=4yI?U8w$pKo)EeM$10E?piM%fb;k0wjtCNqF zd(P%|z#EF9Om6*cu}UAcsDbw@biF0P+EbPHJAd!M)A{Cz&FxKte#-t`LxUu{694i1SBSOt+7iY;Sg8NNC!_K1 z5wOyha?#I@71D`FQYyq_@pyK{o65*G5aV{AT6UK7_zyf7&F#N4WX7^}F%s^IRZ^v8 zNS6{*`B-1Z4&RQMw)D-G848)!W?g+}!id{;+(1mLJ-8oYaMZV%ii{=&DBD@p|Jr1IaBy7I6l^DJ^`0oOtykhO?{g1;L=Q3*Xt zE8uWb4L1XXeq9@ZHj~5L8ro{xq7v2B+HEN#VcQ8KHM7H0?-Gk`SxikJ7l<25RpzqT z-|@$2Aw9QhFBiR+N8DuN6(ny=5O(GOqc2oMakr@YuTQB@&aokyia8~1IoD$6L@=?T zV&-o!9si#$5i+O0g1m*VV;g7m-J-ZtMDACcH$Dsvu?L33f?J0%1bt&LF+}7M{iRm% z3x9&!WRCBQIgx2A2W5Eu-Fw5~Y04y?4u{b&&iIaqHh<_gF3Sq-qxC2eYzfuFPG0L* z)!t1X_a2|ZCaZUjMnT>_me18UlO)lD?;=?_+`XI_>X3>No=qqCHUp0y5X!1s%dPt zB3BakVORTi{hO9*8pb#r!pr(rEN{paHDwE{gdi7*)=hDdpNplMK%t0Rgfybj7fB1t#JR|Qnk1Xn}R|NX#P8UzB{qFo$pqt?oFHiQ!{O?zb!5MDq>^Sk}ph_yKp z-wdbZV2SkWM+|MV72Zs@wmEEVK2tM3DQMEf1k?>t95>8mptF_*vcgWO@bZ-fvDCxB z5@#Pi2RBz%)iQ35o^~oqr(_=j#bvX|Y1D+wIwHAs8G*pqX8x2`FBRtumC1!fw?N@^ z{j*;*yApG>w3s{I`6;WM{aebLfAlUlBcbG{!_OAfpVr!n*}BDZxkxeVbp2H+rK>qV zOD{7*<`uCqQPsr0J~Aia!2L7^#5&vFbCA7uD0n&bIXE zCq3$f^27Dt8rHMt5hiE+ugp`+JcIUo6*O7oa}lHtRY$ynR4K^PMv9o>&@@d2-cJ9~ ziu&!Y_B0N1-u2f+3nucxs#-Ybe>IVZC@mM^FcsAeuQYiDqNAO37}E$3Tbo&0Q~z5& z_!Rp-JXJ_PbZZ2H-}WU0dNqTOk$9M(ET=Sr1BS@`8^2pifrHr>UP?Gle_rmMT+6Bi-8{mJy+E_f?x=) zK>=ZxuBS-B>~(b97i#%M_NKhQboRCUBv)=Ah(GoQI_^uLUE|Oh$)3<(vSjSHDdHa? zs5*%Yu-eSMed<3*bDG7Pj&n{(8~zQ0>h@-v4KLa{s{@iC#GL)f{*VlM5G%CQmJ&;+ z3<0Udq2>616H5i?@KQ+j(kXOhlm+XR$fif7mJ_(969<1S5{W?5Glzy$@Yy!$tP956 zb{!qBl{`AEmV{c_e{NWc!S0fA2Fclr9A6QwAyY(cCl@@naQpnA-FU_^b9 z<;k07bFT*}dt9bd;vcskU>kn<8Yk&n*P$oKYt;?Y3mt6NQ{Za{9cDD;9MaX#QM6CA z9yNvaHzw!YZyv%G_`4KmW3TW7;Q;!}mZ@I{VfxqtIp|yjUxe(dfCLfEEs<||2oSe{ z@2Qhl4b1iY4QvYDQHNCGRc-g(?EjEHyhlS~?RGkRR+A5q{cu3mKHYjmUID4Z+%J_P z@17S1nCxYWhOl-pK6*0q=5cjXw(lwS4E&9Rd_oQe%2W!R4DRlkN2ZNA?&OU`xuFI8_-Sx4xxbR0n zPC!eQz=yA^4a6#1XChRf>NDRtO?_`E^elU}!-Bht^aTItkJK)||8;7HUF5IYi;{B9 zf8P<9ozM8E{8Y=cll=!SgMY6=dM)$svCRy3iTEG8O3ksue)wlNr|Q3Slj^bO{F8sC z<=ET&%Z{qv%nR462c0H)pSlQ$hq4qGo&V73JUuwk=cWy_@Qn@#|Gg{DZuMY1fz=AhqyT*}=W>$b> z(E8!W;qY)^^>?^~1SI9D0scUfr(CDk2P_;OmZZ9vE2e9kA*&Fg){xxAKGcu z|07I#COQw6le2%Jqs8v(D||vr+efnfS$noANNdZ=4lT*04PYCh$_ z|9IJAlY-A@{1apK=Zu2$L0aqZ?9uMrGX!|K?d(um%XNSH_Bxgt3%x5^Fy4~u>B-%m z>)EVZ_GnKIKT5d*6o#T!#$9kwLsSIC>zvR67djm0a2bvw(vQ3V_&Xu5D22oGFSp%7 z@y*K|XU%j!FAqW!qDYiAA~uHdXe6)wCu-CD{fF#yXSKVre^bW78goi3=x}u)Z&D9+ zVQ4I3kJUfs|NEzu47^{#ke&hQ*`Rdb^|%4kj0^5Di53cvhlH?j7>@fKn(pLPX66)zu=|a44WyHc{R*noxx^krye; zA&V%JiOKRt|I2@fW(pwQovL{*(e4>rL)OxDm9>b9hX(>0e!>6zFOAHaAPh#X@-%#g zYmvEnh{!4M6)u`0kkpAN3Q%o7u~&4yxhHOBMoUMi4Ka$&T2vJc&r{X?{(~QPhdQhI z5xNgT;~;`D{I^$!2CZb*Wcp0h)RHht}vg&SghwfGr}$m^~CCqFBd*0o0V1Xx}S zKB$cy)BtZ0k;T-%L1v&bc&CJe&SY%?ouR|naCMl#b=ris(Gat}DZj7e|NbG%^)KF` z*Q;%why_=-REO^t)-R&C;lxl+>VW^AgHB_2u(sdD+Pb_Fc`N8Wf=cM!z!AG}p9H#xWh&L@52Wt6F^=fvyqf$TVrHZLO-Rh?7{-6=Tu zQ+J>? zb>dyT;_pI@nxmw_@K2uY#P=uXpy0 zE6oqAiAc%5%(*Q#uBFkR3n?<=<}yk?%#BnS|?-7=_=AD-+}k#xncTSJU{3M!&@ z11wF-R}*5!Nxn+aoV6#f&PB4Si^^KvYmDF~Cb|fiFij4i3;`2%1DNpMiT&qSjI^qk zckerOJQf?@xzs?R;Ly|unByTQ9J#Nl9;7jJi;d0OvnU%>-I1kPv6#}0#ps32dHBLW zNW!&bz7;Vu(FySLWejv&PSY8m<5d;8vnZLzx;FHMGRx|s6xG(8mm9nKrhZ_6$cYujBfV8Y z3TfG@ng-mgibVX(mJK~y(^QlOjJ#Mr?=*8|RP9X+mRy4gx?38Yz{@hGBA5+omyqT; zAE?|T?{S+h2;>8bBhU{(8bSipz>|6w(Stf##}(kUJT`IS`9Sge;IW}oYiCkeD@gV^X3WH2G?hA9Mm0=5krTyiqOQobGBUO9 zSvGy3q9qkQcTzXUTo^YD)#=^Rd$%kW2h%eA{HaD%wW5kQG3dkwOF6yQjKyPSs+?*- z3zC-)l`$2}aLYr>VGoz3nM3!eJH^zK?8y!T%)A5Z4DW$0i~(n#OcejYr^2zFh-33q zg(3E31y*u2qI}AoGA-A&4pH#sQ5@3lSpCuZ=xCk2Q;9@FlB|4c%Kg5ZZheds>FoL# zK&l?C!_71E&#v)8UdJFuWB2QgQt@Rj~Av zLdu;gZia+^2m2OQ(1uSh+gimfP~DKpiE>A}Dclq25bg;Oo2_@I!WWg;RAzTQ8%Lro-!PUw zF)~6?W&8~U8ra3C*P4i^H_OaaHfr(_xnt%%-%u5y3Fj#}tAeV-fY%XQ;CIE@0!LM9 z@!<$|Aj%f0_Snepw!C)&A0lf?Fn%Yt7nRWC!x_c&M>-aDkJ(`PM zmyGU^JzO?TBbDDp7hH3cdUPF~S@xuQ3Tt7w5%a2I69kZl9D6ZkI0_32V)^n)e>^Y~Ces~1L1$qv+`F+^4Rqku!)+~?AE#{;A6|q{HNECng|8c$HHShFt z*e)Dxw6`#cX^^M99P&!vPcw1^d(c!5QBmpn>@7Jj_?%Eo0Zxifkb#Q7$7#QTfN{a} z`Bdr(^D!7yFnifzNDr`jH}Bd;a#quX@j zx8Q(GPs(*Z95lHhnS{4I`&vx0XYz#GB2$~lKA>FRK6RA0mf3#KYCtg){k;seJ0 zH%B!Z(9kOo9JTU#9lM?=j`qkRiaU&B;v$jwEZ8{wB7<|*JR z@htVcD==d+Y{dmDS)1Dj#A~=aKgeMwrq8KrZe+-CYAxmN0F9p2GTUjFZjZ(J#D(!_aM?rTV3u#BE6%x?0yP(R%-r%V zb&1rZ&Q;VTt=*pn+Grk{IoUGgSFMxk3XL3w3TU4A2|RKf(jPT5UcLQRg1?1NkFCFK zY5CAC*RC{BPiz%)G1LLssSpsg6&)O$x2o9`bg!xF;jp_d9R6t7#gj05>g3nmyxU%L z{W@Kd3^A0lP1GReiWWe)#sB2Zu3HUnSlGBaxaMa1ig0-5qu*2C%UTKsA!!sLnkeG7 z4qGQsuaT^a4x{axLL=%0cZKj%a907?9BwLjXa1%*x+1U>8H*@Ztr7=-btHk79H_$p z@)Q1nd2egR6P9arwO)P#&+*p+T?9a#iLyF1Y=jFg^3UaDsStvKEdDkgw;XG2>!M2& zaa?m7z6~&JZRZWCwXbkPv_1{3y-xo1PoQ{We)Jrz@zv)-?!=T(XUPGOsv1cdpc`5D z(C(Y&c6HDRXcQg3F`=Ierk4d6j=0`Jl?g22aM3|#8|qjKP#pxRwJKw{2w!ngaM3%0 zuW0vzBG{J#oU8+I*})-o;^mlMjpN78?Gxau2|`ph%AE;8k0cZmC1IKu54yo^q(*H~ zt0w?xdMJP=oI_k2NAei(6hcPEF$5`DEEsY`kbWNYikl3sxjMKe=w)wV$Ifj1fqG%6 zNjSzN4NB0fJGoIK2BLy+hxN7G^(RXKyNu ztVEjUFm?D?*kJ7Tu53CDl;LN|$B8~a$OOgTe2S#{`Q!#VE41fbbUfCx}S85>csrh zRiYY+h?O%j0s5I0DP_y`nX`VczP$`{O2pkhQ ztgzojq_PiS8py^+F&u9cp$>qzxujwcMjPAdRB{nO$wtDBhHVc5g*I1lR_@h88EttY z5{l_^B$3&%5=DH!C#YN6TA$H0c-3AV1~p~@er@Muuw{8 zWs#9VV+;m`$~gSduy$r*j|HzDi)D6Z-79gLf+GMyW`?rbpKRYh-?N%f8h9`V)e?c0 zm}nPa@LF(E8d|@Nh8QsGJ+6XH72^ypF%*r>{xM#9Eos`8F$t9$?GH#L;26QI884OS z1hhC}#xAm3Nt;sP)(;dkl|1}-gBq)=GnHQLIbkLB`k*#TyLmi27#t71RHRhS!ia(>K5H~@#{K}3mT{aVpM0pSo# zvH7`D{qI)Xp&~m!S?*qf->k)SOGC9|(kB4-9{eP!O?UhGTM$F}(!VJ2B@dUA*G7#^ zG>DzCT9qERD0*M5lw?w}e8k}T7!C$I-~au;pn=54Zjxg&rKMWesjb#)-v)Ge3XY9Q zc%NSlvVsSR@B~Rd6AIR7&0qCMDC_S4z=s~F`O8_SfA(KO*_48?5$tF7Sw#l+6uLp6 zcvRU@t+KFI#r|MMfEl3!Jg&K2>Qwes8-=7sNH^o-ano=C3u@=K;2KFl{UeYZazK6o#svU2Wz!uf0rL~iGjxutpvZMM zOm_d2E45hfB_hfxBZjEiO1NlgKOaUi0&&|8MpI4^QEDFX+KwCXj{p0$k(K^_q%#!e z<`|9#REXE=Rnm+SfXO6u%1(dv|CKz2jDEJu-?T0=uu>IOQfUk^9COvW&S(8Tw$_C6 zS)YA3>HO>(I&Lb~$ZUUs^t9^fk@&HL2iep9!9TfOY$>v7jBJxH$?WG*vrh~0b}}ty zZ>8hL`6W80&He}r6x;|c4}tHV(s3bLc)o5y9u80Z37kk?CqWPpat--W(C7dmhavLP z(whXS79YzCqfM=I{|Yy8CU@XT*-(eCLin|wW=WuJt9APOnaiB%m2wQhnNKftL9+WwAxL~}Fn zR$~$pkXwQ0ezZ;w7>ZT6{)?}cmrdSDPD*$q7d>3Q=vBhgb;*nyhoSE|o(^XIMCElNE+NLy!634h|17?qGwL~NqxaL zP@HQ{lUC6#^;g%(ookLEcOQSayWU`QZwPMmYF&#PfQ^q{8NB{r@acDghyR$nik;~f zu9a((kLY1I+>la#3;es7oE5=|aw0Fgo^=7em3E84XIJAP<+{D?OJ=|6pMI^}J9EeV z$iF~V&r@hU8pApd@}&Do_<)i5$qm#g<`NWM0-=YoYS8o=6iyC94(&_I+t~5W)3Z6S z^OWj1H#m+qc_=0itjk`UT{j@d*r@8LQ>v!n(0XF8t)?d_bc+M~stULIkqFR-kIdf0 zuXq=~x>J^`a#$M#U_%v8fnGVNh2@{OE~}i;?OD_>y;{%t zyMLy+{we>g75wDSwAjp<-_^cmrm3DfjS|e|95EOEEl8_T@&Kb|E0HNTbJ)!muRagnlcyt8)BGqMBAM7E>LVf2r{Pt(BuRlq^0mk>gWh;IscS5d2W*#cevR| zVk%yY6_Ady+s)$63L$NeE?%VS-v>Y?Ajv*xdknk5b*w4NDNKp-$uhs84@HV`A>&Ho z&D5&TdulZ>wo;Rh#_D1W<`3d5oGIAh#YbCfHXli^P8riQDmW z!%3wE@8eqm7J8wgZm)_V^;~4PQ;zlA)78MdF@(fg5<--IuS-G_>@!%jdtk|BKxbpe z-P{0#AZ1GZZ|{%NKQRHczJ%$FyF=^JRP^tuC}gvlG~HD6ZbiOg_79t>pkZ?uN!I*v z0!TvRs6HN740(cm1Km!)3vXpAJdO0~(F!yGAdwhXO3F6M8K2f7VpUbEK(?a1{;Il8 zR4gI4h6;q&iR%Z(ms+wsl9L;Y~!(sp*qG1PYk(dXjAblFd#ty z{DYGxcZAToAuXutUpB0F7dFwJ;3OCy>`kHlDAP}OGp#cC*+2RZps9KF4BBXgjEfz$ z$4d341i2i)A!>sFze(C5SEVEVzE8RNBtlI1$B3|!$k(OL(kG*N^L{@$qL=(%Ljkf! z>NGe~-pQ!w^uHv=QvMmw#%=$c5#5cDqY-~wWn>u|n)W6CzGveZ|7|0B!lRx@f#VS{ zeN_Ce0K13x5=aC;OG{3hmr$u*MS{hDc0})N{VWnBNkIsHR`#u5*xh5nQoa@~1d=^V zBEdo)3Y2p~PyqbF?C%Oo@+P)atFb_l8VdNQjOyjMPr5k)6R3pENRL3n@AglAt(`}T zTreWy94T%_tUzSttd4X28^-i%+9jD4j0P}Qi^g@)XT2`}8)JI93@{BELIZP*C$qmQ z_7!<1!4WbZ>-}(F_FpnxQ# z)6DN={qT|&k~|So7D^Y2iHOiCmxkmIS;MvZ7%%8uzy)3&!XmKrI7?-TKAthOqY)wP znUZA1%>AfR*Ol{TkB*_wOc-u=p!#X$B((a&hO`>MitMTXpR_lDbL*<^fPE#sC+RJE zdXnDLzKurG?Bf~DjKefxB(+bLP&x`7K$HV>x3rIK!_<& zN(M*>ftFxM+J+JcUs5QcWi3!>!;jW2%J)C_K5b(sET0!GPttw&o_p@O=bq(%u8OAr zJ(}?y2oDv#Mb(a|+oG8O*lm%^N)a9_ab#`DXo{QfblB}K+2?UGYfCNkmr_}{rih~l zKBew*3DpQcv0e9A2u`tMI|9H9=3?s8!y#|je``h`@L&?Ee6}C0Do{CDTTh_&v&YeJcmC!xN6}O>+hze_td1uh_*QdGF!3qC1oJkzVzFAm1rWlj5ca34=UGfHO zo3}gp`+cdznOEE4yHvBbEKj_UEN#?KkDMyVvaJUb<_Ao5 zYIAWope0O{{l4y?j6J3J0bj;dKw9E)ntTl{#fofrenK$nux3lA@=n%ybkaD27+}If zj5)&7u$`fnBB@pB6_XjYibRGBd$p>gxx^Hu-%mxoL#vmk!?nU3VvWndu;}M%)%}`O zoXVvVRe!Wct!jSPoT8K7RBFwHmR&JFkQep3mn{6%oxy)$uKfHpIJk^GXQjKmgyWW$ z6^DdxM+s2n1dcfXI90MJ_&F>Pq!Iq17}d;iD1sdBXDXT=kCl~a+ET_DsyLh-f{J)< zW)#@Yjych1I(l^6uzf)B<=t<7gcRD-rlNjLG9%b5BzZg)J3IX+=VpTP`yI1y0>6+{ zK-`X_u$WkOROg2Z@=_u@$%NnCX>=34UWH=g|0&p- zrFjO^^#!ZK_N@p8+adc8gsSP#0o5q$Cg9c;Mm*VkoPM}+Ff1vk5{X^kCzWmE0vmX2 zKmvXJZ=F|;=&8oj{0#iNs7sL-=wH3nO(Z6hpyxEFGcj>*(mZY^R~iVqP5C)6<9bCh z&X|ZK-tG0Or%Upi-l783JEBz#^(`UT#fgKPjlZV&#Pe6;Wb8w^8a+5*8D~Jd_5yY8 zx_7)qH7Z&JBf>AJ)T*HFvyiwY7@5cy+M+Iun`684EZwPM2U_}_z<$><5T;L8zg(SSzq>OH3-k6;SE zKn1%Cii1V?lFUSpM>7}bk?~MUUPBDM2K9fh?~kP&GkhvsQi1d3zIO$*@u23>yn$Qw zN~(*cue$(%VGSSotAazO_NKHJ5Yk?H!#k0kgQBoY)xf*-RK@K*Id8~b@`sXLV3KzT zKXv?F(34=n)1#Lv=ZE@2N&i|R`zkQ*8*1VBAxQJnq`*hDl6FX@!mesxK)y?3=e#MR zH$Otk;-6fs_yS-TIIXH7Wq&*_;ovP@^G;2>OAhp@SOOmTkXBlF_g%qnC#FfE$>Df* z8f-il_3S!?a&Y1_$xFM6)j#WAcYQO2a!ky&0BB3 z-2qDg#C?h(o{<{kmL3s^Ogc~rzRfdGOX79i<0`@o4Czk66c}2|j^3pXiP1Iut&l{x zabV?ht84M|*O$r$gSFRSfr4e_5!)fuq*uTynJTbbp$=U0-9$bcm zNPMg6ePVVWFOcUFPEPK=47S~u3x%V>FIQdn{72bGVfdjy#@dqA7*Zd}XF9n7BwARU zgkN$8p6;XF>X_qXM*ux>UriLb<+`%+7Nn7S5ph_kUWrBZjcye;`;K9kskBgJD z%Yx)Zq>fL(y0Zoy=V@UirqOwMnBto0MX5*$N;oZ12qRw9<8=gPmi+Nrqh3W&9DIJl zfQ6I#JLRbIA^0wa{dyn}ApzMuMp7aY2z zkz-31-cVE=Hb_mVJQH&hy2Wcr8H6+*pR85e+c!e#=bYa@YK#=xH!;KJd8lAtZ`~;z zO8ZC8TXkS-#XK}txp-a=$y&sSDdAPA`LG*UsC>t1?m2AIx{&>Y&J5x zs&u*qKsIQ@2Jay?w|ON!-g8N3M9Z#UKGq)Ur|%&Uadwy<@ul|omS4YYygl$GOCGH# z2R#HCjX@Gtl^+%zRP=LB8&rVt0^V`naieCP%GTMdhM8?%VIBhl4O8SxBKqr8IXhyf zbA#FJV73xV>`cV$@Lt1aM{PE|N_^M66cwgyAFzK$OPc>wXWQd?QWdkEgl0PJgi1=% z$q%8h0n$a^7Kv?UTkN@EJ90(*S=|@MR9zI&_Xh&`VasQQ5*gG*Le0lPE44dmTj6WM z7W=d{JZDF*w4c=@7hwwHK7GG6oQJdHROeUUHSI|us#~);j0FxIvqj63%_np2Dn|49 z7So)3J}+?oEaRVt(BRl$2*n~$sk1g)LuJ{3B5HG@ zdgsgq7br#_zKsAShCo{WVs9iGjUdqzr-eiUIJh!f@@P%@s&~b|;b=%DzE{B^-od}D zklf(CK+k}>VaKVj5O>|Zc7EkEhQ`>#>#zHhvm?{5pF{f@(HE~Vk_ZmW&ZQsd?KeBa zxh56JjYUxzH*?;NZ=91#RnZ$?G|F;gNl$3H)#G;8{|Znn;jD8f&>t86zOLNjYkZko z?I*~yQU^`rP)+Nd?$WxU91u_Z#4bQ|*jd&-?Hr%nG3yVM!@4_&cqFhl1modZqf-66&rdnh zb949t|5HF4`FT7PT5eyeIwnKFjKafm7L+Eq1MPSb^HXbtJY$|#zpWY)h1@X`ggIXW zW6T{UA86~Vo#&!EL>fCE>e!i?=CRC~--*ksF1UQhWHNa;dARwXK3XHoY9_4_)K2B= zhLxI)+mrNXGPxIz_}_dE&&TkeeW}{tUwvun%n^V6+$mg0_Iqj7BrRGsJ~I|GeQW`j zCnxc1dZZ`u$92q*xFg@+IC3Pma6|cpVse3^>&}#=VA4XKC}=V1+`Ub)8&xrz?X!mP-x|ad{v4!-$6_(d8bb9_MIEwcaA9i41lw928Tln@IL4F7xTFU> zk{OMflIIh1_}viww_<~MoTmoxXW_t3<Ip$s)=M+a9j}B_4d}-1aT1@PjE5+Bi+C*=nhgIs87=7ze;>EuTcDy+&{jxs0j_z; zPYG^|ScV-8dJR-Qk7#MXKaCR9L66}LpqfTNp$mHCyqJW#5pqNJs2&;Buz=%(wx-_tcnUkH$AjElTd2A$9aJ$i2;+CPjzxwmhPzpe z{gc~)0Aa)g5CKQ;3AQ{y9em%tX5<{%1941!K##7R+LDiNxMbhy6$B+1if*Ilia)J@ z-#mH>*!BKsK=ZI`xh&^90rlZ+t~_sZW^~EIT^B2NM1#Uw0XB3#_qSS~bI~awYZwPz z_+7AkDd?~rn24Ch-2v%lItAn5MJg_7Y72=sDd$cmMIytOu#pk?Ci#5U!5_6}$v)?M zXN0YY&@{PvIfJt+wgzA>X!!=juABJd=Zt^|Dss=zvB*z02ZZeP`LE%A=2{uqyMG1u z4}3#k6l(5c;vFzuxx1*G0v@z-j@GZ8qZ~G32L9a`jvS1J&z6x%{v{4zFyCLBPnIVE zI7q{2=#rnn+fhp$uMh~vpXlKX4oyg3GGWS0QX{&4b~thk1T|XzxT2gRapge_oL2Fo zfkS?^1_wsWl3}XX&w~huM$7Nh`-yu1BuXHnY!QW!A~qvf+YkQ?#RSNup=#!-%)o*E zspXmKGHBj#TJeMvU3r?`1%qD~exyy+%x#%!aF1RN?MAsb<6?1S-4_k-1_aD*Gs@mY zLF~(?`nMrLGRlN+KgjMxSq`2XEVb`aWe~`Am^1rV%*)Q1tAm*tA56d>>V!wUKy-Pj|y3Zgf z(I)2s{_>zR8zpt{0)G)GO_{53k;*oeGQnRG7S2_lRfbpM6H=?F=mGsyj+5L#WS?ja zCv#jS8w`;8#n1Q+a1~mQ^Yzav$%dbH-xOO`f#@S4{K@r==BptVj;fyUSY>8(2Mlw|`tzFV_gE>^Tn<%+H=txl%0PL~xDRCv zq>RTCidFGN>lK8cNLZBASyc6IHfU&gkruHuEe+oOnd4>WUwrmubTjK|*IFJF50l+d*RY5kvsCsZ|e|8*o7b1b7MEdgS;r>+2 zV#vBQ`fHs2wtMr>%=O8p-{bn7er#qO%x3B1{0_xXZmx9`-5Qb zN1~goMtrhiVh>PcT^$*(U~|c!SOUuV1P46Y=DwkU`LNGtAh3L7cHD-8$tQ(PUpx>B zGt|UD^-IYJMzRde>I*)9U^Wv5J1_;{mk@7Oqi=m6JiGD+jiIO`Q!W}o&0y_b8P#eq zOQwO+7PjIy<$F&bK{c<4zqoFBB|x$06aO2@+figXp$aII(-|e4NJhOueb_FS{3dE_ zRtEAbEi)WgsRfq~=zdd00rsi2L2X}>v4IrYko=L1r|*gt(7Xc}dtmJR%D`C>i+K$* z;E_jQqGQ^_0n?ir&y@1{ppF_E<;q}b1rVBacE!1<;iv>RpAD=8h-Om`M8n7fFawb! z>csok`TC>?U^kLpZyf1drUZ0K#Bryx;UP|qjY zzqXOkMOBO216ym+9dbZf_9}t-k1BHW9>hQflvefndNb~Cyn;&Zj=eLbpAkULu zRj%;l$1v(gZ=_JnV(B=`y7XXAP+aGD^NR}`pHaN*GNGa7yTUM`SLK;0;wXwGR;0jr z)EQyx-`IgdE5hu}HdLBFWUEn}cGoV+f8>oF7_?&ZLqy}tSPG?(4jhn;g(v@BsgLo@ zVS$b->}wVodT=wL64tmYmK+hS6uc5&$ViHGukEKe_^{7Y4+arF5uEXQR%D_G6-(nG zv%=%4dwo1sY@Pzt`6cfRzK5^R&?mO|BErag-u7=nu3!GIl4K3$ZemYy@2J?i5h?Kp zb+JT64q^aDm=7Y?I2D}tdd}n_Yy9C%k9XedHRvns%i@aH!@q`a!`;L`L42ENq4m>+ zz;BhW4!CBTAK<8{g5iKSxYB@$f04qX2F%swG2Xk|FxG>`nzH>15M$cgOzOXTv zd)tPvy&)65FPc7h@a7xRc#%0Eu{UOiOnfu{q3lo~a4oi}mX0N0m`?P0{}NueL(VmKg=6_^%hF5%j@^Og%SS`&tC<7IsvaGgF#C{7 zp;j*<8%|=C>%0C}IZ$t|DTY~;+MBU$V zixpb?b|^@>rK9m6?6L*+5IS?ObNY{w6M+oa0smBAsQJvtLu;#y6QtN4-0 zn$LbHykRi6KUGoe(ORUO1OVgE`ACAmZ{D3UW6?+o#^hGKT-W@e<7C?w9j|uL@B|W; z1YFL4tohu-+S*F`Mt83BR8z9DGn5G7pOw$wl#Rw>(JTkQ#)1?UJi^6N zVLuy%rF-G^BkDGvA*`0a=2HRr>-^u4no$9zD)_M?IZyt^NH+Et#_pD-)kC4^+HH47 zL#JO}7-Q1f3(u;?%QxfYU^%;;45{1W-lbbkDVQrOg|V^KV`Bs-Fy>o5tT(@XD0KQ* zVgLC0(Adu1+0jvJJK0pX=TBQ6?pwckJWtoKPy15ohs&w5uU62T zFFmNPE~l;@S?-(LwN7H?WZAj@oklz=X!U(gtA6n3vL$QzG7V3V2`uNS;f~T|ZKCQN ztm%pV6BntsZ)VkG#*?4Q|2x(N3I!?7f2<26vWwU0O}UAbw>Z73maSG}&V|=T;tQW$ zsy5FVqC{L1rrl*rwt~R~hr<)WWhq>HT_AbR>9?q{27Shz6J>FNq_?yzNZilN9wE_i*42|%Sc4UKlen=y+ZKLf+xha&L}I>5BSxBJx5T& zbHJ?rTo<_uYV##rwF?nU1-`*z1tKP92ZW8Vukg=Mfen;i-M-Af(tLq8N?5VfXvQw^ zug%eg)_4fQBnEf#9H%}Xg)@r=+n_oSDa_?z1(^99e%q%b#LG4NX}Y#c0t|3^p|nVJbW$Nk`NGd4%R@JL^^|WdUEE^W$DI5BNw*iQ&#Gg3gVL>o5JTi+q77pW>bk*IU5CNd zM%Y=Y;)hEEhx|ON;Xnu@K)ohx2Xx%8(@UxeKZE4#lI^cTyWvMcGA01^9`9|50HNw3 z_EB~}lm8S&!KEvFhJGHnT_0R`VdD-ts2QPD$k2ixkX5>7>xSjmCER@e(g2dsYg>=UZ96_x85o$Gr{{Drl|W!!Ihn{Bp^%YHBy(AQAzc}Y+w*fj z>z|nD=O0}=xiMrW24`jl6J}^5wAocpsdxBRtB_(z*y<{X09!%kcGA;YBk|YjM1vSG z!b))JpqL^etH}J3W39nCpFh_gnP4Tqbfx4+s%1pco;@43(VgnSly7}_YE!~wp6_f$ z1bm=4+x&aH@cWCi|3a=#+ZjC1)`o70B@(e@NL52GX|v1ntM93dW`}dI*IVRC&Iz1Uctkz;C>UGL0(OU;g2I9<cQ!?|Xc{y0-LH->QFY9n}y09wsYmmEXoC|v#dibykhU za+PF$K9^gaf6)2J^O5piKuqBeBiJoJa(xJhhLq&xu!L`4auvieW&8lEGEV&l|CNyT zQmshH{jb)*O)w;2kbXeb(gwhA3)LqtQ^S`qA6stCWZ7kM^Cv6R5mW$t4?|(bQBNc+`=fozTh3Ec1FK_i^^R>Y zFZ8H8o#Ags(yLUZni*za+c@iAahm1}tsC36e|kzE(?9_`yCppUKm%pzcwSMHV-eqq z33**ro9bsHEVOPkxW#GykD6-$RaurY11=c|zd-{L2q9n!LV$7?&RueY;6O5(AU`{9 z``wwViFJulmMX~n!i<+}1om+%z-m(+CW{^By1a-#JEf;$&W7hB7LW_EXc3xG!GU5- z^&j#Bu20i+<6f_KUof~Y<2~#tO>0x$#|q10K5d_ez1x>a24EjH;{L)#v5liDtQ7qh z1rR=HpVxSWzBYrHAdlACL6eVSUw*cxt}S`OWt{m9tzU7pb~I z7(G*b<<7v)z6T5>RzMwGf@2maSCivdxI`DQ4*rTtE;V|M%N&^)g3P)9=XU?{(nO#C z3&^n5;KYA($+uHgclv@`&Dml=R^AD`627(-zjTLK%jR$BGi1B6P0pr>njgo~%?-`z z{<#8JK9@h38&yK#^tWvkQpMU%#uDyo#&z9H{=_-^=XOynK3@Onp_IaL$dY@wv__QsFJnL41#36|DdXPA33U47zJ| zv=&X=WtzD2%>S#Fo$Pa}-`V{OJD2}loA2e$V)#sIY19L|MDx4O`Om2(Z?J+0>0j8{ z&`d6s*WxF9R{EuNx-V1#mNe4YH(fjEN2F{D4i6Y;Ye<|?XJMt%5Bc! zuc?(}^Ydf{I*AczLH!4Hv*&CIxDmL*y-amF`S|}>wp!Eg&pO|`P?Bfji85=xKGe)3`_qw^?h(dEy2`6Rh>2XVh8eyBA3scEgr{ zK9RL};NtJkTn-90{Qk^!fhU&uJ(%mFFUr}4vz}EC`(Aa%$ZP&b&)E0gZe*wbn8{k| zJn-jg>JOFy7rw(tu_X&XdQP2X%V}Rck?gxD4)*sa`@1&U_U}*jEl=vn--GP?$JES1 z@>}Yx_NrvRmZ-I5axWslBmkIvD%v~&8!jc(U&04IhCCB^YM}l4q5t;XG24wL^2#2c24;> zwbTd$$O}KvkVHh0pKe$F6=y1qM+XjZ2+0j_$kLX~$te8a6$$jEBNNfuKaEY0{*s2$ zbJ&V@9YYo$s1PpTJo&F`yn&OIhmF{US($~Adl$IE%Oa7C^raddtt8oVR?m^^h1WV) zea14*HLRgvEEXK%FLBU9Sdtwa@XBm(ppduBL+7ULtbxqMx437ohB$n&kyklnJxM66 z(IQgJfDa-UQGeD#TzG5Re!%tL1lNBjpdED6isA%_{iK;%H?1hn1(XtJnHuuzf2j7O zz#8%4*ir9cZxvWcvtCA(?TvksCyZJVUmJ&l>t$yN?e+p!UHzf+!c%Hy=&$2pK*m)S z%TlWR<#CF{irZ%14Av%xh9jXvmz7tbFv=n4Z+@tj&;Bmun<`nju-^pdbWSntc-Wo! zZ$fwz6H=@@RmWaSs|qvEC%8_dpi>!`k)|C7mAE)K8_a2ZMXkDn$i7(hqxANyS32zV zjmxR4jFjv#5tbCVRPsLU-2I|j9)2*Q@6jVmfotGj%3sc3QqD&28X3uC3wgsdjZlDD zRyG5yLg$qi)nenHh(BXxIL)qu6RjMjaOEX*1_3fQ)yoVZrjFD|;@IWr_1UDQaY+tI zP(>^)RJA`uo2F9<*(NFa8)&G`=;d;(fjxEInAhi7n?a@2!QWrG{)_#AsQIE9@n1!7 z4g(@MnhH`PnuC$XeV#1G@0-BSSqrF^W^vK4Bu3$v6vJxSK!c`&PehK9Fz{9 zkCnVwAWk?}ZwO>piSGUzKTtV@Qp`rQrx}5pkxdXCp`{k>E!0n+!@ekqTRxe{_q4}}aLEsGz`!_q4IC2K+Q$jw@ z_q4P21kk<@_ALUUDw}H#-B(=D&ZWK5V)EAj*Lu=mkO?T0-l9EX6I$HbK~s z#gzkzihLL_4@E$cgB9QrBqk>@2&Bm1H!|Ctpm?Zf=^^1S#D-#p>-4}-K<5u<1MnLE z4l-}Ry40>I9xZSXp%xFgb!AYbArg;Q1&G|$NNOu35<8eAYf3h&n?6{nfe!Io^%#P9 zVWA}4<{elP5J|#wZ95-psv`{;C@OMyUJlYzXb3Q#anIGqN)*cRJ$fj~w!Trf4;N$E zZ0vjVQ1m9k8^ej4RR2b@zi&iQtZ=G;gq)>)1BuAhkwh+ab}Gm2)s^N8IBhHfGoJjPggN6}Iw?-=% zjVA7tC1*N+(Nvc;IL}xjx*32ZJrnitAVpcdOb>&;xUtQM-#-{lrJ^s>gL~yx)IlTz zJSrg;mlpAFJRZI&94};cWeV&b)!%%coB(XlucB`QsYvtrNGcU!gOL>8r~Pr*{FU1< zH{*SMKp=>R`SidDxrJ%BM5D(rJ$>LXGW8JlVAN=7Q#>H+=TuEK7*I!3 zs}udQCHYfTduf?{vz)p)Xhd>qN~#n?Vckq5QB%4Q-(indlitAgRGGvXmuY_onO5w* zhfqC`7|IQ~W1ev;By!x!7PD&zmuvILDaEiFvErvjvp(E1QL@%#*_X1`d(3b+9gO)3 zed(;FhJ1=A6*TS32V>@r< z*IKfq>>MeeIDGUH!}ccSK(Z2FUtrJX?R%7vnGRUK%0RYc#^B|P8JAci{c&&Ld^_*9 z`!!4g!aLy0BFPjdL;xO;OLimt<&jKz_BdR|^M$ckAmPhIH^;4r8ZI@Ko`tG@*5;DU z?kCRO8I%TBlxae3A-*;><>mE-za3b<%K!J{$; zNOU45_5nk?PL{9Jj1Q+{Qxpp^6-)0C5S^_A;IB7OFph3KfE%|`uP6k`j~j7!Pf>U{ zYH)kPLf?AAT=5#c=O-4z5E3Z=6VS32VEREJwjuQY+}; zKFyY9RF!R1)fvgwn%{s7*+`P4`I!jPfkmt_JFKhwz25z*e(V(bj2@;$kaFkdFY)Q_ zgC z#>z!)z>FiDP$`|UR;S$iKw32sAsUGLh7*#$aIm61?g_rk16$yIb?&~#KRitJlnIKA zoMl2yA;(y`E~C5*Hb?pZ8|ErjVJ|yB%NW@P9P}&Gr=&0R!9+a$=IL-q^)~PRfF_q# z^rvG>V|QWGW9;(E>PltxR(eR#Cd%voQM56=__T!0C9>vJphIeEu?S_%Cm-Ec+O%dQ zfCX>~Qp$q>%7pL8^8^;U{MTxy^f6n>UcjTC} z^k6g)KRAK1+E;^97APp=s~cQuvQQ?u$nR>JcqS_8r7ZAU+q{y9nd! zX}Q5l4{5f1k>7ujY-=B(mookVF!sxua-W(~?^Cq)3wQT;8pk%kdm%LGBn?7Mdq6QH z1m>S38OmRGy+m|TFz{!9y!$i4<#JyvG%nDtlHDBN1tnYM@9zr>IgdPa5y7MaFX5fC7eGbQq)#&bIG6$su7B)`hoOjMM=H;lmA?5^L zrxhCI`Kq_Da?`pI$zwzjeLQ~ZIonre1H&86I=w*X-O4ujAA=>VX#(_C=ILFPy{muK z<~4)L$9wS*=7(L;HKLpV zx4>B>Ngb`tEvpeHw2yl&1wP3U+;zR1LhtrRD5?{{4|Hqv#N# zqjY?q|1K5J(bu=*!BnHM6;zFWKhaTh=a=&0Yd9idemUSM`ZFDvsw1+Ie4=-vpqOL7 zPKz{#HVzGKJbUAi$B)yOhq1{WuiH_p?WmR3B*_dTZ=$`XV%52I#=nV#?Q(XQ{{%lp z!7l~SVWCiM$0Yr7lBMhdZD;f|U5VO21ytmH^akp&h|PPEJr0A^2o7)?m+W07I*9W> z?-`CLEqq&HCV=vQG&x*$)kxS<0Tb$SYS}T*3=B*hFRvMrVv$vs?H}yn$fA`Q;!Nq) zq{_-7Blb;Ctp}`e@mZrZb$8#&eo3oeFajj>9;W z{?!9gJj!Oy-#6UNsLSje!uI$TP0H5#4dmv5uM^Xo2Y%qZW3Ap_>swxyw5f9^q-;00 zemD+^O6(1eeJC3DWyWT+#NGp2SN3gkzK$Qp+=-xQ;yf%$^N3B@=R9+THl>UHa`BHk zj#RncnYmIMRFXx>KTW}IKX%@ckW0>;S84;^I9xiPy;2)NXsz2K?S(}w1MaML{`*R; z$|_Fr^;%3JwximWwYpQ-X!O^P51u+E*^!g5|6p!Nl9ux|5Hy1WYbx$ysXNZ=wf_43 zl{EuFQ^F4yErFxifM+u;17O(yZN!$wP95y5nQu4~14e(&?Iwi)Q#aQmz>Y_zAY1@5 z=ltJgTDou1q;ZBro~l?leoS25S)G&z>c-j=NQ>9Azt_nl~J{~nF?_J zJ?xq=l#??5aK5uw-Gwb=q5)Ee>rTg%UzIc!N-#C@t3cizj{do+{vBxD_Ys zLGm(-QnxhQsPJgFieYQJI3niZ!*u_*s^L$B?Q6(u1aPfSgw=;QJa;8I>7LCV#rZo8 z*6z8-mZ}%o!^j|Z%|k4q_vv>69H$?Ne1_d`*ygT>9(u^@IfC-ZUr^N>LiQI8>n3t* z++-PFv_s9`Oy$Rc(vA&a;mPZ@m*!?6(lCm@#fVfMXh#vh=fjfx`Xz6|b)MkKGf3** zmNAWtY|~Na>*;9RIl45M5oj>t{D3oS+^S91y_CNt4$mFbBjhhBNPNM6P=e=mB6*V# zio_cfAfx0?N#}KsP(}5M@9s_1b|wpNm{+k z_PF0`^1?@||G3)-PZDh&@43NCevV5johJFuyeq^`|D;#GC7M9>Btl3-+a32ECDy2z z3PP2k2&g>}l`Z1HLMc)Dr`I{--AW+>qs|9k^p_h^P@+m8!*13~yc2@oT} z@Y3XJ z%?&CP23t58X~1yvSqqg{0s3q;e_`c##YA?PrjuPtmLDGB`=>v2dyNGzrV$o5KH%MF zxwd2&lrwxG-O3-YC;RdPWWhOuiae=)_k8@%>;QMjE#tbG&}yj0Go|H1l|{*H)Rcz| zq&%6r<$Utf(U|l0PZ;3_1T*Aa*7m`s7c&V|@WBUhr~lIE`e71=njM}9+m%Vlzr`=H z8|?7=@Xjvz?5u`@5yz7d1J~$w_)_`-P-=AUc8AP>n82`F$M*+tn$6>wMHU<(wonbk zU$QFD3P?9Y&rr9L(Eeg}z1=^lSZjT`Bv>F2*pw(n5|@GxjvfUM{K_fh!iffFgq2># z`Vz7)8_B0*S0NWNc#s_QaAo2FnbkH*FnK*#BcfA3z&xgKY+VUYU1u(Z)t{=`pl5u) z^Sa$CgM6fJ@Uy`x9B#Q5MvkSpbN+j*;HB=AoAJ4LA(r#9YccPC^Ea4t7Hk2N#|W1^ zs?PzJe9q_FCJAO(XTo)nj(L66ob{*8|D~QEiAO>LlROlOV+g<`kH@{zg8hp2c@}&U zg|sOYe%SfNe?|Hl=t7(C?)Xlkk1DW#(n#pEI$orQ=0Ef0vY*sucBn6Y2YFQbr_2(s zKf@m&Vo3Ut`A3k4SSpG&RE0B>>=5EqG*jm_69hjYbdosAGgEfnu~&{KI5T}boQbm1 zOoDjnn~Z%E+~gD<*RAse>)77d?64AA3f7Ua)2M#b&&v)W@cwG#RK?(sLQYkOX-w?6 zRh~51;sa>%loeN*j>Dxdd{1?r85b7}l9hMjx~_rf=Q?M+%a|GyH~bFlzMUI(2<_oOt58)rouQ%L z^Q?E$C39}gY@cUuG;^Qd^H;=MzoZ&C6Ji~nt4n>e1uQ`$3{HNVnfH04QLis=;?~#_ zkf0489bNPI@)7EeuB(3S22_0jx^|2GevH*g?&l}I3n{RN-I1~p7Kp@gO-?2)*=s6kGY|^|q5*RU zW)>Ft=)!iU->m|V=mpTaLY6YBoJxh%$!CPc1-tJwnK8$_?F%p@<~;a_1~}#+PfLAI z!g`Nyz`ET|sgd{$D;}vC1+zze?Ruea? zmhN3X^iG7XtW;x~M_MN(LXH-L!TP@>uO8E!P+UF*KqxWo{dk3vNZ;1<``V76XL#-q zovV45pj@DaliR2Gt6l2u_zzS&s@@jQ;z>!wzOTk3>Rqv<5|1jD`4f6FLr3Takk)pVn#N%HXORMP%qWWEn%|8UZZhCmE}8}_5A2!`#li_) zJsc@cazHI-Uf25~@ollt{H_6jw*4irZ&n22FLLCp)1_BlfqMRHb{z9thF-b}$o+u8 zc8Nsyi-T(Mt_x)853zDJh28AcA2<(2lPOR(HIU zQ;{ZnozJ7i)Rn>Nux9CP&IoyYKrYFRdQ8 zO~$zYlk>7*ZkS(Q^U^%SN+y9rn+k#pN>1)MH8ax>{TbYmfTc&x#&0_dtggP#w7}uu z@4L>T=OZB)s89?_=}Q2aGcmR_7vqBd)IaAeUGtahx6v!_^~r(Y#0p}5-;A7#+{$0Q z#VQ4AEBH5Ou@&CQzv+v_Jc&MYVkD|plYDQ8Vonk9BvP*Cmt2f`Tz68+)w<1;MM9B+ zFn_`MP_0z?g1{Ok$9R2O9w#7+E<)fNs0L7JuWUh{$~5p(o*dLxt=gztKEOHn5}JXW z=zz3IN3lGN5)&KvRCcd!5GhjjX0J~|z(FK(hn1rBp0v)ESlC%Qq|IIWW5jvbiOa z1<1!=#^Qk$c+PpA^!h4t(s(~E1P>cYIqw6S)M_M+$y5pPu9z(!j%55}hC7~L!;wT8 zduIYt_uYq?x#l!lmk_W$0yc z8qHrv%TmPjUD%R#ThBv{vatXBSb|ow#YSbRE$x8y6UIRrg>a>SPi81K8er#zB*ha( z8P%ws^ZT=UObU9#9>o{afi#gtbvelhOHvr&nk>jKRLUhhbc)GUM)%`1mPUPPOD!Bl?LRl~^S+e*D!LIuo02f(8N8o;&?i%N>2 zz*L@Qm+wA0R@^>xba!#=8T?00_TBT3&>LK)k9HUE=4fk&M~y{i$BWV8APy>Q&&~%& zMjqUmA9-vSl}z5X<%4*B{K1htuIG0q@#cfrl1_UudOkMblaB2^#E^@&dpGK}i2Cp7CqCRBOCKuW;-SJJeCiG! zedGx#oqNTcH^1yP8jEgm*sb$Cl0TlccHO~7vewR>R+c^N7K?@vV~dr&J-N%u-jTc= zA7X)Z(YJRvH~|5c1Iy*)!?_7SQ_e?LMKu`%v5bAr*H=ZOjYEZfhK0W4!mBoXBAp1- z54v=nW;4r9Ak-nyv+@~iR@k>;d)^bC>ZL_P|(8ObS}X~jSv(7mVm zE(#HKPR2uzfjYbXU>-WjP;P1vTG=lFMKF+Q{*0)^S&5z>9iVdJj29JuhRF5*gRjK+ zrcc6GGcNT`T!Jm~byi5%B3#5Sl4^8%~Nc*Ogb7$GA0VH7neYygo-Bn_!?Z_O-uT{jooLt-j9 zH+Oh`K1naPaPic9knN%W{7tJp(NP`BEMg?<)KSq$eFfW6GHWoTx^cB1Ubn6w5jROs ziC17L)UUWfun~2evr-Sq!#LjNcyyO@#*?1pd1eiNVW;GsX68H>$YZWl24v5k`7tu` z8SL`n7{5#+dz_d1V6)9*^q;?J9_x|Y$t785?6?tk?*2ePZY;Wq7&{*`M03_kZkaFB zvnHqniOq%iFGO!cr-aD&@gJ2OmbTbBA!UYxg#6|>30&b#Q|d3 zEuvxXO%~Ho-#K@Ck`wc_iP*D4{Z9II?%5!B&isk=n>&*Fy$IME8qv>ybt}$o+Qish zTcejx7;&T3PK03!JHL?Vdr+JK8=X;!o6+2nTVN3hxfW*94i_gfLSD2paqPT;@R7`g~{;utyvYhcsF zx&_7dOzpU6b!DY8eL1*bqf#y zx_^tnpi#LpuRn)D_fDcBI(iW6sXNVJ z^F+HmYJp=Bh>t~95gzX&R}rt+!Ep@T*3)PS|5yXALa}b(SKI~cZyM>tK*$PXTOG!6 zeG|_?rx7AH8fP@9v83jUUW*KxX_N;h+oBLei0G!Vim#Iu3OO4zLh;?6;i93&mU2>t zlQ(R`mVD4J)yPXcDf_P$-d8=seZaxi< zJqTaTU|MQHxxoupPRYuCyvLNt{-DKOd{CT&*9S#iOMHAqP@bm+h5C~0aO)fJvxJ4G z*S}wpk?X?X1eKK!2mS4PoZ>dicFBrZjzNrAggOvIH-d-~Cq1vdB88$yb2-;plm`zU zHMmR>%d&YpX=7ZsYBeU7=Bu^^P^;Dg-Md$a7`{XsBSL3%HO8)0pH}iHP0>~ z{?guqC#Zv5X%S>vG}~_5jkajzdUU+E^Icoi5B9xloYcPYZoQfByC+bFls$Bu&O~sQ zw$wf7TsYXcsu(sSp^G$xJ?O~ zFx7z3&Nb1I-HwX{As0?^qxKFiHgIJh-@x}3@LbL8TeN|9eovC%1SJz1Ev=ksY;axL z<$hiVvS-zk8rx=OxARVc8HtA51X}1U^1bAc?pfW<$H*3gvtMiIVqRKZ>RQ&F_F9V> zYx%$dV$z;9Og#dRUTjz$diFNjbsp&`qFuuzC7OoXGtlj^G%edsn-BA0I&Xj3D>b;l z>=C0U*7jfmGf-N(sAG@u0MnS?2&fo zh=SA_9sHtX>ab2jK@vLd(TXc|Y0H>8ZhZ!PahJFu8^``59Z}pZbL=GrXX$anY5)jz zI@&t+n_UYKr^Gw1CT8B*d#2obxW|^!Eo;zdk9hA`GJ5*mC4|@2$e=`Vu{8lj7t!r8W&iF$v6GS{ylofiI@S?n7jYZWCAR+VBH($Y7$V3$)l?8XP{1 z<;*Q#Et}5H+ndjkk+7>*!UEsDvvm*MZMR!K!E$rtNY|Jfu$Yp~X5XRaF|wz&__K=z zPS}^@H1O#XO5B}jv_brY7%!fC2m#vyQAd~s-`kl;CbH~u=45EuW@|0$^Ku#tu6{fpgOt6tc|K@6~ za4(SvXg*H@f{nJlOWRRt=mf=9c9mq|vvOTlt@fbWwtY^`pYI~CQ`_gXr3t4Kn5C<8 zuhACvv>A47ec#c60TKbtXGlP>>ii=l4~`CGP7xMRmBy+yJ`KM72O}+v@DJ zkO-K1?u_c0|CZVF2&8|j!IlCmB!X-1bers544855f9hQj+{k$t+dZ*R)s>J#BZ9=i?9*d{68W7PrGk^&qX29?FpOvAbPCnVxp%yM@JL zkQ97x?2(Y%ImI1PU5%4j7uy9FkI9X~C|>=M zboqks6(y@{;N1RAlHm4}um$ZEdk^1c3y(mf2WL)_&0B+1~{F<1B8tnCqLxxd;+J$Tr}F^^NO+%n!3Pqu;@ zy$+?tE3fT~I)Qu;@I5z%BJ!&D?UFG3WYOhfYLIQem*`#2sm{C!^199w+?XG&_=v%H zoM~v=Ts+`w`GJd{7286&$HO!)=r87?*FDw4o$ighjG(kf*CcldFyfw~=QqCa*^#v`6L2NsOxXw>_vXt48;rETciL(JoR$B1{$F zj^SW&9i5fTq);glkLKEa;^@Bwp(bSNbc<{xnploGxZPE*(}e^ifDC_Nc2mGS`VN?5Ef)8kw@NpmsOr zn*ZuTUGDfNh_b>dOlzZSKX`#53(2*Obji84Rj@~k;ASY^HagdEmmZj+yX_7G@I8Wy zwk}cDwtIEyJFaC84U-;CdjmNos=e*Dgg-XSe(JH+Am2FGE@(d>k9pe|*h`TV1&MK< z76D?UIUr!Io2dn@7pYi8m2p=#byE!5H`$gO!noYS7$hA^zheQp_v)evbT4-WacXra z2m?*W*xWrRx=%ygNGKSt8W26%poEm+>j=V}tWF?c&xQ(`lIf;nZEg>$wOmQp?HEnD z<)RUXFrbMN+iDkuKs1-5T8oRzq_#D+H4-6ogvpm0Z|xLZ^XP87Ly-$jS4qPJtY~*5#7HO1 zEVu2^A}n@IDp?z`W7Fm(3~_d6liuHHv}0}zHj15j11EEHLeDl!yX)Rp=}y;lb-L3v zRm87Y|J0q%UOK?H(gSX9KrPqxd$KXM_syO$o{VoF=jP`*;QdhqwyR-3@*2mJ_4Sj z2_sv{GxP?}t+x09*)?^(T4NX*SxUEXE9nQpfiyChQb!)6H?Wwr8Y4mRDs2H*pC5!U zSTdBJ0t-o%J@W{7u|bnZrV{>+7_@h!#GQrL;?FpliH5uPvo@Ow^P$VD9ahCk?AT0# zEw!gWUcYps=(g82p)TK&0da5ltn%|RmgPz9B|@0BB(n%e=IhCAzh~NAK87W+VH`M- zo3U_v?IpjHC~mM~4_~`$mkdU&_1jGo*iArrL-5#QqPXpygeWIE4`66c!c@}D72W+k z>8f_yKPgcTAk6w?G=a&aXHB#$%3^aByAruQrJkkHrQon#ZG%zI32?t_Tx#n*uWm#t z_q5%$pR@IClTp_Jd9Q0)TFl}nH6(S~ZqT;UZA4)^+eV{q_4j~lU+N~kYq5-q5PeDp zh7OEFz1XRye@a4u$K|&+D}~vrt5>h>O=$Iz@6=)ggKoxS z_gj;L3}m7R=I^4!#Vv2bBGt^# z69v32y{{t{+FpZhIr$(3@{^ccY(FK7B;NzwuJ$bd)>b%6Bkx)N&u~Wq#qjr#L-;)w zX{cOrfN!Zyl^$}CTMZ-`<#vcg`-JOVY<^@*2Cwk%S}7zYB!wP6E`=04;o;(pVR@qAJiC5))9{An z0H1m@(;T z0m>ll96(_VB~5`6;`_Vg%8GY*ket(Ec z1b;5Ryy6*JKI%8wTd0WeXHEa;@*z)UIcx#$?;Gp^q-CF^14n#8P5N?r^`Lib^<*%@ zZk4p4@5_JB`v<7X<~K8IhrLVJ)|4oFH&yBUW>l%IUFscP z3s?)$fra3xexteeWe#>h)uz@E#yfJ}fNI2Rc9($tHBPHBq^4uqMB|rr~;+gDoo+jF1HRp;kh^rR-P(h&|gTa*@Tu`i{^($gr_ zr21#VVBtU{to_K@f0`Z-9emsqpzBZ2<6C)Z@aPD>&9soh7@&$mvKqfKpn6uGu}oa6 zO!hOS&NM@z|DUFW;ljefIsK~OdO{O1fhJO4vYIm8iNGHJA_A8LB_Vr(qRn{`Slsj) z<<7>ym35vgybLr>ju%5d!>i`s}kJkCOGhjoOj%)Sv@p2Glo|&6`cTs>Im!5Kbq?c zDQ4#SSEv6C&ehjQmRr;HX?6%6H!9?J>XMz#f1j!kq+QMsR2Mwq;sn*52i_KmIhSqJ z%R^?`V8KWrx$jvoN_A?1Z_r~sl*lB(V8i)A6ctX|R`{AQ3a)I_^N~od7}D+BN-8lA z-VUqR@*Lx>f>WOd8zjN0vsh=_>D0puo0Er%TXz84B3_royjQ9-V;-`-PznJ@y%n}I zku-h}%FN>541xX4P;?f5b)+zp^+r%c-fI=3{0a)xeV!oc{!UX@2+W_Vo!rAKA%|^t zI5#ynl>?l>@Z2zf(+acmDJx`_3nt#Y%_HHF#dpRDDG2qDBe9s71_TW8!TJ9mYi|M| zNm<^F&)ao%^-)z_-PP6iea!UC^xQK$v%52UvG)bLoWioWyMVyr0!D~&X+;!85JW>6 zv#1zvRuMFe1`88PjQ`*BR(DVL?1C%#ptq*(dXMLM-sgTW z@lT;Y7N;89INjiwSCC1Ajhr}uT;SeBmHMVph67i#P1s3{JGXRAqShKm)u~Y=zw4TS z2759)mKjE!S)VeR>1SSDj{S=tyQAr@^8;DjbHg(pDoCq*i?+)a=cl8BavvT<{SP*f zRZK$*XR7-!mzlAQ7w4H@^Rof$_WL*RecTv5)Xv}0SyOrb=Np2*C^XmJ`>@WL!0Qd} zWG)sXfa5uJ2Y%O-`V>}8g~}9A!X}SJZJp ziIb|}{oaFIJznHIR#a~&)Z9V-9*-If^%_Ac?-De6LvTCC(%?#olx~fZbDXGijgH=- zb39>uSMN}e;RM~Q9_u@$cQ7*$ECmNLgT1GyloY0xUa2DOtmRWxpEY>hpu2-iw5m5w z3H{?EH;uSE?2+D6`i53k`vQ$XUv=dWUUob9PQ2rz$YTl&^=kNs?zuaH$L|w-0i$FD zeBp3&r`0=3^!9SS`@@}UEbSe*hDw#@ZYWdNOL{#sRNLsw3}o>;M`b>=q)o5m4Nb8K})?pPvh6-oZh!i!D4HfW4X$#z3yv}Feq@@~UOrwa0 z(a8?R`L1RKhKy88Bc)fmhTIF_r9$0@H#PI7bn?{?@CAdFYcKOm^A%JWDPbZB3?$D^ z^E7WRVN$oI7}>nA3{vxpGAEIXy#Sj6A&3MOq;j&NGSL;Jd~J1Vk#qlOvrloJ|1K!? zr4^qy)#!6m;L$M>op9 zD_F$AX`*VW;;*M;zB$C#irCSptqeUS>B`rlu-JC8g+n%d=a9=3xFvuD-pLFpNZL;Xm9)?A0pYUVK`7GjDJh%_WT z2R9)D?;*jzUzd(-i-XhyDyP7{TkQL-Weki^Yj|^micw6AlfUtlUeDo6i^3+g0ovPa# z6kifCMZIzWIHSmhc!itkyV=X1fhebuSf}pJ9Gh^q915beK$B=Gtp&Av-l7d+lc_Uo z+wl5fgMx9k2R3?w+1_Yx$^i+s+|fQ9tLR#JsA|YNWm&9=N<@+^QLZ6BGLkh1*lVrg z@Nm(prjMk0J!W8|)tAu)BVDzqqK0)FfoM4VC&|IVlqWF|-6^Yi5${2MN70f+>ta;C zm@i!sd`N5{pUl4j2&)i)H!Yd0R;^jgT81Irh?{Pen4$KxfmI;(iLa+vRd@_3*3-#R z4wU)2B5QqGKCgk%*Coa9nT-zMZ1nca4R1YB5X5}4?v?xdq!uXFbADOY^`a>FBu_AG z2H0?|*a?;Q)R1dL+B92>`8^(gtTwwzLh6wkL7>YLP`=XMW~T_q5On5R#n47bib#l# zW=>5usMt4GMG_FY=vTxk0@u2Jnju0g4u)`7M6ZBTFV`Qo ztpO0!gTAyclMxjx%~Hr8{ZoQU1K@eX#Q?~A3KdDN+9^Ru+f_v>7sMdD8i0oi4nAK- zM6j4Q64h$G`QFqKz#j)of@_NrlD>ZW9GZ_?fw&0d9eTs~fi zP=_LgadZeO{dSM27GN*ZExRjFo8+}R)J7tnFOPT|ppk|}gp3T@kM-Ez&e_)vA(p;sr(zueUeg(X9Xb_>-S2%6`CT-qH6SEnxdE`Pe2lM%k+BU zxs+}8W$sV+?O;UT8;SBmMNHJn#eS{CM4?;mJW)Ycq77TRr^$spWMbysg5k>s!vjlv zCrPJjn-#o@BOW@4|EJtIgTf~PT;XIT2QrP= zm8&HvzbmQgcfafYeMXkqB?MHL@3@=(+YX3bC@35z6&{u%qlggmw1r-59E$wPyt|b#UV;CEw)cY@FhH!2m3L zYEG>6g&rt>XdsoTl}ZVe>H4A<*)efBmQ+@se@4~bHcZTPd3iX!dhJ;Ad3@_B+3-pvm{+9C zupS#*UQ-P4$iU9o(u%P4C$XIQ(i^kByeM9IFB%9Ps5_P~v6q0Myj%b^FWEpg^u@t! zwT}v}FSZY=xjZ6Re~Qi*&s-hq-M)JgyM3#cr}eQlNQH|bnUU3F2|M+SP>Nme{YAN6 z%8w|@*8q?#oh-AT(-;O5;`Hg;N|AK~^gw=0PcL89$8r#4kpOi%x6ING#tR<%|L#kK z5`u8a=aOX)xWk_L(mR78p;_1D7l4ESm_YQ~4E<(CdP!YU07H8nxb(mcgFib)`5z z^6ub^UO!ddA-|@fo;fGCpvEX|u-I^&%XnDS7+>c(sM(NVy1G`kz!(KTNgKH4jT=^Z zTt)?f7ygU|{lYVz^4;aXl%z^>;YGqsc0VdcT}Ag|W5AbBl?{~b9G9f4g0`1ko+vyP zX6JIO6)qKfoZ;Jp`T4>32M-C23@GLbEYs}?!emvOOF|1rfwaie+(ZKx%q9db9oopa zI5EzCnmu7@_}7Mq6Pe`LB8dI)Z9=>xJd?EJs2jv9#P^fB?RAQX&dIVgE6SfSBf(?~ zu#(K@DZ~$I;i(L?^Yd#z5Zs+_d>zVpB3uxuAk-?agj0;_V3yez6@`6Emd_7H_w9=YPq{4tQg+(~B5_Sk_oheKSMW}_va$-&<)LS& z|45{Vg9#g^ zBgAceM3S(Eka@e=>Tj|qQ49$4)2w@-&`>OW_j6i7e_z1+)vtO3d5^m^dA@-aLZ8y7sz+#rI-;tt6NKiwY0P~zB1_-hXs&qF?(Z2DVnQ!{^0`hpW-b^3+ zHd_9as-B_hS6rd1lcMl35`jOaT*=4Vjbx<}Iqv0=_Mmw1>K|!2?Us=5i6?xa;^H!h zA=$Y}4P9~x>J|&)QNpXEG6to!X#bkOI0a#O79~}>+Rk6kdET)R8m8(LRr~IT9U`<} ziY}!P-JgGZCd!lnS^lmkA04^my%v=g{se6zQGHyGmXTRuw@UNX)wbPMHt4qnVgG?> zX6n!h=>(?IU3MM?04npPM=4K6Ya#kBfPRFNhzST#+!YkHLv)}$|Krx~LPq;3a7~tm zt`K)c*chEGW6{qtzUs59H$y?}^BJbHnKpa;{ylWUR$`(A_U>4s1pL??$}aqWt6)+* zZ=!L3iN!0kvd62dM|`L@C$B8?Kik!`Z#$i~#fTtWNSY3gMuW__*(?OMr4K0aGC^mn z10>=PQ(X@B${j#_OvgIkK>fHXIslU@u>xc4*5$Gm^m>Duyqq>$RCNoTu+;wWp7z2o z3JdprFl4UWL2LnZ-y@Po2_Ve}Y5_O7c0ucL_SOqWj4@1-R-vO%qUn_`oOQB|LSFXTJZj;M2-QBu#jcJ^^ko+~z_&UzIqh=CV!UJTCD9xU)}y z>_IcNqA&X)z7GE*TljkyXTJlKJCx6?7OivhFcZsuN3LbvC@su67f^Ghmyq1zx^A* z-M?%icB?W)0dChQNC91sTh9aOTwHD46r@{NQBVXL7L@Gr1E@X0H%IMZ8^57i#4sYe zY13J^qSD*DwYT?u#pIr3k#AZ9@(6nbJYl+L2!OrhgRp)>CUPZXl&@qlxT>) z4UdY^$#X9|cam>z56Iy}SPlfFNFpKyMy(9hBF@y@Ki09y=6(2Ev;C;h<+pd+RMOA> z%ZQPBZ7O6o!t~dOz$D{R2wDcHMh8Zs?2dsu9$dvjTxr)xuqT(Df93hh_~!Smp|Et_ z+D)5d)6GlE&Y$k9)lRF`j#Tiq3g5WjYcNtM&``jm^!Sm0AolVjQJUWOhJDj~^ASys zq#{5`_s?WB=Q9jnZovI>Y^HhJ^uC#)L?9RpB=)#}jy9}t_Ofi)YW``B!mEadk0qBS z@tdmKp^$wpZ93thK0JKx@bF!|_+&5Nw6hs>4Et24fUpRF(04(vh5AUa>vasQ6rYUa zH@*mfwZ$oUroUc4qh9||IsMvnnQvNbirc|b&&4eZvHMZvO~4c*39|w_2LKNLj}Sa| zbRq@3)*xR<@OjZ>k@*5jXGNB`e~c)^<(ldd0|CJog`5P1AK29$f30oyvz#2{32ir2ES{)K%uRM7G! zDp5f>kxVs)W51aQhyojZb`f^);ST_e#A;U=rr@ySqe*|f6px!$A>5y>#ZcqE z7A>NI{(z`Wpj2)&SL_7$p9UJ`I37?VbN%`BdFetVglZ&$WLrj@e6U)aB$4}Q&*aj^ zwM;={78O1t&_~0XO{3CivIS;~Zt5iE(NwA*o{;5Og0V=f(VvI}y=0jd;E-je^S*pI zU#qc47MF?p)BP#9xx6`%o*v2;nU0dgK1mP(@Aa}_4$Sqp8`>g6LxArtGSq4)q)8;I z$B0oQ(dJ@dKX8%5eg{#Swi4u15U!3!VuKYMy5@kS#nPJJW3L^kcMzx_CVI(r;PA@H zkxVHm2ECFVHIlLV>UwkJ+||Kye)|u4gnawR2G|^u?mhtWs))a*B?v91EpFKwk}f+jhluikTc7J!<_*3 z?M#0fzqe;{bJek;X+=!<`YG{*T?pz!rwq4R*P1w-C_C@HQ?m|&Pk{e2xCbwtZ7*_@95SgIT(fc)<&Y z4LIMhKo;K;6{Q2MJ_xSx)N>V-GB{-}5(>&12_Ej3+`;Wz$`Os)BgB-s|3g#9N&oq@ zcA^QLWBejxMDrqgBp}IN-#dxQzs@qEDpNYf_bw=`hwJ%_DS1uwJ69SVQ+N?=w-GKT zl$<@Q!_iae8}M~n>}LlrbR%)YhyA`jcn_*9Sn+E<+3W4VjE|!TnPINqDytqv+ORef z4k|jIK<9$h;qY$W{?2!IOxevuzz_S?nt>@p$>H7rD(J0y?O)LQUoTY)PeF~of8RNCp2sb(!y`0RhrkM?BeKUPp zz=XMPj#lAlv=bF?HliU^HakU48pOjINi{T@&yN04D^+2?5vTvOkiydZ#Bu9?h zN}D6c2et#q&z(VqyGkMfr!^QC!$v@>&D6AM8+1OK?WFy0h~;AVo$JenGktxTaMnlj zi;P@Yhmyr4e(h2F-$0lPP8ls!UsVrjHsEYMyymd_bwb!-D{bL-x-VyD`}?zI&KH@f zRA$QYVj@wDFPXbWA1y^)LRz&wd6UosgL?qvxPmnq$y*mYNz*e>k7q0^VR67>mL9@9 zhL*Wa>mW`!D;bF-t(|6yKq_UbX|qB4PctpW3vv8f3z0iUCs-w27y<{TV2}6*NHakH zY6S(hxL`en!w#H$%^%{!8eNRo_W~!)hmkw!Qp{|&4u;(-ktAv#)xIPiqgp{_7yOU< zpPiT)-OJgqK9}9fmt#pQ##u2@0rSo|u}=K_4tO?r8-wt&{<(of2Ao&{jF=o`GWakC zhO0G#E!-S(X4h%425RE+2DRJ6#|!LiC*5o^TxDXfa!syFLM>2{b6p*n__)h(d5Ohp zFK&O=2IBTPSqs67vpOTZ`*@T#M$?@izA* zDuxK=%Vo+s(eS5QJRR4-guSE5f61B*?km(FmaIKCFAH5wW(P4a?##K$6DVRyWrJCD z+je3eEEaqP9;`vCo}Vo;VJFFhb*WCMafE!(<#0|B?J2lo67h{(qV2-TPL^!u6?wAR zSK!Hd<`ZAl{wm5o;r!i?wad(>5|$UmQ>{en?{o7jexl7W-#6LEkVy~gK-jtCr;zFp z{!bZp$R&VtvRwWk`_{;lQhB&6tt0+o9p8NtyYG%2cS^#IY?qzM*x)okap}*(|L{NU zlbBMMuX&bz4o;-_Jm?j?=42kK2%w`B4j4C5P!iujN&r5o9Zh4aVpZwu#FBhns*Y6T zRkXWGu3T_}b0byuxoix~Ni5s^v+$eFqim}|^Kbg8c2JytmVKG8jpT9!0tJ5@J|0{# z$SVfAPD!L3!CdnZgPK)i2c2I%uT`A~pVum^$N9<6wD&cjTSM=jdX+D{COarrSB}a9 z$*?I8ujuVvF$~23ILO7LaB@KE|730Du(z097fGf|^5BXAxtLLvPs%EaxZ!Y6E@hJ8 zb;%-{GJojjn!N=!I|phv@Xzf8D&#y*%Hbl_lyRLxD6G~R>x%Y0d=#rgIn@y%hx_#9ZtSM#=P9dnDGrGw($+;&@8-VNdG2lD#Y4z5e5dh zS%qCKeLoFQ%ksS3Gf_O^=Jw}BsPtJLkv*HkP~F~~2+YP|o@(IW;bt!$Ww*M$aNpgU z1VSxqtkL!IyGNUIFpwSHpSF&{M>D>EKf9It!12vto`+!n;p66@_41}+mcP6`3%ub& zd~{$)t)_-{S^B~+b!%6yfu>^)FzkGD8_!g5&25X5Omk4=Wl`Mn@oj@7gCf3q{Acc* ziJ7vbx$}U9I2@bV7Yi-?Fl4_riEn*Y7ZuZfo@V2DJ1mPn{g(F|v6%7xTYBbqj_7Kr z;WEhXLX(4WWzC|13HnoKWg_L^;K8sHrwnNytQ9{;9F2RX!i@#ZL$VkYI%FtjL0yT2 zR?&!r1v;KcoJEm;$t1-C#*Bt>nM5^@p)s*6DbT?XeUWa6j4d0B;C{)TTQ}#W7o@hQ z=zX!(tI0)N@6{28C&t#rct70ExRP?tb?|9P%e*sF$8`pkQ92tx%avrU+uB{Obja0T z5`Bt5F(QJ3%5s*qUlc7%#C|v!42!;-P?1i|KtvF|KCu{!b?{KPQwXEcH_&I`6{0yd zIVuYczZjj3iil+lkB^7NI=v{hb!&?5$e(pq7Y|I{8mpGFz|9^lHnmEIo5iUitxVT+ zO^$aizKst{7)`Lm84RujP2ihQMleaCj-VZc-X;ta&Arxt#ZfBa9IfgLQ&pQMSUhT z3NYRQEU%9x5(pK>_(h{A*XiGeA%^-PL8o?-N4z+RGckTkC?s~U?`%^HcpoEFvLUJfDQ$I;B{~| z<4$6e*#~D7p8~gMxIs5}=Fi=y|AI9X0uc5%|7@fu2P+>1do-O=F=Vi3^77j*H#Id5 zS0!@bmirmv?#U68uOV_JT;V!qXiM^>#UV=(ERs#{u?L!gD^uolB&o_9e%<`L@X%gO zWf!ZO8zy3~{9rCOn1dH^ZZOZVcQh5=g9_j5YKp%pv(mm~Bo&^L)%DH4LsVrt-N$Nh zDZrC>CffXaK2<|sk5zot0sRioEc}Xozrg;RchiI~)+FlFQ}kt<^9M9c*2(FQSX_(5 zNiI^3@E)LlZ zznIq>8P4a2w4vqmQGHtLtnMwL+!eiiq-rbf}c+RiPn zMJ94r?4FPya>ANK@igQ?!zFW?a=A$1HGKBvVRpzz zeIi?1nT@2^ZkzI(2(FUrumU2SVwa5n`f9YL4SQ{Y-Z8Sw^~SiTFOn3^p*R$y@lZJD=@<)R}(5>?F`s0O@=VAJ(t4#Ew7+wmgoG%Nf3X#OK_)_-R<9aDeQhK&iL z*(VJdt|Gv7U>&QvSm?5-Cjl*)@l0m__)2WYGqZ|xfM~{kICT6W_{q4>;K3_g2acvo z`15U88ijwE+lk{LsoY*wvBcW0T*-y;KL|cN0veeCvg3OQat_mGDZF%p%O*s&)BV^S zhOJY^514p7{axK^Eyn-|B z+2AyUT$;#3qt>(D`O#-l8krZXcGru>F@!d!M_171q9{mfc6@Ewr$(ab4sYb)u9bl= z5y#SF%SSVr(dA?5Y|i#3>jjfV#?!-K+({xIe7Mp7wjCY9nC6F-Hm}I)S5YD=zu6xVH;57En?KMaJ4HL?vnolO zi*BPSa&=CP-mdxmTC)`|z@sOOrFVk7{>rV@mRu5uHlaa67jif|@6!`0QT<{h=^MVx z3WKxq$dU2XNGdh5Y&ew~{<5><`e4-jq!mdLvI>e8($ErNO)wVe47s1Tf^28Jb+ z@c9M8PQy@rOo3#a0{9uL(7a$WTRH@actP9h&>JE`%M-5hBqerxN0F#&To9FKv1ZvHVJ=sW3%IGaN;Fv`D-{} zx($97fJu&K)8Gi<7>o>mT*cA>nP_kqaE6z309i|bQ6BDmt2Atwyr+s8#O8ju3wFh2 zi@WN^0`BK9i@UdC=v7yHV zuYwn-vH(KkmAxUW7VEVnJ^9WpoF{0KdY5VzTo@w^NHhriJ(3ov1K z9vvSfam>ocGW$l%WaRlK;g}vuee%Si5zpj%M=R2FG!=*>Z%YlQvds}sJ%@6l*@;3I%5$R>#hUsDC;oL*n{wK+v544;1w`F z#2};%LCOHriN*Kk`=ogPilK6$l6-56J;a;~EJ;GD1;Mg=$Pt#WzEShy)qj30%7PJiN-iX!IC) zk+-d_FwVg-1J(+F1(sJ7L*ebs08Ds$Y2$iS$>0^JuHtC4@tf+hWjm&(J{(CUV}tJ* zgr^uxbRGm63ME7o<^;h`B)z?fOl@y0ra`Ij*whYs;Jc{^BCpeFX(G@cx68^kqQ?}N zSVoIeDYQ839UL#P8oVhr&8N8y2hsdF(3L{o$GFY~jU;~$l6oP|AZ&?mh$vEdNK&xC zDufsiV&&a+In%w+u&cv%W!a!Vos0=WEGBp=_`W5K2SP!}44Z1Rmks+<;dxK=OaMd^ z9{xftS*e;-m>Tv9F`=Q-b6%k);5qSe6>+)2;LdH)wYnL`+Eikri-_Ci9DWIRQw{x? z1f$+lruMb?hDfxV*3fVR+Lr(hCc@L4fJT&EJTy0UT0IwiB>I~4NIE?-Lz{0UQr_ON zY9MM05%j6dyC=jvrs>oDH|~t4#`3{v*6wA~sZss_HVcm^gA>KvaNT!}h^K@;^ebl5 z=4>DS^ZMWk;<7&Ox|)I<%~o@EtO0**FoawrbR}JS%J~*f&;^Q*lH_ z*tXy)jMWsuMo00aSLhY+r1(`NO4HG1x5>ZY zKE>2z;(6N?1oQ)7Pm(_;`RGA)42$cDK&mU{#<4|!xILUmmg55(R`!RY9!o4tk7(&s z7d(Zmb2vmfw|rZ-D%N&b9@E3fK{Q4X)CE9q@}{dU49(y1&-!CxGq2N++uZMH&oZLp zIp-mt7T!EM?gyDiZ)xj|FrFZ+rJ>ifWH8Bx_Pa7;T647pMk{d;IMN@gul@o+5 zn>%+dYP`pYjuwZD_^}413TdVdd7?Hk0SDCR#WA`>MzJP!ehIT3OMx}46P0VHiK!s? zj1&%V3~+9KNKHDk9}vy-L9%?E?)Odt_Kgb$CS!IzN46LE$uZg4d$FF3KU>c|m#g>o z`lk&$r<|UvAE8W1)#Op1~1UnTKb=z zhaT2bgDo(-4>DBQwF`u?{(uo~i&4;6fiWkqHs_mbq6w#=heH{pkiEtfDuW2E%QN_jxhFZF{x5yM+W0YWY?{(%OpSmo;}n!KYJm{B zECUfE;8{hfRGV~)>^)e{FfMcjgm;`fJbRFZY}B4*PosEHR-8Qdf;CV*Bd^7}^EMjw z0dFp6N{K=;HI(r}G}uF&Mx-7T8&+ko{FkO486U}twrwp#4E$et^m29I?y|jQln2(- zXN8&9>@@VCKa;mDCEOd(jOwc_aPf!@*D}0C!&3?fBE| zN2riDNr@+>WVe5m^^GeVsD}+KZf+N=V4lcjo7At+xKf+}3Hg&#*e81lWC)NNJbNsX z@{BDD@0zNt?SoxGVdC0zH$?{el|m^ZrEV3XDRq20FO6~O_5H)~RQlhM25vT*@ylGW zy%FrSx#zMCr8(qtXejdFDrxF9ry7Rg&z9qJ(O@d0L6u;^3pJB5hEYQ0aAHui?Qkv> z;<9nGXLqs}A<<6po(*9d&^n|#v7148;GGqSX?-N5Zl&TNDRoBdVe^LsEt#K<9eh6tv zx7Dkwd&NA0+kJ3tU*?(IwMo;0vYmm+$RA93k|v4D;7lUnWUQj;e-q9J^o9~D*K0Io zWe^~D&OXb&*X~&dP*Trapv=-b&wUPBU4yek3n-x&ce}ROvmeOWSUFrflp@+wY(w?|&4x7>VeIC`y?KyM6 zTt5sqQn@xGjqTbJLU4x^iAN+To~xd0B$yvdrN?q$YodLY|BI0#z|pFW4+Jo=0LS@D zW#fTB<5~7y5cHTUTVU3pwjmP<_ca~w@LdDqQ}`2UCtx{2L2XWkTt7$Yli>RP0LK1mpCUJyxYPyQXd&J*5N5V#I`wu;WG-9#P7z&nZGc zNv%jp>c&Nl(EG_PqKO!b&3c~w2CM=Zs=?1P=g3`VywPk#xIJ!sZC=T$of6vw`?MSo z@_94{8Di3cupEcFE}F3J1qBKIe*Z#JG=P3~QGR2;77%)R<4Q@CgVN-mO~`>UL6VEx zN?vp;y(%LI7JeJH;U3b2_VB|k)&hU$KOZsv$I}ntAJW8sg82Km5@rw-s;3NKb{X-p zw&aFHQn`!de1jFv>&vBc$?)Z_p6>kzz2VSvAyc_r)J>$q;)-qxox#qrzY)XcBx&lJ zg4y3br)M(yb4c}f${zBX*#m|67l7di^`Sv@H4#7Ms*^a^-*c&qC(U&A+={dJd%9N| z8K3jG?Qw2H$gUu5%R3Lf7)?3f`hjkW0pDNvokQ!rsbTkpBrEv=>19_oor4~U77#3G zl`yDin|VVQe|M3ba2CEEl&ybUa~C+cfpZ0WanP=E*w3R0d&}GTkgk+X_MSzd<^|`1 zpGQ+8tzM95)A7+M*{*wB(X0A$-)#J|A`ES+<)Zh`F3K-|dbVQd_v(gnHYi+5f~LVM zsfZ-P&T38fWJa>U^J9y$%P;QL;&JU>=#8h(5oB37N06jm~w_#MYOEyjHAHe}i z6uAzNEm_5eDvcNu%5jZ|?x>9PqntgMoZvwh#H=ilQR3xkCa?eVdo)8iOA4FPS&Dw1 z&kuDG9Ow{?%AQ=D)ME+Ln;Jkow-&hR{&?^nHHMDe6O12$p-P^m%@nc%;@2~Omu-wk z8dn2&b)NW__*G=jvl!Dmh1+H zW#t{Cm~(NIygGO$Lum2XBWY{lJ9Hl#K)tiOai_nbC>M;1xDVb2srB^i`V=^W=5lro zMwA*yA=@rFYB!Kuj+%;jBql?(79~cJ+11>wjBg%Sl{a9hRQV0EcatKHDF}s& z`2uTw3FM7bCM5QkY}Fvc1-|qHT~!(GE;Q*lP6+5K|d)RhkOwdk&Ut` zDSHr>xrd;1BkcHDBwt;Ui`6g_$$p-ym2$S8%J*V%b`zyS*%d8FGaD78MctDjTX1F% zy>a7=RIsjuOOzI>h1`s+d`A%zQm8X`E69T?&3|~mQPLWZb1{qjxe0i{b46fJhjwm` zk@vByUxcrb&MFjf+fG;DawNq_3g!Ub z-H?e2HAFy;7V@CbS@pPXAhB~>O-I@pjXrJSol;1h&Ynk0V_x(NE%o~qy%sH{R*S)f|4R14(M|=zcbr8REf&Iv;4?`n z;Om)BUSoVoiqn|WLQ-ym7vVKwwe1{KL-x$X18_cpKgqWdqlle_H&O2EdXBvZ-X-?J z_i4*|tGVb?wD(jr_kQ|&kT&yQJ8YcGI-C>}(8tt>gChBzd`6)+hCqP)BTyJrF`RI; zk}MMC3v#vj7oIaNYNJVj*K#(LE+AAGu4xOG(3XuKhK>0MZSHW5&8+YkJQflgvb~{n zNEI98565n&?VU4+=W%IY^6&@n=UIZ?!*nZ9%$4#VWshMX@?iR3zL5aRb{pNM!m7rX zRM!iZsK=;gC;cKJ{KC5$68L-8yC0`-rwuu!LcPHf_Ys*mZ8R5*1r>n^1-qKEUA^^! z{a?YA-2V5`bt<7lf3ayb@+tOp-pCqcN{pnuIFgg?k)%d88`mIX4F*)Pl-58JqxAqQ z4CgVJ`Jh6OvT!ZoEX!V17%IH?N@zdWS_T%i$;&Q5Rr@Hzdr!Gzmb0`ZECN8Yg=yB_>6~@;DHG62 z(~a#10E0sazgTw=J4djgB(emL6CueLpn1mj0}l}R?HJ1GasA`T%K?@wDcSx==5@ztYwV2g(v#c4ZN#DYk<@;i5L z;gQ-ESM17UcJVEHTOt@+vGcUTGrx0pPVZ}7!VS*OPZ_Uoe2abS4}NBEe&_ZX#KJM} zuk$;%Estpn&-7k;>CRMYC*QJb5h6FUeP{mZUt?$aww>F1TbFP{bDjP^5?oOg=_TFS z+|{2;i)Ws`DiH#s|6lIj!o#)8FW;3;@8Vl_eL9p_b^4jb7yrxM*^KG~?j>xU>pyK= zeNz3oj9wncXN+U+-og`I{kbk3iqmI)e$3rj*VP{vE5C{9kvWN~0^nznApvssRJ5Ua zP!*dhZ(yd7D&JG(b5@OBUB0mPSVoCOYe>M&A;fTa$b^;*V5lGd#EEi}}~QW=$-% z_MCIp#$x@w8_pgWd*kVPMq+P>;f9BkYv$%@GaT_R0nU=~l~Y`=Td8k?u# z>Q#aOIm=xJ=Wo8(c+zNGjFUWy6+le7OP*hYN4X;?4X(Ff45oOc1n|;a)ztRM{37d{ z?(@kZDdtBdih$o67A-ZD9$h|?VGPwOHgLmjkZnm-`_ggUtHZfHGpPFgYB7qu6J{`8 z&WxIw)NmTPo&5{{L15mBw@7Jf-)g;ND)XnDnpY{kflG~+ zm*^(YX~nC-Pgp=WbO9?jDpUj`lGh`N@jy$;c?pWEERzc70%bl;k!%QrF&Be~&z0)i^pFLGh zIlq6W8FcpEV5Xh-{)@TX`G*_KlB56DoN*4|7I+o4y3=aH>*(1X9;8{<-(@D89q%%C z268eo7kHSYBz*zrsdt&{nCOIVG-HvFUsIcu{3Z5la3ga)7gHB9&bEtuiqp8!T<83s z8_l!4ZVt&x(s|vx&DRWVpit_kla==3>u`tHQ)VG{ft>>JdsK)Z%Hs(KvFr5(8^(%T19kgf+ z0@(7GI}eZhN1Pi`ha?Sr>}QnY0Fpb>rSti#T(B(?y(&BKgcJI6eK4souN2Vp0Se9V zvr}jymt|L;k3RwDP#*DVgCj~J^E?8m$TMtp&jsE6!D?MBI3Ibx9&rx+UYUs$3pFxNxwW??D30ehf81c7UBU8|v8%LEn zvzg^LwtC%UOtp?6->>tNQDvpUQ~h3@u!4iDP679?IRj(LN~}Cht>1ItrSfnc*$%(x zbN~GFN)*{h$*4F5LYeVQR^@5|$%MHsU{Q|(GeXs#1pC)yp^=~!6?E)s)W&HzFoQ2F zvX3MdndzF9sOQ#%t#C9Nh>WgT=R?vDi_}!g5a}cJU^aMW4Y7JS(~tvO!}{D=y>QNa ze0TM%&9QoJHnGTS*L+nQC|{~&m)X(Oy0xQ`;0m1uL=q6`Wt1)F^ZkkmS=c8J$-b?9 zXU*yRWRH0N?r3j*I#);6n|=Qg$b1I0bQ8h=2;N9e$if~%IpLu{!Z1wPa>nB_ze5b7 z0~VpCBZp1kH-{|iuoiKX3bE?2bDg#;ecf`+^eI*O| z*wck-!OU-&^qD?7n}F{YIBl;~fvSfoY@n3hrr-d9sv;C0SyN0;%95nXz9$+$}kZ^_4HMIv}$rykU=l};A1>|vRb(H4bA}3Q99AFQ??KB83NUS4K zAfXx6)XfM&4Ev_G<)LR+#Z`e|HVPb(#f<4`U;#>Mb6I~dL+6k+L`<$WeUn?uQ7z%M zJpbp`d?hhr0u>)8VIkqmgb+Aa*q+x5Tc`XHKgkghDcZuXum_oyK9Ca$={PQ~DQuba zpB?r1{l9o`Bx0VQTBJTdW8dyc?|}5Pre`04L)SPQ2MjQfc8ml$+w!qM-&_3+13IRh z-)%7M1_z7fvM$Q8pk7TKN!r1vIVrQi4`lk+jKpCJ85Jvge>!lP_%`majC}mk$`v z)DOLN*ihKkk4Npu)ydw4f6OPZViH0G+8@X1tq__a*3z&u} zuo2p6;wIn#KvsBxFoY~_7zVD_mne9_#f2tSD8g{cLuf0c&2y=T_0(*>PFWF?ky&8u z`%p(2$L_X|NC9G3f;O zpwts0Wm98?3PtJi{xv&OodR8=8@Q&N99b|P-`$N2zZiH+-`&7)i?pomA%^C0ZicVv zpxHjuo$gKTrrFrdkEwcQjSk(*3-4m|5P!0lW*RT!H^U*~r@K?V?(1=;IW-Tm2bXq+ zn$mEMHf>ProST!Jns1$*sv^H?EOa(#;GNaf8z|uQ4d`KkY`Dcx3>T}Zwe4FZ+|T~5 znkv%ehI@VCe(FAda=z=veIyd&9=1zI04h5B8<$2IUNXihYHj zY~8VN2|Yu#7w{ykh3?A~dh_o(U*AzOih#}JJtEXW+))#%xEdTvZOF_Nj)czedOs)> z?G}&f1U988G((l>HkT$8zoB@&%JxtcwjahS$<@d?e~?(sgUKpONvC0W6Z~Q&nHcA{ zCaW;rM?>sBRQ?MkX#5k&3j3P-Oobl*92xhcq3v{SJFc<4-Ztz#SSN5n2BAp5l9?^hLxL;urJVg7dfZ~fD46mrbQH>Jv{8EEXID8<8^ur z|E+GRor?R%-Lku>G~mMRUcd97Bf)^PcXvb{^?PUOEKVM`M-b(jHZu}t4C-v+qLI~eZ4Rq z8lAs&H1r>SzDkfcLyhw9x&~fKpy0Yb$79T~X&7v!M?KJDlnY&>oU9mU2_m>IRb`ShXfB^Y+8gL@RuCst6I4sOLnO za`Y3lTVFKnSh}y2u#Kk;B;1S)h2pm1y;&3w3sU`uqVTjJvT0;LI6zUC$zI`ORL=Kf z0&*SwD2C`6m<^0 zI}~vqydl(HmN>p)424cKBhrBpMx%LiK~q3PC_MlS2IwMpd7=q97(|GLR?o#KvNP6s z!15-lbe9k*mVuf1pnj|3YiC}5kuP$c9t?V#BzPWOqf#}+=-b;zbWU$gMju8R+z zO6kf^Mb1WDPn*M(gDlJiU5az+LH>+Kkw>~0&~++U^ecM;e}*aJ&<_VS=fgLIat-L} zD8qPb>d13#CA_S$a8tM}5uDbz5cf{~f4Ko_J6&7>k6*)Ij!zS2E^`kO%jn=GrunAf zi0qw?mvKwXr#GM`@t1KGCy|m2?Kh8@!3U`FLN@9%G6qWV22C;tAl4BoA3TmUf6eC? z9?<;!y~`5xIyT_bTd(6cEqsSQM~&duKNv6>eZoAKUXM*q(~3FMERB`$!+5!aMseFM z5IPHkc5NGodYzlz?X#TGi$X=`*nyCL>Y&d+HLYrD#v2TJXF3m#89tmuDb|@(6}Kch zukCVHUl=OS+)1~9dboEi+{>T9Yxo0fy!`}jao^UucAInOg`tA&ww9v*!|*m(2HtFU z&*eQ=w_B^M=A@TUj|582uPm-MZQ$>oV=OOxjyb~UZJi!_AFGsu0P_E<3kek(OpQx)dNEaH=kkrlm1Z}kId8jnPr9X>@`5RMJ zynf+>i|@iAThHG%mg4VUc&Y2+J)wBK7>}O|uN-eY9=ZgFv^hOlE>D!p^RbIVTRg!| zYH)6QVEzbN;G%_#1FsVAgTA9g60l)icz(L}#~?br12nq5u61HIX_8(oQg?RnkHOm( zo&jzD+VA}+AsIGJ1U=)`Vko@_#rlYLxyyGaE-p?W$3vSJerZlk8(IDsJiI@keu7qK zY}l(n#*&MvpTsM`9xS%G6C=-T1D(?NU0~QSj-LsiKs`lJ@?K!{qX@WLv*ylQL+w5QX zKf?Eom*Piyt z5ES`_-PL*9W_961oyRI$)%o1Qzjs}{2k!__tWR^!h*D6XDrq9la~{ft>_*cA2rC18 zG>}G!crq7}TtUimC6Xq;P7(`5EyVV_d$gbsa|EvM3L^@UyBtU9E=78yzt_JS2}!i; zG-M=M_+8W(rPHH{GVWp1I=?kBA}cjTIxA{mN0G+j{Nks(4qqDzofk;KZ!qwu&imdR z>T}9hga-RuwhZ3M#E&%FS>_zdl6E?Oc136;ZRx0V2G69q6xE_&vm|V*)zlmb)BBt! z@kZ^r_Bo9D@14(G5lS}hP<=l2EWH?u74@EG$^#eb`(QNM-E&FLRV40!jisbbG$2j! zCVbo|(gi+{w0^qWBn_lJ7REqUT+KmqAQcn^hDSf-R=k)@P<*JBB3S%(AlzKCnM0je zbYDr5+>Gj+VYk?LI7!+9*xaduIbYXh#;k>>WhjJxNV!dN2p7Iiis_HKX&~4Q9gm); z$_qar9Y*U#MuJ_H>iDqA7T)dYz5%Z!e>qvDK;lQ;=*Q;Bxv=MQgLAJC0>L2 zOI+s5aN`&P3^Me8bRX$UNjFRk4b^Y&0>4og784xk5Ib}9P$;qD-D1Q)iW1Z*^9d7G zEWM+MHXbjHk8d1D0h(^8TlnY_fOWocT`0N1uX%}O@oN6VW1GhCYe7Zl(sTff*WwyX zz)8(T0y)TVH1isB4!+x$a{fD{Mqr;FL#Qjots@wy1s50s8$L+XuSoP@8!$1KbL|6y zQUYr_$)UQmcnOexBxredd#vDUAr*c+oDyy+kB@I2A7^)Uqe5fhL)|AjaDxzre*&L! zKVeZwFIX({Xa}ox<@n~YvCYVr;AUZQm%17-!z2jZ% zl+F(?yyS^?UN!Wn1St?=niSP5ORR$R%(tXC(g-9I~f+wR#}{AN#{LmXKuuQ8p!fi@?gi)VLqz~wh~odjJJ2nVRb zZ&Bf?Drr&tgXD1VqgMJD@?zp2Jhe}T{-ufoZh`D4_LIL`7mT`OotppKHv=wDdVO{m zH_~&p`3O=JAz%-l!B8UD7U_wmHg%D@mnB)WoYTdL#l?QS5Pu6QYTb{2#|m+FLl>1i z!J^-{Zx?PY#Lv9A5ZC;%5}bQo*}99}?Rj0#8{3fPPE_e+Cd&EUEq=QJO;T%7psD&_ z%aS(9lzaHbemM!VL%`g_6;iLLN@5;Y#JrK~0)umHfwMUn-4f>|Jy&g1%*S_Ugn==i1*2(FSDCGf5i2=6|Ui`oAQ4Qg!EHl0Xgr9xjS9Ux!l2 zLR$INJ!kgp?>W?i?4{rW&mb7CPF9F5A$D&nIyuqA1BI*l`8sG%My)2NiW^!hzAd+T zYV5XsK9v5 z`WE%(rMg<`sxx6HetjFIUWI_%j7=e~uwuFB#rN%|U5>bnlNX#DZJpL*KJUGhhvi<< zO1Ab)Iyjq@RVSsjI-xKPeV`B0^l4nCUgbx61D%u|t_aGtRT20w&i3r;xr)ZcTB8(0 zE(htt7eNV09bhR*IOyb_tFAJ8w#iP@kP!R`j{nV}c!RQ5O&$ybwD1 zmj_6TvKa{ZgJpYD)RL1BiHw{I?_o34p%3a5C>BA-rN*_Bp~e*ih3d>{TpNmJ5m@pE zm&~~tjr?qUNX;c?wE!$>(Q~4`iY=#Xg&dMjY7yN%C3@wEW`La0!934$+Z!wZJNKaeJ!nCwZ!KU7|wP%(sxArhiRQ1D2| zAx#|eG3`N}`Ir<)7$}A&`5<02xn6RgbIi-?az3B9JIp5V3d#tgB1){W4mly$tZT*D5w$P1X|Q9+64FHm0JSrj_#l! z!rvX0I*kks%bW*Zgx#V9*Cde(>;pE4_c$9Jsyv6 zoFLI9S1gqyx-a(oBYMnPaw^wdY?_;B@6fb0rJD{4Mp6ys_I*jfhVFCkf%Y( z$cDL<=Wu}cByw=G_mT^ooq&XY-3{ef6%-_ZjP|M$HGY_oB>sl;*ej4DFfVc*8>&t5 z6iThHBtKZN$JZmU2=053>zwOE}5R|78h<*oGWNly%& zPGfi!Vc}0{iwvU}s<$!~Sb@539^H3Fs1VrYRb4$pWXAVK5D?@=AQIzJn8$|WUN2pq z?W93V=+mWMZ+!RwDMSw8BN(6?XsZ|z_r0q3t)8GPL8(~s>jF|&XuB|s%GY||C=gMR z+cf8BoEC?L_jl8zF2#p)?G2$Ug+u(2X81PK$DL`RqCha8>FbD^oR^CiwgP z-n+r#>i$ug#Hvx+z|%(me<59YU?@TI%pTe#hH!lHnj2Sfvz{CCxfT8xy(CK;|6$h^2!-o#L=f+) z>t1SmSnTaN*mG?M&&KC<`h+qu@{$wGLt<>rA=iF%PN3xMcpZ{XpY%K~ywsjK!7pbP z=WW09-$#S-O@FL(%-tqh6J|y*W$t`E?Cku65g%`LzU3*g2a6|(#C-ANE+lRjk5hqH zK(ov3?sG+P(Sj+6yD}}puQT!iF}wO?S=NOY+jAd~#{PfOz63z7vP!rAs=HU!y|?bY zweMYBRlQd4-AOv>&c1IXtbv3rKnMg85LpbRpt1;vs9bdbK~PaMgN`Guf(kB(gZg9? zWfULycrN4k26PzbO?h6$e@Fmr^Zr$}?&VT;%pXEDq9e;uQ**_?m#t{ne zzaXF@ZAQD#ojAO|ye@yV=mRnr#;jm)b0?vH_6Wkx;5=fW)yYys7M%pG2#*28Xk*d_c2oTXmCKcq}`Hf`%0f>i_1zVh)|AYU{{K~ z#lMQY`Z4t*xfN-KmhG_5IYMpoI>pX4Q>{TT%WjWWrK|_t=lOu2>5p_GS+YI2UyG_q zxBOGtZcO?Exn5?oA&T1~XJF>$hHx(WJ~qPb-dwPiy{kM+ID${$*v@aqW)lqO-i15J zZoc4wH}hw=?t0X-vh#-%T8_WwJ6eB{FFQF<{}ugpJvnl}0uS%*?9-xzCQVvA_GpPD z^6x>55~F=?-acZp+E)#V68X6ksvkY2dr#}Glf2vJKk@nnw=H;LyK?09-s&$RMjm6z zkxzi^SRHpazA4)6Uh;uxcVhyPE&(cgvze`(Z$gfQY&Ssy-ges1UPwur-{D(+9GlPI(pH;3G()u@iA=7tR|*e(pDa zn1BOK2Yl0A+dK;QTI#k$BLNp+$PFHHXJWqj69FTn6C8s8U{dH?-P0N) zW}=ioV$|I|2b!L%Ew5IWZ=uawyd)w1NCS2t4bcY1W>D-|-6tgdQI-rED@yhD5ac6p z&m1=YVgG4(Brg{dkDwnt1IzOWEQ!K-k-T+J?KmF`2VaCh6I7=IU>wyl(5#7xcnZxa zmi0J^%&kaVz0MDCu3d9ac#meIg|M99>d*PDGs@#>_=1EF)Lo8_FuDFhgoasrP`(%Y z{!qf8^@51~aeP-7ng2&Clzzg`J~89v(C`Ot_o*|wv#ZeP2Lcj}c;WyUr8i40PC#$@ zZ}VOjl6{|or{<-mfU7I=pAh(k-$<06A_^Fg#emc~nN;TOMgrC*H4;){dr%ji-QE3u zR8{{B>pE)dOnM}Rc#4rYAM018wAFjM-@tIscnMI-g|?h@Vna`6QwW6YE3`&`_ny^S z3UHon2Jn~0|3NP|%ad~nF)q`SIzI7y-fy3mzzkaLa~g<<+Q(xES`N$f0HWyTv4ohP z9gELXM-Cq%nvuBAGrG@RC+BZDey;ICvL}fTye=w53SOO|Y#4id@Dt8MZpq8*&fU=c z2H41|WY17f5^fyd^Pe{}Ubw<@hK9@g&|T3t2OEz7gP}-uhVGvb55O5Ffp|cICL~WE z^jqEsc(CCt%MLCc%I+K7lFM&7tGZu`hNb-}xw5vGb#cHr;Hzf`A7+bo?OMcg`{hVf z+F#nav7Zq&dYDJVdcz0?Z@tgi!FkbSC;EBeD^DwqqqHdZc=3vn?YZKPy|uT9h9+K- zQL7tStuS8nFPNP=bI)QSe}$+S;#)dT*;=2y#TpAaLpg zT)+m~epkdA;17i82>8vg{`|&uqoy;le#7EqIQb?Lj-G~|j5PRR{DbWD_c*?I!}gDS`0S3mqyH)TI5{+(CQz1Mf>Y-HU&MA0x6NeI zH_LS@nWy}l3Xol!j>c$NL*T|xg3Rzp3%yuGgkG(`hVZ;%F>)Bxaj;N}ml*JTAM?N2 z1tb;iMj);oiP{Z28nGos-HPl<>9-Z*CzG$$t^T(k1V-Y^8!8+X)Gr)oh#nkpxKPTd zp;EP^X_--w|U>OxuyH7pyX~$$V+I==HBsUd7z&Hx3MG}sS)ODA~qTv z<@pPE|Bg>H1V6Sql)dWyE^Fi(W^WfmQm>lQJzTPr?BJml7NU5mdv!K2)b7)2dsQW05WjuW9Hm(lLk3JP=5BjOq*zKNBe~d!sy~TzQ?W{j zcYOaBsOJxQJ?`Fdsa#mD>JEDMAl5NYfN%n8ficY6l!J9_0wv~Z^gJ7N&pboO)nci%7DgX4 z>(f_dYjGk3V=!gMV)6XG{IUY(myFF0{949L|7To>WZp2cJD4abguaviyDZfIut5dmsD3&+@FL2m~q85Rrse!Gz;^CYp`uz7uD;fr?tHGKAb`;_}~`a!hzCBda~G@qfQxG@fOA+q1?IO z`8w!7v`2_w4SK1Px2gZ zL&?F~dJY4ogI6Bzi^n71%3xc(wV%w^UgNNFr}mImU<}xDC;H7!UEpyoe&wyC4>+}l z=B+VNyqer6hi7!Q;WrLn)Rsj*|+hA|#V6;5&aZ3q;9g4&;8CtcL3CfLr zKwg$*=h-QWBufR7kc68mxSU<&r<#(Ofd!cDy{C=4S)4H!u7R|q-^Eh=SZG~GLA!O z>qR_Lq217zg_*8O>bg|r5t^Q6B7jiBcNf$3HL1#K+P{o8huoJxADPN~UG^9nN{?a_ zs{)~l8Q8<8`A7GAUl#gjCm)OU#{3lK)NvK+uyGIQlQI>+XMxa`$negycmO5=(=YzB zwYX-V39UcO)zS2--?9}>LIiw@;GMljbI+K^923w+c7vfP= zmX$K~Vs**L;$mMVHdQU99ORx2APBbgV)B)!wkk`$l{)hiV)B~$COFU20b6#XsoSAQ zDWBWacm8UpE1F}a4Mctz3|KIcSh};e)UoaU+*sd4INV(t%5*Z|MMV?E zUQ=u}_0hW+R&(U{p#G(nZr8fTE_NIjdPA^5nC=Mf zM`$)iR<5O*Stvz}f4;i1x}-c@TUo8HT-2R;Hz}3go#|#jsIIIHmzPvmZdqAzDUSt80@Zt8Vk54S-U> z7I#e}@x-F?WNo#w&2E7g^kB-J9eXzV@4?8I*_WXCM2ivqyF>pR&21P*@O8ew+c2sF z#_hr2?Z#j&9P1lMh_^?oU5P|jHF7&9j6|oJ=I?%ggP1?JT$Nxc*%I{K#72oEhrfM;PWe$lF5=TT}7++U6!?rmiW!56fI^P zHeyz^Cx1BKqp_z&@hS;pb=QmJSoz{E7?z}U(0>_#GCD};UI+6%J^6z&LJeEe_|sU^ zBGLqSfe*!Ok$?+Dw=`@yyK0!VczcLqSiFjC8@mzlwwq+9<})(FZzFB2Y z3c^)L?k((ofm~)@*v&<;8P$I`&1DC8$;9y3&8?o^B%=nER_^k+Ld9piqRNOoI4EUJ4Gu_tm0K3SI{@5x2hwqc!)&NVnD}HR{*j?_E zRci#x-`2|fMVQhCFeFxXoZ;4M)*|;$zY5me_tvZ~_L#f$G9%;uR}XIgsJrN0f!yp< zWs7a`5g?w$LWD97ZQ{}e<=FP<0i$@-i;Lsy5z^XL%Ps#XNExG8ktRl3dMSXOkyzUfd6Vup#OgPRc;o7lwX|9S zmIYMH-U95=Vm(@^k;kb?vPhB3BO1%5z7^X>E>GJEo0piKYc>uRC|&mQ`s|aJS~mvP zdJRjJ>%t0Tm5=N_S3TGY$Nk#Ot6!%)Nkq|#2MoH8|EG~^6ngTkn`ru0p%z|CV&*2< zdOuO%-(CN~Tr9=MAP~;2stDGy zC7~u-qU%Tkb2Hbv`z{JJ$T8XY=JH(rbFJEZeb*s6P&hV)@gw^Qa_rTf<2R ztG`BH;YfqFZ*!x=ol!Sp4;pbGQL?+b(vPLP(iw*$GKDfW!I~8Y{&*M9G>zK*y=#?+ zqOLB4g}6D0l&dZK$@A=e&cUkibx5J4oTSLeg-J?H)SMN{yd*m7c*Z z8qzsNBNcdVdwk*r9|Cc}%LC>4K!6~qg{R?a+-(%y*WP7?N|W3{H;x9x2aMxf!exF> zKs2s3dhAlN`8)TbcU$?!HP-@#Eog^#>~X)Zzo zgAUfyCZbMV8#1b4TTlhuRm=Eg`yU(#v5YBa6@ph6_Wr`2jt%?kTKBdatzu_oep$ig z0nu+rqMZZl?PJ z_dPdRndl_Uu%(a@P=mOxGr_{M=Ur``)z|QrRsX{N8N7^^*_&YpIuE&~3;n5kfF1AuV_iL7@Wk7s4MVO>E)ANfDJe$u;3~d)3Z& zjD%FB0qvExW~e0pScTAFAlR{MXxNGy8Vs4bMx!-RRA05mtd+L-fv~0~OU3YwqTHRf zR$A6Na?~88p&J*)I(`U)V&p5s8nmJCZCXFJPC#B`BRV|X711r^M~#&>!;_qRKcO#_ z)m65=vSFpWW#Ps!2tiT99~AMPQM*FJdXV_k9;gcV&Y}1o zaS4h~EoXXf&e$;B7)AQrgkjbPN^srMhmv+knNc80U~QX=iOPBd&xmzPLL*!%CRH{3 zVF3qvjC>GE9`Y#}5xRl+Cyj`L&PSt1PqYHoFnLoeo!oH_T8VIc{FDW}nZO@9X;ma6 z&XXc<=Lm{7)RFBp*Gw}Ika*L~5tNrykkkA@03n$e^+179SDAQ1vOla`A1amLR35$yps>*)Ge#7|rw9p&7RI2V zlPPbB*FwKyKO!?u14EAdmD)|z8gJM`-+r2RnlD^rlJ6NGW{lf~w)A$g0I%Q`T!KUD#z7Uk?TJ+NNy%m)MeL6}*f z&OuC2PSo)br_T<*%Npyl{zf-e;w3pjX@rDva)A{>!UTDGPH+*xYa+_N9Kui=0Po~l zrg=t@ z4J^gNTO&~coalGpM5ET1&@%Q%m!z?4T}eEsOgv~GW?}j+i;*9m9LjPYbnfztw9z1A zSv#|JGHWf*$zv)!l0Z1bfHv87W5iA^xz8G_*k80qz0@?nk#jTfs}tt3D6>`( zee_bI7)ua({2w>1UmeQlYc(b#p z=G-rt)8?Sr1x|zK!C`tQl*_b(O~>pv9<;{7x(hNP47W~5B^|aq6iYY<2du>Yf2{VT0i-E4-6w2;^ zB9h)(>gaWc4jY->9A1yiCT$dfy>ser%FBf07H9QO_v5okGYrQtpz?*Q(qY;F)Q~P1 z!9K3oLuVbpUwE$qgr0ldxS2Vf^n@f>z5Z1@eY*mb*q;OtQ)eo*%S#!%!K($kk)(&_ zuV{ON2Mg8frHMVsYlqoaK5ISE*Pv`?BZ$V>26Sw!YHX+p_*$425^~EX1SB~xn$mN3 zm_YLRmY%hbkw}PeOYZ+s?W`gu1Iw`qT9Uz~4{(I!=ZCdF9hm^i8J=0C=i=DtOIicT zs_Vec)9P0O4UbXI?O`tr_H+Cw-@GNFVLo&s_)rk8qtm6V_Z2!!w=6i0T(OVPL3|!; zJwjdQy+s|=^xyJxIy(#KP`$YU?|IG7c#pvod)S#8Jw5ljGrb+NzxlbfNj)nJDj+-C zYGAxw_Gj7*t{wAR2G#*Il0*#!bPcHZh_mx`SbQX2@w`FE;0~XfYs3WMiJ}jXMCYYe zHRc-WU*}rnhklDdgV-}RuSMA6>Wa-Uk5y>a+(dTjGP4FZJsvdyqIwVvK5!lf&MM}u)?iwT7!IHhYOD@JB7`;B!2 zTg|<7+5HgNBT(emAQ;cUne>c~eI0M-;xV|EM;v3-QKEN$DZ%&AVmLWI=6>b9YM$cV z`Ha|E^^(g{W6}|@zV>ZsSv-I^AF`W!iX1SuvB^8rSY;5hWMos@__5P7bYNxinrHcF zp-(oS$L~s-pKcwCD2;UGvG%=N1zlaL>Ox)=^K-lGe*Z4-d2?GOT_Lkd+XCb7Q3-I0 zN~ZF+(R)SjwkHTfbWAZ74kn77h5}yXwI*2%LrAjRjRnve2ovYS`T0Fi+z^F%HkeU3 zae@3u$^yMXfJk^61J4+ZA2<$4o-QGbWOy^Rn=r_we40-9ofCi>eu1o2EC=2fho;HX<~0VIEO;dO+CtPTu$$ja&fuHuVS zENw4GL$61hR|I_8yX+BqU_=8^x1V{ye=**E8@-v9?}V2#jco0tej!2))GaqjQ9s^bn2^AQa<>LHbWLn$H$Md01u36}wm%Ww&@156-ffKtlQgsRMu1pL z6120lk7qSs@^=8wmBcO{B+XsQ)bG-1UQ~0g7&piWx|KJe2R0C6q8-2aJ^wT>CHY!3 z|C(yA~rSsnx%v%yHZK%(wk?|&ZqRF_~hlvC08 z{Q^ElYnOe_dnmeyKm6YI+c(dZ;`exMGK%N(mo?u7YfRSrg;KD${MoY+YW~e={>}0k z{$6_|JPBPz1gXZVU`^|ISm3)m?meO_C`;vW^hM=$E$mMbNAH|@eG?0Il5&u1hVUX% zy-qeN&1+=58E>OvG*T*i`>{`_RGIYv z6db*=mAb{>S-_V(340CDfDd$B-|>NVZ}5ql=Y91*=9v8AdFdASuGC|e79><)c z0}>dtKShw>6o)F5e;;)K&FiBekpP4w^pmHyoBV+yE}<$cAtv0~ysts%UKB&i_TxAb zku#RvgTd5x|Z>NqZI7Cd8cc1wmR=*MAuKo$1t#+ef3|go#_+4 zgF%u+H$bBT^R5A7q%;Uk71ZoCim(5rl$S;>)m7~TJD7~@-5W^+>=QIqzm$D35;@-t zWVP*=>Xtk{E?fGg+qG=KoLw&1>l`FXr{_}g=xH+3kXaAbmI59fN9y7D#2|!u=t(PF zlLOg`y2F(GR{>ZfwT}h zefr87b64Y%;b>YO+_iU|IdIwt`$DXD8Lko1frxUh5xOcmd`V-MIdkQ7X5_R1bKTxu zgA~@_*)M+t1zWTMl=KehMc}uLSGV5|GYe+@R)TOC4qL@w+w|^Q4)M3|R3n!tp+GLE z4MwB!Om6)2v9Z(lpFXCVdt{heAc7YHerMAS=!xWca*JAFKt ziI1PN2tvvQ=TG}3BEJj+V#(`UgO^7Sz#I)HM_wFC)#F8&u-(p7PwYlDbRMGC@T!WSZzE)!G+B6@sYf#QL_ji+wx>)S}ZRb3W)hZ4*LLh^-S z<$^$5W^)CKBk3Gnhl|~HJ@yXYO@U%K;W&U<9logT4Ft9lmL^fiX>5&V?ByRIRQDJ9 zvHiNC?e#vIfhIb4dB~&s!fd4uI}*Ap>F7tFkq0|IrA6z780neIcZ~VXH#M z><4@7X+?dOUFE5LT=Nk*vy?BZNRT-|iX%!HRxfU~p*Bj})4t~At@Yvz9mZx49fJ1` zZPJ$e^sxQk{S%U0aMKT~lJaIco`~3y;RybzkqA4lmfcQ= zvDxk)D<qMpmVJhORH9}({1OCw&d&!1atQY^-T~M3G3;Gi9%qjg= zqODuA_68?Ypg@R1^Pg_)m_2LCUMFoLwm^A?J0`sV5^v;llQ!wcNWLvhop>pOx7F+FWkG#i;^Vah0*|`TbzfB9?$7nO}L4EWX6WKo4P_MOovNPry`Q|ouEpQb%2iM>?&^wpj+aIXKq0oy!f8#rn~ zr`caB=pEcssk5sK)1JnpOuhjNsO0bGGp)1#LGDN#rJcP+G{a2HZ98>aPGn(IOe|l& ze5}2fXrJBZ#c{U@^=Wo5Pc%RUh4mT@iuLNk7NrJ-pC9H{uSMrM5p-7HqlyW&)YV;) zqd@G)m2xbmCPX!p%b}E^sEjsl{%#JTj$NrFWhJ+xfAA!vVMVk-a`na)6N;Qv5-ZlP zNJ#K^l5#sv-EkfKnFxN}8%@u!#>aVck^}j1_B$w)q*TNZ-6R1}M6C4mR768%QlYbx zrCNK1*16_k4h=^e_D;b-TjozW`(%_cYzXP~+Z*f0Tf6Ji?bB~T)(P@s@JjhC`{c3v z{OH6ve;B0gw|0m*H)i8@Ol{b)VORh}kwAN?*10P^?l61z zTDws5Q>>-A*#$re=)*R<1jI7tFECA{GT}<9d0zRq8Cm-fkKAAbU&;1~pN8^JE7=D+ zmy}Fnvc?`6bTYTmjEUHet_&j5n-3XgX-Vl4yBPXR4Y|Gj#&N%VzAvw41iyNO7L-pEWw_8GndliNL*plqUuE1=I zsM#_UsxJG10tdC<1hb_>@Vz}$&I&zS_Vm=hB`9|1;?AhfCTeE3Px_3c?s4z`u2QH{ zo1m@gCyj|Qq$6!UWE!OB%||Qgs!-dwv-rGX?a$OR+4yMJEw`j9 z2#Vk2{{9p>-}TN^W!j8?EM4i@SX0w?WaGH%fq19*<;o~4!nZywi~r?5@f)+?j$dNm z(uiWlg&gc@Y4QP5<50+v7e&j9fbJueP?@z$bddVP>B<_^Ax~y&=&4l{Z3d@2Nj(>V z@#s(^+vWH@8tcwdXYqb({t=q1RW+gPz8Jc0ovhA_@6^g?jNGB4so4 z0{nsmbxptDW$sXKczV?XJoB%pN_DvJh+g7fmf*BstPk3m+oW_@3HD8Lf@qcAFZ@zi zfDd$B(@F@OU)G7$Brj`wz+oZFhaeZDTjKz%0|9E}4DTm~;=F{?2rsEG4w-^HTKTfr znfMrbP`X@wrx~x1v)NOrN>B?2B~hIUBdqh>a3~USzxF$8N#o408k?w$#BLD41oMD0qS2NXeZ{ml2H!eG>na_fF$w`I8aiHL)zm32w43fzp+1x z%PkqqZ~rNVX>JrLTzTC>qa&kO*pYNmaJT=#%DT(@!<|dmtqhP|@Ff-$;6*K@3lL8& zyE<#{RRuF7iTe-+c&-QyPgaB7WjRlTJlJG#kPw*(50^D9q9bab(I6cYV65ES@*^2>MNj@xlu)EbQC2Z#8)suikfvyZ9a| zT-cqhATFyyrwSXmtMG<>9#VeP2x{PvS^W_vQa>Ec=H z*4c|U0hz6D_=;NXiYu-d&em?fm|Yu9?z(~iC}uCX&;H$#*-!q={_*r0&v)>7OdknM z9^;@xa^}JkoCZD|jG+vYA*(`_0Z-#;WK*6<&<|n&Rd{;APaem(*YDUVwN*WezHHxc z;*_i3yfR=p;?Sm(*7n?5sEGBMam8}v?!_~s`Nc>q3p9&(KE|9IxP$QI;huGM!Lb4> z-+c8ciQ#?AbftGqMHpEBc4d5KK&li3Rv1i5<%#0J8r4~uG=cLT%g4V${>1rhUJc0A zD(h>R&TH48v+QXPMkrlf?3nc!##K~PAV5vJ|L0f1YGY^bx;`xmpf+FDw~~ROEvIel zyDMK6`e%k^CnnYwFCWiO4X}75ri#&2Qcs;ThdIzYCdjfdriI2RVeME*JKVPkxs`2y z;jmayqFVp@p5Vad>*V2?exVA%tuUMx%ag_WS~(V4swY!X5m{iPQP1u9VA?qp@O$fL z?cQ2R>AR1&w)jJRA$6V5a-p5yUw|O- z>u|jNH8D91XHY^HKxW=QsXZ&+NPk6Be&fDaq-!U7M;ELQ?V?z><8E=IFgNh3R~iro|ehe?adYV6h3LSYMPHd zG*{DyV)=PB^>lD^H~RN^1x{NJ&K3AUE4}E<*&XA~H)wV|=Q^t5XN~`azFF6V4*Q0O9#oieUS7N57XYm zlKcy<1-gs=@xA7wuq;GwroG<#~rv0ChxW0*KV#}N^MGUJ<|j9G^AC^ z(bc%c^MuXCO^`#YXG|~cX$cm9AxLU)OgsCz0KkJhh02lrQ1d;i(0w%oV|ZFG%t9~$ z_>=2mTBUuC+V69;cN1;4W}FRn<`bX%Bz}Jl%!Vt$3ls!M5HsXl{n5Bm%C zIcjy__Q0z{HmYH=-#pgde2Df=%EqQ++57IhkAIUHG#On{Bk#~@L6{~l!rBVhV%b;8 zN*k89!$!0ge*)!mS(Q2Mv@2G@w>9=1B%X;TYJ}m6dxRPCD(&VOXI$4hIJsPb*-7Wr0;mwaq(EPsgp@jaxe zrToUryU;h%j3CUAQKf6SnP{6Nx5{g&hg2(;&&M_vwiY+i&bO(@(6ppJzhrOeS*3?Q!-LTxG4CQSuox~XH*+G=6>#L!BnFc8a!%wuFJcg_O6%Y zm(%ttJB_i=>`Abo+c>bo(LL(aTY?toe{nShcO0xU*i=58Opo`Cdz4dpl7m zgzk(A;XqhfngHMMG>fyHUvos(_!j8a1l^WV3UZSK0w=V`buc-YWNUI_0z%ZB;dJr? zk`~yWkL`{J4P6SH9MsDB{EuTxdRXlJA@gk5C$nORJu8ck=7&cS|Dr2Ti}G#j)`?Ef zlGyv<^Bs(+=NK(jG1zbeFYm`-p_h}jhYyh$Da~XoOgL0EmRjJl9ydc)017~=@Yr1x z3x;&G*u>axWwk(bUMQ#^msG?{g!FVY6+DBEA;_-~@3IVbi8F;#idk4k&o)EvkFjs5 z(%=lnz}mt$Bo%T%C=ie$PPUum7HM%e;y;DOF%;W4M~wutLiqi$C9}oHondBp!{ldB zU=#p1dx3lV7OCs5cUsw@LFm+im?L;UQZ)5oz}L1AXcqJ{Uh7WU)iHUr#!h$N{7!38 z-~UQ!`&GNQPc=82YG1Fd4wz82SmySCr0c=-b?Kmv*1kwdoHfK%E-P+TQq9BhUKpmk zyW`PlZ~RRX*R%w!Z_2tP$ul#uB`OY{><(?0RtzB)bgr-f z_oBj%O-cmG04E5)wAD!)ENOQNTy_7i>ziQ0+0mx z38V%Hty>og+96hww5XuId?QLeyZs60nYit(bcrv8J7SPQdOB9PFP`K$4GKAgHV88_ zCz8=g|2qNlqp6xK5acyYU`IqSh53w1*PgP!py?k)Dn>*2D)FoP?c3?=^OIIU(WMWG zK`|_MCIhN+aY$GZR9S;NJnL|~t4BVfN6leyvOR;e*$7*>c)O_U$_6FOw7V5pV12#K zKfz>7KH{BVa4TGMJmSETNg2(nfWb|8Ubih8r*ea;+r@?wjEX%16T*JtEzP?)hcOUm zDE&$a{UciLYjot>-=^)4#Xdb~%GU@%L6b5z2Er+7a4e`Wgur{bl~yUCh^C*XU8^|? zY#5;`{`O9(ntWLhp{=kYDhH98prKkgle?ARz1p9~^5R9O9rC}n?&97iGL_cw!2;z;vG{>9!32xQt* z*fsRE-<^549BbSOI|(eMOG7}zfL}kdi!+AwGzaN0{oScat8Dpln68xN8l?QWQqznQ zLNt92%Oc_}^QI5(?1mWxjbDKVD~=#hnqf*btBBw~1~DPDI8fp5IRq4|uKAORraUQ{ z`U^l%4!lD1$_t<36OK3eq~9!x0l>G((q$rawuyE4IR%ggo@uE0Cq;`SLYG<5(`fdw zRAGrJze{8xX`Lb-BJXY8ZZg=dgOg3On=_oQS>Odn@1Jny)=V;`M z3-hNU+6N|oP`JSf{ck1w%JVceJ(Uc^%pdYeDscnwQ$}ivB;70;N-(t&n(7q-yMbsV z96nhI1DwbVuf&`+X(TaY-KGdT4Dp>}AS`bPNjJy?0D#FEF_yoKL zt-4`l;g)nS5v2!-Pf|a!A(`8qWCIrg85;5)NqwLrF!9MzisyQTeNokCASy6O3^ODr zXh)GPxCG*&A8?VRScotP!bMp|Mn`rlNJSeQBx#TvhU_#FDJoJ40 z{T41nx6!#1gMt*bL?%jLU*taIOF`dY-{epc%Xt*K?|@216a437j+x0rc=9Vt^?{Dm zuh-awSJ(34kP(K7G$@5JGa+D87R4_pp%Dx`*mv0o8F}5uw>hcpRT{!K%UV#BFct+v z7lmMO6>TtrZU{?{{G_MH5D8~10R>Qxvn!f%M)*a{&u4e!GO95;$`^E2~j z3{SLu!{pZh{jJw*JW?zu%|FZ$;-8^IZ+v~rC&E=wAwbIo{SLQ_y%915aEDHO1W%t{SHJd4`jJ6_((ZjdE08qsX5m>li|me3>7YEjnW3Dr{MrAJ}1TaVR1{#Zt(v%nm%xUD z$0T6ZEt`sI$bwc+S}}M^n&n00B6gY@j_`U(|DBj-w9S{DW05cWctAEKIV#8Fnw+ni zVI78@SXi~Ap@#0Dnla+ucg`_qk54y$eFOpf!pL*n9gnD#Vrc&V=+x%3|H2{di2{EG z)gC8@J}x3|97R-^8`=w5wcjq^VnoVg7{6NxL;g63IWY~Hc|Hf8o1xtFcKG{wWWMP5 z$b8FDnetyn_vvHv=3|WkzY)IA2f+*kjnfD&1VgrT1Q)ByWfJRJTnwPzo zqo}3)A-|7c&@^Q&gs>RM3a8S5m{e1on?;j#P0~|eTPUd{)W)e8Q)7DAtmeVn;vpxv>?lIa0(Orz3?DD=@AIcWvR@4wC2kB_kjO@vOsk0C%jJs^ki!!p$j)LZ>A zD;W$SOH2^Fqu%X=p?`} zi*k_EeL-0SDmNG!aNn31t=VQQh`gWzHZjw#A%rUa@UbR=yP{Iij06Hmvl9qJ%%BuS z`c%0` zDB^+i5xzbP)7)R29EQ{cyelCe)E)G6I3NgtF#6i5n{aZp!U+p#5|IG0^gsj&X?}J5 zQ-!2MB!?HLEH-kLC{zYQw2~7EaURHZx+Izr0S^-*CRc49$E?v_#XiScqk$4NAIEq& z?g6ooclZ;?|JUd^-nrzMgQaoAXgS7UIp&XYqcp@QGcE>+(lD>@ix;ciojz6=k z{cA?jb;mu=yn&&G;dZRmz%gc=)*9i5(1~n<$TfOi$D7eWBsF_XmSkqyCosFCGL<(r z^Bk5F7O48z&SUvehTZM+J2!5Bo5w4_k~W11qqov30TOA@%+k(`X2Gy_QJSl6pwjL)ma768^bVizDforGzq(NHCo))p|?GY%rt*)DWw6 zW`MF(c32^CfZgR}FfldvW0PUaL+`*ML~A~$MBrQ$$<`+aGk5FeFuW~k^GQRp%plS? zF-#Oep(kJJ(=>}PJ)nZZoh*^rqOD0Pp@C!~RzURl2;C8qdZR4ray$7(I zoXyJjDN-`Z7Dbbi!afbNLZtbkQHaF~hJWvKfMmtJvwt`gyK#Lz-?0How9`S{glgG} zPV+0e zpi@XE;^{yxE@z`8kHSCxNYw64e~g{4Mm_Q15k@&5@=#}-=3aVl zB#Krodt{m_mRcyNRbJgw}Hzd_-xDOcDi4&E+N7k& zL>?+!N*Dx9-0$y-6dK3@j?t9h1B~o^am@dNd{~i-P8?~BQDr!Ydp+0=t2#WFn&?tE zqevApt0RAPBwWoc6$C*OFWGa6s9|quuG(MNB?;SOxu-k?p@Tlq&_zgL;8y+vD6UVz z(RG*mLNgXSrO+Qn>WPCs7*V-}IpkI0V~o;A%vIqpP~^y5$GL^qVyOm#*U&%IPorem zrk!I@qySLRjJ-)zPeapYfVwY+q>DH2SImGimg!7QadA6xuctaZCSg7kdaIL(>gXU~ zilhRKFmNu%5dLKcQ&L}Mb*RS+I3q12r1zHO_5!?(s_;Rc;b4~DDy`7gUVAhs^SK4y zwKT&(Il%=uXf2>}l-q+8%>zsck1Q_=X6?o@8^+Flco3xu{Q)PG92_k!jjr3V)a>f| zP(W9gsd_*+&Hdy}pQ%sK=5IX+DAur&NoyyE*cj47*%|i<=AtRlo89 z;vGbR9EpT>?~JB{(XOJseCHZ{t&koX&Wcton+EbkKa7*n9fqMZRop46O#c9iUQQ;P z4^K{Qot)e{3Ac!N^!(VC?c+kdmhQLAzENu`TjOb0>L*1L2L*tF%hL9n=&o`yRGpmM zGC4W>%xkg!P{W0JyhOZ^>vqXpz||^ZZ45kc9Od<+J*q&hJy;rG_0qv&on5tZr5d-x zVLQ&&`+oHGVs~8-uM@;NB6F8fR2@_NRGUAuX+ zmN4N_w3d#1h&Fv=?;ewb(ePv8D8h-n=fH@{pR=d}nl)IA*w8qza_^>?&%5(I>=7#+ znw}1&Ew-Hw9iq(_BTi86d#FziI+59j!tt*J{90YSpjOq4OJSbau#1*03#Fpb6x&2c zK1Q2r--qx3JMu`xMrk}+{W=mSnH*1c-GVMa>Z!02i*JrWI68kn5ATP^InW~D!pqv_1p=A?;S+;YvXe|TqWI7 zG@hSb5shD|O&$jAy$Wj|MZ%1Bcs8a5g+S<#SR?t0mXa+}7v1;f%@oqj^4WoOO0aSu z6%Hsr9ZSTv9T;X;uVQSK8goJ+C)WJl;O11-1 zKuOfJk&8E*so*zXH(JhIa>l7cp;dc&7D1aFwbmcPR0|B%7V<3-yEkjlXrk_{!!5#t z2c|MEY}O-clW|Wkn31&CtCeir*>W~AF|7lnwhHX87_?>k4BwE?*q)l%RZE53==|n{uIDK3xT{CZ7pD?&- zUAH!|xw8(B69y4Fx|&9@woN6g4f)S7R_498~^3BS=_Ow*yaEa z=O^5U^6_%U;{@qyaA4PPquh+=0f`&prhYy?23B4AZ&nY+s)(Bi1 z45ONLawnZ%o?{u!&(>yQxz$@2M=UIugzRX!FKyWpZohxF93p$<4S2M%fA4X!2YKH@Lsp$Wf?_0O4UJ=d7dE%M~mSDX6*z zH4Km^5EhNdL_g$oe#~(%XI)*HPz4c8Lw1{oHGj=hPcKUiLX6k-!q|pwyq{@v8hKpt zPzRsWrK>EJ!2=xY+Ijr${e|)Av4Rd#5eL&t#|t~jbD)=`YL#rBvkw*HD?}fQnOwntf$Ul+W2KE+ghqa zO!m*+t%>I=%K4&32=ML0srDp%fL`IhL1jJju92G0@pBFT_3SE13eJd@I0FN5hwp$) z(Tq_D1YWB^&HB1l89!RHa2L#0v%IcV#uv0ogSQxxbwFVCTV~T}g~%4-Gl_C@c%H~R ziUawv0?slxzRX>8N|$#g56MQnL5fIwC4AV9`G>=8sbTN@TN_NCe^vAF{FDDXsJvZ~ zg;hZvuFxPEBAX6I(E|?VuxVtp^%9eE(g3kQSmL1!G`)LvMd%@Mu#ROstq@}#geRy8 zzde~Yg|$A15%QI_Ud%4Ys^sK|8a^?$bM6Lu&L`*n)TGxI-rxLVSEK!S_~*Uoi%O7}p1v#`7MDm#X^BXI z(_X_+JVy+X$;%Ow>nV)L>)G>1K^_->>I@>pu%)@k(?J?#ppv$VmQTD8f{9QX6ywHc zGv1Iix_sEZ_CSP7MSDoy0!fizjEr2>K_)>(vTy(>x9#67Jy7sYWQHnMrTOdDdHxaw%*lIZ zcP680!YVN?t`anjSnF&5m;DEp4g?pr6^!TXd2=Vh`|Ax_NFa)e=0*hH%~dtw`LEJK zgD8g};OUQ`zNWbk9vT(PjG7Fk6C#eQGFqA3+Ms6wr}jCOnUg*vfXAtLT6_b4gyT$8aLR7k68MDkS`%{G zH?TtJL4eI^h*jl`r$lrB$$_t}yy#Q7?oq>3YSlIaNw~lHnbSS}`f8`V{0E8yN2eeo zeVGk0qWZ;jg+=yC5n>vbV#1?(JML}IJE=t@iEuc9zg`Md*((8TogwDBl0I98VPA6) zc@t#uY@AgqskcE-BfrDr6&BrHYOLY+CK6K=u(B5^_b^Dj9)A@Kqy+b^C14*H&#N0W z6LY=FgKJS$-tlwQt0geQlKr0ig(oJxUWY&m4F@#|#Us}#Q*0HH6?6fo2be+OL4{~a zgdoRiIhM+Hhta{YObopgHqkYKI6M}clio_CtB?8MM;NwR2{*ajj_>K+|1O3FZP9PwRb!Wc;cj^}R_;^52 zL&9zuDDAt0H{j_$VU!Xa>pDsA1Erv>nw5n@Pm$Kz;YcD8i6p>vJW1+BEGnX)l?iBE zfzI7pQ^sjs!FlWgxx^iQSY4h(Ff5jVm9(^|Yl9ta3z%PY?|)cbn(~$t04eAil!xan zCd%$>536-}H+nCAiTw@SH1l}zDy~2zT`Xh-C~+YVfWcSG2r1(HoECi?%OWxAe)iu5 z9Cm;3??OCvHE@s2b8-U;5GZTelf1R0wi#(AhF=xzFpzxDp|$!sl5!~>akl&#-Und$zg_BvA@dpuc65TmP2c7y$OQ*el)bh0EeEa2y44O=uAmVlVQNY+Upq^R$M zpOxdT-W`_^xW^|X(3KObCB`Anu|?N&_%Z11RIxXf#(KXnP&gN=96PSA{KAJ3IsJ+2 zY2>rJXxppR5)fZ{cAYCYI#&BY=)j6*_lGtX>;79wmPlaX)q^e~Jt9T()1*%mz*&=i z(&ofmK~M~|;5r&S6*~@1K)P4lPruIE0@=v0(0_@aRX>3-st{4;orSygL>z!MI zW{7J9#PZ5RXBU7{;htNK_qS?kFz?x`iMqSt>5{!ET?<@A(Lq;VsD`Xec=ZGC?as0% ziKlfdt~5`%^znT{G)x)-wZ8H8jR>4>H|jI&hwxm?!uu7~BgnbA+If|$vGaM2 z3aBq{Y=ccvywpfC4_!jCB3?})JI&|r0U_*;*k(f8%)U!;Si@o^3C%)2c{O3Nv#s}@ zo~=E++lr8aK}M>m1-;tY(cSLVy=~g_>dV$$E{2h&YCmneofMW6sapPI!p90tKmGZh zoO?MT)S*MM?N++tP(u{Ao)T~(-{h->^`AeG>+#=4GEf3$4*s@X)Q3nkKghVYB__v6 zmCRONhM=Wj6ZMjVeNGyJezNkOn7)ma6gPfmf2R7;%P3Ba-ALO$sT_mGqL@Im^|ouc z&O+P&nd9}IuNl$N3d+c{XDMgO{#>~shW#5 zZ7i%{x1=(4U{doHKtF>9R^cnpJ#KS2J}vXtqh)__jS>fp65d&MH;=>zyWXP+NU})S zz?Kq}%wRMa6$L=mK+M*RpcypGfa=~f67L;#luiiea%4bZO0OniKZ96^u6|-W{%c=^ zpyPGqS#uqkK6XdCd0ii>99M_A`*pn^C^mB)^k@3VeN5N8ws{($qkF_sM*NCo-` z;NsLJF=J!Qe89>5*e5R*KzjIzLSbTNyzpKrfUpcv6w*>qwOJ(Kr~xSx6vbfN2!;X{ z;tANb6bJi}i26=)ux>Wt)~d&fHRP>~y@{xGta46~XPloX)C&^{m}6v7%1ld48Utdd zECKvT99$0J!>e%%h2{K?D+$JA6#yo8u8n_jNi$O3n(r5D& zytOKaUl~}x)sDO^Vy^{Se?;3CWPyDYa7>YJ(4>Azk7rq?uUi#ENG>QU9DQZ!l?6)b zR?yY&s=8vz#rSvQ#bP{{is;V@Lb<C|p}GgXf!%6B0#Tuuo(tUic4?LS=({Yv zN@ga&e_-aMa1FmHscf|>&q7Wb1h$RCoK#+Yz+*Q~Mu|$`K`f@fN4FOfKl)TaUCH~j zi^uOQxt?>Dti4?jq?57mTdLc*da)|KBB{a10WW3R+z4xxjN0m}H(3?;Z%>f2!{|z) zW3MZ9MkS>rezf57I^BaK20Xn_+3x#Zwx+tie~4myiIxF0&dK<8BesMn`V&IXCuP@o z#agoXKrYs75|O?YVMTA|^cmCFLs=QnBF2|I8l5ZTtL(RserMpu_ZA9?%DY=G&}6r9 zi~4VPq@JMKP*6I(T3LRwB>y<^CYp z^)wJLt_7pufwBokW}87)^5>wMR1Xk94j(v_4tZj9G66iQ1gVtcAXx~86m09B`4e&J z*v=fuD(tPa9c=#8jN8wq^o*kgP5^clC)D&QaE2fFffWcxnmaizNOvSWzLW^wQbBa|j3NORKs7DlPFgT<@(>?AP&xwa8xYz#zAET1=o8w2~lH$VzQgf-eFsH({BZN98nWAzvmT%(f(- z(Mkfsg8PeeG<%08>=L03 zHuBGK;lGQ}dsA0y1_z34Jjs9>I*h2;P1)@~%J8>dA|< zx37qQFTI1LX9WbYtOK(cW(?1EM_?P|SdA42k|HHA@ZsFSglOhQ)Y&{k3I3o@Zk7Pk&{La&Sn!Py;W#6OP(8I5G- zId5bzcam37JYi$ihY6DwrS3Clim~O-Dun{D7v-ec{36`O5tPcq)z86@PpE} z`V#`2_|do?6(jjFR%3A-s3Lv{jSoB}EQzbNPV^M|gI(f>^X@S00o4qP!HA?k{M4YM zL}aHYQO`7-tzK%PkRG)=5t)OT5H5@%IccP_3Nw^g)KJ8r85Olm+D;Zqb@pILRpW(1 zc!w@Ug77m^>bAb7?quqT9!HKS&eoerJG(xlhjkMOj~0uClX0ZlX1ZW$$iNr{gyxgMov8INN@o~*JCIX>Io(?Hl?n-KSP-Ld^ilp_ zXV(E9Rk8iu-Z#5Rc5lhf+z<$%1`;4NrHG|Ab*seWj@?*gA*5+gc{hs{nr!(86TRIOmW%$Q5)c=_Y>1iWQzBSV2?=rbh$?x33FVyZ3vfpu>{e!|icR@R_}Kf3AOm)AB$x;Jx_den&I>lt4 z5b*X(*cxxaovbSsRL|#=pN5GgHnq(lHw@8_uLkih(Ke0Wl+~?OVlv&dYCVf;ji%-w z#Ozn~2hQ=4u-z81rn%a~3B+vc0RI%&kJf-|lm)jiQ=xXB$}_pWIb~8{7sM6J^y+gBNsg;9?M85gv#Mo2_n-Rkqs!_W)dIz9iBvw>7`J=D+!{ zb;tX1{WhE_o#1r8R*8q@$LZ;&$TZv*FhKA6pYXY9P6aCk{8l%%@{6!ii`A3FVgYQv zwyhnwi)?7Fa*$tncMvOk;k1K+@X25K$T^MAPJBa6v-nco0Ulx7Hg4H%QyjKwh+vD^ z9Lmt4ar2U2=aYu(e2l`3Qm`EfMS)T*d}MD0)l%9Ty{It(xPt9!zG`y>ZUA_6IW1)B z6-xV@8*wxFglzl}(^lgxW*(*8$%jH|J9nmqUd3k-$KjfJK6Yyjo1DvS2oo~+aI^i# z!`Zdn_-0u{dHgI6R)6_`D$F*ABxF-_g990XY%Y<5oGzHIy_L?q_NZpZ^r64XxGw0R z)mmZ*V|E||+;D4L0$W+bY1$u*(hoT1o|%eNuM5Dc52e z<#IT>b>nxv;O3g{-R-v8prfA+m$&$OHhGIzJUDl;!)rNfBVP>2&mLm2k1}OAy5R{* zE1r$CcJIcm45YO7x4q7SIbG9N`!R{XS9ZP?!`)vNO zXcEz*=?lIOchX+hIw>TysrCYk+csM3v;?lRhz}v@hpV~494-M3h|8Nk@Efn;FBoh1 zQ+tKtCZ$9Tp*iApEE4981Lq3(Vjf$X>0x?L>||K2QdZ_hliA!gJO9aaJf>-t{4TtcU298T-qUS_DPY%lLd|Y+}7BnvA1y*i%XSkw*I`!bpOF_w&uAr zRCk^gXGGF+yM%kY)C^Z|mxFdT^dLLeA*I!BYs1a!)?O*uzc}1I$K*kBH3P}bcBwOU zAZ2=%t9Q6dE_{B?$!#*@&J-?++&Mbl*@U8Q(utPA5bNBkhx4q=EYrCBR^@R8kehgo zdux8Hk6PttLu~T#`q;L{U3~TN@S@tpBxx%Yb1s(n&w<6opLjD~ljg;@#kq6Q+c^Oa zJAXa^#uqN}$bt3c;LkDQZf9&l#Ak>65t#(g4X(ptmlHjO4>Xvd^96zs4{@zuSj4vd zVDZ-))hMu-a78Jvhm5Xa?J_~Kxij!+@*FJ0;fZut#xD*m@A+^@=MlV`iGT^uX7USF z`1ND%d<-`Tn2ahrodKiLJIrqMER(No2h8nhBm*ymV#~c9cfz&z&nh^J`zf8r!|+OtsrFGh8ukAnTt7#xSofrQgNX_oMclZ z43iuwl`D?8&zHeiadjX4u9N30Dq<`?rm_3-!&L!jxFHM zi;PES#?47ynk#(2Gi5*SQj=(VM=gY>eYmQM&9lJ=*K2Rb zMXrND@t?^Ygt9icwHKG;$+&l+C#FLqjjoB6nD~k01h_1Q-`VD=bT-_ae7%O(gGtO;K^t zPixJMd&%skhz+kUv=RT3KU4WcdZ`@b|2SErcwkge9__Sc@gRQAsExCb zzm_C{g?%t(5&|biJ**S*Q7KVN@H^+`eVYoI4>En##M^_ru8$Zkfl_zlxhsE=v z7Nig7U*ddb=k>A$YrEi?SiJS>51xET3XBL$#O-5EYRF1VmQgScYE=4(H}YUK(o zBVhAAWn6M;a^7N=wGfbPdv>&%oZKZ+sJf!K7}j#%*?ZGG!`}%=8Q%6}y44pnTOz6> zXmX}wrsK0l+VT?%we~Jb=b-IeIKkkta+kdb7a*l&n58uS)TLuYq@A^OmK|?jaQ*2J zH(ib3H*_`@LZi!eM3*%<$WM{^O;GeOZD9yD$RO^!aR~15iv_T~3g>SwiQFp#vvWiA zp#K^PVzUm+j}YM!!5=Jg?P9q5=4D;v9*P?Q&&IjTysSPxzuL)b!R0b(y<4SY<=^6N zgByOV1b3M2!Ok!w?;dz$gz&#j_dJ*$E`E)m+nEP7#l>!`scqKLtUR~h5)QsT%;mWy zEAJy~mV_a~L0UtI-8Kaqc_wSCPF-xyUbtj1Cx^=u0H+Nv!hmt(d2qbi)|{ha%Lu&} z`cHhG?zEL~pv30X7ak8sT<%k+unWz#T>&2(6g;$R3uCDUe87mn7kyXAlqlRG1)TVH^9;oMN94}%XrT%y>X57_z@ zR_Se9ZENr+hr8&CO`rZS70r-Zn2mfkLZ)$9U0QkUk9l1Dx;yV<^vn)Ecq-#* zQJ*yfuLRkx&UDk8Qu}V|V_3P7U|>AvNY6iu;)-}YB9wu#pqo8t<_g_dQhWrHV1B95 zaLhlqx~1i&_@g$jLQ5%FdmjLrIHO>=Vl_*g6T#LMFg>u;mB$;-80v70&Wz?%Ifc?YCH-azJzVoLW0e4$aDo&>U-Cw&SjD=M~N{ zZF9S!z;iMD=R*x#mTR-Ow>gJ!Z85~@ws?9B>g{KIFNgVi59;Buocu^?Q&`wWidTr= z_Sx12O|1)p7noZ2 z9jv9p>Q1xu?=t|FzHcmB^NO!MTY7oGmHp39UV-vJdO?mUI4tK(yo8xql;yD62K4D~ z^P4Rv-)Hs5)7lNdDZr2)ZeDScKE}3vJL9}tqxKav;SIE=ZR}tx_gD(sHEHULEUkN= zt)*w!o3!>oMsG|jXxFICFK*R4$LbGsQqt3#H2iT^|DnCBKFBNKZV|dozc^be?^zpp zHu79#TV#9W`N#{A9g!C!J0t&$ycBsk@(P{-crEgJi>LayasR>=WC*!{3?;+Ja6A__l8hpw$%SML8A~oA7n4iKI5M70AQQ=@WD=Q7ipdlb zB~wWWnMPt{I+;Ocl2URRnMKOTY*J1tNF|v=sz^1dA#=&)q;?*;g3KpZk_BWTSwyZP zwPZ0_Larv)kZZ|xSw(IqcaYVDk$;do$z9}b zat~QU?j?0(Em=p_lMUoPazA;1JV+iQ50gj8qd4U97^)EmwNe|!?14I|i@K?YdZ~~4 zX&MdCAWf$kG?U`^C6%a56{^xK8li+zs?luPissPPv<=OrZD}5DN88g5v?J|AJJWpH zg?6O{@aOJEyVD-Ew21bky=ZURhxVms(z9qk+Mf=f1L@gx5Iu*UO9#{Q==pRAy?_p- z!{~52f{vu4=xBN&9Ye>`i|EDl5;~5KrxWNzdMTYmC(~j&g+}RAT0*DM7@baM(3!N9 zUPfopGCG@<(+XNi=g=xzO>1cFTzWa3N3Wps>6LT=T}T(vt7t7uf5&9_INFSq*(@k_U-9n$BPtvDq>C<#8)#)?zS^6B^Mz_=F=?ioR zeUa{@|D-R`m+33?Rr(rzoxVZeq;Jt(bT|DMeVgu~d+ERFJM>-p9(|wwhkigmq#x0J zbU*!=enLN`2k1e1h<--@OFySy&@bs%^lSPJ{g!@557Y1I5A+DFr$5r4Xzfw@Kl(HM zg&w2F>96zz{f+)kPtsG`8JbBmYZlF_*)+T6(43k}b88;WtHGH;OVa{cP)pY`v`j6e zg*8c&HAPdkEG?oDjcS^ft+mo}wANZ1Emv!+8mT zaR#`DR;2aRdTG72K3ZSxOzkYKpVnU+pbgZ{)&^Teaod3T>r!o3={3UAqIP^O*Jz?N043?QZQJZH;!XR;R7i z)@kdt4cdL${n`WCgW5yd!`dU-quNI8G3{|}leSsgqCJtRJ*hpVJ*{okbnO}KS?xJ( zo3>ql`4Z`C6I*&%_OR#1Nq&~zMo~q`7OSeNsAB78NMW{ZoaAE{I%J9Mn<04(aLIM5 z#9kRM1@z~~OY`mQ!ih?z-g$}?%wV%BRS#QHB{|viRg#NE@02syo+XlAV4>vD|5Yo!X<^&$ zl!9!})soEs)7NeAA3F@zxvIJIt9Nw`e z_QW}$^2KYVG`4oEB0@6zXd{YRe68fNR7{`F=KKJXL5N+T*xBdGasw!0n06ba#$&8# z9wSkic#H!VE3*FCwNk{y{`r!W$?~s|?E10mV6U^=_5kj`2&HwsLGm!^25BbSe;pV* z;s(jhs_&Ilw&DhTPF3XY=;Fkyrd4 z$>y9}SzK0Hj+TjbM0H!h7c2v7c~nO{Dl5284(oaMO78}k+8&}dWvOKM<6~+I27g0t zEqz#blWKhMrlw#kE`vOZnvL3oN@Q;0TwA(M@x*0qv*b#MP}gBfh;4oVydAVzsx`5R z?@1new5BFnGE<0`zI%%_+QfDZ1!-SD0SRBf5yIU5NvX`j3cr>!^_#a!y{&Bb3Vio( z1B0jLsIn2-5>;gD)L3*D`}8GQGeR4`kTv$N=cFF&?LCUe%fH5|ies}YYv!|puTe|n z@e1s4pIirRzv#!ZBkDgyZR@zkbkF?$L@Bh9_!SKidihUNDk=( zc1pjS*tUhTi|ro_a=N@E^=4Xo)EXf_8*`ft1qn>e6X!?_fFXotlan{RX% z$A{3B-aI6YGv%}3K`Dd1a0o3@9g9|h4SW4)t*nF43aQw2%aBbH%W8sZfbDw@97y64 z>6aYks^v3qqwi-@8N0R)-2w%=*gc=2FZ5l8-jo2nEcP`@Zva2LB?a`by?F+)BCUu0 zcojOvr=Lkdeb;AFU#nm#8}*swv{cWFvH@ShH3a{61{J8H&m@->*h;qZBXE#MGQb3N zE|KQ#jrcg_D=9ksN ztfq<~Gpg@AD%C;q(a|mC6*atBC06;HTL)RBvMYcS44K_!ga}DpmraVA0NUm>v06a@quf4Ll)fiEmJzp+=E|i_vDOsktCD zLaYoUyUHJ-=64)JN31<2O|r1QRjAF1pCy;Ax-wcdYjqo_R8EsafMqeZGaIQm#)!an zPHq|_S_$pYkOeDv1@+=^GLQPlK`gH~J8}Zr?X4ql>Zn0)EQ{8#g8RY$%||4cGmb%# zMv?N*+W9v48*++u1Q{Nwy`?)P;6(y_{?jSIuhQsYys;gvX zw5Ax?S!~^xawr9?|3C`qp@^L3VxF03i$~kYLC3skRe5Ro3?|fOBg~xns8u5juczK6 zS6*jgk1hlGi`vRrthHOo)c3WOOYE%VTlBs_XW6Is>nsj_k5%T&yZGo7 zVqLn(ud(#e=-!oGWVilv7x@kw>$?HejOrn$u_~@Vw)c<&Z1!5!#Wwc*|N6EQ#uFnuu^sJKY3Wu6z1fxun84!E;=g8!$AAzwGUyQl2c7xk2HwsP~uDAo(Z zWM=X7imKUj%c9&o2?=T%qn9jh3B9^SEZZ4DD~lSHmq99*)}q65a@c?~Wjp)nII`mP z;NTbR%w3@4+cRa4Ywql+Q27Og-Iy3*o8ZLY+61@cVPw?=*X06qGmdj6@?+5zph6H( zd=?1E_#E1mgF3+PO{`OUG)n(opzqfH_%Yvx&zt+pnR;1&`Cbb<^BcwEF5xm+Jhil} zObm>v@Va5L>_~pRMhc~Vgx(9YUV~&md*cE%L+?3A?rdWnyP?2+=b=j;FM`}m?1a$% z^B|M2Z&cIR54DM}&%6U8@4HRlPsRC=ysT?vj~9f@omOm&)NJQVQm8Q|3Vr8EiNogn zBuSpsG)tBl6vlI5FLqI+#(69`3~?zPB8T*ghsaJ7+c#VeT1)0mjTtt<+7Ytd0+0Ug=`#cY@{P#>(EUW97au-u?mM zC|-|x*G-TKTXr2-vuUE7uD72khaK#2R95WOGfQJ-(+o*D7M1-8gbHXEwzR+n{P}-%QyTjK_j8I%8&yP+0w6gOHx)TR|p@ zt1Fe$+4dz+=ZnWf2dyoYw=(ZU*_T#cQxz?#DTaYr#dBk$JAow?29~v|L*=;qI7VS7adSm)Hvo23o)Xqu&yr`jS;1vs?e%lzerC3>0%Be_PmZvE z&p@^!v~DAWZqGcq)Uak*{sQ?fcH&{h#b$LxFkS()QH z%)aWjVOZM8uNw>#xN%(ELs?pOoC0qACf7Gk)leE?rq+>H;= zsV=?vE?KsTu3>sm)yw<|DUz4D6 z&2~^(xD%D=awkFpv}}N85g{@0L=(B#$mgMvUfnKVVP_iyn9NM8iq2z8o|7aabfg;= z5;p%+w(7%Ql7I2CLlt0i(i-M>W<@+aR9qRWs>YADI(56YK;5JbE;(z3T{@8!y&#i1!SB#+x_d^wMBSBxY zUw+@gPFw}#sLx>_l+KRMV8Q>Qqo#lmJ^iZx%FkgImW96N`vP@;c?J5+pfBWiS&u@5 zJYUMg*!tO0ratsbd99cCiO}k!5buxIL(tdsMgFILLM9qg=E z@O8}z*|GWrx}}(kR~(Za`r;GvToap}fg(To4a3{*mmvcYS~Uy02qC{Q*%zT}VRo(l zUH*_QvOr>ha~&lMV!h+_M1b=W?e5pmle2@&d~LHoQhq)(W$htb4%O9G{2J0_D{tO zq%({9l}Ox>Upon{K8t^Q!>{ya8_`vFa#PsG=K7@tC%-=;(PM@xBtddAK`WTA7d%xs~=W}o` z*y$@(6Y$E%0Y~mdRes0-Jrje^3_9*$CmxqPK7&^YL%vm+6l#Ln8I_x&Ryx#>zC2TT(#-0eXvs=RfdGz$JXc7H zgIy{qXR=*igG_*SHuWJy5uro7(RDUR${e$~vVwJeAA>`hs@OO%G67gCD@x1RoGDP- z{F{w~HEhX57@8?C?3qUJ&e`bS`0g{aVdtRWue0z|G7+L7LRTk4m_5XCVv5ecT1uj2 z?8x=#yBx9tDr3CW*$VjCv!aM{2kV=OzHtEur7@6jp-nkxiB7{HHxRTWAYqYrB8sFR zjwrhv%r^&wRJK7w{d+k|79nBjh|rIZgTfBEil4380VeT~ezc7;*kqeAb6#{l8@)#g zIcCDn;6QXa%BgItIQ0Q-6B9xcg105bwv<6^9xW#$BLBiEtaa9GCso%=_mUrPuYbE zvzJ9H`9jUXqiQG-E~t`{;ej8bI$U18M#SQ&Ri&}%?Cm;LNlV3)M5~#YFCQ2PmhKv; zOto@7&7v79Y-Le~P~2kWE|Hhe=^&K;(?}>K5i;*a1M!e()%vAK0VvE?ePYDWn0F&M z4xGfyi%~R>n_N2(^EQrQ0is02w}zDnDNq)jUop3)xFl9q29-1y%jM_Eq2|~*`=l1I z1)3Tf-!ungQ!=-@cxpvWO~vfuve@*R;)0^$>6jtKrWKWz6i+Xk8!N|LzDdE&ld*yG z)KJb}h!@0cFWhoG7JXTjTJ`+1V%dSs7)zepta?tDPudn)>d@`a@+pQCiYl|6_oyM- zGEIp{Y{i3WSUr6lc3`cf4n6&M!_aSFZudW4{P+%I3~4 zHw2@8uo4c$8!d@uqkGHY+UBu~3%kdgyjOFolBuiWja~wQzzn~!to-$awMNRquK&*> zleG5!>$HkVR)Y7C?7y8fN$uZE$yPj|hDWx*aiiNc=3&8Ks1Q%VR{XAnB7c-A>-U-( zZgtui33jmPP$eVSR7$E?|2inFZI?@)WS?jz1o3I>Dm65`aWNI;vErJUrB&0K2uRn{ zRjzRY_Todhit#jAvRfWd)h66*NS>%wT&_;b%_b=XH_2&Aj8D%SsZN6zO>!xo2?HY` zVJ)i8(VrptNr^f4->6_i0#U)HqTe{nq!65aI*rtXhCi^7TF#;tgYci`ksMK4&f_m-kr=hl zY+REJZX(9zIZ=0(R;Og;G>O_n}*xfe?>S_Xlv4s|AJ&SOPp+2k+`<;jHIb#G*6xAB?JE!PlerY zWR@HNMGo!%a{0o7FZkQVvf*`7#2*(FWASM&Te(3FV?HXTe{mev)K*EMX2{VgN;MHP zuEXQAykvcvj~Xo|5K1*+Z?+tRPwFN%A+OOupO|4L%Wgi)Ym#%^`Y>o_n>VYWgkJxn zh<{|_w49DBpd?kzO`McOem+xHm{XJ2@5ABT9a7o?v_Id6QM~gp2^#P{5klt znw%{+NzHZ~l;qUr=Oz&|wUNV#6#*vn#+!mW0{zilhhQiuV)`I^Qba2JBn zpf3M*$|N&WQsy?1&Quvmkw(+Z=F(_HJ&hnYeB^@MvY_$WB&95crWQ%!ksHZJa@hC_ zDQg=iZHWl2{5Q5({-}Am}ywV!`ST|v-bI2sLR&@#XZ?b;FUYh`4x(b^tYaU2| zUvh95K|DGMlz)0DwtIyWodB`_9b)B^lr(+(BxR1tCAOZh(G+9B71+s{62(4PpjdIU zTiRi#EdhGi>|)uuwOCnWXPYA^DLaPEIPOoWKNM5KWz%Di4n{&;MmX;M)#pac=`iE611q?odeXiu!@@Z^NOi}gRnaWC& zU<`YtRJnq|OUC2iv8GmB!PZ?ZhmydYxpGMFe3^2zwW%u(2cr1eTNOXTd|HtR3(wm) zUN|Ji!@}P-9Pq|d@D-HwD5lu^VQ@+}e$-3fdmU%#nm zNo=f z-YNO?PRXZtNvCUh$k~h**ym#*$L3g zW?v4Uj-`+ceGyacOfwwJasRWOO?g1ECw0sEx3Dx3z>F@C^i!J*f0D z9JB+K(fPCarsKTQs#sa9x|;1oX1+#97F;Jw%_FYdj*ZV|$+^pRb1Xk6&>Wk_of1BJ zL`k!;r4w-jX=SXUvJ74z*r699e^`fWg0>kw*|u5f#Lj#ld$|dm%jkB?o<@{Go0Spl zU;Cg7IB0=GG%I*gAtEq35m>_A1-foQ>wmu)YN75*_#40VJlg%<&Da5+1Wzb^-)7}q zv!%4UMks8Nnf~~b%I#*Omx-eNZz>x5L5J2Td=m~X-VG>~e{fdMohtkcMOuCMR;6RI zR@|RS)N0H3Xp*ikqKR+bri^9_x#LOp3%F1$3YW+l<21$Omy+;-1G2(W;K{$K2F&V< zp;+(Uj^zBaOC&Gm=a}H|25<50s8;{&pz&k8AIgUDlSK)fUDviN2VCs%i%Pn+GKvP* z*29@*)SHUk&cRtRw&fdeYUG=W!(kwBu!TFkH^OW=2jw)v2>**lnBX;t!>b1u0Od=z z?02vVWH{IhuVO@){TN!|*sID8!|!SBYiNaT8&EBt2Ycf+Wh;AUFh+fRaIh<0R|;7j zE?gI&JwEJhxedO&hP#+C60(9~6zRDQ?hK94qqhP5`#AW{q@q#NA5g%zx5-WMD_R3D ze6V*sr#STOuPZo2an^+>9>k@wRZl8m{kb=lS50igweU0$b-Z$ya;M?h&MRQ$?m0M7 zqqMRa{`LiH(Da_$&wqPqyQIwD8PW6la8(aDzIr6ylSL70ys_Tl46Vy~=BF z5_<<4Xyksy=DI z6xQ0upDGp;oAjaLwN$`INA&8ngNO&U>tdHOPyp~OHcOU0a3eyL;;5<^q3B*Y9@4%^ zgfpPfi?;lpiT8tAFj~!85dWU*ty_Y=-%a67HP0399d+>>U z6AUKB0>e&hf>&*$*y|Fp)8pq=in|rY&FU^p`%R(l1ipJOmi<4B9UXr|m#Us$Uc!zX zg|*WFijx&QbK@(YT#2&Mc;w>3Uh!1FsGD`Z2yO{2giJ>ORt3NXQ?5a{y~_{`AP~~d63OHp-TFeQ_4E4 zIGbjvs)#a^NePR<&WUPRpJY`}nS@g^tF)^&eUe>WZ(@Bzs+Ya!P_4q-k#%vZ>Fn)| zs&jQ2qzTY+t2lt?sxFC^r&!M%6rP{%dY9U)V1+nG_KwL&UFabDs;+PZP7v!Hj z@gq>{UPTAxa3TWtq-ul!$HMIUfNIxY2&m_o+3yUVXz;gW1NX>b5eWF=0Y0-a!;ozy z%URqdvxXfUrK%Pl*c^qr4P@Zdg79cUXe?Vl1_aN~Q2lx=Lw(x9Uj7_zi=8Fa!4yfo znAv`nAv&?D%NhAqj_4(l3LE@{kF#7+ZT5<)8PW1mW_k{`!43r+F%TtebUFy;QIeQ? zUsKG=S*T^BRKoG9G3ElK`alJFYgM%~W@AUe%N1E5Z+Dh@pF#cg5fvvC)A0eJu%OO6 z6LXW|X!*3N3YaNFW;RcvTy)yL9*QDh@DdMk9xijL|n z9yVq%^6c6Zxs#si1#Bs{+c^jae}0trj-EIwxcvy&g;2dQPT$-Iiu!DKmcm5nw#zVT z@{llhWoq8 zsW3mN>VqOkNy0hW1jSj}1a)w?g0ofq)oaXb;vhA{!ePI}E8DgkrxUI-`P3=?ED}MJzdt8ghh9#DF=B`g7P} zq$pSzw7K~FXOza)*=y`HH z?6Pmit8K(&$^iK>2_7;GcOxWxLHkWm1A2!EY7dhOt{IpP!&9Cu;X0!x0rSbYq)(ox zjzEXyj5~0tnznipMjzL$VBXM4ke}BksSg@++F6&XE*DNK!XoC!(<_HzHePnA>O_;r zaWOXVPRKWp=xIR0U6G%wYKFzhs<6l)oD#QTAZf>aOuRdzJ`p*n@J4K6}SO|ii zqG&W8(rd0%+xdhuv)x#QV9^{^NrdM-g%itP-@sGcpblkkO^4iI34`rak<;6)p?2em z9{)lny8A|TsD&+hE(O0VZO^j9bl!I1@1#HIC-ZU zwByWiaqq4y_^hh3z&{`-v=~mI<3zkr`jvN}@PFNp9RJObBSJp5^?tQ8n>-n1C%)noLM1f)oA;{$GpbZ@ zE6xz`MEa@w)d~yCghw(fd06emE`34`DB`3@b)pFE}xG+~kl61!|t9gc$d z%?$Nrs6)q1swasVUC|W7_d(g)%Rnwq>We3Z-Ch-|5xbxbAHAs`S>RXcHPOL6FOa6;9~L1UbGqoFFCvHzLKwS3zUg*lkXTi?5)@KPAah(Uh^kK-(_(N)%O&pUo4rQjm>kgwyCmd4! z7JO%Wc49tpYM<(Kh$RMG-6EC^8)2g3q>x^BNFC*1o_YxQ$6u)~XGLXA>1=N8ixsMU zU*VWz5+zor8lz10=yZ(}igi973o}|Qd#>b5mmIVJ9 zqpR|eu{zF=p|iUGs`l02K$Y+#gaX?7jXK`Kj|ie)eW&_tm2=BrFpGOwQebgXgTrjf zYq%99<%>A9{OMsBCG!ufAKKXZ^N{h3{Ff;C=cEDO30Vq6Yn} z^?Wo#rY-A2mbF6i_~*?mt%*&oz(^rx5PS>S-GE~+e61>t;E%8#bBis@sn54%6`R@Y z8H&d>ue2QM9Lqb5w*U+HvmE+2j;!7mR&j~!NsoV?jx!Chs>&*UGKSYN&=k++d@o7C zrdS-S`9Kc0fbNPDuPx=x@1O}a%cZ8cdoMlW$qHE6g8xAef8)Cv{yjb2agyKGsP$th!#oBTwG8IKTZ(YkBP9!ZA$yo^wf6Nnl1vwfF- z?Yia5z7w&nS0Q`X)(&l3wrSX6@gw-$ILOxlKEORsx(v4Wf;1hv_UfCw#IJwg`#ndf z+pR9$TetN3eXN0)_a|UK-nC`l9!V~t*YLaxpP!;z%dTycJjlQO8j2)sf)M*pkM2Et z4SW4VN0AhH5clTwl)b6+=nlldCxaf9nEX?oMn!oswyg;H+WpX@ukWo%KKg%3?u zVRs{ZL9)!smLE(;5w=tw9=3{G!o8|+?62?BUNfuBUq6}m@WbB!`t|>9X;CN>H|;0$ zb=tK*>lY3qA0M7dCIln3QS`A{KL7!^T9Q882+l||)HMN|b< zMb%JsRAX9;fzeni)Ec!xZBaYa9(6z+Q76VK)<2iQBTwh^+A16Khz%$ zLW9u|G!zX(!_f#d5{*L>(Ihk(O+hozOq76Tq1k92nvWKsg=i^SfmWfv&|0(wZAZJ% zZgc=0LWj{2bQB#!f1~5*1Ugv_oknNSS#%DaM;FjVbO~KSSJ5?e9o;~;&~5Y|x&!C^ z0eXlYp~vV6dWxQ*=ja8T&o}5T`hY&7Pv|T9hQ6a8=qLI$t=yorF`IE1m+=^%i84u; zq)ajG3l8MOeQ8Xlb`qC7Du8X{HQQmMO=SXDTw4n958QrW#Y7 zsln7@YBP11R!nQA4bzrs$FyhqGXt2B%qV6oGl`kTOlKA_3zQ|2Y}ih0evW8O2Ln9s}?<}33JM(RgGyLn#fn9D}l zBy0*cC7YT}!)9bNv6;U#pb|5>H9mWo4N3bK=QS4}T3_F$` z$Bt(wvs2k=>^ydU6?P-LiQUR>XLqnW*_hf3`-FYUK4YJ=FW8stEA}<}hJDMvW8bqM5^5~)l4=qsa|)+%I%jafgs}^} zw8_(R8MusGCN49Vh0Dri=kjp*xdL24t`Jw4E5a2`h%fL;GA$AcE%dS{9nOv5MslOL zG2B>gGB<^r$<5+sb91=4+&pf6!lH$q6IsKp<<@cQxsBW=ZZo%q+sbX@wsSiYt}XPE z#?Er*xeMGy?h<#IyTV=Nu5&lIo7^q#HuoQQhl_J}xqI9b?kV?-d(OS!UUILv*W4TK zE%%Ol&wb#2a6d7NG3K#=B`jkFt60N2HgFJ!u!(&f#Yu27oE)dcnQ=B;02joCa1mS- zm%ycQ8C)@jYvcO30d9z!;g+}+Zi`dklsFYmjnm+aI1}!IyW(!R5B>x9#r<%9{3jlW z2jO9OI39t=;&FI9o`5IfNq91zil^a)_%FN~ufbdJHoP70z&r6Syc_Sqd+`B$5Ff%v z@d-i1*Mt&2&mEXp1=XdZs`Ca^O{s4cFKg1vAkMKwNWBf_}6n}=l$Y0|B;s4|B@b~z{ zglx;a!Z9oeLO_rNSx6_O7cvMLg=|80A%~Dt$R*?!iV7u!QbHM_tWZuUFH{j~3blkf zLL;HE&_rk|G!vQ&ErgarYoU$MR%j=55;_ZAgdW20LQkQ$&`0<~7$S@iMhfGENx~Fi zsxVEMF3b>S3JJn2VYVr0iLgpoEvyqZ2pfe>!Zu;MutV4>>=Jeh zdxd?%e&K*{P&gzU7LEu|#N&m{?LQC6*P-iRHx#Vnwm4SY50k))Z@rwZ-~k z1F@mlNNk*tWTlr?>?QUV{}B5obX)1Atvy$qCoUG3h}*>N;tp}AxJ%q4?iKfo2gHNo zA@Q(yL_8{<5KoF{#Ixc*;=kfG@wyln?~3=tMDf1(Kzt-V7N3Z(#W&(R@q_qP{2t&E zdad#@CM;a#C5z<@%g19+rYcPdx@0mJ~^qG)b2X zDJX>`Q?eyTib$Rmm6AwFrQ}iyDUFmt$|z-$GD}&ctWq{9yOcx9Ddm!KOL?TcQa&kv z8>ygFL8>TKm8wbAr5aLAsg_h%swdT#8b~dqmQrh}jnqZzFAb1l(qL(*G)x*Hjg&@9 zW2CXtIBC2zL7FU0k)}%1q*>BzX@Rs*S|lx&mPpH_mC`EdFKM;3Mp~P2aE%u&dsVt8 zB}(_DhtebIvGh`UCB2c}O7Eoi(g*3I^hx?GeUZLN-=!bYPwAJ8WKPC1FH5p4E3zi* za!?M*X2RIDUero0r;*dj>E!fs205dgNzN>1NjS6C^ORC@8M&-nPA)H3PWZmoOWV4Q z++OY=cal5HUF5EEce#iBoBX@nQ|=}Amix$m$o=F2@<4fz9FvF0L*-%e2zjJDN**JR zmB+~wd`rG9-;v|;UHPf}T7DzHl|RTIqvTccDfyKGNTC6tm% zDW!BZrHoQmDX&ygswgd$R!Tdiz0yJHsB}^~D_xXsN+0D9WrQ+PS)wdemMP1XmC7n* zwX#N8tE^MjD;t!J$|hxtvQ^op>{NCsdlGJK@I2;J!gomQmhzu+UwNQBR30ghm8Z%x z<+<`gd8NEo-Y9RCcghFlvx-$-6;x3*RZF#1M-8j4>Z!gORgI>NIt_IzyeQ zCaAO2`RY7%p}JUIqApigsH@b~>Kb*ex?bI=ZdSLcJJen3ZgsDEP(7p`R*$I1)W6k} z>M8ZKdS1PtUR0A_Qm?7k)f?(9^^O`>@2ZLFef5F*P<^C6R$nBP*zCD6jnP<*)37FJ zAZpW`(5j)_15}meYKc2OdGDv)#hsp zwIy0?skTg8uC35kYP+>P+Fos+wqHA-9n=nKhqWWxQSGF5N;|Ec(aviZv|HL8?Y{O< zd!fD5UTL4SZ#t_pIhp6=^WJ&B%NPpPNY)9UH7L>c#aEdO5wkULmGe(kts#^jdmty^da2Z?3n|yXoEa9{O+k?|M(Ym)=_+t&h>i z>J#*-`V4)ho}e$(7wL=jrTQ{`v%W>&r|;Jf=m+&f`eFTuenvm1U(he=m-MUpKl;D= zHT}ANL%*rt(r@c`^!xe){gM7yf1*FtU+Zu5w|eZI{$Br}f7CzepY<;WZwN-fkPO98 z4Z{c;A;UBrBWy$r*YJ&$Mj9ink3WZY1B6A81;DKq%qnUV~jP%8RLyP#(ZOevB+3u{AH{*))?!I^~MHcqp``@ zY-~%ow8KlE_ndLTxM*B5E*sa3hsGo0vGK%sW;{1u7%z?2#yjJq@!9xdd^Nrq-;E!} z&mbGrf_lQxonG5m>0sGlxnTKVg>n_$~uyI_Z4r(oA$ zw_x{RkKpgYp26P1KZ5;(1A>1B2L@xo;lYu?QNhu{F~PCHal!Gy^}!9njloU9&A~0f zt-)QvJ;A-feZd34gTX_=!@(oLqrqdrlfhHL)4}t>3&D#C!fr3*y$HSxz6!n$z7Kv3 zehPjLehGdJehYpN{!Hky+w+R&4dn~v4;2U%3Ka7O$$vA%?Qm4EeI`|R%3z_TN_#z+7Q|p+7#Lx+7{X#+85d%IuJS-IvhF@IuSY* zIvqL_IvY9{Iv=_ax){0?x)Qn(x)r(|`Y&`R6c61CJqW!DeGL5w{WLifo4gq?B~vz4 z(=dakX?kYVOll@Glbe~$%w`rdtC`KrZsss^nz_w9W=6>^ldC)v$9yU*#XUwzaIrE}<$-Hb{ zG5Wmx<}EYPyl*}-ADd6jXXXp@rTNZ$Z+Rw=8rRne+sRkdna4Xs92W2>pv%xZ46uv%KJ ztkzZ=t8GHQ1MtVeq1GsCj5WcUXic(aSPQI$)*@@MwZvL#Ewffyo2@O@Hfy`J+uCdG zw+>l{ts~Y^>mTc1>zZ}lx?$b4Zdq~bk@eV`XT7jqS+A|P);sIH^}+gReX>4VUu|S_ zHnw?NuthsyOSWt)wrXp(Zij5swrbe6?bu;EV!O6y`*ze$Vkfmz+Ntc+b{adao!-u9 zXR>qJx$Hc4UOS&%&@N;bwu{)s?BaF_yOdqpE@PLqE85lU>UIsgrd=yRKIA18Tib2y zc6R%OJcqoLo%`DZ>_6>+_HcWIJ;okukFzJ(6YWX%6nm;Y&7N-0v=i((_FQ|OJ>Onn zFSHlg>+JRR4tuA)*FJ0?v5(rv>=X7$`;>j!K4YJ?&)Mhg3-(3(l6~3!*S=<7w{O|E z?YRBW9*~gZu$R5+FNbxoBRR68II5#Lx??z|V>wBjq)sv?xs$?4>7;TpI+>izP8KJt zlg-KQBjC4jjW1O+hIA^>w!I|dFaArCQ&MarPv)EbUEOnMS%bgX@N@tbxm$SxM>+Ey( zJ4cQ#u<2-ghP3O5Wl3O5cn2{#S5 z4R;K84R;H75BCoD3I7r98}1kGAO15uC_FqoB0MrYJ-j%)B)mMlD!eYdIlLvjJ-j2l zGrTLjC%iAbKYSp3FnlO{ID900G<+=lclb>BZ1`OGeE4GcQuuQCO89p8PWWy(F?>J# zIQ%61H2gXieiMEheiwcp{uuri{vQ4j2}GnwC}Kveh#hevZp4fDk!U1IBv~Y7BvmAJ zBuyl3BwZw9BvT}FBugY~Bu6A?Bu^x7q(G!lq;RB2q-dmAq;I zqeWqSy~n-up+k|wkt316BgZ4BBBvu~Bj*!@ z6JF|s%qKiQ_BQe^@;>r0@+tBy^3%mG?+UKy23*ONUDefG-3_`S*K{p6?7D7JH<_E< zP3fj`Q@d&0v~D^#y_>xBs>$r8@dT#xg+sJL~HgTJ}&D`d0OShHV)@|o@b-TMg+~3^a-Jbk> zeu1=9o#(c9JGdR)K5l<^pc``syF=WO?gV$DJIS5wPI0HY)79qvwdm%H2D;~sPmxrf~&?orn|=ALp-yJy_9?p61W8+Y%z z_Y!iP@{*SS?0$8>yFc8Y?k|t^I1hP@hdtgCJjs(i#nU~*Q$5WK!awUw&+&XO>Lv41 zduhFtUMer0m!4Syf1$Q4Vecs~d90LI+AHIg^~!nWy-HqHuew*mtL4@9>Ued%dR{}X ziPzL?<~8?PdL6y4-tXREZ-_U{8}3c?CV6wcdER_)fw$0GX9(zx`r`|K~x%a|*>AmsZdhfjV-Usia_sRS0eeu3|-@PB+Pw$tHe8y*e&c{CQ z3%=s3zLqfdjF&N%(a+@P@N@dP{XBj?KfhnlFX5N+OZ#Q~vVJ+gykEhu?$`C}`wjet zej~rJ-^6d~H}hNgE&W!0Yrn1E&hPAZ@w@un{O*1a|2Myqy91fq<_{w?_cn*`v3U<`ZxTW{w@Eu|DS)ykNbE1dw!yS-+$;o z@*n$8{HOji|CRqH%0$^H7Y#;3Q8Q{q!%;WtMg3?rnk1SuS|VCHn!Zf5T(o?&LbPJE zQnYfkMzl_}X|!3id9+2eWwceaO|)IKeY8WgV?u%RFam!@2S$fRhed}+M?^!RzU8=@Pdo1$B}(cCz0JU4+` zfUDrDxEii5)E62E4TY{kH=(;QNQeo8#dG5MK!HFkFkG#uR#GdgRg9)aGhL_*9{AvNUgj!Plr6Eny zWKGcwy`o+t*wJ*&ie@FVvRTEfYF0C^S$C~_)^}%$GgU397E%kVMbxTlHMP20LoecR zPFg3Olitao71zt?WxXz5cdv){o3HzZAM``AZ~7Lqf!WAxN>Jy)pVqjC+#~KWZjRgG z_P7J?h&$oVcm|${6Ywm&6<^^8@PG0H`9XY)AIy*C$MNI&$@~<4DnE^%&d=ay3Pprs zLUA!`U{&C+z-pzt(nI-88>@}eCTJ72$=Vccnl@c~t-aCSYVVAhMgsj)O|T#QYv>kh zo*TBkl!|}Cx#w|9)%utV%W|yOKl6spR_KFE`yC6fGR>&!**m zVT2jst?*8GFMJR_3ZKNM;{MR_22pL{SQb%D$VH-PqfS2Gmvc-?)$B6I6 zPvU3sOQ3F`UZB3zN$M=Gl{d*}v=Q1!ZIm`zuNf*HDiI5P3w<{QQ#8w&<<0754eM{~ zxSh?;Zs)LDISrjgPGe`Cv)ObngT4}9}R#q#gmDeihjrAsatf}5s zZ>P7{JLnztPR3kgp4q|dXl=E2SjX(Pc85s%$n40R$lS=h$o$BH$QiG_*CDzp`d4&y zbPZ~Tnxht|B^rog%nW7)H<6pf&A_>EZkz|_#rbf49K(b05Ihvm#&hsoJP*&u3-C+) zipP95zP#|8Ff1kv7bXkyg+;;{;f?TJOf9An(~9ZD^kN1vqnJs|EM^gNh&jbvVs0^y zxH6DGP%O|o@JHah)KF?BHJ4gR!=*XWUg@>;RZb~qmvbmZ)PSn0y|kbS=uEkeuCXfy_mMW@hm zv=Y5Uuh2X69<4@e&^ojpZ9p5*RbW;L^h+01NVwldq8?aU5lC$o#$&Fp3NG5eX=0p<{M zm^s26Wll3^n6u2E%thuBbD6oq3})^ziOhZ0W*s)ddaTbTWs|YV*;H&gHa(kxt;5!3 z8?#N=rpyrL81pyViaE!&V>`25*dEMz_II`?+l#r&JYXKPgV@3B5OyLviFw90VyCdv z*%@pzwmCbKoh7id**Waogaz}xG%;(sU6S3*Zeh2vd)a;Le)c$f!X9jIv3Ifmu>Z2R z*#Fo#`-n|o53+~2Y+PP0AJ>j+&voEBa?`jCoXUkble4%STuv?*mzyibHRoD#3EW@Y zYVH)5$erX)b7ycH+>`sq-i`O;!dlsD{viL7uUm$aAVvAH|1C2?zjj34fn#molSTE9*IZc(Rd7=ju+v@ zcnMyLm*M4j1zw3);k9@jUXM56jd&B@jQ8PV_-}k1pT_6$MSKmmkH?;+ONA`Mvx; zKIh;3ajph`fRp}#Od_){1t3>8KR zqlNLp1ZR6Vy|7eRCM*|L2rGrZgf+riVUKW3_**y`UKm~!X)pW|*+_>-$4Dp96+JPp zm`}_v77z=Gg~cLbQL%VTEFqQ_%ZSy)I$~Y1p4cSPSsW}55r>My#NpzINEacG&`X>p z&KBp0^Th??LUEC}R9q%57gva@#J|MV;u>+SxIx?~ZW6bMTgBbNSn-(nw|LxZ;oT8$ zh&RPs;%)IiVWO~JI3>IiKZ@VP9|0WT1A_2b_$tOCf$V`Cfn0$+fqa32fkJ^Yf%1Wh zfl7hOfvSP(ftrDaffj*QfgypRfnk9WVjrUgU`k+mU`AkOV18ghU}0cU;9}rf z;8Eal;Dy*<+$`=BF9bdWJ_f!8z6X8;eg=L?oP;G_3P_d|mR#|ocuBl0`BE||Z7nIC zlwQ0dm5@qGwWUTAZA7x+qCbA%la#lH8pi$sN;CkR@;8x(aoKwyv zmy}Bf{tMiZE65e)N^+IJUAej3R&FPEl)K4&<^J-Y@?d#*;GR5M9xqRnC(BdiS@Ilt zp1eR_B(IYHl2^-{8HU&?0B_EIXlmA%S!<%V)oxvkt$;>ukmQTZr6kOQ(R8_Eym zr}9f>!tzcJ{~>dRzTZy{A4=pQ_K) z=jux(rKW0{rfbQy)LI%Xo0eV6qvh4|YX!7|S|hEo)!J11 z{?KMc#YudMUlMI#HdZPSLCC)%5CmJ-xo( zKyRow(p%^)^;UXob-&(O@1l3r|Iml(qx6aTBz>|zMW3e6(r4>))D!v=eYw6uU#V}@ zH|bmTZTfb7hrUzarSI1F=zI0cG5v~qR?DU3)(UAw^%wd}{gs|y|Ehn}zw18@2LAk; zM*n4qhHNA?k{cmHW@I;V7&(n%MscHrQPMyLYcw<(8I6r5Mth@!(b4E+ zbT&8x8@gc`zZt(9J&j(*AI2zSf-%vUYD_bx8#9cBMr^6E%vf%$HMSUAjqSz`FQb>q z%j{)wGaBcOe~f<(+whF*Mxt@wcwi(kk{PLt)J6v5R}cl6AQv>q-&7bGjXc3p`p{sR zVC7(yV6|Y4V9j8yVC`U?VBKJoVAEiWV9Q{uVCUd(!Ct{W!M?#k!6Ct+!C}Ew!M|d` z)xkBvwZV1d?(HCf+t9nv`_PBb zC!=oYbLdOxYv`A$n3`#swpq|DViq-vnRU#%W<9gM*}!aQwl&+CUCnMrJ)^lf&>UpO z%)v%Wqm4PioM=ul+8SNV1ar1I&s<=xHrJZ#j7i31bA!1hW^OZgn7hqA=3eu-dBQwt zo-)sy7t9CdL-UFG-27&KH-A{j;;e|}TAmfPl3K~EqRt(DG7Z)LJFTUo5^|NCz) zE4P)$Dqt0|3R^|2qE<1hxK+X`Z&k1=TXn3uRz0hMF~w?cb+9^G1FS!-fz}`^7PAIh zL#$!eaBGA$(i&}zwZ>WFttr-2Yo?W8&9dfN^NrcY6>GV*!nkVOwpLkxS*xuz)>>pExN+CGXPvZ8S*L@IgYAQZgCl~wttZw~>zVc3dTG6}zF6Pb>(&qJ zr}fKbY}QU@C%03?>~wYpJF}g|&T8kj^VZMeTBSdAovL$*ycyv8&p(?SA$k zJ7$lxN7^!HIldEh zQafp!9HGl5a&kJkoqSGyr=ZC=g-zBe;gmFer@WcL%;+?CS~xA8R!(bf822xC&1vhj zbJ{zDoFTY{GtrskOm?O_tIe8b7juAl#5`&qGyisuILDmh&I#vI1?R3+&Us{2v8q|s zowv?==Y#Xns$tc%YCFHeD9nW=YqmAV+7)gQZW(SBZXIqDZWnGJ?qKZ+_XrOR$HIfd zL&8JD!xDNN^P;gC;hEut@T~Cc@SO0x@ci(C@Y3+I@QU!t@L%E8;Wgp4;q}%&>!5Wa zydk_XyeYgpyw^Hoowd$|uZI5#{~Nv*{x2L4-wQtoKMX$#KMOw(zX-n!zY2c{e-3{M ze+~Z(|B9dp6JaA_M2;vCHKIlIh!F`|=dBCY#h7);x*Q2dB9Ww#Y?17dT#?+?73->P z*uh8zduyahq>jDK2{;v;icY1-ACUph9A~a`z&RM15;+(-8aWm@7r79*7~0P>r<=!(<#mg=McqKujbd`7x9bvCHzu;75^8% znqT9dbI-dMV(vxvl6%?x$Nks6=3aMixHsKf?rrx!H_^TCK5!rUZTvI-IroG6(f#Cp zali3v`E_2{i+HZ*c}cvaUUDymm%+ahZS7U?DteW@Dqc0OW^@Si(5vq?@EUoIy;fdp zuano=>*n?JdNGff=U#8Gk2lgA6W&Gdl6Tqr*SqFj_ilLqd2#QqAMhn#_A{{Mxpdrg?!K@^*y?BYv-ny4Y<_k> zm!H=!;1~6a`NjR_etW-z-_h^n_wq;jll>|FRDZU=%HQN~^Y>Kq_xk(&gZ?4^uzv(s zz*BId|F?hKKjB~Vulo=D=l%=-rT@+U?*H(A`oE$milcl~h>FobREo+`C8|cXs2(+< zcGQVR5*D0;rCmj$MWe-{#iJ#orJ`k{Riag+)uJ`|5&WEJt!V9N-DtgN{b++|!)T*u z<7g8x6m1*r6de?eMF&SmMn^@*Mkhq4M5jilMdwEsMHfdmN4G?`MYr>Fae0&!?Ld3b zWptBC&17ftF;)3_`~{{1(~;@KbY{9RU72o7cc1CO{Kovw^kjN5y_r7DAIubH9kU)G zMwpg!!`EpgHb{m_Rt48s3y<)hB-*q>8_UPSurgVoU@b0+ixjNNMezUy>vK_DqF|*i zipvzN*+p@Mg4Mey4&R@Eb-XC9QLv&H#dQkS_M*5!!75)A4p(8bY&B zs1Y#dfpjJ?FP5`W_MsadV0IaS?adJuktg}XOa!LTKxJGeu zN&u|AMsadV0Ib4B@%$95$42o26s*if@q!er(MEA{+CU7JYojycnJ!YdZTzr3Ko5%cqs~&f1`M53KoK+co__09KKscqIzflcRWL3Rae*IGGUuYs^uc z%m{$h<|s~P1i-p;6elwR&^S2%aWW(T7Nes$84>`?(oviY34jIaC{AVsz*2P-Co=+I z(K?Eg83C|-9mVTWu#g?a$&3J4(vIR}MgS~sM{zPE0G7FbJ&Kc|0ARI{wPj{3c#{|6z@&J4FD)k1`NQh04V+k1vdqtI2kqow+EnjKMHOXK=J{pP6rea6NC5X1z`3K~E&~)FOu_vIC{BhGpg8=G3@3nl z5Kw#=1$QH$_;3ov9>K;43T{zA@sSkVtbpR9D7b9_#Ya;Q&?&ZQU(okuY| zbUwuh&;=ADLKjj@0J?}`BrZn_0#gu^_p_sDJqvZS_g?Cd9c8*a@dFbC1QxSTcVk$vT zP)ud$Ns6fgJw-9qpr6w?}diDKG7 zFH=lg=oN}-2fa!$?VOV6f*!ye$N1AB=kCn!2>f2Hf~VNSm;fPnFPH>G33&_ zO)=A<|4|IN8tzccLTH>~7D4Y)47nQaQ4F~P5-Damx&H6N4q(VcKcE;gxeqCZ%)=v! zA?Noo#TB}sEzc;1oR;Sl+|Y#LFDSUR3B_Oj4;=q2uqthmz+28xvrIJPBA4 zN}dF407{+%tOO;`0ak{R=K!lf$#Z~Jq2xKhYEbeVV09>Y4zLE4JO|hy6rPKb2Nr&# z-6c-~)`XHL0qa7^bAXLP$#Z~B0<|bM1=Ob4lu(CaQ$xcPn+6)8*o;u}eF2*ZuK&9p z>;M*q;;v7zFcWvl4+JdC#9i_O0h zgOV4R8qPnPA2!G<1Z)9lT8b?PO-He{q3J2M9y9~R)`yaJ1lR`9OcdJ?O5PA)TR_Ph z0&Ghtc|(A04ULg^1lTrE@{R!87E0a`VB15q- zhmv;+*x#V!jRLkel)O<8WBb5HL5lqYT8Lu%LJL!DKWGt(?GG(Vu>+vRDE3ciaf%%X zEkUtEp(QDH7_=0{4u_II|G@)dN5D=QiX90pOR=M%-&B z2edB5?u6E(*j>>26uTSR0L0*d-2)qBU;z6!v=POggf^zwQ_v<9dm7r5V$VREQS3En zbBetQC4&dp+fXuifV~54MX`6u^-rb{u=k*32mzZ2B|`|<`_Oh2`v6J?60i@UWFP_i z7}}9ypFqiQ0`@7iGsQlGcA?nkP%^Y#;rz2NV1rC8U|&MX)B^Suv>KFs z6#Ew1lVaaNdr|CrD4BA=et?o82ka*(8FIjWhV~`rzc1{3f%c==uTV1Ofc*v?K(XJU zWXb{i13Hjm;hzI{2T|-VDEXd%L(st#$3TZr919&vaWVL;yTd3BLx)qG1RX(fGIS)x zDNu4%08WFBrZ^oshT;t9Sc(fm$5EUO9Zzu%bOJg5@W6#(XClQ#ppz)hg-)h84?2b7 zd?>k$02hT$qqro{=@gd~I)mbpL1$82a%cj@r6AY;EZ6~DO6Y8gO9h=najBtmDJ~6k z9>t}F&ZoHa&;=Bi0lJXlGC~(oTqfvZipvaLLXh*61vZvaTvq5ZipvgNPH}mlD=02M zbS1?VfUcsrg3!Mxt`KxJ#TACGp|~Q@wG>wr8e0bsfGZANPjMxn8z`tp|Qd~LcHi|0`-A-{8pgX7-R}nULQd}kIE{dxR-A!>-pnE8;Ds(T! zRfF!Mxa!dT6juX!fZ}RG4^mt$=pl-$4LwZG|6zFdbztWR#npu#rMP;~V-!~(`ZvWj zfF7r~hR_of*9dx&;u=FwQCt(~X^LwKJwtKL;QZe`OK~lr=P0fV^gP9NgyIOL6e#V|VYt4&Vku6De*8^ghK6g+8FT;n0T^ zHv;;I;zmLrQ`{)%6N(!HeM)g-q0cC8GW7ZX#1J=oad6`AWuiE^GVW!jIJh$I#j?Ny;Na4@mzCn4L$gub3utzV zgDc}+4vK>-<6cgRdkxJ+ac`ixDef&a55>KM=B2pz(0o*k`v4pHDGsLiUIB`ODZW>b z!el@TQJ4&6VG5I>EJ9&2ltn2_hOZcf$?z4YFd4oQ6o$FGSCX9nk_cfke5EK%hOab* z$?%n-Fd4qG6eh!0j>2U4%2SxkNd*d%IjKltGAETNOlG4pg_FbizgLCAyJhr;BY*QGFdgY`fR9x!=_^(jnVaRUmI zZ_<#$;v2i+ML2|p)Dw!f?WUP69G;MC7%dzDrjp8 zldGW(h0{REHvl*zv>k=XbhoE)7ib3xcZGJOa5rct3ip9_{+}4af4~O$B7pls$rl0K z4@$lW;Qr9=6#f(1gTe!$zfpJ)^mhsmgZ8BGaA+?IkAU{3@K|Uca{l4f;Bip$LI55Q zB`*Zv2~hGv0G&z&oHLDZCRpio&~~qba-_I)=h~pyXWxycY^@Iz}Gw z0oWn$8sLLa@~#0s1SRhp;Gu(D@X;16@GjICLR}??D$4 zLE-1nl@xvfT}9#7P;$-zegj=iVe)G@ zM$S3FAE0X~{1Lj2!k?h)Df|<^&f{Fz&C~7rTAvhdlcUsnn>|2p!X@hCG-Ksw}L*T_|{Nz6#>2t zlw3uCZwq}w@$H~b|0jm{_OS7c;yXZ}Q+!A03ySXqeM#}1p|2>u3-mR`cZI&8_-@d* z6yF{Cj^cYj-&6c=&=2JNe}Gr_JM<&P_k@0;_+HS@6yF>Ah2r}`zfydE=r@WV0{u?$ zL!mz?ei-y8#Se%6qWIWIgy87@4?h9QQ2azFOYxJS9L3LuVv1h|qmT8Wg`98l?CG&=AERgqjq8 z2x?LMVW>^pWc$nzK>=X|Z zotT5-KR|O*JPdSVE{cbNPRvd5Fwlv4s2KkRHu6$DOmt#Ciie3#%un$!(TN2p9ws`m zAjQK(Cl;dkU(mu7%ppoFLJ4G`i&6p$Ek+4sh>Mf+UmPKUOl}EEAk$is63Dccq68UQ zni9xiE<*|AFqfqSa+u3e0y)g(DS;fd3Y0*OT18680p~xl5+#tsR+$pWVXHz3;2AdnwamlDVq)}sXSh4m?c zd~yRyXasFY35}s-9|%pLWFH7kp=2Kj&7fo-2+g5n9|$eT^-n$kgqBeF03o!7l6@ew zfwrQAwovjM5ZXb>b3o_>CC>q&Gn70Bgf7tbl+Xj(ff9a)cKn|h5_-Z0`36Af4JF?I z2z{XB8vx-CXje)Y0_{c#BcR9dVekeKIKsW#; zM;izS$@NbTHxLd%$>9dVVJJD=KsW*=hZ_h-q2zD_;W(5WZXle1lEV#z(@=7_fp8W| z4mS|aLFZG#c_=yZ3&>%=02}1c1K}cc5hYxLl0y%K%TRLYfp7)7loGB&mr=q$(B+iy zFLVVZT!XHpgzHdp+JJBa8v6?#fN&E^&LI$PLDx{iZRlD`_zz0XEfDTP*HgkhC^^qS zNQ9E}421hoa-M0VT5lgqKh< z3qW`cC9?p8FVH=d@C{1l0ti2#WG;a46S|+Ae;5uC!44S=ATrQ{l*mC3Q6h$txd0*$ zC368p0ZQfqi13XPk5OU(`Zpy?P%o)H3Wx=vWTt>v3`%Ay1`lFM*dRj%#8OZ)R6r~XB|`Lj}YN(Cd^~5lV&%h*hCvrhr%-N@fa(HK1gsfLN1U|756uSPOcG5^F=_ zlvp21h7E`fpk&y9*bqvF4Tz1P_bIV4^Z_L{g+8RjX3$5J*c|%!e_}{%0UKlpf!GpC zh7gFYpkxSv*cwWP5QuG{WC(%S7W$GB+d*GZVtXhVNFa8AzM;g9(6^M>3Hpwl|99|7 zouTh3u?zGAC3gKkmhL*hjj~qRG;i69X~D9W(4u88r6tQ=M$4AHoK`G*1+7~4 z8d|gLwY>l1>zp8a6OAl8hBhqwC~aEyF&bO;ahh263EHyklQgyL)3j|_egKW1#j?-Q zS%v-2KF`5ymVJTFZrK;<9F~2F&S}|~>0Fk5h0bl+H|RW;eUr{>*>~uCmVK8Fv+P*f znV%12Kcx#;_A|PmWk072S@v7Huw}oa!!7$gUBt3K&_ylF0F7VFvJ>dymYqbGuuis* zgC#9HnJ#77>2zt!&Y;U!_9r^RvcJ$}Ez8u8U(T{j`uOE7(15OBfo5z)3p8UZY5!N^ z-;1vyTiF5)*(w%j$X2yLL$;a)8nV?b(2%WRfre~N3p8YFS)dtP+XBtlIuVUjV#cRZES&tY!eGKSDRX(x!TME&DG}U@DXURwy;2h zwWS3btgS52U~O%I25TD&G+5hOpuyVC0?pO-7A!z_ut0OQqXo=WXFNZnCD35)Y=H)4 z7Yj5SyIP>V+sy*DZT#*QX!-ZBK+C+R1zOp?EYPa$ZGl#5pP4$DK+ChQ1zM8*EYOPV zZ-G|i01NcA2U?(~I>-V&)kq8UPzPI}hZ<#p9_kPa^dN_7{}1Kqb;pNUpgTU?0^QgV z7U;%~v_Lm-lm)thqb<-49AklQ;8+WE1IJmw^~N7>K}Q!o!2(_ML<@9L-5dg4^dt*( zp_47pg-)?R7dq7fUFb9mbfMEN&~?tRK-W1_`_D(9FF(ry{f5yN@N47Gwm`prjs^Pl zb1l%XpJ#!7{d^153oKACv_QScf{XS3U(5-DOX&X#ZsPb-3vQ*CS#TS@+=AQb6&Bo0 zue9JEdX)wD(yJ}Fk6vTJ{q$PV@;<=9brw8GueabKdV>WI(;F>#gz5$nJW9t{@EE<> zg2(AC7Cb@q5D1>6dI$th(T<)1!PE2(3!b5OTJS8r%Yx^qo)p3JR8NZF1*#`S@FLZd zB6x}FNfEqE^`Hn|p?Xksf>$}vlOlMH>PZp2PW7Y+-lTd`1aDD2DT24Do)p15R8NZF z1F9!Q@DbIMBKRNGgCh8tKBN8TX@gHV(UT(hlM2C-D$bps#xjfY}Ay=SUCgh4#tAt#MjPN(9bMa zrJq}_Mzxa2Mf6L{HT3?E;{>^wer359{n~PE`i;<>sJ2SZ*FV z-g5I(Z5wh6P;J{p_CL2E2a_zf5bd+v!gR9bhSPq_EkdVQZc#eba*NRc%Pmf)S#Alc ztwnB0I^A+h(HYwR8T`W1^he7rL$$Zaji5hUZaMmk<<_OYT5di1o8>m5zguo&`iJE< zp?_L#Q~HfGI)~PjSjWk z9<8^A)j@6mKq)P52%AiW_6w07V?o`U4O73)8wA>k# zL6zKDv~0Q2ltGo;+3f!WCRK9hP$pG!=hC|6&ZA7NiAEP0L+O8DPm>LKDkf zNf~CzT}4yNT~8V832mM*cLN7Zw&ZT4Ot$21qO)0U44vI_x6nB(cRQWaa(B?VEO#fJ z+j4i&c`SD~o!4^r(9V2(Aa_3K@j>9UraM3=K% zA6?#Z)94D?{}p)pK~7e*+zh&s<$k0qTka>iisgQ$t6J_Cx|-#FrK?-+H@b%9ey3|% z?hm?_<^E#-C#-EbUc(9NSdQ0l!n&5@HJq@X<^H4V8!tj;!Uh%&r5jq~5x3sWIx3aKCx3(~%+gRA3+gjM9+gTXX z?JZ37{_nsE!WP}pLcJzCS=gpKTgV$SVHXQ|LniEMq23bx&_Osm-Q7aHC3{#nH{H|1 zdFWmn$P;bNj7A{B+uy7%IpoMxH4zf_gI?_T7>%kUk zSVw99M{(Dh)s}qru8rj8Po}fTc}|@!a@z}krrxLkFszTdbEWa)?+NxfF5gM zM^ky6g__FaE!0q+V4;TcL<={dx@m+OvXd;-kezIyhU^pzHDsq+s2MxWLe1Fe+J8Pm z4cQqMYRJyCP(yZ>g&MNa7HY`OwopTMj)fYsb1l@6oo6BQHQ{^^Aq%zq z4_m0^f5bv9|DzUa`5&`T%m28ATJk3>)G9w|p;q=O?f+9ey_WN73$=vLSg0j@)Cy3Q*WKBHxS)k0n9H4Am2*DchA-mp*?decH( z=q(F%p|>s6h2F7H7kbx1UFSUub)EOo;Um+U;Ceh`fDFssK54! zh5Bn_E&QB*YT+03GYh|@`Za{(^#1D?5q?GWiwM7={Gx>4QvDjj@95VS_EY^ngwv^h zAHo?_zYpP$^m_|`p+8voD;+-*KmUK@V1k9e(}@=TK_^-GC+)NFFFM)6zo~8n;XibW zh5yp2mLEa~EZ;?^S$-%T?C2wt$#+vd1@ZyaQy`zCdJ5!2`jh4J^k>T#sGbh_BGuC& zU!r58cs6ei=@BEI)$wT7FrYwfu6Fm681Nl$DYE3Y3+R{7RIS zk^IV(m67}^l$DYEs+5(Hy#D#`i7bueSEnqEStZG@LnF(tPaBrs zkTxy98;vc$J7rZRzc+0O`=8&3gVgf-(zfOIqqA6ke>$t>51_MI{y;jr{2g>b%il#8vi#k2 zVawk`hg<$$x`=i1_i?bOt51YQT_x zm9A#_*XZh&f1PT+kbk4Y!J3wTi>_t)cj?-ee~+$X`Sby5NO!XQbh@+UXV6_N|0CVi@;_0%g2?|u zceniSbdQ-jnL;-Qds?A~?q!8uy0;awbRR3!=)P8{Q@y4rM09^EH0S|VXwn0%kkEsy z(4r%?|0DUsdjAi$LYt1Vg5Li_tS~D*)C#lF!>q6fJ=_Y5(<7{~1U=FUOVXpPuoOMo z3Z11nIK~Q^?qjXMWKTTK3Yy&Gt)R(0!3rAO6Rofo)x)5m!9B?e8r+kupjkS_3R}@r zwf}q+G$^N8L4$I-6*L=XSV6OKrWG_BXIVkBG1>~6jkB$w**M1vnvHXSV3ETrD(ad zzgJm7`+Kz&w7=I_L3?_w6||?)ZJFlO5I}xt<=5NDQKzgvx1iD zek*9H9Om{$$se+UR^(wT=*b_kf}ZwKE9hw-vw|M#aVzMdp3we3!PDzWp0tAQ z{wXWy?w_`T?*17o=!T!Qf^PCTE9l0aw}Niq1uN*HFIqv@dC3Y-X%Akug1-C}E9eit zY6bnl*Q}sF__`JJ=iab_e(#%B(C>ZA3i@krTS0&A9V_Usy^9VXh4(pl&k7$% z@C((&QTUbW;wbz^b#WAar<1Ml2kp1QpLB{9htR23?4kqOf9|+Aluol^H`Se^*h_Wi zC|0TN9K{;djiXqnx^WaEsvAeKL3QIOwyAC$#aXCs9L3J89O%wboQ>+vQJjP7&QY9` z{$<6v=-*bHm;PhL`RKn^97g}M;{2Hm3pG=50m=$UaY4!obo5bNh!a*oiVIU#K#Id@ zj};fAtcDa9r&%j5LjxA}1)WPD@tYke02u5v^Ep zW6Cy2aaYPVNO3pHHb`+#$~H)GFUmGZac|nR;yyGM_P@9<2ZGoD)YA5Yr zrMc;jR+^XYWF-yd&Q{X!?P4VcZ_=(-8cuhcsgo%!!oluV(va<8CC$U0R?`0NWhL$D z-d56{?qelwi(X}vv@QEtN!zl&m9!NHSV>!Pp!WYj{($!4AS-D(M_NhCd9anVoTIFy z=Utd;a7$64tt zdc2iJ(-W+8Ha*cw=TSW*N*B|UtaKSYS^LjN>2gj^vCA{`v1o=W>G5^Hg_>(hKx_E4@fBu+mHPLMy#Y zFS61*R8NP}d-M`3eLyd@(uY(}jnYR{4}H0ol?o_4g9C{3VatTc&gSyAevT2_=M(_5|7Pj9o* zbb7m$X3#sV^dr5~N?K#Rh`iPbD^ik8l41LmL+W*J+O=V6Vw{nF(VdW})(#kdZl$9IwX)8DBGggkN z1`OqdK4;~WK5ykYspbpic{&`tXytjS1`Or-s0Iw>Ve}O%FF{|m@{;s5D=$r7xAHRd z4J(hJZ(4a-`j(ZKqi>_bM|pV;G{GpZK;O0Uid55#@=El5E3Zj4*(k3?HQ6YyO+T{o zI`n^5UYCAs<@M+%R$gE4|5#2?-hh5;<&EfPR^FIu;!)m&YT{AelxpHp-i&JEQQm@T z;!)m`er@G#sHPv~ZRxi&buu#t=Q~a?b8x=r7&8aw2aZwRm5#Ua?sS5c_n;H4d=Q;v z<&m_{%A@FHD<47i=Ae8eoud7p!pYHes+EtS16DqcPP6jybkNEt(CJpzkJ=el*7yBr z<&)@7Rz8{PwL|$7`iqr2r*iPCl~1F;S^0GOyOqzNe^~h}`lppg)4!~IHvQYm=g@zw zd@lXh%2(6>tbAQ2!vbYG`Y2z|$q*~wNV}|j2OVnVJ88F-@1i|czMJ-1`Cgi}@_jV0 z^8GYtVrjfrF-%U!<{>U!sYXU#2Z9zd}{fn@ zcIMy%<@e~ER{ns_Wo6#$zPYW;yWKaBm3g=O=C$%?bUrIjq{FPt+t4?^mHX%dR-Q~3 zv`%>n2Mbx5H=u7}EAs~Q4Yx9HK;I%(X6pMEwKCJ)x0sa~;=aYL%;5GdVPyulZ%HdN zxP41mnZfN_TKm5=PtWA`En{USw{L`%|E0@XMMJrqRWuvRTSc?6f>ksRD_TYKu##0Y z4=YWHnNK5VPmUk9yYOx=3!H-XdX7RisoT+t7sm!u!`nk zORF#soxZI&K}7?xwN*3^+gL^Ou&q@z58GKq^RT^DG!HviMf0$uRWuJfSw-`(vsE+? zyUf(dR5TE~T7_BY+s!JPhuy8BdDz1$nuk5DqIuZMDw>DAt)h9@$10kKeXXK-*v~4O zhyAtx`}6dx&;zWZc{tE2numj|q8S)z747@MR?)tXvWoWo5UXh454DQ+`Y@|#OAoh7 zNBek$RkZv^T1Cr$lvT9+M_WZpc#KuFgvVM%t8<)Hv^vLIMXPgyRkS)MT16|u!*}#i zVFmh5vWlMeWUJ_DPqB)g_Ef9rX-~6?p73<5=n2oTiXQ4rtLQ<_vWjkav{jDN`+qhk zsOXN*v5M~aT&r+{edk$47d_u9y3hqy(S+*mwN)Ob*I4BddaYHSq}N&HX?nd?o}rx^_(0`ZdZSgI zr#D&U1v@)EtpDlgMpt?~-J%_^_b+pY2@y~8SR(L1eEd7Fc~tnxm++bSQ> zd#v&yz1J!qQLPdx|D#$ZR6eF!B~(74S|wD*QmqmypHi(7Dxc9uwErws<#SFRwaNf} z%qr99<5n4@PgrF-)v}`UGkwY`zfvtRD!)-JF)F`PtuQKo(C4i3C;Q*0r@+{$1tU7{f+fZGWYTHm4b|1D_6^lFsJ0E&HR%^tU5kEc)wSt3tFBMKvg(HP>zO*4>P8%VW7W;*w^rSp zerMIK==WCLmi}PX?dW)`Zciszbq6}psyot2R^5g6S#@_hxuXxBU=P}F)jjDHtL{ao zT6J%#;Y4*GI?bvhsfH8PgQZP>Ts+UpT5UE~Ec|)XnJ@5bI94DyWNJFcRp}Zne zy_xccNc9$4wCb(2WYybf*{ZkGidFBRRjb}fYgWCB)`k7A-pxT|)q7~es`t{SRqv;< zRUe>2Ry^ z&QD&%s^jRQR^>gPyqHzLri)wk8@hy5zokoR|Ci+HzvpBrt4^RxTXm2wW7S{j2&*#1 zlb5yXzjQgPX(*Ssnuc-(t7#}#v|2z{vRaP)pS-fwG<>UAjbWR-s@2MLHLGc`R=1jl zX$`As2G+Ei_I)j@X|LC|8rwX19jj@p*F}eqnwES$t7*yCx0;rG1FLDtH?*3Td?TxA z$v3u|mV6VdX~{RW8Y?_`GplKpH@6xq+nKxtC#Y#@x3rp;b}OrCIk&c&mUA1c>1nsM znx1w$tLe+Px0y1&(qr}_faPNfH0?KFCj)lR1)t#%eY*lMHcD65@K z53$-gR5yZJ=UfhSC#YRObt9-)!w68delCoS6S^NdbQR5 zN3|uWeN3;l+NZRm{Xy+>dcD=Ypf_0UORBv>Z5-8Bq4opSR-rbYYO7G2M733@^-*mV zYW-AOh1wLVtwN_Zz=8G(wP{p)h1yT_F01`a@3z`6^d77IO7FGWZ&Z7W+VAv!tNlkG zu=)_HZAQI|KBWC;uj^S(9=3WwAF+ClK5F%lYX4Es)5on|pqd5Li&V3KdWk+|^)h|h z>J|Eo)vN6PWDN}JHL8I@y-uIEdPFras5hwQ1@$J?yr7=am#sc0eZ}f?(O0cLH+{|O z^U&AP;iEo32X9z?0s5xZhts#Lz6jMoqP`e?$LfpIcdfoOeb4I4(D$uAf_`B2<){V~ z_0{zLYf@2PooZ51UxR8=QD2XKV)gauSgUVDHNmKFPCv8ycJy-hHXRM2l8`nU<{n3N2fGEUj3bq3y2<`(I~r z`)gKba{KF6XKMQ+t24Fz4XZP`{Y|Sgl>M>QC(*>}%u;{L>etfL>dZ-h+v?0le`gjx zP-i&$XSF)>&_A2inTP(_t=YT3q|T zcqS8Rzm~8_`?91(+Lom((zYyZ5s%Qnj78d(5f*7%mbFORvYbWQmgOzdwya=L%KrDS zXp#11C5yBzD_f*(S;ZnP`KlIa$yc*TOTM~ATJkk4(vq)fkyd#vi?p(9qr*p}rCrA& zR-=Dii?kx^S)^51-y*HT1{P@*Hnd1jyOBkD+KnyJ({5ss9(GfUc)(8oW}G0>6K-yi zo^T6`^i*3~q^H`-B0bgC7U`+Bu}C+(twp+l?JUv_Y;TcnV27DHnMgOVqeZ%boh;Ht zceY3u-NhnZbXSXX(cLW4MR&JI7u~}mU35>2bkV&m(lz(i{_oAx>!SNuq>JurkuJKQ zMY`zz7U`k~SfmRbXpt^-kVSlN|457U-3MEw?;B-NM_+h|Mf$=+Ez%brW|6+|aEtVX zM_8mUJklb4;ZYXp`;N9q-*=2f`o3c=()S&w{pTan7anhszVHN#^o1u{q%YKJA<~ze zWD$S9|745w9j92N?>N<>)9C+;PUralMWglppTS>1bT&QHqI2k37M({&TXa6v??ZF} z)$c=e5k1$Ui>dw$qD!d$45CY^{tTka>4l=Fxq<_I0ivs^z5vlRR9}GTTBz7eVw4)kP4!M0F8FFH>Cv(JNFJ zLG&uU$)Y#t7>nMdH(T@;y~U!p>8;j@-r?Xji{7QTTl5~i!=m@;ofdsS@3QDadbdR% z(R(cVAJtPM`jqOa5q(DQx9D@K|NO^CCK|`dgBE>7AF}9M`mjY4=_59C=pN-5(Ionq zMSWDuifA&`sv_#APg*pEK4sBV_P_sWiw3Bc9?>-VtVM(LIg6&#=PjB+U$E#$`l3ZY zQEeNdpQ*MD(JxfnhUizSZR_w6{l>v-7X40Nx9AV5{Y3O9)qW!Si)ud+{Y|x>i2kA4 zPDKAvZ6_LCRNIM0H`R8ck<wdQM$lN0erJt^ z==auGnEqgm;dHz;7NPprDD+R@4=+kJU}!8sHB@LUMKx4tEK4U_V|m(djTPw>Ypg`4 zT4QB8V2xGiG;6F*2d&Xrg98l}8f#Jw6&h>NAFZ)A)li|a0o72Uu_4t^p|KIwP@%Cg z)l8wW3Dr!Yu_@I|p|KfdraJm)Y|e>>3XLtOh6;@>=|9%kivDYjt?7T(*d~+V;b&@W zONUrvN6O$yV<$S)8aq>lP#XL4{!i)Q1daV@uQm3kS!*0X18W>enObQaL>XFXjHG#M z983$=7)6WLIE0q0aVRYd``gNii{r&VhlL2K4HlGd$p6pgHLG;LVp1llzHb462P zYn((AYn(z`);N`RQa;c)jkc|EI-SKDXV6)#aTcA;8l&m#);OEaVU2SrZ;Ld}rE^*1 zJUX{EE}`>Sr*Rnv^IGF_I-fPJpu?iv!aBJL3 z7qP}|bW!d9qTK22oGfOIJLuxpxRWknjl1cR*0_f*WsPU)($;v6E@O>X=m=}PMwhk5 zn{+vAyrq3w-Wng$6|C_;x}r5crYl+F6S}fB#?n=+@fls!8sE^>tnn>f-5TH1HLURi zT@(8Ezcj{ku$DC@(6y~Gk*;HnKDw?oCe!t-(NEX6#uU1NHKx)Htua71vc@#Ju{8#H z|EFxi2^!Psrq-B2H?zi1baQL`Mz^rW?{rIR@B?MaR@UI>$&{_F!OxH>+gRftx~(<- zrQ6Na$ux&>u)Q_A=nmHGrurd*W)I!Tn!R*qYwBmjF4hd`uGY-c-K<%lyIZqJ_pqjZ z7VN40-;)cJ>0Z{<&w{X^gwII^dM^{bfh&q zEe;O0CO-$JjIySF793*DS?HnGoRuDC&DrST)|{OlVNLxkIMSMP(xa?77d_gVbJJtA z|9mv(;pA9r&P$K8=6v*cYtBzku;v2vL~AZcwTfsiL{GBj!t`Wo4yUJBa}j!~HD|v6 zr*VSjV)S%tE>6#|<`VQwYc5I8vgT5Bv^AHeXIpa_dX6;<_( zwdM}=Hf!!kZ@1=7^bTw8Menrce)KMDYSQnvrY8L!YiiK%wWfykKK=Z^kEhqP-fvA! z>jT!*v_5FfW9UQH)UZBmO%3QH*3?iwYE8}6W7gDMJ#J0S)f3jdl>MLbq&2lvPgzq- z^|Up$BF|V;PyVbm^(4<(Q#ba!HFaYzSW`FfqBV5`FIiJJ@G?4lG<5^7SW_2$)tb7{ zYu40-Ubm($^oBKcp*O9m3%zAcUFdCV>O${WQ`dRdnp~$d%+rhfAW*3@tQ z(3<+aA6Zks_kY&ZuYYV!{rV@?)US`VrhffXYkoyPn~9(Q-*E7`HNT@@Sn~(^r8URX zan|gk`T{g3)32@BPxW1BPN&~ma|YG-qWL5J-kLwrA3FNr7k;L?2%5jr3D*3JPPFFV zbdojyp}G;o0o9El&QaY6;wqhDag9#3xK0Nw?nE3+v$#PAEl#MO0&$D#DG;YrPl32i z^%RI_p?V0!v(aBHo|Ec<5YI(_vv_X$yY`=tcpgsvuy|hjr^WM8tpMT$s8#^+f>bMj zcp>_)#S7E_EFPZ8@W3<0i_sw#FHXBGUW)gB>QGJ)FHO5GUY7P)ygco-cmgLov>@E|^zE@AN~x}?R2 z(4{OslrC-YVRRXb52qt6K7#$9s$oNXBwfzpqp1cD@iA0`hxk~k!9#pJ)!-pMfokv& zpFvl#_)Mz7Lwpw1;2|DOHFq68;;2}PTYVZ)BOEq|i&!cNwd_L9SA-;gFYw?9t zLx}idsv$&t3DpoHzLaig@n!7))Qvbnd^z3N;wz~J67jusQ;YAT8c4+VQw=2I2dD-T z@q=_riyxvIPQ(vW4JYD9sD^VJ_CJ1ZmhL=UuB|Nnoc9%S*ybfm?f(1R@=OEu+)Kc$*-#GlbaE&iMy zX7LyFaErgBM_4?L9%=Dc^eF8=AMw|m9BuJ8^cai3rN>(Q9X-zC@9FUtkEbVCJb|8Q z@kFZCMLda~WN{xo+2YB1|4-oraX&rP;wkhri>K1lEgqm}SUioMY4IRE%i`%&uNUGU z>Dd3Dl2z$bmaImfwq#AJA2dkTqR(2gK7G!T4e0ZhY)D_QWFz__I(#G>bMTTS zn^FDHL9#i0#gZ-PtCnm@U$bN@`nn}s(>E;HhQ4XZ_Vg`FcA#%tvZLPrcQ`?^6Mff` zo#}g)>_XqSWH9p?_O)Fa5`o$LPP7=nebN65cSLai-)+-v5CioFI9Mc3HwJHZar@UYUVzOJ1Zs zmb^rJEqR${E#ZwB2rS`^8OT|}8!!-B!W%G<7xp({`UeV@yi1FgFs%b6OTM9HOBkSm ziY4FCswK?NK+O{7VW4ga^Dq!u!aNK#EMXo7I!!*1Fc1T=CCtM>VhL+C(6WSi7)UK) z9tPT$Fb@N>Si(FE%xVeqFff}X%)`L!mM{+kb66)~uLtI|g#8_u%M$ipyPo8P$i%)kQHVw(pRw3fDXA?^P{nM_N2y0Ephr^BtKy<5au z+Pg)qrM+9sTH2z;t)(qm!dlwWC9S0`UCLV8x}~k9fBtJ=8Ea|pMp#RGw5+wXo$6fweF@@j)_Q@Cw$@AZY|&l3%)vRR zD|)T9`l$9Ct;tk-j#fXt!8)xe9B9wcno4i7)&L!2t!Y%VfYuCpi?x2Fw_58bsu@A+ zXL`G}exY|*>sNZGwSJ>_Y5y6Z*6*C$ZLL4(J=Xe*-fOMD>3!Dvhibsk`j=|J(E5*R zz>sF>LzWJqnlGeXRP%*&DEmL~sHNRh!-lknK5l6*)!-q`(@X-uC-hmSPjK+}n|MPIa(M;OqgB5hL*D$-e~1{LY7RD+6iHmX5IIy=>% zBAtW2VdEQ9=>k+ki*!Nyp{0w_ zk1SoB{?F1S>Blp5GU-wrXtI$mL&sV=f_`f0a`ZDxSEZj@x;p*B(lzLpmaa+1S-KYe z%F?yz*OsnB_3!^1_=XFwOTV>r6Z)N{ThZ?=-G=^P>9%ydrQ6X7mTpfcTDk+BWa*By z&(fXfWJ^0cbI@<;u5^l}yV0qZ?oJ0R-Gl0NM7kFpv~+Je-O>Z-3`-BBdQFiYM1Qh$ zB>h?Y&qsPNC%;%aivDWpA@ny(52L?ZdLsS9^iNO^{AuY)^e;HN z^a>6_ORuDPORuH{ORuFxORu9PORuM8OK+eROK+rAOK+z&OYfw0OYfrk`#;l=-c1{p z-b0&~-b-UkAE1e)57CyT57X4rM`+v9$LK7UK2B$~^a(nfb6>(3OW&pQS^7R5X6XlXeoJ}Xr!Bz#^N~r%a0*|CL;0a2>9=$VOTVK_TKav5gQYC}fi7(+@AkB1EaiQk zHo{Wg=V{AY%KJQRIZG$g0CRcmX~SF^ST zYjtaDu-34)25U`gYp~X`w&rJTYinlKv9@MrU28KlooVZFg0_ZceQRrIHn8@rbVFjpNlwl2DZ{ZLGZ$ z-PYPW)9tLi3*Fw@yV4!3y&K)p+PhQzd9?SWJ8S>>lJ?$IUx4;LR9}GhzEod;_I^}f zfc61&4{IMt^<`)uMEA1xNUHBe`(Uc?MSB$0_oA(T|K~J)G1`aH{jGf%)kV-goa!QI zA3=2yw2!2^2--(cT?Fl;sV;){F;v$;`*^BrpnU@68XbMKPvJxtLHksyi=cfP)kV-g zo$4ZJpG9>Mv`1521nqO^vDQA99%t?IsVJtp0yvKdOEZpq;WS*oNx+I+yT)q549t0EX)Bv;yef zkZJ|cy%E(4pnGGg6+rhUR4ahqZKzfNz1vc)0D8BhS^@MPM6a_>???`GLG&I>wHoL> zk=|&%tkSfbtXJDI#8%#%&)#N%w&->%tVi$A{@=mByIns6?zF-N z^e!vt>F>7kPV^ot?@aHtvR2_ftFA!rx7rd^+k-}vYJ1R#=|k4gf4@8JVQXr6wQp!@ zc^|c=miIA>=ckWbTdVMdwY3UQT3f5|l(n@APg`57@Qk&!3eTd$M@Dz{oaw*+nfAQt zzw?>)f_3Syy=WN?&`Z`G(3h?IQ2L5x-=eQt_HFu_Wv9~DEjyt1{|!!%okriZ>>$+_ zAjndE0Rk=WI~MFs-?iLcRI`9WnZ9p@3jM$enx_w~@E84PCjR~Ze{=9ZEBr%0w!**k z6DuA=$6E1N`l%H)jGtLiKO;W3%7OF?tL{p_wCZkjoYhvNUuplp;{Re@7y8<2y3jY) z+?sxC$-4AAOV*>`Te3F&!IE|8c+2S3o?sbX>uD3MOItU|x;omsJ{!6}ooqu7p#9dZ zEtq0iO}+LI+50GaDB)aGTZm9ktSv;SC!TKM?sSHQd(a=X|9pgZbMljgy1>sC>H@!5 zct8EsLSCt9zgd1G`n%;frhiy|6Z)s+H>KKh#DmVFcBlqp&Rf&kCz% zGCask1@>%kh!yzt!7eN6*N0j$N4u>U(jF`3X|EN_lvN$fvi~LaaxkzGcRHvqKxIFw zFF-A!`U2GS`wLcEpK^7nZ9q#_+mPxasIfwW6|1pAgH@|BxPzSkzuIrCRdgmTJitu~bXGsHIx+#Vpl|FK(&+{ZE5S*pU0_k~ZW4x|9uhkS=XQ z_&G7Sj13t}8Cn_gDP7ivXkWD@81gyQmSD&iR9k`}UsA1VhmRrSI9SPsd_`BbAzxE1 z8HRjA^(J7*w^VNehEAlb+t5jL4IA1=wZ!P&g=&S-y*FLky7%GzA6$nMbni>owQfzm zrX1b-Q%yO#51^WIbYDg_<>+xNo%Nhcx3``P=?>O&5#3Q=wqqvKqt|IC z>$!yPY(1A!EgyRH#JgILo_IIwWeW#)w_a`C9@e`G-P5we>0TDxLA4DCI(KrwK1gsE z)ixl1Fx567KZ@>e`9r9-0YyFWfmYNLA7sVz=twJ`PxW0W>gn}eDC+6?u8uy6dg4Q^ zs3$(miqq-gR-8ePu;P#ONGtwCkFv5>0HvNy_!Bb5CBY5ysOLX(6S)!FX-4di8rHB4*=%AJ!`n#cn7ud|du5CaoOZE5v z2T`3!HH4^6qS_Kv`>3`A)yY&th-yF8a-%whYPnIJO7+^IIzX?o>NI+_)#jqtSf@5O z2U=Ft=AqYFZC-l4)#jr&SZx@+(P|p3o2ZTB^LUmIJR;79f1glX!1cKG+D;69; zU$wBV_y09ckk?YbZh0;B8L zH81G>k$&0H2PZ$#an}1Y{mOcOpfeq{1J4z z_MeaP7*1wbMa!e5L4}nW)XJcu)zQkJqSevLK>x_apjHNzf2f`m6>Wi@6jg139u!q= zfgTjqF1`PMaf0el`nOftv_X9ts&mkPtvV;w7o$2?Cd0kYR9%kp#nQZnc3HYU<$ERF zfbzYPZb}*W={-wvvNZ=xyrew!bbh!<%2Q7dEaj=E=Pdo@iibuGS+vvOlo^$-6)6iL zT`SRwb*)T!dg)q))~st)TDPv%Xk=ZhQ++SG)}T%6T9d}swH8g-Iem1k%Sp?+)}yI) z>A~C9r3atIx;CV|l| zqLyXOrY~l}R&;R-w9`viunk?(a{JSzEO!81+QNJ2G8R5aM_Bj{?JUa&il>$!=YIYE5H8nekcBdO@{~4ghcARK< z(Ab{pf@thObwM=lrFv>K?xUMq<9@n@H6EZ_TJv+NZA0@5s%=B_OR8-{a~%6WeOpV~ zR5yY|ldc;};uSau-W=le=1~o7~OP%cxeM!$tPzLuV8HT~JMtfoIZ+Uk$cv#tI(J;&;=(sM29=c-K%482F#|LzeS+-u#Mhx@Gi zB&r`S=+@BahYPxym+22$w^r&Q>()v=Y~5NZ?LWG;Qjc1Tev8l zV4+rGqUBelldKTXJ}VUHWGfs_`>j}^Q>-{z@BdUzP&}6oSn&d?fkg3QI%p;BnQj*4 zo2X_4Kx_YsLoMYkN!Fn|DPKk=Rm)Rs@{O#t*STR53A}8(8{2y zH{dU;Y8n5w+UoQltF1x*wc5J$KdbGN$#B0j)wtmqL#|l=_#ta_&g3v_nG$6oB~zw3 z%dl`WLeqZ~nUS|ljk4I1iD}U?3DtK&{{_zseHSvU;S7BjGK~EUzN@2;%p#oV%aB== z)-AIbjVv>QHY~F&ZCYkIs*5AD3f0AtS(WPI$gDF^Qvp${OG8@o2EW^~yU`!=*9-YfFS5tmcNah;KPYTK0Ncl-2nOi77DQ3*q;p8k1 zhFNAbo!>HNQ!N=XZ&587GPhEGQb^`@`hP6l3!GI`8-Ve5?>W~w_xr5~A%vttiV#8w zNr*xeLI_bgMHE5^Ata?z2qA}m9eJ!^DSAuP!?I;s$s;~E`R$m6uW4;PSGw5^dR=)Ojtr0tA6McW&hLpvCm zOLcl6&rzKo$ULgk17W$Y>1gB?dZ4Mxt88>KvVe9rp3d(<#?$%LxrL`g-oIj|xBe;VOmCipr9Uh&3cse>q8Ba&&XyfVV=vsiM zN88qmPV?<{(%@rKgVjCVCX9d$0e5p47^-nH}$@*I?#A}^yeGzPI`gy?x7bN?_N5{ zczQ-$WV{*lV&my*zr=X6=wPQV-ZN}mYP@IZWyaH0eYx>;RSz-V^YjYiy+E%t-aLAh z@m{1l-0)taI^6K|EV#yauh3yS#KZW+t8}>WUZXlC@Ls1nCGg&$dV6?p(viknNJkm( zeX3IhZ!y)Wg7*Q{se-4^qf-U1{s|j58t+p&#(1AmokMs_sLmlgebSqa_aoK$g!dC= zK9#qE>U6@-Q=Lxu1$w*jOO&at>xEz8${oh9Qk`e`&8e<(_}kG5#^0XaZTuant_AqJ zP+be~ccl}J--_O6{GRlFmGnvD>sors_&SyKIb6WkBYWETI$zHiU+3#tV5WddKJI2>(S!8^jmUoS>)AFA2bz0sxzD~j-{nd>z51#@7-2%J_PKU!%^2uSfWe$?Kw6X7c(3-DgUpuZSj=TUEnhP|k+U^KL+znO*(^mo&69@WK( zh5>YyX&6ZVG!5s|zf8kj^lzsw4LarF!&y1_K;LpPd+ zwseze*q1h$hJ)y4)6kc0(fQxPUY|w(F%1mMnyscm7uUb0K^ND5ra>3qHq*ecvY$@F zyVNrcEXuXMX<$*VZ7_lUG++Yy>(kZ-O#}0~He~WI(Xa`;N+Tw~X|*K1 zCcqS|EtbxN^jdn5t9mdWkphv9piNFl1 z^NGN0+QkH(pof^ilT@b`fv4zUCNPKU`#&xuphvHBj=(eY2ordP9%%w^(QYR2Ha*G& z-l4iK5YSUj*98I}(e5Vj2|d;XKBvc-z?aPb+T%^&YudvEmeCVT;0Jo539O|jnZP=# ziwXf=VAkrQLSQr1HHE+ydWs4BLwlRRR(dMxTm=4QLl+)`F?zZw=t$_oL*ZGf3lD|4 z^h{HDj`lUdB<*K{DSDO(rs>%xn4#yGU{TNi{#-#&&((8HP|wx#Ot3-+m|&F-G{G7@ z-vpb{3rw&%z0d?((m^J;GrhKL5dYjHa7r|G!a=QuYsWsjN-=TMyh92}z6I?{^ zGQs!h1QYy#-fe;((tAwsBYLk1enxc%5nQ6@|9xCR@N;^<34Tc*Fu`x=BokalA2h*l z=|d*CoIY%VE9hhs{Fy#tLNPkUgyQs3#hir_Y)mzwB-J59NKc{1OejsKn^2g}Frhr1 zX+j11xCs^MEE6ix*(Ow`Pnb}J)}Q18Aw9L8GNBrsV?xd7(Kr1p9o5B&(Dqbc z6A)@apEIEy=<_DDBYnYycB1o4J*4O8izc))eaVD&r@G=1+JowXL8vv=^@31)s%HX1 z9q4N&bQoP=LV9+*ZbEu?ykSCmcIaQ9<3d8maAlzh9ZTObq2s77NQ923x*!qK#i)xA zp_AylCUi1=&xCr>_f2R3U2H-FsV-E6&S(DDerQ5NsjgInuBIQGkiIT_VnV~{rzSL< z>S{)41YKf6*U`^SXbkREx1E>L})K}Z+nk0!*TtgrovD+uXauP`AU{+~@qhyNE7(qaD9 z6d$H5O>r{)&6Ebv-%V&6{lkQPy2_Mv3jTEX=kv~I<1bUXfc|a5WxCpg^;B77!tLo= z6V^qy&V=`)>rHrny1|4y(v2p3Al+obooJKJe-l3i>P$DA@WFJ833s9YnDAkAs|k0d z|C+Ea(*I1jC)J}y_+33`g!@oNLzU_mve96|BWb{dN70}OUq?eGd_4`D z@Msz_;Tvewlm^j86KY3eCNzY`O?V!_g=bRcS%n{`RTE~#uB(|aD|THo6Ml*| zH{m&SI}_&Ba^3bO{48x@!gJ{kCj1iJQPo-bH?y&m3BN^KnlLMW-OeWb4&B96`q5oY z<$K!7gukY{neaDscN1Pl_b}maX=@Yav|U%vYZ$(Y3_cq9Q@s!9;W|aki*>h}FMte-qk54=`b!qmCw`J9D6k z=+1O95#52#CZbPzkcsFM9c&`{cwJ0HAMX$o=}HgP`9GAsJCZAhnaEL8KZ(fE^l%e7 zh8|%e$5OoyBFEEiCUOGR`y+BH)%zo&Q>OPv-518xsD!ZB6^zX10!++ z?P0=KQhgGHucjxO$T)hEiQGzin#dhgUk4DGKzo_U-SiaHxrp4uMsE{&km{ZzqNm(x zCh{;n-9#o+eGx(A6MBY;ETw0f$gi}oiL9hL0*L%c&obd_^!(T1K=>JYjtRd)`@O@w1JI_Of*0*Hc?%%mzZdT z4mMGJvANVli}W%REz`?QSVv`u34cnjFyYVWl_pxHSLys;#m~XFqeD$}XR33HsGjo- zlZx(2hnesaI^0CN&=Dr8tNU6L)%Bo{kLYQ1q>1V@jxy1@&Z90oM0Fl@;UTIcGTKCS zM061%sv~lviRy@qF;N|nu_me`a+8Vbh}>+VIwH52sE!DWwyqaZ9g9P9f5s>XRV)5!EL_R2S?+CaUY;VH4GLFxfZvu&M8BesndsMax`}>6XPD?RI@3hIrH`BFcXXDCeoyPOxj=L|)uBQ32l}LG z4AG}dW0=k{jeF9kP2*nl857Zk_N<94qH|3|cltRK(H(u>)FZm5FPMn#={(c84}H-z zwxutb#zW}KrtvU3-$eAtUNI3pGMzRwcB8MEMqZZJEijF|EU$ZA=l^wX)SWADn1~+z zo2IcZU1%E5rEi(W^XS{AQD1D{F^v~fJ!&-S>HMx~yoSDK8i&*OP2Rvu0z>0CstXK_w^3bSXdF*YP)c|)Tvrmsz-E+(BrG(JOh3el+Zu2YCcop+rQH0nX=l%P=$O4k}1 zIiUJF9d0!0p6OAeQFrDi)2KVM!ZiLte>RP)=r5-6FZ!!#{F|;cjqB)drg1&}-862a z`dEK3|1n`h9}E$F(mzc^pHv?hu`JaGMl4TPn^=LaF|i_DYohv?`s9f1LiNc}okBO5 z>Z5d{&i_U}F_msIRXxv}O!YCk*;I7_Z85du=|4vQ`;T>7P3#EzuZeY||C!iPRF4`_ z&;2zJVSgm5e+hfNegM(KsBfZOX@iNfL+b-3$_}j$n%FrsWMcg(uMaAAE{&MjMU>%C zv7xll#IB|ceO)hNoF3~r3Kbhh6DBr-CQWP%O_|tOnl`bUXvV~Drc9NJ-9mFFHjXlF zDt0Sn+Enaz=6^l&reZT`$;6(aWmC~RR!n6FS~ankXwAgt(`F{d*|NU5iG5ACGcnGV z_1l{mXUO^%iusRy&xSrQV$11{CiVl}$;5u5ElrH|vVLb1{h97!8g5E{ z>wQg}XFc3_&9o+iQh_3H}QvP9}}NW z&oJ>P=$R(|EbVLJbM^f1#}&lq(X&kaWqP)Wze3M3@zXjuiT_3~GV$N(#U}m-y~M;%lhAn_Z4!Oy4JM(}b)!k>bm=-m zLPu?^N$9BQnnFTHjWt!*i-ZozEheEyt_uu_yQwZPB<`WNnZ&*Hc9WP$$D71`^bV7_ zpWbN_ljvP0@erM05|j1(*C#>Z5vosu#9XRRg2Z!Fp9G2LsXhr3FHn6FB<9fvOyWhV zPl(u#^g$ETY1Jo3OsDl>rE~BW8N$84w)Fi&6Q%zzyon{h0(8o;T zN2;eF5JTF-REHR`W2g=>V(-$IjQ-=$>-BX5 zu}yToNjA_|OtO)_YLYSfn$G`gJb|903rsRaUpL7#eZwT1(>G0WJG#&$TTq=sBy}g= zHc6f1cT92@y2vD3QQdJQTQmRb-!sX*>H8+R4_$1M`%+yONVcQ8E|6?bKQhS<^kWmx z&`(T!IQ`VbN6^nq@*uhdbuN;Ju<^M`9!kG3$;0TECVm@TYT~!kuT1<7`n5?OOTRJ6 z3$=*~~Gm@uLUCl_IMt?NPepFX8l4sEsCV4j1m5t;% z^cNG?xz<&V!Nhf;Y&6L!R97>S(`b`P>UqA|B&X9Y zCOL=xW0Fr(J=Kt$OaC=V&b0OanPmNWHny4M3nJ`;lblCAlYEi-CixO=FiB3e4FQv! zPlG1;84Z~v>uW>U#6O@BlW0q$%)eeF_T>uarb=|AoSQ1qnR05XB*VWUVUi60hNMX{ z{2NjxxgSlNBqOpRW0H)>hO9|48XIyZ`5*JYAVYJW+v5w>gSN!oo=VYO8W0o5r-J#`@)?MzA+R(q4uh1J2Nl2qpw zDV@9hP3ls5fJt3Ob?qQ^IX%#%hR{wXbrsb$h13{&kj_6tmAaKHI!s92Ms=8wx|8ZK zA$1o$)TAcR!%XUK+SQ~UriYuREcUNzJ9lm=vez zhVCZy0zKALbn1>X6`i`{O+}}!hpFh)onZ7A25mUeq&}i2q0U9>3pRS1)R**RlgiUx zCZ#L=6qEXj_BN@XsIC#DR#06dNc~K8jUc6GfUXgwbk(0>QY`5DhBLW>)K0XoN$Cph zXHwhfSthNE^lX#%=s70cK>M3?ik@rqw<2vg&**PO+MtUQDIE!2ocin2Q|;I|-=uYZ zb#+2t|2OF3L|W%}kVzjyFEVMJmWxeVr{WTm*5My)(mMRQP?6T**M*9-4*%sk|Ce(` zhkuAk>+oM;(mHHcnzRnvRVJ;YGt{JYbgnjO9i3}TT6cb!N$U;_H)(yI5hh*N2fx;& z^#OH4H_$0Ya`aInc{cR$55R&WOP;QydiTu)p<_|%4E)@b4=<)`n1XDI?)|OJ);Zd zS(CYu&NZ1a^f{B!^`P^Q%q>*s9~oT_I{(P%yuWBNI$bZBj850fCZp3e-(+;UUeWn~ zg*)q9=;A>}kK{Fz(H&c0GP+~0o6JJ`hRM7|-!vIrX!<#1-l6(AWOS(YbI5!_^>fI4 z$^36vWHL+XyC(A$ea~dRq3@f_4^$T_GCxsWsK~6Kx=@i>O+PXjT~8mI%v$=1$*iNF zqRvHTJsY2yj4rMvCbNm^o+EWK)jdb1iGFD^o2l+NGFzzbI5OMl*CrdIdJxDa=rWVd z>G}UHSCGxq?@aap`n}0^qRUOTGu075_8|JB$@Zi=8p!I7>S!R_i~ek~r_f(awm1FN zsY~`$HuOA2_FSsxDYEC$-%a)+`iIG0OjntdKEa}d7Tue*+$Q###7&9pP~&W`z#HZ>~l0|vMeYGcuGGuc=&IX&{S$?3-` zCZ`{(nw);DW^&qQiuunqXXF2J+p+zBx$W8hzg!Ep|1Y;Q+dG)tE_6qe+m-HQa=X)( zCbtLG`ykhv>V1&go7VMCa93Kf4Y$1;+sL)0`e4ZIOZCB!Ye)6LklUZ`X>uLuUM6=S z)h9u&6K!L1o#{TNo;!#QeG=pjrurnvb)ots$Q?=Bn_M^A!Q_si`qT{^kn2r#r;y?d+Sp0wpFPVB;EL`Qas#RE6msWN-6`aTQQaxzhSNh#ZUjBlCq-Pmg-R>cQfs7a<|Z9O>P`L z&gAqck2kqHX%CaThn|2s7rA@cIML)L(vwW?0jk4_+=Eny6)9d!HtMh;U5Q%&k~dYVanOHVhs8G8Qr;RIbUxkJOL!0+V}}UTAXf(LpBnKE24K z^$0IEX?^TVOj_5$V3XDuOFLg^7^r9CO@0jAL9afJ#D6&^bd4~N&iS^n)FZfag*0mXqL%8P4&T$ ze}+C`@~==mHQ|3v^*PXRHr4y1-f#{Z`T)qkO7#Jde~mtC@(bu(lYgB)XYxzv^Ctf} zeZl0vpz};#pZGmElj%kmn#@u3EtBb4XX9;?(Fb|Q6of7^1&_XK3TgVDDP-vTrjVzL zO@TXa{J<0{^g~mq(vM7`Mn6WKi^5K9d}0bM>8Ga9hJI!;S{-g=b?0=rQP_v-aHFs< z{n8XV(50rZAN|S{4xljb zwiVT>LUuQ*Q-wlbs#AqRKdRI7GxJ|Ki;Z7Q;cWV=DV#%BnuhD?Z>B+y@ORUoNBD;+ zoJ&`k!g=&hQy4)1GKFjD-=;8%uGaZq&3}TYd$h(BZlh~W;dZ*t6vor_rZ9nSFa|{jBYmBH|Z9Wt?R1%$7FR?ZZ%n5l>eH7-r+w}(A#V?g?B`FE~mg^ z-Q<}9>t_>Fs0vKWrUsKcf(A^1QQj0}{`Df)ohu=eJC24;?syt81x933)D#$nO^v3& zC~S(E0;8}gZgM?n!sHm0O-YkGiKa|p8}q*@%@q{&NHV6Vd(1LdMSaqo$=yfuCU-wA znB0q$2T(<(a#P6^SzDXRrpVgbR58VKC<}O#{_?kCe>Q5S$imsw%oGRE=B7B1Zf9~Y z)9p=hFl}LqBj^sM$m4F>(G*A0olJ2QZE1?v(fZC@pm;sq#T3WTT}^Q;ZDoo#(cMh( zX1cp6-a_{<#c{N?Dc(x=G({HmroBv&MZIZnQ!kEZqm3!vLH9AmJ84@}yo>H@ig(j? zrg#r+Z;JQQ4yHJf?q`ZRW&4|=PT2vbIEi-D`R~ZlKgg8>P4OYx$rK-@olS8nJ;)Rv zqX(PfblSxfXVODVk<)V1p{6*C9%hQOX;)KxQs?P#lY508VR8%TktVl@b~DAN=uxIP zhaPQ;Pt#*e@g>^b6z9`pO>QyOvkSQo=<%p?k^7L19;Wy?J;4;epeLH*msC$36qnMT zruZE_*%X)4UZ(gXJ;fA%roBz^7ka8G{;KEyXhnU=F z^a@id(ko4gquF$oDK)1O|1eOznudIWj^ z$m;=&G$mcEqfCA-z0Q=nQ+=I5sV5z+^Up>8Ij-Db3K4pvDd+=@F$G=qV@*L<{Y|E% zM|iU->CWF`3is1-rtkp0)f8T%x0%8MJ^yd#3QBj-@uswZ>UESB(mPG*Eqa$JeMl#m zl1|gzrt}HD$CTF4drfHrooGtBNbge&T}fA|?k!4NsO~LF+vp@y)`j_?DeJ;~$P`|u z51Yapbh0Vyf_=o48|V~M&eKOtxj^evxj<3pW|}FUL?1I{T}0DOxk6`{a+S_BJyY@H2VU?}T~)&+*LuIRT-Sy!|^Fv_~37n!2=T~pM) zXUe+Z-Z$lmbg?Pw=XFX@)X(bzu5(fPiH(m;X)D#$jMBgK6H^{WKQ-my^fOcWhAuH> z7VxIeP5A-(g(>U8{nC_m9V|6v7HfUeS6o4vMY!o}Q`Uv34~()dlx3!@>*QNg)^+lo zDeF4<-jsEnEH`CcCqJ07u9F{4S=Wgk^iRyc{>G9`D~$fel1)FG>T&cJQ|(EAH9_`z z(@GQ6Q|vbroI-y$!AI#ICa7o3DifSZ|1`nJ>0dhkfANnG%%XprGE=f?wJGbgtTAP# zWYbzx)@fO1$~rCUO+t_)$~v6eOqtQ-@trcG+2olrquJz}YJb|m{Od)P71|UqRgS(XXv!Q(Q^=G# zlBTdJb0kd>Q)cIzqNdCaH#M5zY#K8`UU-_~rc$H{Qz1IvOUe2N(-7dl~%N1s!OOoA!_H)lBsl~`h5E96Dvotp-+g)F|=wb-D%BKj-$FG zsGLA`M^GC@w=G~RC?1LOyxAXqp6%u>pO9Q%GtD~spy{WY$_MfT}+QWFOXlqlsg6?VRl`GlU%T#Wpdz;D_+QwAI(|t_k z4%*g)bf@<@;`|NS|F>0CL$RA$hQ#^YRVI?(v~ z`A(+t9PMl>FVKTbTDclDxc7e|qiimI-SKBlT`;0#mMxjxfWna-xZrm9oa&s24a&N5Y0*O=2*5)TvY$!%JruD z4;^i)Tj>p^`Y+WXM)f~B##Fb_v8E>UCQ}R0n@uf9Z!xtH9cOA`J^ydz3ThF0o2fGzYh^g&L>r=QutrdOL)OMp&O>K8N z&D1zwn;tW@)^xh5?MY{t+Fo>~sp;wZxT&?FvrKItI@{E1ZP|Fj)b^!Mnp!*hl&Q6+ zb4;xRecIIaqtBR{p7;7fgW3UfuBmmT&zV{$`n;)irZ4FHzrY<1;>tWzJD9#`YKPF5 zOzlwmvZ)zPq+PU;YQ@enEWNO#a zk4;T?`V&(dNk28U8|i1JHij-SwVUbZrgkg+!qo22^Z!e(pmrx+YHD}UuS`u>_t&PT ztN0sJ(^b68)N~DeYii7Y(|4w(^Zvc5>AWvDK|PaxaOx5`l8qmYG^alqVO=z>FrxGR zvk^VQUySI^{AxTs{Z<-JSN?Ct>p*`up01BSOmGrir9-)jKmY5{{%O2{^e^L`PyaTa zPRDBFT}am$ZxCH;yo=~M<6TVG8}Aaj!FYq|M&s2lWn+`^E~8DxyPR$|-VnOQcvsMW zjCUp7YP_rHzs6%;oBlK26Lg#Lo^*fBJSF@uoMqdS6#xDX*%ITHSbzke=quEhCozX#BSBausK z(vip))c!Bg9oR}c65Wwz9Et8k^@y_kSJWM;9#D?$1F0TRp6yOl52(O)XQ~IJ-=~ir z#74=H=)tt?Nc2!zaU^;;tvV7tg4P^~9!Z-y5dT*olEuk7!4>&!>7stmx=fw521_p>$_QqF2*h9Es`_?CMBVr=XQ1QJsR_9ErX{ zcXuSZfbQW)J^DHutsRNJLHBedx{&VWNc1hbwuen2NKChN&JX2ABk5s|#75Dsj>N8` zx)u)SHrG>K3rDa$n(A8M#VkIPc5@{D6g|q3_+qMSB){Hp3(DP zhp!hmdX}EzNJ6Kew6n&ypf*kNb(kXo+HU|bbuqtg>;}J zNnO0>JCfAJdx0ZKUA!0Soby_qTto*sl6;q5PjP&t#~c??c(n(yJZGttb+a;>61J|-$rh34<=w&LDMPBDZGF7hVDj&tR?zOJY>)38a zb(LSwc5|xJFq-Y{=nal!wx>5blDU(PaU^p;9qUNu0eX`oS>4f_9my8;{MR|Wg}*GI zd#ZCdj&0qUTOG;j&fMlm_HTN-BiYq-yd&8)^bSXI0jfK4CqG#W(z_hVh3EuFaylJ% zD~2LBkBxgA$-PGJbtFHPPIM$cjo#--{$;91cRxQ~Z%_5;9$;IiXp$oZouUUFDGZ|z zIa1Imdf1VIuBZBBE+%v5#q<$J3c9$aI8ykKKI%w87n<(WRBrz<)t#Eg_Lo%G(PM0X zMRgrbXL}i);Yi_II@6KDcT`_n9_P=0MLiv6Ia0ik&UU1zXTuYYl=SoZ;_@Wll-AEb zPY2Rs?+=$Z)7j4v$4RD z>J{{LN2+>CzTrsqD*C1))uD8uBUPR6w;ZYJe825TRpQKCV-s*FWR$e(A{Rb>15^FmiSIoGYL64-oE2zi=d~JD`i|OTIMg z4lH#fsymq!1Fy3UdObh_S=e0>HR8yv~cq#GT{KTbC}lAlGJ94Y9TvDuM=o*7#lDd?H;k0S*= zGqyTX&@g)dNTm3I^rPP6K&ymu8)OVzGEp2f4S4%0= z11XK9Ob?_qiZVTr(sh*Sft0SNOb?_qnle3*(hZ#d9`ggKcBIS?qJJ!NJw`y?MP^UnsFq=8qhNb5@HSLnF9&$K=X>%|L~4%6dVa3O^c31rqGfj zkwf3X%J*N|k!fWA3 zBlG9!9`e4Rk(JJ`*;3NTO6Og!lEzsm|^GZ1Z~I9pFgz2inn*?2lB} z&Vl@;hPSD%9o`q@-=UoyDU6{9IZ~LUD_QsIV7^>FNOiBeu>BA{#F4_o^iW3%lj&iO z6i=aD9VzyvhdWZdlpf(o@iKa(BgM;UH%E#?=uxO2#pySf4PAIgv(0O{cZ?$?J;Lse zlP)U-fTZkPj#fUh@R$1Mc2*gj#U1peH^K*re`>!f4K2<-JQuF zMct71JJJxO=Q`35rsp}*5TOGc3FxUk z(2+oi^6!6h!S{dk#YPw51#Ih!&4rGHGIWq5p)9?~k&wQSTPS@A#MO>O z-=un)Uc;AcJ^h9`5`B*jcO?2g9pOl$N3YfS=Z!(5&lMdBUJV=fr6V0_Y)3~q64SZ6 z&XJhT-Sv*dbm~Ss64R->!I8K=#*L1|_48vKiR`W&)lG%km z=tyQ)%76ct@*d(wt?0v!WOk#I9m(uY^}K$B+w4L0)S1F|Yx<}o8C_6Q9m(i=n&wDm zZ>py??+Y?*=yXRi`%oQ?8T|P_)0Pb#j+xv@=UzwSakh2tbu?!2a@&IHXv}7N2dbmN zHx|pfSamdbUr^RXsiW}}+qyR9I8xTR*C~9OkL%n&<4EO4`mE0Xv;4XJC$7wOq_ToO z=ZL-=_MUe{-|2cUIHK=#`F(#%+>6$WY`@5MFZz-r?nUcmws~LRUbN=3J)doT(R#%Z z?=)TYuR7vhwO(WUHEz_0E^x%XYQ4_(>wH{S{u_>X8|a&kcpK?LNBj@yTaNf2(zhM) zKceqA;(tsRp}vSehwI+I>qvv{{d>*pY}XIDHb{7sO7X`XszBi1nuW zB)l((>AdTc@V+3XkMW5kF@20r9Z4Lo=l^G1`Hb(|^q_i5En)iv`ne+s9riCAN$9YD z=}1CfNR~R1xR!q9NJ5YHYey1#wBIJCP#`9+T=)4r%88;pBa>O3UsHo zu&q;|JM|CSIt5!DDeHRU`RZJVdo}xy5B$frF0O5k=&PA8j_9kI?>VBcX1?!;zMA>|wg2BeBVJCr4tB(3XzGI7|GU9f`5H{aqZ1u~_|G9jV7yaDFRCVk{1S zH%EUWlk#_WB(6uhha(9++SZOF^l0~VB%w#Umm|rxbZ(~Ig89>5o{Q)ov=GS|=p z9mx!%ogB&N_y7IQj$}sAgB;0ROAmG=qqpheNLF{|5J$2n(L)`{UP%vgB&$c+)sd{O zkHa0w>UEj3c@4X?I8Rqv)}YsIllZfJ1MTTZNzdn#9VzMgtV7(3 z+vxeML#*!$wx_)vDYc-dI#POS}tXBk5adUq{k< zzV>q@eLFqNk+hz=XFHPCQ}-N4(t7IZk@n|%L3-+*>quIs<~&EzIyD1yZU=Cqd+0z% z()ZHy9ZBoCdx0bA`{;#^q;)O_Ig);WUgStxPu+_hNk2$0aU}f^9qdT@VS1?}Y5o4M zf0-j`J$Em6Bt3-=aU}gHy~2_7RH}>ZN_Ki0y~>gFV|1t^>FM-pNA%~2{xyy?=&0!G zAI5tPebpVV^FN#`dc?Z=`GG-1k4#rT?+YS&WI95;N=Ed^MmiEtQeAkX_+z^+E?s!n zvHcFc-jRf^mC=qQ!t@4568gk9I+Eb;f2RB~Tp7b_K!J{RBw3<2Ig-?!xY?1U?!+yQ zBz1VlIg-+|LD$QzoJ|?3>*Y4K|D(EIZfAi%M|Hi7=PRL(j;@zGII^o zeL-M1st3gTg23*yu17S9D?O+l(SvNCKy|_Jz94WS)dlk~-@!VQ>Ux>Xc3-ON~$ls@T5rI9}6NF_$+IKp4=PWeyk{PV`3dOTO2 zaippv@vI|!A2{XDb%gH&r+l5m=lC<-PgED)^K7r6FF4}9jL&16_XY0D^^0utzMx?v zeaVrAP4s0)8u%-6DSltCl7`Ln6-OGj&{rL4_=mpcNW)gTz>(lm`nn^*ujm_&1iz+l zI+8ewE_5VuG=0mF#4+@3)ZgY0geS4_jw6Ylbde*8Q|P;nBzn{L97$eD-*+TAlrDB8 zc{TmOk>oY>Lr0Rs=tqtuhtrQ8NsiF-{}Zl!!Xmnse(Fe4N98j|l6uZBaU?m4e(p$0 zci;<0Qn~|QI+D^I(2-fn3zSZmj?7p5!BwYBN9Jp`b;`bR`1c=vW@DKnxnJnFj#PC4 zedkD37tr^PRCR}!J5tph);012Ye;uk*T|2&Ezl#@C**xWuocxO%Ia`-0%^ z^cOwSUpSo(puaj2>PS~Q5;~Cn=15Ez-0zOWbiw`MNJ3B1RgNT1rhht;=tci>BylSJ z+mWiFNVMi+W(1;_Iduh~>%0$}eNaa2nbEI-V zjXP4=a&L#7{e9|#_(s@BzMnOkKRA!Wa=t#b!&q>fiEiM9H?f+vKM;Ig{~=AjkL~_7 z!1QAN2+3_S3;8nA1@l?A`-7L3d_TTJ9Kl?^tnfUgmf(GJnJ+CB?o;70>ea!l!!o#0 zv)&@jTY*n(H<7oe++lkjsKp$S9cGK{7(gE^;0tyhL$FAsWeZFa*_rM=7nnc?2;B8ln?8}XL z9BIer+s83Mq(d`I71^&f*tz}p#{!W9x?!bA$FU*@w!$KjPTaN=kJV|tNN3ua9>hT( zv`XaQfqY>-m^*YC1U_&GJ%k=Q5DP^PYXNrXu%#khdt;f%;r*~e`1TCBB!LWM5H%&K6S9jY3#`9{YCop5IKX}o!M2SZx@k%I16WY5;;e(P^AAjk#l>3 z$2_kUwu%hk`hdA20|znx=eH0!p9jBy2fvU9x^T6~pxGi9@gx`XAQw*+xr93m=5~X* z^Q9|9F5~l;PY@Y0UgU~_B3E|7T)qv~5`27U07J1-Y=m$PHY-F^(A`W4eiq<$-U?i`>j@Zs9iLnqjrbt>Z*) z<72mT+wmfD2e-Mi1$gkgmWoW6D{?oFb@wEZd(s$<^& zeDMD6SRnF1SFrb!c!Ejn$b-E_9%_v_A`iF31d+)-L>}P@9^uKRusvmw$fKRW{in9W zY>{c)b{aeISQ?{6rn9rtM`FFmjQT(>mWj;d&NCN^Jl+lSMP{|ZRFTD&=PY+=5@kikr(@jyu_2wAIcQW zUm@~JSIiT6wKcfItE)v`>xtPS9EdFF4c%rZUT6FD8DRU3?wEvCB5!uWB9Vn0$in3! zZ?Pk9@tALS!!nU~x`6A8$`~i|E<5#J8|MFgZutHZk;U9`F*p2xC;s4Lkq`O!hx8+M z?xUq59}mV>kx#huC+zg6JmIGkz>|MA2&+Yw&?Q{|oE`aMGCzAwV>p(JEbW5XB43RZ z`MNvSi+sZm)?`^T%nkfll{u$u8d;{_}Fjsx8=eQ_W9%=+~JRTBC7_1z4~*a z$X}hXOyuwJBCETItm(~f>*Vw6MuMYX-xYKChxb}wC>HaNZLsbBRi}^n59~M?_aA02 z;V;#;0{7cYH}knI!?9fCpDxV*KQlzOmN8D`UykhGu_FIPFj-_RzV5cp2`L?F4R=Wjn`qZXA}0muDyP+@Ua5ydobj^7+yv@ydL@!tJXQ#j8yfuNgbh ze7<$pFrig#cfGsNrE3iHJ4 z+!b5JJ7}zU2lwQslRR0MmEs*TUp&5Wd3?e0x^lb2CyI9jPjuvX@w)N3qj;>Nd9Gvl zTzBqM@6HYQ%H$n8TD;?Wh%)_t!N<Yd*M zlfe^T&=uV0f>q*O*a_pYg83iR4x_O|yo=V0ckw`Qqf3Tht9XMKh<7PZbSXP_8BcH- zPk1>Ge)&l8h71z#ivHqV$&+5i_RtnsDc;qS#Ji>+R){x@j}QA;yy0yyLA((>`3N5O z+Jy{%eYAKZyNNd{0zPmZJ209jyP=18Hx3tX3?CoEUXN{!u~;SEP3-y2UBH9iGETg4 zeC#%Ef7>Li6z_I+=Jv(njpsJw=ZSYmYfKgI&N8_DT>=j7E^b?&&_}!pAB%T)H_R9B zo_3fa-n}ir-cF2z8{Wsx-9MP`uQ%iS>UsTsI=KBqW5s)Tfq0Xbi8m!89$)>usolk! zHV8|_d#o$wh&P=lpFUT-8SLl`c6=s}_4pv>e-<0Fwu(2KCwrnDJ{IrEq2fK220Jo` zJ3WnOxXoNX|6Blz#e06DcrOe@lX&w+i}xb8f02*9)C%11<^JN$XL~+7_exLkUhN{@ zYi%$=yagQ8g5l!59$@}o?}Itwy}<|H;7)IL#cJ^ujuh{$PM8Fq(OI{UPEl=1CSW7w-e^`{5w*KH|0?trzd(1>$|u1Ixtwbg+1z1+Y-O zC3PPB^EAea_XQ8|B~S9@O7WI*qpyaF_cgu|@cCtB%ogukp6t8H;(gD9F6VZ9Ve)<$ zFW!%N@cEzGh_|A@ct4L5?-y?OE4N#T-}#;CWVY+f`hz|H)qb#SQ=FfmTN_ zRlGGk>DnIRt!s%T;;rZUhT;75ke%4b?KcTJfsgYKD|k%<#oJs4pWnP*ye)&k{r>5K z8RBitgZuv*!32K(!u9{y;caoL`_fW;uPi>_t@j(+h#wdzesHk(p|RqJhl?NSDSk9B zeq$f;W8<(=e0~R(AD=3IA|ig0`=-+3r+FOyCa|Ar5w_73p_B_G%OT=%{9Sg{nt{&+XvdA^_L^E|Wj zy6M~0&B%8kCi%_s9Yn&x_3|CUd;y7v&dN70UA{t!&2N(LhywYF#^gJaB1e*YRJnXd z({Ql`#E&Tj{1&iXFe2ZvGx8mmBH!_u@|{pDUy^l&ue1gx+!vTCT_sv#&r2^DwFT#F8P`# z<-0YD^M6~Vd@TfSpOx>s|Ras>CGzrR(!cH$4x$U|-N zb&&h;xO|V0^Jt@do#>WPY}t%_j}dzueHWT1o8)^6pXFKdJzX!~Gf6t`DV6WpPWe{M z$+r@--h7yl?>Q1)aOCSl^Wvy{FHzv-DfwO@_G*!QuQl-(NB#1>-Xq@|?ecL^@(mhL zBi|}=-lEaB%j6rX<(K{S{F0yeduWEc`HLiCALQ~$elW`);r8;!w@iM7@kdO4Tmr;D z8IW(ZLB3BBemW)JXEgbFDbVN`jee0Q-RKQ7a|}3FW$%h_VfKo?w_Ob{l)gLHW-)hZ#@2{xxc$$TE4kf`Ti;3 z{QraT`fmCDt&?AQFeN{KLgM#T$FBpg1#-3?mw%f9`7>y4TN>IH-FA$( zcc4@LOmcR}mOr_}l>9qlnnhB`Hy!1qZ69sFD-yM`A_VS|0Fs;IUA4rV{$45Po0#1aj*QRl>oYmA^A_I_!%SepIHhM@>k+hIV(SlQ2$vo@}G^a zs!9HH@IEIwD*w3{pU2L52&yqVzd`;Ba^=5}4ll}-|KdUUFKLs%wpIR1a{%)tDNqV6 zK+($#$b)L=fN}ZjXs`}VT{GZuIhxD!p%!{!j`M#-uKZV!bj5!=bXA`Y!}4EQC;wFx zzuJLG`5Ol0zotU|YcaW&qSvu)?3e#~%x;*L|3>sTXUN}7p<5_;Yax(#8}Zv_Bsr>85VSsQ@}XVE<_1&(4Q-Lk z<_jHdLDH5b&?_UISbC?7ton2GUm0&C=_r2^O-xmh4X(zjf^5Dj%0fj+oQ{66q8guBZF@m#)3*2$9Bjl z!Qi+l8OM`&LZgh*QW+=^t=F3w7uTy(uEUuDqTBeK& zw5Qj|I3w92SWY4%D9|5SD;T)ryl*4a24a^)f8y30K;oK zWn5b$!%{KO*A>Voemgv0cVZO)_q-mC;PmTk>Vxnks{v z490C!GFs5xj^>U58Fx;~Xzi167yEZt%DAV5^WTQJZC1v;2<~f@aX;O+7s_}b2k7#F z9+;N#AdNoQ24gZFs+ZA`55ymCk-<#{;}OP>GT&JtW0?cwJw}no@qc_sMi+BWl*)Kg z&@1C9=DO23|J`)Cyi>;04KkiV@C^Gstumg?gK-%vI%KRY2IAakFrK3^mgdIun7mLU zqmQ{4Q=v)5O9s@+csT_IWb{|dc!lvRH2rF$jMvZ)l*@R%K*k$cGTuyP@`cU@9UyU# zqgYi36nhJUw=o@Rknv8djCZ?ayw@*dcv8mub22_4@54f1f23K)M>O&gJ^vL+UM8=q)MaCDIoc}NSWPC~R%V`;_DK?%jV-12e#J?h8?TCyCn)!N8 z#y4!gMK{?f<2!WUWAMX&%*dFc*&ktDr;MN4WK1{9__ z%lM;4#w_=Lj6b_&{Dt{kx{QBHWUOzL@$aAlDuFQtHmFd5?*#$hhywmX1&j;@0uGcz z7tAVP)+!LRpkD#23CIiO0P)a(0%7!F{K9Q8p#aPCfJ45^`470G3Pf5Jh!!j0Vd4!b z5NlK*UZ6n208CRb+At4h71)UVjq4Oh&4qCVxaJ2oWp2|c1vbMttw@2*Q(#PiEvjKo zfi3G5NJpPOslZn43T#cSt)~^(CP`8T!fok_>vdrJJ_Rzn6xg9dfgM{E*og*qB6%kY zXH^0PcV>PU8r-#1f$U5LcFR*>_c8_cAePgnz@9w{>_y?dncJIUxipj805b}(91rZ9 zqriUn?l+=9GLM4$_bYG!;sY=^U{ZktIfDEw1rBncMS+7;fbAh{4=Di}EhvB<1-QHh z4yEW}wb>DR6X_0>ya>9K-rQuz=vPn3gmv za2(0UBR-*5fl`_}aYBKUr9fG_0;iC~@;k7oOM&uk1x{^JU~z{6r=hQCQQ-741#B|tOJcu)s4*;4`NpZ)LrKRc?xib4feW-8E|2ADioufX$F3cOIM zKp#c>=={YD;7DE^QQ)O)!1QHuUTy&j_BSZ-3PoORQ(zzmC^j&p!0Y(DVL^uiZ|1?6 z0)x!0N`YR^|67d;yj`Ne5a#dnD)265@3H?L1%~?+cz-~F57L3AJ{VWv!vYvoV5C)n zk1+oj(@z}0c$6Zab}H~0IiI&HFoyRRg$jJh{ObQ0QeeDIfi-Cge3c6So&UA93QQpU zxDDV%ue?~d~>*;QNuLA#OL7Pk#;HD~JMCJzh z&@R(gC(~al)5w(>NP%gY=CI5ldJAnR5739pVNRyqEYl&^nUU#s$&3uijLyo8ksHsE znJAN)QX_LiM@e(T4w)Mv-e^?j#)vjam$_-N%*|?LvRpPd@0Yn{rp$E4TMf$G#*>+m zC3D*XncJb+zFTG{c{@5XcglbPnOW5`cSgUG9{DnJ(C$Uv-hv*Pxs6G_ zOv&7*7zpg^0m=Ig%iOP3W?s3>{VB#DjhF{8e;@_(F*pd_!KpG2$(C7ABJ)s==&(AO z^SWggQgnVg5Sx$3{1KUlqd&X|#$+B*Ewd;ECS@L(?3Z~|o6MszK6*}OF_SDj&0{Kn zxdmxZ1G6%ZMOTssV=|8`gK?S1*ULP?f@YcAyf91Ao!Be$q$Zgs<5`BTjB!~XOvyYY z4QS+)I++V|WhNI+%UlHIbaQGUbje)oKotzhJS|6NMY+t=8)cr+4RbQjM0aKjkW-lt zO)~#iB=al-D0DUrS7pPD%yW8Vp36QrIL!0VS9i!fpM$#~1I9T2tYXa?3e+&kO4huH z0+%pZTP*WZ_LpSBgv`svWY)pu!!oa+vE-o4`T?0&QsnA1nGM-8uPKmuZKcfXN@O-N zURoye`UaUd5NjfKBc3-2I%VF>xH*&ae~ST_aVa%#<)gbT2fAh6o-XrF#I0R2?+dCXJtMyBlAgk3ghk?Ahx_#=F^C|e40aTzyb7SLWj&3^I=rx%ZAMUESQw}O1sS0Xec?*DD!o8-oR{-B7@U1 zS7ES<_*-c(DD!QS-maH9gzlXlneVcFk7C2kGT$$g$<@*PU{dCX{W3>dWPVgC^JC0E z9+vqD@llF@%J?&Cd_E>~tOSN+e!=zM{IW{sYK+DSuE~`7Rkh5um`>2`1ckoNmH7?h zZ!r3HQ063w-xUDGzMqge#oUiMGS{(PM*$X}=Fe#|f9aGtQz7%WLYcp3$^65ES(&pm z^JkyTzwr8dkn=y+CG#H|SYIRa-x39tqu>UqFsGnzTtOpEK^B`qvs1w!`xb;I6bv^i zXlDRpr(Hp}Rl!J$g3)FLJzxzPjAy}!g4|08rVJ>!VUL0vq1%|b)OrOsNiwk+1=8{q z+`K};E!q^^G8YCFOfOe(D+l@&+?sJlor2pnD7bxrf|`m{oA62?ev* z?!x}AsS0N2E4W*wg1ghy9-Rv2;Irqrf_odlI2XUMPh{exdIe9;SFlVlpx`N(Ewq5`BIe2) z6+9Idw~_Vne8oXZ)JO1k%BFl@#A#xcAB~)9p)6g6YiquT_w;7?BC5i7pq_! z+qMY>@2yktJ`cJSyq{*cU1~vb_$>wYEkf=G6mnI=(`ku53l#;6l6IUe7{aXE?B`2QxzQH=fB`b9O=iF zf~?_!++q!mwk!B)wSu3ODEN7if@92oQKjIQbUp4VxQ4l}C^EtPH^mB07AW|Ao`O?3 z3a+D(Y0RgG6#SV&zYzO{qQBDcufqz?Oey%gpiRL)DixefI((tvpBVi)so-DD3eI8j z595D(`JaK!vi#Mu0%_1L%gm4!bYP1A8&d|uvO*;=CX3~l#ZR}ET?BoyoMJ%dR=~Kd z2;0cCtY|*8$?{V8kNsXFgE?6-io`KU)W}Lng8^9^R>|535kK)-8&}Fo^?;@}LAR*| zJ+d|{la+=hZJ4JApxHt|-WDUWw#0Kwywe+GZIumUvbJuQwM~hv3~FSIDrs$7FKfFj zS=%?u%A{LvK3e>=Ywg@3Yu8K|m6eTVHw<>C(C(A6_NbGU!#Jl))}DE?_F``D3}^&A zatnb%`{2oaL~Fl%S$P5uYJZwJph(t%G;rWZQda)3tb>MR9Xu%O5Ga`B5eNOU4jYp- zk4ENGXg=E`(q$E4d}K9D$U3SN&>xN2(cQ9&Y3i6_SqpMx9ZOD0fvn?*ozN-kL<*M~ z&?M`W4Cs=z5T8X!IxA0wDOsns$Xd+);szL!bs8q8QRK8Xn2=S0c?Hd#UN7qma#)U8 zEXAxd$7NL(L65Ber33o2m_LgmXXAJFsI00gS?36-ac;Y;^Jx5hi}Qa$wyX-;zePar=aQ>HKa6LtCAh`+Qjoq?t;uGKsXf+qWh^$-6 zVMx}k=x*(lbsLS|)(Eq*TF|wixgDR|$>qvu-9dpn1^C@L#{Wt$gmL~ydO81b%m6&@ zCifox{@1#v6(;$gWA(D`#rR%K?sEX+`}$?wPwf6`7?jnH_5n;D%z{~-w==+VcA8{8 zTqf(0449GiD6vjr%ZNRO?lE+a6YHX>CrV{KnF({Up2Dv?iQ)1}Sx;vJ!DkTmjLLd; zLe`2lSt|=*T2?PSHzn%@0lGd+Uxb&2Wxb59KOe?rz0x4-RSLY?E$g*(XqGi#0H4GF zp0Ah6dZS3zoA|y-esU1usw!D;A$|*kw=o+k;fX;Wa5V3Z%6hL))-cH*VDw==(7;Hq ztdB7LSkNTv6ZD^u%TK`8r_6nZ{_|;BV|acsBJ0aBS*wR-jgzw`9f*C^%lTj1E^DG# z*4GuXzG31UCce#wE?JYrzDolVzps_`L#eE(To{-2V~ebH1+so3XWD~FSwHiU{?ae& zR~ne1&~F^r?|iykYOPtk{zU&5`oCsn{XHb>AB*$9p5p(uE2MFSd~*r~426Oj3WYKi z3J)seG$|CRRLILxC^n{0qC%k!J%u)!QYf`mp-uA?N-I@p3p}=Dyyd7u=`}nAwwQ;% zX7kWj3nmoG=u>FhHifn;f;ok@?^P%>S*6epX)vVFj$;b#L=#!l3hmsd&@S}~?V1G` zX16J{TdhL7W4Z^;<&-G2XDXoIt4pE1(dHH@v`;Sd^K@Cp`#~Q1{m~vk69;fG2R15{ z&;CKwJc!)nA(-$JZ0OJ?o*0XuFi)ZRT|6bWou|XLC{#3}&{3HR9Zm6Kl8VtBQ>V}Z z@)is!bS&`_=8vmV==e^BP9Xn;F@;JS6*`edPo$ZXDit~z&B;>=m9;5!N|{0nnOiu^ zgIyPqR8GNDnP6QPIt?ls6gqvFN6)74#94zU&E_d|R)s=mH!Di z7V4w0i`KBxwCAL8>N#YYMi`iSN}!t>*Pg+3u?6pv3W zXjka7e1$%@IRBrwDKwU+&={c8mEDAjK;eaT0_Db#%nm5HFFAmRR9>SC1-7) zLKE45`NXV3-(Wmh!oz$$9^yN|^Lncl;`$K!iTHE|v?=s+heE%=uZ(9VIsd=sEA&S* zj4L#o0}a4Y{@Jb2UyT1QS7?qnKPQI%NdaR2^eePpkgw3c1u(0y263Y(t7!90bnDuqKG3Wpu2hGB(mzGa5(7QnzMRoFG4OW{a5G{Kz0(RPKsa)o25 z(5Y}d8-^546f2yP0cbWHQFtTfHqM7>g;Sdq-UQty=r*l{afLTyZZn!pv!DQ|n@syJ zFs1P37;WCBFzdJQ7F7ywNi*pbNFPvmE4tsRPvNa|p-bUyvJ~bPTzFgJ+odVIeTu@F zumc`DQe-CzX0pbG&h=na;eEOl-WOe7Axta0f3v~|! zfboSCuJM4zF2euf9)&MyRJgVbhB*J1BD$2p5(+P&@MRqe*VQY0d9}hb9D^g(yZ{U(+ansy}d@^JMg`O z{5w;j0){#Nt(dfSD|}ZvbSiwe0~p;+(R&cyQ>}0tX7^?Q^Y>LLe1EpW?Ij97D1{$F z_b|qfF!$)J!YsYQ%a~s_r0`??3O`QX<82CeInbc+6J-iN$^26m^l<*W2`tBGd5^+R zS19~U4$LXsgVD3;Kzv0ZP+%n{E305g;a+sT7(YkObCU``U#sv7B?|Xp{vzftPAL3R zv%)VIz>LEEtqQ+Vr0}cMVrdp0$XEFFWI10jd3{9TH!yz#@tYnDD?CWTDwiS9dM-{bQ=P4df&@DG&=Pg&5(Bhm|DQsH%tod2IP75=GBVb*Zr zpGo|M{a@374rh85{td(5If6e@p&Eu2p3R0v7*qJqLZI2dDqvFKztQ|XtMFVs3@ZGO z2c^&s%&#YRJ)Y~){9BilEkT3q4J>Gq?aP2s*<8PEqZB4(2kK;-1~khKrb9i<$+l`> zN_MCM#$|`m+qJS;Gukei2wst4+0kt1lFb^@_SnX9phtE*nadZ968*AMnAnh{jWF3* zz`e=y1!!n93^r>5#@su!(-?1FBzub#7?-_eyKFAs_ExDdCVT5Ln2@~< zjc&tyMxE?!X*9VVgY8MqBzXtKJ50&maZvV7#Ik5)XO3X!Y1zB<$=;P%HU_&u|ma1nWnW@VSt$f-557qfp_CQQh#sFQtqK2Z1! za=66XXX16{knGArNMiCo63)s1CeO|RjLsgDUDYD{oFduh8qh8KJQ_F;lWH0~e@6C& zrLt=pWna`T`{GX7wJEYM&62&OK=x&2vg>MOU*05}AJ6S%hwS=3*;mr=m9;P``>Gt` zSGCE$n&bwGUxO*DU;DZ_*-I(96oaJ*ugCCuG&f{Juk0prZmi?+RHd?S&XwJq3O%xK zL3hir>|4uZ-&P{KB?HD}-%gWv5WAE8JEvsdl_vY{Djp`4%K5*C#I|A>k$o?Q_s;MH zsRr5iTR>ua9uRw=TlRysvL9mqA@)1EWIs$Et7ZF= z{;^K>Iz;ORWKVMxKhyCq&9Z+TmHiuqSSj0oRLP#D**^zm|3&T`1^&sCy}m;Bzm0O# zDQANM7?94oIsNtvr0~|SdNt=C&cf6ozS$LFk*X1j)Tx`krSzu6D^eE zrNWe)*sz>a(3tEKWC3zK%CPd zXHSl1uQ54$({1jwoP8ZR{2cG(Rm<7GNe-)BC!eDEmC!5aAh!G>+BulHgYhV+l5^;c zoWt;$*D0s4Mb7+IIfvKFIfA*O8aYSOg%LTY(MUx;=l^s^&glfsXqLm>Os7&nF|LZvS?O}l&X!Y^FXx;LIp?B1w@c1> z7@s#Mr@Bqf`52y0(F+>oT$lqCu4$KZ5%G)0E#pkQl* z99GEA-T89vsf9r~ZEWu)c5gNG%el`3{O+UB{SNfWX=m;MG!Kl(d9YE=L%A>{r-SA? zW;n=)F@7WkI^{fyIX4}gP7*pv?8I~#rpu~eK+a>tAFG2=IgjVSxSXywIo#EBxWqb7 zj>vhcNe(M&XL+8Srwu^+OqZOVN;%J_0>xM0u`(MbIRCvZa-O63^Avl5;(ZfxxU=cJ zY{}`*lJiQjoL8&myw)sd0HfD2cq0v}pid5~W#`Qb=#?{w@hS(}LRpOwq`oF>LffJVMZ13bS#`(+W3zna);YOS7x+mtd+BFOwM$^oS(bp zu!44Gn&kXW{_n-m3V8m}EN8Yr&Y#5pq=DpLi2g2=Gbfmm^UtW9^-TQRBUc@AH)xgX ztCj07lxyV34R|mm*Bq1^?3HVE%H^(x8*Y+o*UNQkx?bGFE65qj+yQ2Yk?KCMj3$L9!2SFfx|Q89$^69MZ}LRlzS8fkHYV08Y(W8dkp!@6Pj>-LB5gh_Auunqj%u!gWJ(8|&mQ%>#7Tx5~YtOm340G<##I+?#0rre3)> zH^^G)}Q#*o|dAH8y)b)Xy=ukfG(FkG1eLvnj5@Ep0%qvOiyzCh7F zj9IX{FE+@1DGzA)!<5|LN?}0m@0k8h?01^^qh9W;1?>OnL5tkKQlJ{f<^G)ub8_c~)V>tRfpUD8fC3h_6Qxe<=_%T47X?01J&spbQ8EMienUD27g$QzTddU5Z#zB-E#f zU9O0m0&R*!Y7~hwj#9|WS0t9J2&?2sqEL|(=2#U+Hk?spqi#huu2v*94F(n2g!m?S zZc0Yf$D^-y!60;EQTnL?t?1ITI zB<(V!$gY_0+5}UIWT!(3G{UGNyJZ5VyA3L`J3hO&D6&Ti(0mTX_e8r_jv{*#+Z)~9 zBZ}l^K?95_vQI8A_SvTu=x*N@MfO9yAEG?O`(wC2Mtl#597uuuPDKvtP~_l#MGl!z zq@VzZ9qK_1V0c&&pr6Nf9{YtAFssOXa`>JQIlK|5bwsHmMbtVnN%D~djv7|v=r%=) z9jJpTMUJTk<`!VS027wwkrE3UU`~0*zE?y} zDS-(^7Lvax1!%gw0Pswnig+=C(*#UZR48&fqBAf!b4-!{) zU5fC1AaXv9UeKb*g;juIO}!!)(d0!O(8a_rCU!|7psy_!4}XLKMV3@5a+$~ZzidR2 zIs%tBC~`$L9Z|48Q;{n%xsv3om}@W;;rl@3T8`klc10Q)H%=rxFYQoYi|Y&9$@}~HkeW5!D8U39@miV&nfamGMg`$J=v?sQ;mwSbdGd0xjYB@6yeXRB2UxAGgXT8ko;^Wkhs!Oq&HQO z=Ta2mdqCs`OOZadFH-C!De^Mg{xn5i@f3NL@vHTUyoTnrCPfCw8>oXxKJC|w6nVV^ z*m;9y-lzo%zlq74`Opd+)gZb-bgM8~HKNE{ZHl~|0lkV0^Ap@BCAVbR*~^(Mb?nN)=*@kL6L7P=uqU_JRpCP99GPc@8JgzXmSeA zAIlY4R}6EU|DQ0NCipX=Uj`KUmBbm$Svp7l;HYQo75Ot?k-sSN7xRBtDKeJ^nEg|y z$a?br%~w=4if+)as6S0nqgv5GucBs;qCv2_6b*GM8s^E6Hjkfl>J@b%lH}Qy(H2F$ z4n?`k5lwiCrjWcL;*ACs-I(WHZjz(uro=WwlQypC7CeA+OXj!YS(RH?D!R?6qT7pf=07z72S>4?v0A>k*a8N4-#@Rpj**B(}Aw` zoK|!%6855jy>o$LxoC1b6lH}R-M1Vj6y2{<(Yyvl_itA8fObU>G=NyXpjFX>;9znN zsZz86zeDR3J*;2RdDJTGRdhbT|BcQsWq|4YSw#;o2F6?-qeUK!D0(F3N0EGVIi@tH87=m}^`**~#D(UZ`ej87TcvOz^pA$H1~q6;S#T{Na> z`3#Q~UF;}&8b%f6ik{x7=$Y(S_9=Q+6VTMz7@kenRV1HdK(C_bVt6jv^D>|Yn6DOx6x4dMT4jvS3ir%V?<1 zf+0mOuUGVne9jlTWVfRAMKGo4l}(CXRjKIJG{lNIdW`^&YnZ#1{A))Qy^ig5)M>1O zX+@XP@KUzd=izmIJp+o}fWZx|iZ)@;M1dPIy(tqA-rS*Ra{(~Eh4HP`in5fB-ZrOb z3k}?k?v8X|?#>Kg+uE$?U2Tfq&HO#tK#bLNw2g-DWqcnV_w)Ck(e^?`Sx!eE=u`AT zI^}X2eP~EgE}zkdOB8*C_2sG_}e`dpc!&m(?;$v$`y!AqIY zqv*@^iuSYrO1`467Ag8#jiLj@UT;_Q4S16RtH^zerryp{bO_!_QS{wOMTdJ8eILyS z4T^qPqUeYP{fd56t?0)|3Vc$i=xBwapECJbfuf(6!;qq5jK}&E{i0LRFJU!DJ6^Bo znkq%VLc6wD(TN;IzfM>5n-oRAWq-0w(eH*8{UKM;sU}5#EK+ox1JqbY1MBcxH>2oJ z?TSw0pPZgj^yd~ue<9(QaYcV6X(mt6-?A0`JrgDs{i71t&a(ZpTG7AQ&QWx|2VIK( zTPRQ2@;1nj=gXJpFXOd{Eil6?5gD7z1r6wvXVvoB!*X7CNRG{m)jTIho|_JH@*>0X zq8;+QI(e~Td2y2Bz48)_6O-~%>g8>i3H|amV!UyIyi^N@ZmGbtqfVn*>x);WKH_2m>>FrY|Z{J#Z`{A2cBX9pIc?VR; zJFrMzevZ6@k`y_p47z|~2Nwb+2an4;gyci!`Rvb}S8)Wb5=6&Q{CGO#(&?3wd}5}& zlPGdhue_6ql^IYD{qjy3mbZ}37q-HjyhX$pQMjC(@+O#;cWMccyO?5&DY|$<-f8$& zkav2zyfaduP2QQ@|M4o>`5y^q!P%Yis)~UlI)?)1=E9h~^PrlBsxhgqgC3yq^E04K z-USX&@WN_&HNJb zw+%z`t|jrh8F@=FxglF#(m@|F`@-YxHG zYCJCh{0uvXrx zJRts7x4gH@qG0m&*HLK;Do1{O|qPA#YtC49fe7 zZhxARH{Brb7v_F#mdB;o`>j{r?*;PyNQYT@v!y`n&s5;({v4C{SH8Tz1sKmY%KN7n z@cxI!)>CwSo4kK(6jPC68)U*1FOBi#DduZd%+J=}r8SO9^UqFB&T%rX=UF%B~h zHz{Vbwc8bQ&^tqlxz&nA(qKTbXuV=yG0gE|@D5%G-pGr<>v#dU1=QFu9lCh&H@O=P z^V08jUhJ0I##_Fb+!XO{w>640WO?>MyUSn4VQ@p~C z`E666kJr{!@w&PUXo6YAGAn`H9g2YMj%asEgHGN|*UKB}`2A087tD5@;MH?%cOz*J zL$MqOD8@=Sw%3eedlS#4$$bhH+n2e0>3+Xrz%;J}*zV8V{{4y_kPF?49hd_Y$!9;G z=D5Vi4$cR14=IOf#R`&jiXCb}t73=I`C$a-4e>~K_UF^>;mjY=u2|8iV%#T)9Yvv| z2Nf&E@R%OO7IZ6iEXF0xiXB(Wn}w=)qfn7zrCExdn4;K8f^o%8?pLg=Q?XOfEv!~- zQHf&Zxt#w~(-m8cXfbA|F+L4J1tzB#D0T*dGijqiy4vK%H9yNYJ7npW&;@)|OrTd`~MVMwuS z(Ox^Q*mY>Gn^ml_Ua_SPpuHaL_1&ER8!+bPL97Y!jYW#xRI1p`6^b<@V#OP~g$8bE z2Mlg4hEBz9Bj+}nyFFF0JJ53vId*5AVyzxj!hm9Tr9ll0^OpG>-Y#ze&9wC?c5kg> z_vI^gf0E$+7_}38pjEL4nRswev4`mHA-a8txsGbZ9u~AH_DC9ZDE4TBVx5hOEkpBI zonntusH;M;C(>b5u_t>Kd#YQp?k2^SS1I;%3iK=XOu1q`4p8q|;>i^hS}~&7O2oa0 zpTqEZj9w^KtS?Wo7uytjIZZKcCd6LJR_s;a8Xg-U`SoVS-XQ1AO2r0=t)lo_g^In6 z=i76N4dMH4mSXRfDmL7p*!x|IeLw>rTEKXO-~Yx&rWN~$q93OMW}gUZVMejhYM4^& z(+sc^@2eF1Ap=Gfn<73%!jB~Ui1E4J^J++{oAHEHyz>|6e;d=U`lZ#Rq;Tf;%2$x!B)ksQpH2WLbHm8 zdlk3Iaq1LznU9c{jAkqDv~k6^ z=utd9gZFEeD!z52;@e=FK@;2ND85~a;@f+QXBt564qb}xi04k|cN$YXt5xxxOBCNF zSMgoZvZ_wTvqu!)tzYroI~3oeM)4fF&l%tqW;uX4m*DtbnNS8ZitpW^cy0=y-v`Y; zX!m7qU*`7X6Ud{<1DX`i&rKKfFcp zBT9gAQJdmNW&`7+Fh80?#dV4wLy-k}yiY9!CKNB}QT#ZJk1tjHgdD|79Uy)p<|lEa zC)dEN;$^jppF++lor*8)QhZUn;^obXpPB(lOcocyl;T`^;}sOBV0=0TXH+SECdOyZ zDPGy2`2R5ZUx(t{j*XuURc(r&gW0)k&%?aBMe+0N6u$s1m*IE~g)bt0@u=dL;BhJW zOY&h{@yqHJuk$$nbqFp;cm)$lj=DY-dKJI2O!2E2U&Z)p8fmbgPw{IiczZmF*Y+!Z zUA5wk>A-ktx#HKS0s0%zH=%Drepo#M^8K%>nZY#km^BA8u9r(IUl{If_5luXtCz;!oo7)U4vmdlY|$ z+#dXTrWAj+R`C@SU(u}iN}5}VS1(TkOeztpSv%Uz21w<`Wh zgW|7Z@EY?2#frbq_>Dou-yBzb6~$MzDgG9P-bVjUHuNd}F4}jQe~1H#iux+AMyWjQ1NxtT1Wh+ zGEVtV6q-gn&BV`nfZ;C)f1&WNsesvx1@$ni_-{1#+oa;Zm&2&yf0V$G; zix&iDLYv}$r$Ce9b9l_*F^9)LH890%&nh_o>&F%Uw-knyP$Bdwu|bX!KJXXAq!LDx z5`h$GR)U3T!faO}=m7gx8ZaL+phk%>dA49w38x&`x6m{TI7 z7`l|;W^!T)E^K$D;BK`_ z?7>_P`+L**J~c}0*R905!uA`>tSD57vtIkdxs5=XLsB>P9U z0p>?FD{*wbxKjgo9z*VeR3(m0SE8gqiR0KmzCno-+Lb8nSK>srC)FvLIJsPjvO*NVe~Al8s>xE~qEaO;=4dX-gl;8j^OU%h_!3Mm zt5Bk@S&7U0l(=F_iTYwCuH-1Mwv=e-P=bYKqS1f}C6jvMjP->_8S9JJFxHi08yV{=G;;M5jCIWd#`+Q-e)&qq`U>t} zi~Cb`X*W8Id-_}>Qf-HsQ(hr-{#k+JSTfggN|vF=>U zSZ+6C-GvwLdWo@qxSO%=zLc@2)V*G!EO7}J6z|BVh{Ufh4)}t3P){ku*sOT{O z^*G8raRFmJc@AU!WDa9J6~*x;W9>(SPhZbiKi$SyKWoA90%JXcI(~t7o@E&8SLZR- zb13%$o`Zv0FXH+)I9^UN)^7)J?8W%MvINIF__a5@^y;yU^?M!%D*nS99FH>AYtJ&) zf8qI`qBx#ltk*ALEU;4R&uHu~*Dx03KkH2Z{MRNNc>XtBztxNb@4mgCvHp(ZA1M3Z zINsUKSpNfz9k`jX{&_uP{cG$z#`<>)j%OKb{8GkxcP5TE8JpRL<8{U+r{TaSi|_2) zjLluk*!&V4?=ZG-Gh>T;7+b<2|D3TEl+%u7Z2dgOHa>;pUySX;wGZ$4QIG#6#ty7y zY_l5&+6eAu>@f=!+kK23p2OJDBxA?7Fn01B#!k;<>Ug>^c8p?0IN#KHgh!A!9FG$=Hif&!V>(d-1)Dz2s8HUW$9mP#&7Jy?o0U zW3Rx&qb^|Vm3Vmcm5jaWTE;#GpU3WG?A5rp1`T}ZG{#=LlCjqTi1lcA!ySyh5se=Q zfWVCG&5trR#5EhDntd|fJq5?90N^wnTXFyNwTyiR-alhEV{c<{9Lv}c)$Fq{|82-> z_Bkl{VSIiB4SeKH#y%JK&wYWh&(m?tVC?fz_@nz7`(wXj>@*q;W_pGo3)ma#9!dzS!!9X9IN@dRUk7Crdf z0Aqt=*_Ywv%L6#@(&ur#6EE$2hq14Cfw8}^pRvD)o?eMcu0jP@{hYC{M&noCh~sU> zcJR(Mc;`!a_9Z;~@@B^V%5uiOR>yG{V?*n;zb4~&ow2V&o7W@$-@g7{#@>bF>v-{o z01h;ABe3y}796<#W;c#2ar}$1ZvqfEU(49wZuTvx{987Ty^Ia**WTTX1LePS0b_p` z$E~<`8|u7$BV&IL^?m<(#=ZkU`~YR{e4DXd0CN`~!0A#Pfar(IaopjyT+f~=X)lq- z8I7qibC^}m{@rYz5NH3*tnJ9(Vg1g#j|!&qkDJ&;%r8VLSxFoT`uxqooXRULy)6o_ zk|F2TADRh=y^&4E+wwCzWuK_A38}f+FZ%>FnoLGjVf-EE9-fc8U%HuX;@pL|v3?;r zEGddKtm1Bzw(T*J|fZ44ir{lVc#g*nUze z6^Gdjp_gKrjF5aJ>?2*I*f&C|y?Z68|Cd#X7?!kN6xXW>QLX;6UlkK7{VG{xn`BAV zs$>}i1#D@ZCMDGVel;O!>m(Z`7>03Qn$A5EC3iYEujHD>*fclH_E(EJF=YElu1vOe zEL@Svsy?5(Of?MKmpkgn(HvKredNl~X5zDr;^>mm;(^vVc_nVxhCxdapQ^|F;en+q zmsU)>e@Q!DREwj<;wYLUI2f{*JVJjMkQ99+%?^`NA1*>+A3IiNhq-Wu$g^Y^z7NJ?$_AtjHQbPQ*!I7;m|YiTY#f89|X0?|#Ki0PTDRxj!h^7D>4ra5F2 zqRS(St}DJ)az+2KYlroYqq-EoCY4u?B#qu9)*LxKY$a9QF!TWe$aOuaXPoJ)xShh2 zXkrALsoS~#q;tJljCtGxs&N-m|E{J>F-0A(?kN9Vfg!oNhM9j zXvcfe^Ml|+e3g{d-QbD5)=&CFTMConSA zNSgRasVHGSR3pPA;tdOh4rG!C$aGLjWg-m|oJ2`XlWMIhp-Up2JH=A5HcSL`Gbg4< zxbax1Vw4*dmx@)PS|)f?0ud0rJ4RcnRnznyYW`WNB+Hy3H8n{FCx40$h+$I$=2dw! z#r5<+SmeGfa*AKfhIvl+3&BB2A(BKCzd>Y~806!$IPZPu#~m@i2NR9*0!NnB-)Y?0 z#~RXDy(O8pOYWyN_$TViNu1!bduY3xWfgNIpoM&Vz!v;E$A`0`U*W{W2nSdF3GJjj_3h+=H(I6zD+Mje!^4+t#Ikp2_F|Nw+s+{!Pp_RV86n9=lI4rCfyPeE}hukoa}7pOt0SZz`-P zvI?26=p3t$u%y%q4d-WrivAQ5voEhU=3Oh#MxD1jFE{KCaA`hDmQY zgb9i{O)mZ*E>6I>{}0&s82EUdi$7N9;uaN*E7%;)|N6WN{y*6RYG=nI_a(U z728SmgSa?l>j&9*`v>uHN($iPbvDj0&TlqOGV*gafsyxyrHL{5=KozzULTSPMh?~u zmR$skmeMRJ0JKs94jl7J!y2mqFIe3tI5#20W5<>MHRtXn@!0!t?mFAvH_5iAaBWg- z%xBDE5nnKQojsQs_82B|qzs~^^V?(25jxi!%mBO=jJm$^G#(3-cTC|8;4gTy!MRZr z81y8EAldh2)an#IJ@&zjdi)|VYOcGAKqhfh!8u^-5$4>IYo|BGCD3X`5( zL-#cJbcLD49K{^RoWX3T^8)w+o+Pn2MwiCMgg7`GJnkx1CPt-1m(vJcA%S?z^D12- zJ#nBhDBwL+?|EY!=yUKPkLlC$5*P!94YklJr!wua!??D@tERusv^@s+UYZ#%cD@4z#9-PXXJs)1MJbC} zTCHU{@bHKUoM(VCKh`=b-qUYpU$s`*O5%`bWpVs>-Ums3-dS4WBS*;WLfOCr9gF@a zRC)Zr=L&>pMZa{4K!lS;Q)h+wL;~9cEwG+Ebusxqnr}t(lmSoymNsAUnh!ZI|DLnw z)hZXh4JksE{!u@FoZz!p@N413HNViSO5}Leuckh?5PHR25#(9QM=o|!JGdnKv3btm4z8&>&+C$Rx|cYfy5tdG7YLN9 zMu%5X238}dIhWBou3Y5YvV%*Hk;RSm;!biN(*!86l;$RMD^K}CH5n{o6b7n?JSOU< zxJ8n-0KMXz9UV(L2{BB)sg}KbmxNne#2^mcl;*TA>J((dkh6oiI%D=AeHf5tF#qax zJ72wzi*30_5Egu{x~4xEY+uyzxdnpe6HgR`6S00?0hBN3UsF}_uVYdB0@0@lC*j^n z0xGN5`c1N<-gOUso>n-4JxWYKUjy>Ul}`3^T%27s>P-I}^t_o9M6nM8kYoB$;o%sg zNcXDcN7!ob#IQ_3GRcdqxS%j_OfT!Re2Ygr=Sg}{I*WrQz?~^s|2SO?`dDGZI+5r> zd7DUtZDgltN();Sl*dG>QTC3q^CVj4OkR{^;Y{hnTP31f(#Eyk-EAlg!%BmC=_qF* zZ@w2$Z)mAbFfsO0tv>!!VU1&UENQD8+s}fdw~Wng?Or4h)i0ig`=^QiS5ATaOZW}z z4&DU*ed3bdRehec+%~VBki{Tz6c>ELR$<$z0t9@#FnOy1C?%#06+a4C(EbB8je6u% zqI5u6=tL=!mc@v&MFbI7DQrDRuXlpm3O%t=|esVEoLSP#h7C#rtkP=8|7{gM5=v1R~ALbBZh^4pmnF~qK?!?uj^2yk)rB=@1QnuokSVM=sCKgQhOGs)*Vz+) z<)XhVa(3_F66_+zO+3skiT2(P{XER%VVRf?x{^FDRbL?>{)9}A-Z^SBnUH5g&ZamQ z86)vkumG)6rG!$(94%8zP=rpsYpG4`or+(CPThOzQsg=!fs{;xM(j$WHIL_rgPtOoKLZGA_Z)iIA*IEu0j|s z=5JBNg@PIo+A$m3MZYF25ssot>DMPu+vt^dD$pzE#pk$4HYzMMb}Yk>4lqdP? zFsqIIn!`$X%S)U-;+~)7f5*CiZsvE3ZmffUU2-q$<4?35|585YUbv8dDx<4Fieh`LT`D$hArR7^7DL&lJ9)jOgYmi_`PAN!|)r3J|Rp4=Qo@A%kcxj z6t0H>SBjbeEldGBgVhSFek4y7HA=>i380&mNCYO(A4J$X`u5!hmoIOik2*3|OkQ zLmRG074U&bK38T(i1Uelvpel07^C%xGO_6R+)76(x8~28(#V?5y z;}nZ$Q*@YCI#2&www(R{mb30(xA2b<_qtR0H)D?U8U96g-NpPiRochIAhGp2i>~IA zV>PO&dd3o)n#H*o7(srk){Ja<#fdt3eO9N^0DR^ z{>bH#rl!dF8%7Xi45cY@DV_atG-%tw=$arc62s8cr_vSVA+%nA#e6m}(pPm}y`7JY z)kNECy{}Zvm8jtcYC$hJK6C)wiS&!P5^zM}+nfqL8cO-q1Ag@{6gz(jrMBt1zwD={ zM9im#r-fBt%$~74G%chDIs!V`8A_!><9}BDsviWM3jI~0fYOuUi*D8|(KqT7Ep2nu zr(Zp+>%)=^Wo`oA-JX3v3Pa{1z&i!ZsX%oR=fN-Y@eZgXfSqccfV5Wipa=Xfz@pj{ zz)bF58krvPuF0#;g}3veBR;BAQ1r;mX#FBOlgtH>vP@4~+BB_AgdI~9Zx5%_;qhPM zS_g{LVZus^rmr+KMKlai(TtV4rbveS=vDkH8P~m&|69NveS-fM=XO2K&sgS0nuG(* z?wNCi-=*9iZWfB1`_>lWM9%&5Her~b|1GkYNrOy_=;n~u(IQ<_sI2V6>LPk802PE_ zbYY7nDc@g_Y7wabGYmD1QfpBO#!ctl^MuftC@z;JaA@7nTC88&r+J^SRdbTuym|AE zTZB7EtQSNrR4{;Hf%6G5I(K3`AEUDkC$fjx>qm(3lt?TEgvGbBGgj}ELAL`F!`bPRP zXSIs>*T;*ZERj1~l7T?7rm z25Ya5)i(ppZIHpbUIR2>Nbp#I_AO{9^-1ZRcez;(?5$L%M3(F+@CF%v;Nbae_d|&IIlh@++X~AqlGd|-6PbnSIc^)DfEBD!vY<* zkh85t2s>|nL$ZcQYjk|PGrKh&x@>$$CvE3+zhPO%wMH;#1e*H_87m%y-ijsPcc3}3 zJ6ryEC?{<6THfj`D2LPT(2K$z&VBH=!keOd{_8@T6lp&VCIUqW#>GY-F^On54|Y;; z0(1T0>hD))%fE|8)2+{Yy(C)-ea&dLE1ue#jwS5vR+9EOu^_>9WMYJ`cX_>IzG&HQ z>tBRKXP@vmo%_c(gh6g>Vl4Aa2Lta%t>T%jilr7UPPj>votKJZs19yVX zF&^W;+>OB@oo6^%BL3HyxAs#zC7o17>TE0Y=V6ePz>KlVPtK<-b?{&e2p)KPIbH2w z&aRR>pN=JrVVMiknFQh|=e?F&HNXw68dc)UdBxyomq*dwX!)|;p~0ES5n1W{r#I<- z7gt8DRYTmss@!tHP=uSB%H^5mGUHs&iZ>LVZoo8?S$G&}OeM@+o3*k{DPeNBmwANw z2WQvms_pz_g+FP%XC(b!c7Aw=Ip7v}@eIBRBSuG%?yrNoL|HS~iJ15NQ9we?dHNkI zvbEF$S5>K)hyKyvFoc`~juMFlqime&XmE?c&?UJypI~Vj>FxwQ*TEL5hmvoD3s!tfVTM=h-g@f~B^e_}0oE@Cg`G($V zO?H2B&`)B?-p+%Ro_j+tx<9hTi%4mriDpFqeGi!Dt=Iolzr=0BXMN455vuE|#M)(wmd`QeVTVFQSOce5% zw?ueLAp|W+43EI)MrTSm(&ya%o9w-xEy54oo|KaPatPx}LDD%KUjT5{vSd zesuSUbL$2@?JQnxtNCaC7j2!n+HQ;0qbA}@mJL{OtkbowV+@;*#mNF^=S=~$HJt`l zOjp%R+!|Q6Bn~eHZyT$6zT@n{V~7=Cn)pX(AV`X7Lm-d!ApoQo0ZYvh!0_v*ru=D0dJq1n2ef@q> z3%L28iYIisGAk8i_Xn!)JB9Wz#bh#uovm%i?f_=ekvlLAxY=!q5 zL)l=kLkh{)jg=Zsmtc3C*DbK-D&=Cw~$7UFQUbW-NsFcs5f=)@gzWK8J z_^fb=H?4pmL*S+sNQV4`JOK*tgk#|txPTG5>#?x95n_ncU^1By&?*$ZgXV2&>M6j_ zOkifI8@%CIs9Wz!@g==1qQD%f{sk8z(@9xdap;Gu8l9EZ-Z`g^1jYu_JIEN=( z2d{ab?WACd;iP7H2fNNkyTg)o=R@j%Nd%uD#CcBgFD&MPN-g$hjF+M8zu+ zkdQf^R{e&Ryr>N?og--Y(N=t-ruJ)teTH8{H7VTDGd$tXK`W?S7ZMgReNmC;0w1;3 z*|!lk?+QW#1mY9_7mKTx1Z%u1S3NX^62bF6#csB|UkKD|)-ZA48$Q=HBWqi*NNo1~O8 zutUQIi4u`>U(C_XKyL|nR5d~h}6ZaPjnjdNpL zrJF_fR~JaVhMV}Dq=wvI-5`BDIO|c^48x!txF#FnB#{3+Z)H0vlLMWzc_G{q#^2xS z9|Av{>=^Ehl**-ucj|O5yHh%)wuc}beWIR8yw>YLctftMEB8lw;`mW=>5Pu<*I7V08O1ReL29R7a@lb&}JxM+!N+_ep)umOau9V~;~VqS7tax+xG0 zvvgRhWtPH_x2EcY(j){>BA`J`fOep>G5$;V;`Cg)YO!jh$pjsA?oam*dum^Q`YeI# z<3xqaX=;v_MXs-(BXTvygrPs;-28EZss3tQ&9oRVJ$=ZL!T&kD62X##p@)3$Z>!Xy?K3ZZ0iL0_~ zm6HT4I?lKENzeYDW3hx0tP9)Jbu+htQp~;b|!w|IcLQ1Zw_x=ljzSNXMhOAjd7if-7@v z9Ji8_-J5?Qoxx4g3}r(9VcMAme@p27DP`LijlB7u4m*1u4P;5T^YiDVS0;Nm`;Z0> z?cMXwOYI-jyIcMi$oIeR1FfVPoe`&HZ>?8oZ}pxa!v$LiK0HiYK!T(mYr1stGVUi zEXfG`e3Yf9F-;Q{$+_?)a9cXuRHW`=1`fgkt-me|(%289d92xe_hso#VeD!0 zGc0|O1gs);35f?6K$s*wyEpY0fzh#|G+p%b;%V^c*H^*Um++d*kH7v#C=l#O2wZ+( zI8L4rd3ZC!F>hI1!pj;@roCbIRhlxDgn+ei!Zr8|c?Ml>b&h*Y`n2?T-7N3?z%1_q`yF-2!n^>NBPIfRTM zYyJBvGbzBH1Y>7rzZr2JcvJF^{l{q=g8h$jc1+JTcAH<-L?J_&yXf!mQ+n2C+N}tB z5cwwDlLR&G(-g|Qv=5%>?UE7zaf)I_5H;1GAptrq^v_FhJ$tu#QB^fb;AJ{RH1~|Z zN^g@q6^r1p!4#qH)P_9b#cV;Kt_S5hWRVoh*6Ko}=Td7-l|$sE70#V+Ntr^rb9k!I z6qGeksC*I(ySSj21Q*UbZ%L1|QAx`5^j6AuCq$u%xZesIgsHB#TFgt%Z5u+-WsO-e zDPb*>B}w*9-<8B;sXqj#TNBs!tXV5>0ZC`Mb0r=`Ie+*JPcu zyX2Vj@9V`Fi8wRb<-9pe-Erq6Xb{GTh(FVLu1n5^h7f|0fWurGrslw5`Z)1T$Ly9X z&M$6M>DygZxz*9XCfY=Ex;x~oodCxWh6TkCnjyM2^M=Cz!+E+}Zc>qN8DhsQDnyre zgi36r*V$Q>!D`LG@SLHB>P(-&_QBIl zS4Ni0pAI{Xz;DYTr~*#>mR>pUe90Hln^;aHIe4}S*2@XC_7(C9fNy4IQsCdbUQ9T< z-{BQ!<}iN5^SANNYG>R#b#Q{ft@4(>PLAtjWr0gIh1&w9zkEV)>=`8GD|(b!w-m!I5Bpd?GGQaL!wBm{#P@j@Z41WakgdGTg)r_ZdFE(>P0$K!?d@#g0Ez47KI#V-?KT64S+(?DI6L0Aer zK0|HOV_t+Ud=6M4U>m0Hujw!vOf-lG#rzGCJcQ{-n=9dDqMZ0MZB&;GpEz@64434g zRG3r>mk7DYHauQjh+h>i#3dc}xN;%p#IVJ*(Kuz!CWz5%9D*;6?H!{{vw6#hm4Vtv zhrr&b2ZSOcv^!B^A20O9oBhdVJHVC_M=z0Wq=YRc_rV?VSaZCua4B1gZ?~J1{_$TY zng>PJr!}M8#mSbIBqbzBjv$_=Vf_hsScVkRXt>_x$k;(V(|*JDMA7`+XhC=`kEp^# zcOp|t{)UneAJdkcnkD?%Z9L9#3P*+ffAVpp=%;?w16B{W%Ya34ZOgETUY zvU9I5w6FChg$bVksX*SQV7YsflArjp_ ziZ{qJ`D8h7<+qe->+OKivSw?0P9U;hX=;vtHCA{WHvhWApg6h_?;%DfBbfqy6+=^p z`UNuZFesw~tHY8*a7G~qVMgjcvj*Tm)+B{QoX6j?G6yL%83qZ)awF|Nh0GLvhNJ`h z*pFj{&Cb8yvJ&|^t+vU*Q&4V4iI4Zs%9eQ|>QdaJ)k6HcPS@L3a(W#wYFG4xHtcVkP6;g0F$n(UJyXLJmrCFrrKBPC{FrJFgQ)teQ+x7 zqNF|~`iiw5eat7%%J-U){_%GXs=O!+y#sGj{(F?i#_Lu!(BuHH*oH-D4Rg{Vc%*LX zVm{?rDq;GaN|+u4kerQ23p3IlaTaT-h*m3YP4}jKi3!4dK33T3_?F3)JSD?gaf1*H zhAu1Bu3ew1OpDo5=&%sKz}fQyD~o}wgY`VHX~Sc&fZ51wJ%s8iy%7qSM|iN}z+3x1 zl;^zsJE_Ond9 zQmU0CU1}An@F(YS58q0)7Agl$fcZs#72Z_*xzO1!!t{Ec93Q81eKmwp;mp&iwEB4}m&)e}eqL~1K2=Ku&!LzS zK2M*WexAVl1xO@2U^IwzS@w?Bz zBR|W!Kl_)QCT{3mxn<>0oq0b6cGCiL_R$R%*pRduR`U|VTD)KZcm?3Qt44-NebIJC z|7cha;`!Q|jtzvvfny4Ez^;oeh=l`6NwRDor|Lq^9}diOZqtJUiT??Ndl8%x>n-}j zB}ou`s({!vD9U8H zq6&sgG|4a|{D7h1jQv$Djq%DLBxEt%*CrcHhTxZ2H6X!6|CL~zkVNoBE7nJp&-oRe zsD`422g90TAWl~Crx!|!#Iu^tbAq1I1&#=aIDpz|^<)va)T~GuNoht96NYd?z<3I- z%RX@W6`qO>8%Xb1?tg!s;1yY<#<(MdD)l9m8?J$LbZr*2sQsS9JonA)Z9 zvk{!JZooM>;)fGQq>dzd8(SlXZ~_PD!oFuJYnBl401giV#R! z#e-h~^+^F0(QWGejJy9>Wmn$aG^WG@?);s~y|#PLHl^Ysw;{*|W`3x4RgbXxB^n+=eS{+TskgO= zGEr*%*uFg+*ydLgoVYBg)|NH69!yO&up>2Wi z%c@@%m*tO`W{w3;SN+>1VMi{pPVn*K+EB;b;k3VYIxRcg(Ga)qgj5^G?C+<+*97zp zwXX^zAqEV}k0()DVH(hlk%XK}H^HXaYOO?L?dY7S_0zdQ{M*e~VdV@hv`A(PX7qw! zv~>kWR$3Jaq>7<@G8`7Nz17IHbbI?yTiZ9cpCyQ7>Gr63Mo62!vam?jqvq4m@n4I4 zP&zu*Y!}mkP@qyRhxx#D?K9f(H+=SX(G8xi{)V`t=cvDky2tEP|Hf*FbFvyhJ7?ad zw2w(s{U(RU(H#Jkv0Cg>N>&?mc`^MQ=EyYqIbBR&ZpO9;qZta^guQ|;MF@RtMAy|} z{;CG{qv$@I4E!nWX$rgv03T`!Pg*(-hT4Jixb~Imk4o;+`_x%WB^n+iA$)s0sD)d| z5y773VC3lV-7=qosb!U>+)Zu_Hure<6m1nEGg7>Kx7(p<4$ z#?;zd;EXPj*e)7BG=2&_?^<^duHKbBoq(30DQD|jfrxf@&&u9T3Tt?!`*XjxoehbU z&msV*4rLKWz8W*=K}u@QBbJs?0`pc4z>qxl2XwN)=vrMlL{1Q8;` zBQcR9qQZ;m<)>)y-G}`Ci#4AwgpdTS5!T|pF#b;(EgFrRzOcwE5-Y~pYy6@53-u4T zC9VCK8+-xNpBmt2Fvo&Wdp+YjSf7Ys1`oa(jhz9nj2sS}v*}A>$l1S_4RycVwq!8L z$ER0YwQy^I0vha!OLpzJ8GQfxkGu!WK^kO}?sz^sbVQqv4YeiNfS*Dev`I$`-?Mh> zrhy%Y!uqHeg5&-tXl{4#wd>)U~1)JE@K~G%q&Swpi95 z#`cgI>rPtLdsr8K^q$y!cV>%rQrsPztF4t=o1r+J#$3pJmbntt3A$+H96`uCjnDu; zE`!4r`+J-#`qa>J7)EF~M}`byGYk5HzrK=0cgu+0l241*=-Q7zdV{Jk2(0oU(yCOY z+fVTVaOYKx*p`~JtE5>rIY;4xMnXcC66835Zw{LYohyDL#>ZHmt0}GPGKcpOlStAxZ0Lcxw0|Kt9){K8YCzZPK=EVM|g4(CEAex z0*`1xxPML{U`b*9oXo*2pWp;esNX?sw2WY-g!CjLrD>a%1$kURgNXKOd<8szFV5|N zwN|wxRubX{WD`+M$O6xzGZ71tO2>z`ZJz!)o=>uI5z8enhJ9v~(@s<=7|51_sMY!f z!_=9>-^t%0QO|)Mdm(cva}Ai|wMS@YTJHJBYsdDw<~7=DntSq%+LMC&(cRio-hJzP z+8%l47qNxJhHQ#eqzhKpVm^zQRg;D)4v{m71tPdP<2Bw_sP zR7A!`JNJ!yv?80Pm1Jq~QLTdEEFD&n1K*tOJpM^jv(EgmHqvTNsgj)Pyzxmh61=sg zD#WMOU2tyOZAGpc3d&=5dVvL~9UBTN5OomQ0~wGeRDqCG>@Q5^Q14NiMlg!UsH$PX)^SzeJdzu~!+rEY>=+rloZNziB@f`CcSJz{R3ZWFbmO`Sk!^%T>Rk$>H**Mw zh$93DNh&|*Itdh==Lkn?FyzZzUj=PM$qh&X!j1o(Q)QMLIZ=>^BoM2KzQ^g(ht`NX z!CC>098(fYqC4$jt+VWMf761*{mxt3qb+VMrC%aCOOK7I?yW_A74Hsp>f=YdfB2M+ zu%PxG`e(}SRX@}h5jS(M-eS0mAJad@yWjt%-olP)Fd@c4W}OiAXL!a58jgu*Y|PF| zFCq%1VhM4%CEO9|9-JD!B5enVBGQMgq0o4!+*y9!3!M1j*?=@iyxATwTc9J9*C-xDn*J*8v(xe|67kaD-Y<2 z3QK%uT8?r8JI0b#(LBeAm){_2xmw`Z^YQ;HU#b zT@4Aan>KHk(IRM^>!`x->DQERdHNDE!e9~djI`ir$mA0#QnI7AvNWmXvBkMo!4@Yx ztL3qm2g@wgr6h1KP-PaV$5!u%)K{v6CCE9{b3?}g!Lya(Ya#z?f2dut44LQJy)XBN z=5?feiwSnfOEIlmV82Q_;)N5hmQ2I1hxLGNeoKs+m1RNFBgW|G2KHnDnw2c4&DYp1 zg1%Xp?+b6MEUuZl$(3C-(X3C!l(7`tn*deY;pNF$HVbE(=VN)TgBck7&-LoyFsN!P_KI zr;ukrt}&(wY^ny8dp?xzHB{+7oIs|NkW9Z^v~U?LWFiLcZ)>`B2<9Hq^nB;Xvro3Rcru%iDbh+%qXM1^}wcc1j`>sR&cj%O4%>bQ;d(B%gy+=VpO4fiJlsFLWZ*3z`x~+ zj~o?9=Fyc$&MtAe(Ibz_VMDW_c6qy=+2X7z-y-B~e~ef_U9B}FP! zzQ&1)Mu>{`vM7B(`lKEjQMwiq(PRp|9#)bGDO1%QDc0!ygGuqGBvks(`II)|m6cwq zJTfMG$?_UK+KaR@o&xlqBvf>}ikGkliQ_OtUTiI;wfCiUPe73KX>Kgk@kz|Mr&FMTe1(dFDxl>?}k7I=-a9t zp1bYCNB9R$ogry{rEO`KP+G8^KG_1xwW&KxE;l|bcG6rL3Fnc^je$5#uh&B(Jr0u6 z5b-iaIqz=7uJQDhhU85DykU-cx$=-yk7(GUI*~@NFeizx^(1_9b9Ba__pL_vsy7ql zc0ze1ctUP4#Mn>d=Zl4P`kZFuGqrGRwgNNB3Fr{rz+NX6YWVPgsv)KT1PoCs?`X$y zS)`!DhR53B_(M`XW>7!jYh6@;vzw_Gk8B%~{5r;*m28ArTR3 z*eqSA(?gSeS=cU8W|0>xOt;EmA5zO+r%bggY+_ot)2`Sl9c;o}(R=^MpnXT##Kdc-{ti<&%9_%yo0K+vdnz{5RV zL)xhnQm5bwRumeWhlqg*`SwuSXCn@c)*LuKKU@@jZA%AYUV=#W=2#xiERS{{v?F}a z;v<_5r~lIYTC&+8_xN(P1I1Zwe8eWX;pJ^0>|i>siZs`pf=mOZPi&plq{a@L2kC(Y zS34{2@ikj@bM-6a0WTrROst(JJ8LdC63!c6Fk&nM%@S~Dni0J^^3-G-meIf zbN?5Oj>fyc#w5u?NSp)7?BuJjG)hq#lXwsbki^QYH*K8ZD~--d$Kq~290a`|K0Ru& zG^+~D-XxKOTKoLg#%vmNzIvt6T>ED0{B|v<67Ev1A}ot-NO_XF@lFe?+u}&9>>Lb66%RKzkXh7 zsje@KmPCb4A3AaF@>}rG7rg27px@-%=eE4xRBLl?e=LB}Lc$W>^dSePH-$`H7DSre zQd}u2sv?#~3-2@MDrY-AR~yalQCAzAg|RTiau~xACk8W7eIcU(3z$6B3Zk=NoAVZU z${UIj-MijM3_3);2^C^#N$sRAv>_2NAh>JEosCqXNCJ8~7F65k8&r^v z7IS~SsJKhsFf8+`>wGh>+Tfe{gWvc*(zfYRgz2O(rdg~h;zZ&sSlDTEuyc3~RtCvM zn9xX-b%FOmJ~aY0$UpusdcKw>%AyIg-o!N(#;^5b<4J?-K23Hw+HQD_|*>@iaK8~#$e$2EEaK?Ht ziF5w~IlL0`wp2HE)M34ZwSD`uou)5gkN-04KR}^5;19Pc5;>isVEhZnxp}S;EH`cO z+3cluVvv^j0`0|!$K4?qyKcX4r{?_ksPAfL;auO_?uv_imuT+R%YFMqSG(GW5!-Ns z?>y7xT;CSOt^COMV$S{hpMBH0Iq$wp{tnlEhUS%k-asQvXw(z507zk+x1}(WpNJ~M zVlKhc3gH==JxrR^ngqIYKIsO!TmCL&I$3CiLbM|nA}e4Fp&OJkZQA;%gWLCX&MBI^&|W+-Jj`b+47oWI9jHplsB-f|iX3Uv=Jf1LXycs1usI zB;y&c*HI0}q9YDKy$~2S1iu~BK5y`+W0KcFg)}<}Xg=Z`*lAXBnT}W}MXcszfQ)L3 zX_ARphqXD?mgD`A=p!?oqNCY zi@O5t;R%*QcM=w$h%IMUW70YuZ}}1?-7XAE2L`?F0ur2#9|oFBG(Qhn+C8tkkBa_h zX$x5+(-O9fzRZ?%NBXFG_X+Qu_`^VU^xS-RcfKuXylMn_zq%;Yn)Y-3MzD=0m$*l_ zEU5ify4@DU5HD&x0@~A}u#640B!yseMmG&|=i9zLJT~wU>H(k%HkCgo(FaS7Bw|cy zbkC2`yHo-(g;ONwTK{-B(6)_i)n1mYGa^m(J)y4iKNTRL#1Ms_G3!bc}>|D>> zo};3nG>WCv#Ef>UASe~3!NZAkCDM1W|li2~yglPgCO*ZUQ zA=lc|6Ujykg=iMsmv+gB&S=lsR3^7_ZNnT*Jj8J{?~r@rzkPoYk9>f91#1C1Y(>H& z)Up7!Tr62x%1b0V5VdUeeo?$X6B&Okk|WcQZ6!zc?#)C6BTZp4`_^z% zQ~1`CdH&u^cY>pwEEq-sj6!tk=)%VZa)n`bbxR4lsC4n4Gx5LsX3;Xu*!VgFm zqj2(dxJwF&Cr;+MQv~rOBrAYO%`~+52grS1s*%Hnm>TuGs;lJwTy*^PXzo5yeBi#D zB;oG61?eVfcJp6mm4Nc48yVhYn(g+nBvBOywPx`0H_ zvH)ivyhKP@-N?U-^yx&omdXGdy$vCjz-fX8@r+=}Zw$8EG(RxPj(O~oIt75p>0a73 z&~6%%1%TYWHjdO}k=@iD|r3!*<44x0fgdMIOi7y9uZLs5aX(Zzemb4&iCn7Q^H6Vvfe@jm@-{d}Vbim}?9VZ5^4@-2Hkvu^4$f$(D2{b>N1&y-&TWYhiCp8|Z=eb6v} zN8C>b%xhWq*CF#E+x=L(`M02Z`%H7NF!nC~5t>TZhsB3Fub2Xr5U~DK;b`vf>!OMf z?LU0fo(P5?Y@nVxZ|fBJRs^dQk~fi)>_l$|t0-x+R}wR`vcYL{irNLWr8VryY@fFx5XIV+D`!q~0=)as`D^{{Qnd}~*r(<$GwgM-YagQN#oMQKIycsh2{Rs>H ztl69p)e+#-AF>ZPJGYp5_Xnq$mqrAdMrFV`{d6-u7J1KFT^Fl7{`P8Umz@aq9G{A1 zL&=_Gvd3%khf|ksx`mI1)n43^O~Ak%9u$h?!wuL_djG+d{Oo?jG)co=d@Y8v&T!#b zHE&M4+Rf(Oy!+wrn9p+VUvD@6!a0lYH2>lL=x%c}LT+G%=!MvaL|&e8or-ksscYSPl-OBQtl z>2f8XZd9igb}Nz;d6=qQ{-(ho{tY&beLqzW*ikTVJ0Tmoh(sV@fl?ys>q?1AX0j`{ zKom(scR}u7sH@aeTP&e*2%q#D=n>vsDyCN8HK9>%m`juudxV<9vm4Brvs>8CIm7t5 z7enAR1gy%zs3eZz8{%nGp#vU;UfCT>lAUQ}WSQIV%VZ>QPk}`^hJrjqvEVvUS}X}g z0GW z5Ftq$ST@r!yH;S?D9d51cAYSuA@?K5KY;`i?pGf%zol0nAoy)CXs&%U%?uVU_&CeLMFp;k0&PQXY#XH zPoqc+qzJanarcse1e9Cj^OwsFA*Y%mb2 zF`@+mn2vo3)r7=^7CHmiF{Tr22+5xWf(c+qd6a+?NTJE^ckW19*+7!_-uL*{gJ$No zIrp4%&pr2)=0o<8ikRoL&*VoINB9 zxYX@x7PcofC=Mp)dg&!ftk_kDH(gC*aqVf|RR>OBq2%LS?TEyV8ke(3_t{3B)QWxS zJcu0!xH8gYA#R_Tx1bu`g*KWe&z;mFG*4=&(^)`Fb|qQF**ERhhOvhv)-#NpmMRw+C5bOi%4ESpBp<=ZVeJdLqt*FFIoCv=n*I(?AL}!t+q3ShGn^q5Htv z3Xj!iO{wM zEZ!MDXZY|rOJ@%sKD)Cm_iS9sOl*BN<(zWNt$j!K)u$TfA2wbKCEW{4WtPqv-ef#m zH)~RJ@!95MrZ}%eDR(lYjX!LDL#i|*zY<>K_Ag0DV6=Q-k44#-XfDK@%Kma@$h{m( z1s?^3bwZ0%j49JnZ^nJGrd@Hsb;`6C8y(E?66S+hN+y3%wpNPrfEpJJp=~ zm(Yc7gV%NtG9dLk53PMqXk7Vc;zd9*eVeakb0*D60Nb zF|Wy}8@=YU!_{9$`7R+0z3u@x0XeqjWMXQNG-}ZweQRY8NV+jv%1JPxSG`zBZRK+< z3e?Tz78v7!$VgHFa7tBBI1A0rXq@Gt0h~@-k~cxKud^4=!{TSTjHYn{18o;Ulh$@N;{s2zkf&W4!GXLN2vV^wC{3y%Wxej~9qg!2 z6UwAR8e*8gjvqj@fE!{F9zToB8~lg$SQzUBl2wXdYw4{Ihe9}b5gaa2uqF;Wj)m-`LA8C%Kyz)La60b| zL0SDNe$=+$ls@Fe)3;pK|M^3~BOD6DfR>@+H?!9V)$Hiiwap+6UI7KWonE23&~M63 zmn+t`kSzf(o@Ra1)HGCG*7QGtWknl^q&r8E0Rh_j-##@~wG!rN~x&?2SVd1i#TC zvhJw=b2XiT>I9gIROk{Uzqe3B#eYN>;f^vva-KlkpiIFi`5|w%ib<4tM@RjMXcK>> zV>2R7TKq=iTnb;wmjR2{TZD*@>arra(050^xeCCblC{kQXt4S3ss7B1s>$v;5B}pM z%qjFOkaC&cydhymnV>B~F8+-OeluN!M}~@lDuoD)NiB9z+g$vd^99f=rPk>vzuBQ)FPiZM8ZwA#dkr# zWt#Fwgym&==5$2gjRbzjiulkcOD~-xHr#<3>dx_mCT;PwQL{=LP zRgL2vcGjV4ll#ZP;E^5!0*HtG>R>g>wj8R)nk>}@)|Rqx0dp0HB^p)?83`l$Sp8xZ zDMJoZlcKPM-Mtu2oqO4o!_;BZ2MD_O_5lgu#HGCvzx88)Pea4dOSg0r#q6Bt&&1;y z*n?M3ad|0UL91V6X#FnizkcW8>l?wpo-X_&|L063^Z&4#ztE5W=J1Q~ZA*G#XyN~N zYx*svHu|BEDSOe}32zPBcjsW@K9dKig=##4?P}@h*fQLTSdfok?c}`T07{eyg`Kq| zz;c|VmRttAhao}l8@mUxX|t;LY=oOKG5pq4E74eMGO{d^e1TuB2Z1PmySD}cUL;^W zOSLM(Y+4nfl?TEF{P#jMfwuwWFn;dNZ5IYN@^yjUyOG%#?_b3=5UF&_HzKW-fm9%3 zZAF^k+;afzxnNJx$Aaf5;X)RcZlu>kW)mC-LET42oJiwAPq6g$(YGUj0r|9&`3>}! zfEGEu(6jn3s!d?c_mFiT3448TRtNuf%>edGGm%h9a0)+wA5JPZjhp5`pcGsOq!Z^P z8pN&fLWB54I^h|5gB2nb6*?&sG(tj@T5>w2t#IU-0o!2&&4*!YDl!u%V|X+etLJ&} zyzXg|d-iO!nNA$GGM(@#@dj}=9YfF?vJ9eG=zP9NffkXTE11ovUyC+Dc=;4 zpt47%rqa4!5gM?<*hsZ&`G7;MLl}bnleo|J3A^BGHpvyLy*S%qufFXL6fUc8qrH&o zjd2U)*wZBX#Bh(jPFwvK*vSaP>PzpusecGpX0Xr&cXrD1q@6_!ZmK=p4Q9bWzWwB~#0~LYU(?kGmC4hErX)6GOFiTt_ z^vEL>UDXx}w?tQb=O}lA*hUa}QI=S%4yV!js_kWt7$o08z!}McKW$VqGW*pP9(5-F zJ=mIpGnpVLt`s3Z4q9Eip~E}YvdGNxSF4pmBkjWYbAfkab@>h+;*Zj9LAdLI+=u5?+p{2i?kJ|zA)G49;w(o7 zOT4QRW+}h|&;tL6_AK$wqDDL^l(iZB`4BY%jVhvI>70W@FL#EK4~A6lC=viKr^_g> z8Vcr(;#+j(kMTrUUMVyRF_JBeZG>4R)`VV#hMViZD4_)=n%{RB9)U9 zM;{AS;P;m$h?NyU1V~6neRQk`lLo)Y58q(`f>Tt|8TIzyxS4W)KstlnA4z3fz(632 z&>e(0+?8IDU{d_&utGh5o(k(I^qL=Xox|mHJCev8>U2iuMjH``-oUX6GAIkk9>SaC zh23;RjaYY-Y^18C8T<75BK*x`N-*Sh1YLCj*du6iBswLQ^(w)*N3DRZS~gtseDO)QW}43Y>hIEr(|U4>z>VT)BK8D~^Jx*WbAr@uTw0srbLB!B`JEcvq}s}}YXOSdBpWgvn`EjD{V z^El$pmiWRL>=V3!I(!TP;G!YH74!bz%eYT@WB^I^s0e|T^iP8$pbihrx?sI4B-%V7 z)mMU2^~Mn)eQ4ttL4I!H5SI8&Tm!UIVj>p6N+jXfpLDQcKu}B=Vtj){a7dOX0+PW> z5M(PJ>?fJ%nd+P3=Xrxa?Jw|fRKkJPZ;y5>HhX=*1M7|0;y8Q~as23n#b?CtgYh5n z602@0l2z4B5kWbJx;%HEf`T*Rwb{b1bRdC~C^;6J9nY)Y0>DP$8lr&;nGkmg;h30Y zXhJ4(-l$e3Sv51!SLHX%nO^Mu+pITZDC1E3 zDT*Ie{HWGL{5vJRQ>d_TP&7DrHQOD`Qv_XMBVF_1c&Sarz0_7_ypqS?g*ZYkA#^5O zSiyijDr~D{<%wFvk0Bl*xR5)7Gk4sSc>8PB++2{@@vGG}k<#gnMR%Y~Y|%a;RF%esjFKveAkR`=9sMZ21Z5WOmnL)nG5+SM7v-*w234 ztA@3&QMF3orxHCN&z{6KtX5NO?HV;(u(S&QnVInD-U+yM%Jvh$4$Gt-&bvoB-4;1V z0tp`S`!=nOP`lr8+(pVhY~X1`kd>RN$-P*pa21QG@Brb7K*Va%?L0voqJQ|}k3u>q z7@hLK;pbCpYc9$XOQ>*!7m#y`*A?V_ILW?TrWC;fOOPMi%t_d09*&S{I2_2bCr(tm z3bq&mN|Z^P+aOAEiuTx~bB&PuJV_SmZ=m{j@D$-XV<=p?p@2ATtA#3h52ii+PM5_a z!nd93ER=~K=`V;mJOr;d#5^qS(z=MdOmKW&rhB^GkH1VYh9aLEfw0x8%ehLd;saRL z2EbdKg7cJgg&%Nsu?_H>&_L7oH_hTU-@$BZnD1oZ(C_w!Jna1`&=b8fS#@NkDio%z zwv9+3$6YNgQ5DPp+1_liL;h zsOZCZ<5xO)?(0q}C3WH+Ibfushv$_#zD2;R8}xCMhO` zC9zWQ)^)6IQ0>n^S3(dXJg2-j{LlrVf;gnCbs)ojHO-kC_g~`)bZ$eqq1&QS3p3WU zdmjtx2mT-6M{_kk)Q*fSE91i+w#9DO3S^&QB{&`d598TzvpT8fA_z~F59;pY>JAG0 zS~^T3S*a%`RWym^^v5*mF^Z#jF%yOLLIW{_hjaeiCfaW*xA_|+=YM7*!jFSd zs}yXEHy)tpp!2b8>is0aEWVl4uMOOPSi9XzBr#kZn$E!ri2 zOWx1bLe!t^p=P>dk+)iT1+6RVmtlH zuxWXxz68GBL1}r@(X;DQhjei(U!Gk@*xo*CerBl@@CuG)DRJ~sOU7Ukv64M?r%H|# zFTniO>I)4Mlt?h20pm5Y$(1dZYq8&#dgi7w^Nwjg&Ea=DPD{EQXWX40IdN2d{iul} z)57!pM=u>?uRqP<_B&3?E|@)hQ+i~&^xl*%SC zhgA~mTqhhh$k2a^1QijtO*KEbLj9-1Yx$2n4SRHdO&uEpkF;?(nJA(3r&8Se0_;M) zp4JpH6cCE@OYvfl7mr`?#-&eFaBMBRp^%CJzOHOdx0+`6cB^srR=27_kzTS$EOmoe z0cBen;#WkKJz0vEN5TEd8$Da;bdA>hJn%ka*$(sm1<~%d&1|C7(VaHMewbZ&sd}V5 zUpOD@qe#SecuSK5K|ee3bD7<}w`Sj=TXwpof!B1<&@%x!PxC>c?4Yy3-0*J+Y(F>x zUlGxsa`+qzv47zor=E1Mff#dMM>9M`j-(L1FsoR9lRD@xAbAj??mL9x8NAB&IcJ zKY+nBJ;gkwjcqB4AqK_!Y*vSRnA!8F+88m1y`r9H zJJ<)~^qRg1g;E4FfIKAO)P>{$(nN9b@Yd|;l)Z6a>OkH;*f4x91@LBc`QjjZ>$s`? zne^y}&bqvD9X-RvT=uH^DN-=LuKtaUdqe$O;U# z<5&dubQbD|3vKo1;<4LR2*kx$;2f7{`bv7uIqg9&;g#b2R<%4iny$*b`gzu|L^dRNQJ^po(u@I6qC=$P{ky_DXY?XU}| ziKCbDsBaV=#HhzqtZ;nWu=%U}N|2XjW%W~oA=JM}_FA&XVPYWB8dBC~h1uf>32g98n zbMN-><1Gcsd^;M%Ki@jw1k=TsOF}53YC(YF`n?{UY38I#@Fb$S7~i9VK~r&vB^1`c z5(k(BT!sYK6lwD%cv!wWwNlmm!(YOKt^tai*9No4LG1KSBT=)1NuI-xVOVW00Fg3&`%s00R_u<%d#svOlS zH_k(0rHzh;PO#RxScgISxjwjmKf+CBdEHJr3*H|}#b@(XAHHy?^Si6^*uO=IY}l=c zdTAd0ukiVTd51l6h-S`CMaG7`uL;ipI%Hrlwq}rZ)JAFxB2OSmUnr#=a5u#|3;7B` zV2Nm~^u~_T#uf%PyWs-j=r~sK3C`&u(GzhmTI7y+#L#r7;8Tu$!)|Y-Ei`P;N z^L99kNw{;gQ?&AShj1Q6LFZ+J2i#E6E*|d}gHd_#SS2JmI*~3tq#QdaM}wl@11HgT z`j&S4E4)p*eBeM!)PlqNC*C4;Vy^9u9O^IZgnJw$IJGk-LwKnbG1|T;#ka!gDkN1p zAQO08U-Ik!BA1DG9X--BZPkJt!iCf>I%`F9!_u#9I7qw9<~TyM3x;T4@5>91_!GhG zg5#$9MlSD;k34;*H1U+vrXUz?lmBu%Ja6pc5f1z1B)hTC4vZXZ{_&v5C?~sUX5_c# zhJz!II?eeC1(aOY4DI9Ac`o-brb~4kmb*)aQ|1RpMM47W zJUkF;BW4Ctw(@G9O3Ae^re&SiPw`~j{PLkym5p5r^vKXI5X&M5ytZ|B(U$41b!}n&$aQu8`ZnRn zBag)YE(`1ZNAhjY!hSt`*Sd8#E^Mp!r`D~z+mFYkJsxdLW7x}NYVuv+7vDL6EIK59 z8N)#OFh1ED06X>NNS$#B9AuYe6YnA+6V4I`mgN%J-OKP_0uK>!D3U0seCVhXfvDCM zLG7V0gmsn$YUl_+uduGT9S8XYSL z8jU@$vRjD*e5^24$7V6eW@(W5Fi4SAfb9uZ0k-sA1ke&t`v70I_Lq@b7jmy0{yii> z5zSk6L_QkD-kau|!%jQL?lXTVMPKMQH%yA&Et)q?i?+ti`lF-U^X8M+M4z{tJAV`% zhnTF_N1J8ywCkdaMfT;b(Z|`XKaHl%_Mb%09BaD%9GxMUC%+$k-DmFjSJW<;wlAa2 zg892*^p&W2St8cqG*8LLu0yJp_SicDdu(j1!`wY0R$OArH^n|%Zk}w5Up~1f$&+6k zPSjrQPPI=G7qSF`+!8@(TsjJ`6QElBsY-$d1X8m}OESudZurtGkIV_fMSY422L?uB~n>-Q&_Z;(|V8f_TI2fG$Xo3F2k|6D9kfT{n~?1Y@o^c=rLCc9LhPjyWe=F*c#=u$s| z4nnuO{h~agKFTp*3j-kBuv#^0Yn0>cZjL z_;ECO(^XgZ%zX9yy+!f!lU-U?{=h&WbST``St@DPv>yG{A+UDrR$(oo5yKLYnC&IOzTsFx(9~OoRazu&m1;6 z6`e3IH^S)_gQ4^o+APDYl6KA5wGhTx8pZMyw&0@<{LV>qfD&cc#-k6Gd?f0Ne%nkI zUxu@1Je#PVO6}4LDHiW%Z-;}mW6P!{4AE>Fu(l6CATG@&)Hu*3fW*FiN@eIWeIono zA0h9Sfk+eV_Qw;UOf=aS*>95fq?&7cQi9(nx}>)Jp`D%$@XVKH(*)%F~K<5QRDi$F|@;)|XcPj!|0 zC590g!pRE5|ii4Nkf@PMd?tmk9;zi043za`T{p8fa*&7jjxPue*%v z(hf+p!wlKyL$+tdt+-JM*_X{ftc{O@9*xKeYrxZx+tM|SY|o`}?O+?NCKfj!ad8rz zk)%Umh+nWRUP*U)CQS?1io1_TOr>pfxRG|ILicLf`ov1Qn^>D(I!bn99aE0FJFBs! z>*K8j!X_|1C_-Z-@=}T?xjq;M;>WVmGTMSHTj=J?)>HRDP~r)j>Rojz7O!M%8Es9j zTqt=;OW2|JfbY@ZtuLU=;xQmGLef|k3PXMb6i^)k3DAdko5D_o&>Df&xDi5AIKh{? z)48$1FE^*-bLTh}uPTkR(u+6Q>%v&`>SNOB+)m)I+fRtqKRE}coLK(kvE>1f(F>(X zXaYmn$K0hdkOGz!ORS)k(1L+5_Do9Mg`7ILWjeXZ3*jbmxo%6U{Lj#DEMlj*TA!CAu5Q3&) zA&t!PL}>|q`e!-)mXn4I05Z4S^(8!h;J;i3U_+&k7Jg^4HIH|%f zSio`1K-w8cBiJm{-kMoGw&xZ_H{#QvfZgVD7jP#dapcCj=uQeScWPic)KwI|5jv|K z$U7yy|eE-;0~YzzT|h0ojU>>g-JPyKPyQO4>*)-7qKIW zkWFG9y44}LLMJ1_oYk5ffITb~77G28JEFJ+e!O0UZt3UACmGcK2bPNX2)kYJ0HC5r!8G6l?V>Cpt$8Vj4rW7C1X)CG0cV)!)Il2aE2IA1l&|q* zo9$#tMlMF=>64}11uRx}d$%u}i2z$?L$Qo$RXjEunnp4eD9mlt6?%3!1sHuIKMzk3 z6rzSDhXVmyN(qBRTaA71ULf-h;YZL#^uZpFygT-VDuOPS!3reAF#ux8l<+x^Kz#&P zl57aD;Ag^|T59D+S0 zWu%d$J{(KgEkKv17+s0@51mr)wD?%y{grSJz&pL>jGFUl`N%qYY$Ny-gQABEH%GKl~f|n5I z6J!S1RdA0u0iN_J$m>Kuk*~?25WOlusRBo5Fb~D%62?bUsc7+uSjr~)oPMR3J8}0a ze!o(D9>+BATFh8DOaz)*D2^RIP}~MWa-emj>?9fpb8cW$T_eCfB=8&X7zyM6bOxcI zC&c^%G7Hj%x@h4G>hp1@({-y7m`tAClLN{ZO2Fm_ec$3G*s_;FzeBhcE6zyxbzK0^ zcZ`GxPO$6kD)C+bAGle;Nq|?Bh!^BDeM20hubyxgWPvk5sii>2e{?)hTCEUMgpO#f zSe4NBILOo;WvlR2r&rs<$c4`Fp;L5pw=3R3^XO}wzW!Dv+F#C#Lwrtq4y`*t3@awwwxzvKWqiS z1X#g=QU%H=OG_nj7mHWJ94wE+7tVYvb`EHRp<89hJ`8`oFyj)qiA{U)jUm>(FoBrU zZt+69a8U@T_F+PRm51v+e>xRXUV!%#=hxt-oP!gSEr5Z8pW~MxHiMFYuqvO?Fakkh z_5@^O11~CNMuYTQvL9-z;^Yaz71sS1+LYoWLAz}?a&txlyM4-gZZS}s#pxsG=)|lz zPsge_#NZL;(j!=0o!Af(zB(jy2F6rj>of%=zPAi@~HOu`G~51w<5IMYbZ z(K#aWwXekQ!TNDZU>BYP?9Zl+L)O6YIhfd)7Q2rKnZ)IG@0K}=RNHjfpCwlvL_(MA z$CiL1Bb0DSU?D0&(mHT^B;q5%3D{r|SE}Old~F9V2qM^}>ANfbl4gGLT>Q?M8GJh~ zE;jYCiDsL5>p_W$euoO-dl-AAOAXsHJ$d%4HHk#wHdsPAmXq}0E$Owh+B;enp_N~S z;b^PuSXL^K7#y5OswBM3Cbm9IkzgJ!2h^b*8o3(|$01yeTB0+FHj|(yqO9Idf$T_1 zPoP!e+HWYA3go=p15LYSGKX!QJjo)Hxi*?cOd0V9J~8g^Txvx zH@NH|+boAi>t#q!*lEWfj3Jwv?7uc6?jPRZx4Hwi2u%mLW22ibIxD*u6q5y02`c z!#^*V?rcoHnruWo*5bz@@8x!%bZWBk7BW!nx8o0U(u-;D>7iZxfZaY|Q!Y8fZFeC* zc-F6+?lW@2x@2RaF}Vh|UngMd9X_=&c@gOZP#OPFtrrRdTQAsg`Pl)dSJHQ$;maAc zc+)Z>{1zv832fO%A!5hS6e3M1pThb`uf?WfIUqP0ERkg2*_V%o3&@~YK1ju+;dD|W z|2!_MQkcF0hyeWpLa}sZ&K6zr`an!@$Kst0TkmlPTjZca>h1=katt^eU0n`@_N5QC zdtWppPfRjA_F!G8%bE1a-HH%SQy`YD4V`Mh6AL_)bJ-d*oqGzIx>h=c?d~FzF?OHd zC);BVcL&XrHQ&I1-rgZp5T4?wBGrKpCEddCWaC%C=7yksgsTv8*_|T;&AG6644fFf z0qGn)(ZT1QPy6NjAwX%q--UXj7Tb_16tu*$3r60(p)bdD<5)5W9wsC908J{Ed}~t4 z9@3gsxJpAr=KB(okTLz(dRQ33i5$iS1Ci#`)q=x+JNRou7~=T9+>MUUVRQcwHPGU8Y9a4)nk;q~ zY9nGeyiOQ{n6#sVt@)_98j(Qb%IZMAA!f(X{IE7CbOgsphZp~Z%W|pOoCB?wo)hw0 z$3?#Z1;j~bRAFG%&>*i6*a=utPPD|J2&>0unq0OXV6_&F!-Qj}(B*IRT^NL5?RV$m zfs1^Ev^z6hIae(3OxERS%5>i8LxEThZd)!Xc%iR}{di?!o%!UNgziG17N|RDAYB*; zsU~~b$7dv>1&CZ^7KX*YO(Y{Ao@D@W={wPOXFOW`Q!Fss6B*N^mO%YE48&GbBR2o5SkYi>zSo}-KyU67S!V4qgMky;plP7>KC@F)@9}t!i zF-)@gqYr!4aIi6XuHRlt!;p$3b36+Jn0IMDc9g+jWdlZ^#BXB-N--$m4yO1HXbUQ- z7og;+0*r8yotDd(T71*zX3oGpRy=sYr^X~NrCf*x5q(?WX%~=39=>=&HgT~%GHC}w zL&QP|9se0>?C?V;!;>w5uoB?}5-!N_52eF8bN$-Hdk!JV)|{J&O`tTj@I^jSYfm|y zKgWJb)e^2P$uX@#ml%@9w~Ut@PM`2%tfedQj7vkzG^hJXx2Jhb64`jiw~UjK+Q59{ z+{A5yIrqW@0xaw1HB-+XW?plBVqBm3=4*-f1@n|Q5*JIRzB}Pm*#|R|$dqH#BsTLO z2`9VsvxLh$_OnEzhiw-%7yA$~o7l^H6Zj=+0k$@v1>zy zIW$#ZpXT+j`I)R40$Vaw3$w8b8Xk0MU$a$i?HD%guc(7(UZyBoGyC$-2@m_bq8aAd ziZ)v`KlN%WZ06daw%*0^y7rK{SJy^L?88l(Vs1%muLfAlt!SX&MWc(lHK&>B)^J!o z?~6pxJgY}52<)!Ep~WeEn#yJ-@&155tw&&Y{{{7L8lkPSnT=z#HapuAFckL3v04wi zw*|k>8LP=``Z#SX)AMM4&3G+rE*P(69PGMDTAE!tOY^h6lQhZvUz4;?1@`q6t%Y6s zZbD(N%+l=a%&D52P5djmq+1iWCpb*^EbV7DwoV3Yo91ZOvD^NHHvc|Ht5eJoi?y2_ z?90`f*Zg|9c887aIYx7`|2an6&E7m#o6oi^(L5}0BAz)`X(zIqS78=*osV&CTdf7m zC9AbDcKS>OQKbR)1t)2{6t?zk&165P-p(m#K66z;yIx>B|A=qfb}lCD&U3Z*1vdR) zU19f~g-V4Bw52wJB|5qolkvMBXb-Xv&%%6#F43N1UyA5$^m=^OS(jpzi!RmLZ0yd< zwMA_3SU_^d71~t+w(DuMwr~@G9NMJyyUiEw)Z8w%AdIRnJ*2tK;~&zPz&vXW#Tl7Tu=pWcDYt$IahAp>1%momXMdo1WFqWFwx_CbAo@!I<_uk59h$Iqej7 z)=Bt;!V4J5UC-l1TM{qc`W|YOpLzK_vn$%sZTVS*z3Im=al6URQvX5=pFEEi+Fu6N zEcslESncEXPi$}pZUlr|25`n)~~gj;lNcyZT4Htzq385n)o%$ z!H&BCLwWg4&BtcFsXfXr?a&qTcfZ$?0)Jy}eOue;GEaJ68!a&(4fyd640!Mpl>g-? z7y|zYUoD_lekNNV@v;pcQGVvPaIt(VvnznlY%S6ZSBgmNWomwLEyn2Iqp9XspJ)-8 zeRwm5x9y)=klpc5?Q^#6U)ln8oTPiqg`aD49BfxnJA&OHYsv-$D&J72?_`1F(Lhnq z10Z(&Ja)0DXW156_pq@x-EM|#`VzOf-m8xiH{5{U7XQ3{vM*R(&c|)s?3>3-X`W%s6*QIwbr4_C09j?3B+1;q+@7C)b z?1@pj!#rc8t_bXguP_JqPR9GgN9%60f3&{I$?i2V#>FG>uYKo-=IaUb>G}HU5mh8pWiO<;)@pk03+bHpWLXgWnbmd_pY1t4mZ2^r}&QLKh=+5AMQj8x7?viX66pP zL1K61(8S|ELyM2ztB*A6?$bfwnFsVm?Cx23v++UwhwRI40D9Jgy2Lg;q+i2$U&l?w zvv(dsJM3Y7guu4`5~cf0eFMAUCwOzhqxkr)zZ+rtC7BBz)vpzqdy9T3TboHL<}F+F z6Ge9K6L|H?HYj}ArAMREu%~oCyYES~vEwD&?s*Cb^5#?ePMO`5#;8~C(0yiPhrUuU zAKt0UHulj8Xi9!Xzg;mu-la<->v&6do5oxEdp2>^_n5F&i<+@_^@k*;zmE29ejm+W z^1i-=z4L)Co6mlrgN24=PMBR$5{E^ywxis3Mya2s}!50stbHQeTB9^=P?`Gn6f zM0UgmeCIaR2$+uqjU{$=+co&cpGFNYlUuA`KGxI%lRP~8E$i@te>BC7M`YH10xCAA z4Ug%oHYi(eSCs^S7u$eah1T{Js5BDy%^m2*IK`P zEZl1}vAe)?&2xH<&obt;BaD>Y+_A)n+RelY1557#?_rVNK}+RzD~%ue*(yI;^{+N0 z_U`G%a^{%}PW-_c#t0jWpKbU}|JjBDUUoYoxGlUGg-$ph!<~D+(aLuI2_JIb`Nmv! zRug7w%>{;PGjF@dxCZl$=INDeK6HuE;9wtJf&0LXhRj@78V9ovkHYQPtBeqvu0amK zuTNd9nI8$0d;kaOLg#`A7o-t2wY_*`T?=NJ)o z$)kpky*Uv8y!cULo4NWi!!EEJb@ciB$Bh6xU=rpXzk;m)J;*(Lg~e@x-Ss3U`_wJQ z1s*f|f-%EpUb(~gk%#U23x=b-4!nB%4dYfYgEx`a^0@!OB%Skn%v}2S#`Si#77?x4 z74M>u?Oo&Vf*JUOaht$bP4?9v0TvQUSZfF72)9?**@F4_+54gKuE_d#13eD>*m#-U zrX?fnu1_!>v)3enB75+)nLWlNm2R>^Q?Yl3=`N9UmHJWvu(9uc32T$5#NFcesj?XnU@xgoj$hXGt5!Z zn~a+uc#|h9?CeBx(4xM$mJF~P@5ZqAoCyHn_OEOKv6!7Yx}EkZn1BB+U(Jq;h9LshrPx{zPxHYHEOg^PD^(qYfoh3=H>06pS#>(Hq8j)RgJs|SV#pq+s=48_P z6+0>EWeZNmvsvHA+s&013f;cFxm;uWg-O|b?EA^{6}IJYl)n6`WIuc2ALwAyMtuIv ztCK;q@9N~SHumg>PZgoIXk%Y1_<6pg_Alc929GVH&Hw-a diff --git a/source/RevitLookup.UI/Resources/Fonts/FluentSystemIcons-Regular.ttf b/source/RevitLookup.UI/Resources/Fonts/FluentSystemIcons-Regular.ttf index a885f35b4db3fae9892c22ad19b43371a3ef42e0..ff12b065423fcd8bcc349411d42d69f575f1d06f 100644 GIT binary patch delta 330718 zcmXuq1-um1`#F#dnZjh4h?nY(llCFz%cY}nW0tTQ6h>A3OFCj>( zluG=c^F6=+_xpP9XNFy7&Yb6&6)tnmO#JB7s%g~RY{_};-XcWJL7}@Cvi81=*8`j&%N%|y>quVoz~ok&!#}>UhoUd!>l=B`zXlStN*Z3 zRIPWP!|@&>H0oNvfn7V#mzQiubodeMr}}puH7HAS^a9>bz~{3L=-j`1@Vv|yWl^}i zH9~aepn*e&%{IooLgA{4h#fS2-qGw@S{X#ahrx{f$0p5t=sRzy*VMo8OCqBG_tyX3 zOe?b>jXFXx`>s%dw%{%G%f1)UGZg*q-@T=Or>TGUJ*CP^`<8ZEDC~Pn9bxvB%F~2d z@P=xzZ*ur}h=@)qsJG0L~9bZon;QFsM~A52c~3C>P3&@}hhw zKPrF80P*qe7)j{=8ebi{djX{301L}x6q0Xoa>WO-x-lz}i zi~6DdXaE|B2BE=d2pWopq2Xu*8i_`s(da|;F&c}KXd;@7rl6^48k&w~pij_Tv;Zwc zi_l`U6fHx`(F(K@twC$iIOfX<*F z(OGmJ{e&){U(m1UBKi$oLRZjLbOYUlX2?Xh&~0=F-9-=3L-YteMo-XF^e6fY{SD=Q ziC&@q&};N&!FPjmCF7JqnUqD@ltX!xPo+^=sH{|WDhHK|%1z~=@=^tU4`h14o)HMNe~Ol_xjPzR|))M4s#>S$T&OX>vmE%gg^h03IEQMai()B{um6-C9+ z7@DOyTA@{1qhoZOwrHDn=mhQ39_^>^{LjmgEKQfA%hT`CmFcQ{jLUe8&jd`!n)#R+%Osg`=|kRnIjDu{JKuUm>KoPCe2%vNV>vbEUSY(2I<+kkDzHewsIP1vSvGqyR~iS5jGVY{*2*}m*xb{IRB zO|s+I3G7663Ok*h!6xUhpRz01mFy~ZJ-dnB%x+_QuszvcY;U#?yPqAze#RbP53*me z$JlS#@7R;eY&lTVba)r5~Tyd@>S1QSs<;rsvxJq1Qt_oL`tH#yfYH@YBdR%?3 z0oRag#5Lxcam~4wTq~|M*M{rFb>_NoUAb;tcdjSbi|fY?k~#$D%b zb9cDA-0$3d?kV>t_ZN?NnYVbGclZSF^8p|7X?zwwE1!+e&FA6s^7;7ud;z{7Uz9J# zm*C6t<@h>$UA_U|FkNT;3dw=|V16h+j33S~;urJj{1Sczzmi|YujbeAYx(W`E`ATc zkKfOK#vkC1@W=TR{I~oW{w#lvKhIy}f8#Ilm-(ywHU2t(gU{q|@wfSV{2%-S{t^F} zf5N{NXh9TIAto4tDOiFnBm`IR1YZb*P)HN9B!z53cG$@&wPXfJdS zItpEdZbEmVhtN~#B@7mZ2t$Qo!f;`PkQBxVELSeCxp1zv* z!{h@xy3vSvX6F~ml4Z~Rm7@dE3viMM*Ki*E4CLqh@HgFVi&Qi*iGy%_7MAseZ>Ld zAaSBNNt`TB5$B2X#RcL*agn%KTq1rdE)~~^YsK~AMsb_CL)^dM=;(aus^q67Z;5xr z$Kn(5rTDM-T0#;nu@Wx{k|?Q?F2yBNvLstdNWK(ES){B|4k@RUOUfF|ic2M=l2R$Dj8sW_PpT|ck*Z47q?%GKskT&CswcIU+DYxD4pJAXtJF>E zF7=RlO1;vnMEpX@ang8cf;3T@DovMWNS{bErP5g<)`dzvw-IxB59!ig-$I=t&sr0AxS2yWdgp2TzNJNe(5jCPkqLFySh?o&8 z5=PP@*&{h3MIx0V??tLbsz+)@YDMZq>PG5E8blgK8bumMnnapM-jB41w2ZWkw2O3( zbcuA0bc=M4^p6aR42}$m42=wn3{UT%_R7TPCuCRl}oK4Oy=a6&CxzfK>eK%Q7t|(WME6Y{ns&X~ChFnvwCD)eg$aUp< za(%gh+(>RBH&@dA__rUMMe;7t5c@OXX$qa(RWkQeGvmmNzHmE%H`*o4j4# zBkz^>$@}Hcjxtx7rz}v?l_knj zWtp;ES)r^{Rw=8MHOg9LT~gVkY*BV8yOll4US*&1nQ}lms2oy0SB@x0l`oVpm1D}+ z$~on{@{@8wxuM)rZYy_`yUIP~q4G$1tUOVkDu1e!%Bq~otAZ-3x*Ah0)m9xfp}MN4 z=2Y{l`PBSsLA8)tSS_L!Rg0;`)e>q+wUk;~O;%7Vs+H6#YE`wGT2rm1)=}%K_0$Gx zL$#6GSZ$&PmHkx>4P%ZdJFdJJj9k9(AvJSp8f*lBRy8eye_` zo>Wh(XVf3nv+8;EC-s8*v-*qrt9m`XpaDJlQhlZVqyDSD)&xz{qMEM7G*9!jKnt}j zT2?KamQ%~6<<|0Od9{36eyxC3P%EMp)k3q4u}-Ona`qi84_(Dn!L- zG^$69s2R1Qb~F+7qJA`phS9WWwrGxMu4tZUzG%T{p=gn4(P;5# z)adkRaz^x%=)CCs=z{3N=&I<3=&tDQ=$`1_=)UOw=x5Ob(I2B{qvxU*qQ6D2MXyJ1 zL?1*SMju6=M4v|AM&Id@9?@l8(N$g3qk2O3^gs{wG(DT1UC*KC)N|>D^ul@(y{KMX zFQb>$%j*^Nih3ozmR?)0qu15zCH4Ax1HGZ%NN=pS)!XSE^p1Kry}RC9@1ytC`{@Jq zLHb~Qh(28ZNKfkH^zr%xeU?63pR3Q)=j#jfh590Wv7W9k(O2oK^)>oheVx8u-=J^Q zx9D5-ZTfb7hrUbSt?$$K|KHJ%>tE~N=;!qF`Y-xrox7r6)1ehy-_7$-f22RwpXg8Z zw=pV4$Jm$>Q)5~z8jHo^F(YQite72h(>J<)o>IkQC1a&xrDJ7c6=U^c^iVaSG-}AdCH^w%{w#2r^w#9bDcE|R{ z4#z%^9f=)}orrxGI~h9_J01HW_G9dP?5Eg;*w3+FV;5tWVpn3ebOtAH>_n+r`_*JH&^?hsKA+hsQ_9Ka77A|2RH2o{W!+kB?7` z&xn5#pBbMOpB2D_=)(*_^J5m_}Tcm_|NfQ;}_#s z;#cE0;u-PG_^tTu_?`H@_@D8=;(y1V#h=Gt#NWi<#*^<1WKafeFa~RI25$(4XhaR& zh#7IiF%m{#q#4_#3VuaVCvY7{q08l{agMp>hrQQoLvR5ac*DjQXdszx=VhEda~ zWz;t68TE}8MoXiW(c1XHXlrybx)@!JZbo;bhtbpMW%M@s82yYP#xP@e(imZkG)5U? zjE{|3#zJG6vD{c=tTomd8;woIW@D?d!`Nx;HVztxjU&cU;|t@IaoYIa_`x`1{Aipt z&KW-$7mO>$RpXj*!?^~cZZnUW*UV=YHOrV~ z&2nZ%Gg-;3W>z{VgUrF^aC3w?(i~-uHpiF~%}M5DbBa0LoMC=q&NSzk^UV3?0&}6c!dz*t zG1r>w%?;)zbF;a_+-dGM_n3RleY|SFb@23doxA=Xf9m^IuQVU4yXS(B}) z)--FTHQSnNEwC0^i>$@gW^0SJ)!Jrlw{}=Ntz*_n>y$OX`q4UX{bF6TezPuFm#r(- zRqL8{!@8YbF$a8)Ket|3FRfSBKi0q2f7V;;osDeDrftR+Y|)nNh%MWyt=lm>VY{|( z2X<&@wX@qf?3{LPJCB{$&Tkj63)+S3Vs>e}j9u0)XP37t+Li40?8@)Vy z_AmCY_C@~wZHdz`(_K4-u4nRCE7?mE9a_njBcOXnZwU*|vPwe!Y#mp}`>(TOpMiHWI+PZBc|vl0su3lobHixcUIC5fep<%t!Em5Eh})rmEUwTX3! z^@**CZHeuP9f_TZy@~ya&k_d`2NQ=9-z2_GoJ#zV_$hHYaV2p*aU*dvk(s!ixSRMr zaW8Q{nfN2|An`EqDDgP)B=I8gGVv<$k4w3<%ebtox=}ag8m{RkT-Wv7G&hTz)y?K+ zcXPV=-283xBs>$r8@dTv9vk=xj9;x=`gyDi;TZfm!V`+?im?cjEFJGq_R zE^c?Xhuhoj{xKrI}=@at%xhk)9*SYK6 z4emyFle^j7;%;@fxjWpQ?k;zayVpJ99(BKPzjVKHkGaR)6YkgUH}1FYN%xd{I@;d- z-aV6Ew1A%{_S}8pzI6X}|8w8E?>x$5(#I5lznhqz<=LL&B|O*jJl_kv&`a~Ocv-#d zUJfs(m)pzZ74V9BWxaA*@9L`g?=C!QK#Ws5i_T?v3(BdtI!8{RGNw)eYt z&%5va;r;C;pLx%{7v4+nl~4J!&-kp*`MfXtim&>b@B6v@+=*Hi`o;X> zehI&%U&^oGSM)3S@A;Me>VAE{x&MLR)^F!`^gH?O{SJO-zl-11@8S3Kd-;9+etvJi zkKf-P;1BeN_#^#M{%HS0f1E!d>5ui3{zQKg{FwYG^@Tsf|HPl^&+=#cbNspfJb%8w zz+dPu@)!H*{t|zczuI5pul3jY>-|msR)4#{!{6oa_V@UE{eAuc|FHkLf5boPf9ap} zfAG)yH~gD^hM(y_^dI^E`2YI<`LF#q{@Z{G*gy!xKnap+pas!D4`M+)FatYC1YY0= zL68BsdzJ2)+)! z4ZaIb2B(7WgCBzP!B4@3;OF3%;Md?{a4EPI+z##pcZ1)9d%^wSkKjr0H25?4EBHHj z7SbUTav>iIp%luY5~|5i3!|YO24NVcg;~O^VU93Ym^;i9<_q(O1;T=1p|EIJJS-EI z4J(FK!fIjlux?l{tRFTC8;4E8reU+NdH8!lU6A;g{jp;i>SK@YnES z_*-}>yc}K)uZK6-_2JDhGrSev4)26_!{5Vu;r;NB@Im-6d=x$opM+1tzr$zYKWQk9 zN~6=bG(JsClhWigB~49>rNz^XG&9Xgv(uckM4FrCrv+&R(+Z^(PAigDG?`W`tt4}l z`HDHl9A_@D^VtRLLUs{_=tuOU z`s?^nd=$Nw;XH)ol%6GOf6-UKh{ zjYCV&r}RVm5wnt6#nurUi%lZWUoaN36RZ&${Q=@8D zHJh4U%@M8Z$W9@ruv5e-s=XI&7H#Q&@BiqZ_0I(Zf`P%HU~n)b7)qU@PE+5fH~P;@ zB-wxs*)(I}d)a;LJ@!8P2m63CxYFDW?k1PwGPq3c7WasI%st`$=ALoS zxfk3^?iKeBzm4C)?-ah4I!c|S&Po%dsnSfFrOnpnYV))O+Cpuymae_fUTUwjfAoL# z|H$9%V%Ojg*>$V|Ua}{iEpLyv-P`JI^ZxT*d+&Vozb_~JCDqaIW+aVq#&~0@G0m86 z%rHJNW*WzgO62BdX>HEaGdV{`?|zm zoA|2|zB189VYjeH*emQ4_6wgS#lOX8;&btZls$4wPRSXHpoog3L=;(36jjmw_np|+ z`!d`e{z+G7tFTqsYQi*Ox-didM3^be5|@d~rEJn}X^*s5+9$7(*UIaZLP}w!x$?f! zLTMS};~nFj;+^AN{C@EX@k#N?@hS1C@oDkt@ipW()iR^ zYOFUl7>|s{#(&0ZYl-!#ZP~W%*o~cTPIsq=^QH5ZbIdvJN^ZoJ;b&4Uyq3Y5U`wz) z*b(dr_6GYxHtZYr3;Txy!hv*ax(&UKW!VmFSFX3XTil~`R7a{Gsbkfz)o-+FT6L|4 zR#U5`)s8NUE{`TxL^nn^MK?#cM7Kt_>F;95wBb*gQ>^J$y4}of;kI*sbkDly-1F{F z?gbC|-}v9AWaFd?x+WvfF@Bpsjrw5%-76mb`(3B9m9Uee#Cyvo?(Av z&$8#(hwLNvG5dsl%KphU;hJ(ixVhXG{=R@TffC|ERw0MbT$n1%Pfzi_mHbXPDV!2c z3*U=%#rxt@$&(68QzCZ6j|7oyk=l{=k;#$6k*_0v%CyWV3FQ~%l3HKmwVB!+ZN9cB zS|G;7TE<$(+Qiz%wi^wMhDIZ!vC+h6YTPsK8_!J3gn!sZ3}O+7cr+8mk%3HPAshK9 zKw%OULWNOrR05Sm6;MS~2~|fmP)$?|)kO_ZL(~{GLG#djvk*@LP@)rNnXu5Wdv+F9MH5!6U(6g7{UPc5d>sU_4hYB{xnT1l;; z)>7-K4b(PPAr|waUsE5=e>M`|%`jmP@y`|pK#kS5=jf1@waSLh5nlfFg&N&jUJvv=Ev7@A=i zk%=%0W6($F`}7}72c|pIgIUTfW0o^3n3K$HrUFxismfGiIx(G@E=*Tu0JDHu#QebA zU~V$+SeJRrB9>yevPW6PKFoz&Hns-ak;~52=IXHh*#2yC06T;o$_{5oup`;=>?C$F zJC&Wre!|XV=d$x0p8J6~Csv6tB^>{a#} z`#bv&`!D++`r$MNI&3H(HU5=riKE-G7Pm}zg{9pXv{4@SJ|4xvEh@c5k=VYR)kXy(j z`GtZ)VWEgnO=uuA6q+QCCBAl-3yXv$?h1FMyGqz8>=MohKMH4sbHY!;1>tAm z7vYj{Sx5;P!eil?@LYHyymnWMff$NuViqy0n9W_ouj3zzCB;%=vb0!HtR%iCRu-#^ zHN=`?EwP?hUu+;Y6dQ?6#b#o2v4z-DYzO~E>M8aTd;4F65pkM0U7RJ(7Uu}MP)ukl zOcpnZ+r^#YXW{|zpfFpQE9?+6#7yzFcvrk9J`f*@uf+evH{x6IokU5D#7UauNUl^? zDwmYXOBKbqXi3$j8d4ppzSKZ!D7`PWkXlNuq-oL|X{EGES})q-`(j6Nq_k7oCGD3! zlMYA+r9;wD=?m#g>A3WxbY8k3juJoO0T6i5k2BW5)n7zMT$m> zMT$pCL`p_VMM_7?M9N0WMe@}M^+>%)(@3*07U>x26d4g2DSjlbi%gA76Mv5^iF_Ja z8d(-u9$AsTU4b8moQs^7kxWTZDJjjB=1U8tg)%F1vMJlrB5ASg%YhuqS)?U$Nx8gS zL4HrJE;p1L%T47LavSMWxxL&`?ksneyURW0{_;S1usl>AE`KbKm6P%`dAdA9-XL$3 zH_1EXUGi@EYxx`bJNcx1Rz4?Rk*`WilhQKzk^Dq{D*q`RR_HJu8lf3lVWsc{{2^hm zl3mHE7evddMkaDAtr z%3sRg$}{D;@Kp-PGagcy+2eOP#GQP*K?tYE+BHqVZ^!Xx3=>#h9p{uI51-d^vd zkJ88JAL^6!Df(1>nm%2hp?{*!)IZgi>dW-ydP}{v{+WJ2Kd2wpPwA)i@AV(_AN4kR z@&mn>-e3P!zo`GFU(&DY5A;9vzw{USOZ}DpPmGBPF)=2^^q3P%#JrgAPxYty)A3I? zVkKhbV-@rP`mk7~SnXJySY3UjK3e}+AFEG{b%}M2b&K_g4S}D5Y|tm^b7C8!Cu5sp zJ7c?Idt&=y`(vNQ4#W<|4kcq>#g4_kj(rpRHg-1lOYFDU<=E9&M(lR%PV8vDipA zmKZCHEygxukFnRdZe$pl#w`<>lu4Vc$(g)qn5LP{%&za%kD0~H;${i6q<%vGHfh#0 zYngTR@AR`~W3#FGzS+|3VswoLd%${anv%fje9AXYNhnY!poH^c{U`{osnXAmz z<~nnudC)v$9yX7fUzwN8%jOmHnt8*#X{O8!^Qrl#`Iq^x`JegPd}kqxvRDfq^syvM zwiHXXG%IT9Rx)NKEY}LGTvl!?kCjhIQHM8EgT3W5F)>a#< zt<~P@V0Gk|THUN(R&T41)!!PZzloK#Mp~m{G1iCHN7l#ISSx9bv&P4&#j0Bq ztckH2v6``3)-r3kwIcRq?EBcQ*u8iK>vYok-ul5hW1X{pwXRz?8Q#jUGOb(I9qX>m z+MLbXimlmk+ptaBv(xM>@viZ1b}qZHUBoVG7q?5;CGApn1-qx+-yUF(u|KpwvXk~W zds4i+J=b1nFS1wIEA3VGW_w$_N4#gem%ZKIVehn$GK-mX=AfPY!v50!%06aawy)S% z?UbEi-?8u7_v5|oKkNtgL;F#@PyDC&h4^)&jUzgelW<%oa6%`WlikVT6mSYUWsSbZ zN~fGt!FkWA>{K;YIn|BTP93MNamZ`v@S>`Q!}IFdM;_%3lWaXOLwK5-`TW8!S$T;hT?$C_^~O;+ThlA8@SEfu5LHCr`yZgXl=6oPFm00kL^9~cz3$J*U_COPE)6u zyWQRGY;ZO@=bfM2@7!nZEB7Cd_E?YevU$0@yk0&pzgNO5>6LOWI6r%(y)s@MuWq7u zVvyI=Ywo@8eUKQ;^St2o@_KuHyuRK5Z=g5A8yP+6edJB^rhD_e`QB=8owq)b9FlnI z?eq?MC%seNY43aQ2k%Vc&%|HeZ{DTE--&0Q;T86Zct^aWK{@Y=ch$S@-SkpkhL`C* z@E&@PyvN=X@2U5v_ZR*#L$EX0&x^doNBAhO^YGVn-teQo?#F$@w|&PC{LoMHv-t4O zT7Gsvho3X)=kxRX1^j}+>9BHG)vw}L^{e^y{ARq#U-5_f!~Eg?2!D+Kk^iwjIjj?2 z@i+Jz{muRsf1AHEd_`0Ke*ZK7pnu5!%0K3x@=yC`{Ga>_G))Ws&;Bp|ZU2t{*nc9F z6UzJV0u;~z6L0}fi*&2N2&^FK1a6Qe2!q@~k)UW$EGQL}4$1^&gNi|=;Ju)7&@1R2 z^a-ZX-I*55Db^Dz2%CaWf|(<(mINz;mBFguo8V0FV{kS&7hDb=1n)u= zQlS_+Va_mbSU4;a77I&+CBsr-X?8Yyo2wa?3(JQUl3|0eVb~_@6m|}~goDDt;gE1> zI4m3GyA5I7-hLghS;f!!*xFB2@E(#Zi>EV*_({O3HEL%y!qwrLaBa9QTpw--H-?+S&Eb}CYq%}k9_|QthP%Q&;ofjxxS#ox`71mWcK#|n z79J13;qLKN_+5B1JQMyHo(<21=fj`E3*pbg=I~mW3ZI8B!k1}G8k;7hMbfmiXqukp zrDaXamR3BiL|Un|(mc)1LfPQC^JC~T%Ag8RWvHrDXP)89q!0Ml%T{hH^&NGRuOw^{ zw$hX7CG@BCGGQCNk={garnk^r>FxAE`YFRQDx)z`MrX1xS()tcBssre&DysMj#1o# zRqMFOz)tX-Is7ke`GTG6Y9~E-@SP{%S#FdP2zaU+r9=Xr??x$!fG53CDnh_B-zX&$ z@bouIDFi$Rj#4TCPlThCM!>V-C>15(DRGq25lZ40cwii*Vgx)oj#6;~9wJ96gMi1$ zQOYFX!E%(c2zbOCrECHoHb*IkfXB{J3LeSF-~n`$atU}89i{L^19&JsiBdl70C+wf zr2+z;R7a_hK#d^r%mCCBG7Ev4L1raTbI5E2dLI&|9YZZ3a}cN{B&;jIP%B70tGSY} z1M37*cv=B!1DS_FA3)+M2BCU;vgnpcHN}0E-?_ss#bdA5f|# z0Sh5e3O5*lB@rlv`wPJ02$aJ81z?#3O5y%W!V9dJK&iF_terq9++YA!QJ@rVFaYZ* zP^tp~D=Sc{BLQnHP^uFFt1VCpHyD6*7bt}r48RHulrS`3uxM!>2Jxc|Gu4uAz3 zDAj|2r5Y%O8xp{x4V1zS31ImKO5uhCu#f|#a6zy62N*7 zl)?=OVC4r&;f5TDi+vV0a7O}I4uVp+AptB5K`Gpj0G5cL6mCcWi$zds7y-*hPzpCB zfCVHdg&PvUQWBKH4GCaT2}+G3V0j5j;f4gT&;+H%;QEh&og0uJ60rINr9L813KDlJ zfE6hyHI{(2DNs8CR;i%WI0DwIpwxH*R<5Ab1OnEupwvVHRgQ5mZsg z!$guQ4jZ2nR0+r<1XT_4C_&YL{DPorLVih5%^<%bsP`d{5mXDv;{??b@&rM(g8Z7G zT0?$=>;Da0eH+;MmY~`~en(L4Ax{!i2T1&-1E`LWrwOVPIQj+pt?i; zNKic?&k|H`sDJ7lLG^(=Pf+j$k@|_CK7_nLQ22Y{XM*||@)v>{3;8QS;bUGTs0EO} z5!6D+O9ZtF@-j%m3$+?Ht`HRN(5nQ68}1rG;byu{P`H_H5EQQOO@hMpO%c>#$P9wQ z<;^50T+&+v1!YX8Zo>|saE*Y}DDS^g7{z=d*il8mXe{ik-fpwxbkP23CPz3?LxjGXb~&Iy)py&^aI(f==dy4Y)dt&IJiqhtY7~XYd4_2T~yDypSS6=Yy08IzMEDpbJ3C z1YHmkW(lJULBcFyG<-K?z$_*43k~(ih!QlEBSR^0wgyRf zVJz5aLohbv2L$6lwk4PZWIKX!A=?v-2ibvO;BLw2NH76pCxQtfI};4<`Yr?mO`go? z3Oj(oosK&mFu2pZ6AbS39t4vQvM0gdPVYr91t5D940L%$AA*4{&*)1q(Bv8Y2nL!w zqyPUT5d&SGF@RujcMl{O+}(o+rYz)Of`LZO7(y_(S%(q~ZqQ)_gBx@>!QciRK`_vq z86yb>`YvM>uKy^wV%&VA2?jUc7=ppg_aVXH=KF|XaPxgkFwl4zV+jT~A3i2vpz$)s z5e#m=@dSgLZvw$2aq~?i7~Fi52nILbWP-uXH-%uJ@iL|o3~s(@1cRGzI>F$En?W$R z;XWZ4=(UWQ1cUo+7Op?MFu37n6AW&+IRt|nZZ5%egq%k(xKHL24DN>o1cUowA;I8& zSVS4)$CC9ne++z_7<4DNxY1cQ5E8NuKlSWYmw2UZXa?tzsAgL_~V z!QdXiUtWO0J+Ovga1X4-aGjw38S4lJ_rQ9B!S&rhFu1-O2?l;o#wLQn_1#P`xTaeO z2A6az!AyeOMliUf+X)7jG`Ry_0E26~lVEU7cM%M(>289-HQhrnxTbpv2G?{S!9Y1P z_7e;)>1PCkOL~A{a7hmmNe0*S5W(P@9wr!E$Il4{PvH@Q!BcpYVDJ=vK`?j*za$tu zgI^I0p21@TgJ4Bp5t{rw9hm z;Aw)vGx$Bh;2HdZVDJo{A(*|mmOl~YZnLx&)Uxf zgJULqKLwU-G7kNyh5;1ORX7(C!>1cL{BonY{Q zZx9R~@J)ij15ObP9%Kf=z#x+unXm&GJjz=HgGYIrVDP~15DXsJU4p>_`<-C$!0r(Y z9@u??!2|n)VDJDQ5DXr`!~cQre>{Lk1cL|gm|*ZppAZZ_=~IHiC;gLP@Jat77<|&d z2?n3^8NuL_J|`G_(ia4SkM|PS|0P^KKHw{Y!3X?@VDJI|B^Z3b{|E*j@HN5U1HK^` zI9A46g2BgoM=)?axX%A!;CPu7!6e~;nKZ$`0W%qbfdgi;1dHG22o@(#us8*R#VHaj zPKjV~MhF(COt3f=s6T#Tqp+hAtPZIWYzz{=2iQ0yeh;t)Bz_ODCM13juofhK53n{Q zeh;t?Bz_OD2}t}NV6(#gpNZcDY&J;zCSbEex&)g8(j(ZMkUqiYf(!^Y4`fKNc_GsX zHXmdbf-MZ0m0*iN;`3#L`m;r01D_DE#Ub$t0s9^#J|SQ$L*f$xwmM{Pf~^UOPY&2x zkoe?)tqqA!4%m8-`3bf@WC4P00GTWZFMw?bS%_d8LE_N@wlO3gEnu5K;=uy8DP%E% zZ3c-44%p_9B?z_?WJ!YU3|Wd`yFivEl598FC_}K_A@mpJ z1p6&y8-o1~@&kfB3E7rlPeZmN*zY0R6YLL=9SHV3WJiMi39=KxUcmQ1ZVbR)hs2Em z*c*^t3HBx=?h(MIAaRcXHUkpR7+^CY@r(iX79^fAz}|txGX~hZka)%b`w9~GS6`?< z$H7KFg5x3k6I=vx0Kvr}2NIkKiKiHF@P(3zn-g$0!Ig!aL~!^voJ?@|Hk?9ml^~}QTxG~<1Xl%e zI>A+ioI!BaAV0zNhZn8}?93#%T9C5{t}f(kf~yCKZ%4q@hn!1raOY&sBRG8b%qKXw zb21kY9NamX3kj|n2KK`tXWXyDA{ z1P2YAxq{$u6R#w=Zjh@84jMRfHHPZ}U7NXv;Be=zB{j(}v(0YQy4YYyapm#Di z5*+TIO$3MQyP4p|LvA5BT;8n&hs&GX1}}ia_1#WzxV}3G4wqsl!QoQuA~;-%-2{iH ze-FXoDc?(Qc$)VS9G>O<1cxv7Ga||1t35z)_-YRl9KPB^1c$HoFu~!g{hZ+N)gB=@ ze6>dj4iD@Lg2My*lHl;bz9Kk0uw%IX$KdMm$c_^n9@z^p+P13O7@cwnap4iD@!!Qp{@PjFvC{WE_cI6Sg51P6o4{E^`Bz|ImJ9@sg8!vi}{ zaCl%p5gZ=a1%kr^`AMznz5PSgnlHfzgR|KC1`47Qof&7=? zvqJty@Yx_=6MSySHw2#t@-4yVg?vZw$$SXG?EVj*ACe;Y0+2Mp7ldR8z9=M1@WmiG zf-eCH(}D42Aq9dj2PqPK9Y~4b>q17L{`iG&06Q>!7~c?5A^4_{D#15{)Cj&gWR&3F zhtvta1!RoiTSCSOz7?cF@U02J3>F}^b-ln~>)Ktc&IzAGe@@K%;2>~w<-s3FF8hlCnpd=JR%1m6=f z2f_D(%t`RQA#)LYA4sS<#`lGUnqz!FNT@l+_lLwK2mCt+MWnBNtaG)cwQ-$D< zLsljD6Oh#i{#(fE1b+sy2EoHO;H{bje-5%1!Jmh$P4E{XarXoMH%Q$5fWHLwzg3Um zFGJQR_^Xf&2>u!*zAFHK9kLO@-+*jP@R^WJ2>upiQ-Z$@iSHc1--B#U@P9zQ50dc0 zKY)!E1pf%KCBZ+2Y(?--AX^jsTgWzqKttj?2M8i$TS8DF+Yv$xvOOUfkR1rY#P@$k z*a3nC*@+Nr$j*cSx81ESgy2GUB?Nry;X4NiJ|w<#fB?7MEqvzyA%yHn2x*YL2q6n( z@Bc|6AscM;A%yIZeTn}as2^+tAtz*iLdXRoKk0^F^`2?2NO2ts%dawH*Ch8#r*xLHRN0&dnZgn%3LLqezl`4J)1 zg#4HgaD$E|ge2}zJP05_citLD2)IMX69R6~350+fbRr?(Mw~1l(k^2mv?QY(l_IHir;!lg%Xr++_0z zp(EscLcsmAfDpRj`+p(q0HHhNB0|6ow3rZZ1EmuJZlEQEfb07yA>jHhB?MgGWrP5w zy|tVWaCuh{0xs`L3{N|r{#As4r++me;OSpO2zdI}5(1w7b%cPYe?1}KDc?W{ct$o7 z0-l9Ugn+MnGa=v$Pi}!1AmFRrN(lIBw-Exq!tI2BuW$z;;49oo2>1$j{XdrOIzXzr zfBX2dEX-Tq*1`f6ySp&30}~LtQS2H;#8&Js>_%(}ySp0|$z5QvTkrS9b^r02 z_c_b%%sJoh=j`u!Zp&i2!wDAC9X?_)-QYxv=>{LQR!n#Jn8kF5k6TQ4_=LrDhfi8e zcleaWbcat{OyB#A#a^P5EcP;;Y_a#~6pMXKr)vMFa`&Hb@~p)^rO#RH3;MjpzN9Z$ z>?``B#lEKcK*Xj|eIR1r&{r%roxW01_?L*KU8 zT>6g1=F@jA)?aOBve<7_OM}?&R4aqnA5<%Y*q`(> zi~U7Ex7a^aD~8y=R4azqe^e`mcpKGK>Rzm^Q~cB&;qyf^*U;vMumTl7X+N-TOK zEhXZ8=nRYZrCLcWdLOMM7QK&F67d+-O3q>b~0IID*T>ty;_u4DOSESl2#8+x-V?`H<52Wms#8;+l zmBd$}Y?Z`UrEHbNSEFo+#Mh#1iNx2YEWN}xrmTERFY!${Vd*74gtGJ!--ELB65o@u z^b+5Tvh)()o3iv0- zv|{nYXw~9})0)MPqLIZ%(z?aZrVWdap-o}`*3 zNc=&nc|rU!s(C^D8LD|fd=k~XAU>IDUeJn9;Xng}_*A;O#h;~XSo}G|*hmbXSWnpu1VThwg6i-{=sF|4H|- z_}_F-3)<*j7PQm7E$E>8SkPC`|Gu0ch*3SW2;y{@1t~h*f-K$7f;=5zL6PonL5Ut< zL75(CL6sh4L7g7FNUJSqaBzqPi_t?ZSe)t^N3aw<+=71e2n&{`M_RB9J<5V*>CqM} zPe)qNpB`hu0D7$U|5!eNM{33>3s$7ZSul_uZ^6p+1PgeKW}Iljs`Mla^dy~Z!RquB z3)Y~gTCf&9&4L!wKjU-@^aPw?!A4XUhk)syai#@&0?x8PPrzskH0ftsph+KN!615$ z1sd+L7HGK7)&BDmXn@YMK*Mpq1)7ZuEYNIRXo2?qA`7(d7h9lxzr+IV`=u6W-^W>S zFulxzL-hP#&ItnT`xO?j=QFOfK>L1`1={zkEzrJSV}bVlS_`!A*IA%_zup4v`wbSb zhse-*2-(`##L2D1={z!EYP;M z?&bx7_Wd3UwAc4qpuN7&0`2wv7HF>@uz)R`@t_6Tx`!;#);(;2wr+w2+PX)q6==UE zTA+P-)B-K-V-{$s9=AX%^@IglktZ$Ciace3R^(|5v?9+~pqrg!fo^uP_J1;Wue+UM zf$nyy1-jd3EzsRQXMyhac?vQ)%%Me8|BE7JN)~j^Gpekp-Vp z{TYJKss0SX7gT?SU>g0*g6UM>hv0jv??W(yerdr>J^x>Ef?yW?+Jf0sZ-!tF{lxBtYeNqHw#26NA4_aT|Fy&x^gl~%+1ADi zE|S=a_Ois*wA~Wh(%zOBOgk*G9c3jYu{~uaTY5?Cz)8#!JJPr%cAW{KlzWQmh#UD*G`$s9B+aSCN`C2=ZcZzXXWUCa`v)5R@u z23^7uyw^hJ=@X})94^ed`tCsA@Lp6g#+m67Op_guy94Hi$=H-J=4O0^ehWkrlT#?qpe>w z2v?SBO=>-;ULNBy%Q+knwo70Oe+=5AIAy8t>|SIZcQ(@a2tAsh1=3AEgVd*vT%EPwS_y|b_?|c++m@H zzI7)r2oIrmS$HVD+rq=>Jr-)x@3l~qexHS!?)xnqNguF~0iOAwg&N$4EYz?*Y^_k! zI>ACs>mwFwS|?hl{e9Fz?eAk2YJVTMP<#4>h1$j^Ez~wXWudn5X$!TD&uIUj;qJAM zlPuIePPS0{HN`^h*HjC&UC&ym?Rw5aZP)V_YP(*rP}}vQh1#x{jQ@5M2QOQwrG3Rh zE$yooYH44yP)qx|g<7dMEYwQ9X`xo?Eeo|$Z(FF9ddEVo&bw&w5^8zgvrxODTQQ19`%g}g^=<`Tl-&&~e{mw#t?{o|Gz294?&RB%s|2mjyp*qV#b+(1-91GRC7OGtq zs@)c<^DI>7TeyJ!U-&b}J=*`Cwzgz1y3mrn=?|9dpg&r&5BED*D(0?phqyJhmqW@X4-qyx~Es|_dZb*_% z+HT2C%FRh~G0M%g^paeh6YfruOHl4kl1tH;C6}RbOD;zPOKwCHmfVQX+XA)lwpPJk?4fc>>i+ zB6%WR(vl}pttyfyQ~mSbvyeQ6E^W!vsFoYa)9JF7JcDZKkvxkoZ^_ZLza_`e0hSy~ zSFq%{bVW;^M^~~|@_Y^kTJi$AvL!F1t61_Px~e5FrmI=<61uu2FQsc(avWXLl9$o7 zEO`Z8+mhGNb+rHMa5vX-vaThsr|Vhr2D-i_Z=@So@+P{WC2yu0LL_gY8bTy*rJGpt zHoB=L$J5P>|8^Y*n_Kd3s;NctUb>|vAEtvWIe~6v$w%ncmYhg6v`9Wmx3%QsR0E9U z6LdREK1sKS{`t@3Qygfrk$i^kXvs-*CreJIJ6m!J-Nlkq>8_T1j_zj37wPVne3uTf z^LydxE-M_4LLkF->d z9;N+1if<~>qb*gYBP~^-$5^ULkF`{djxdmKsP;v((D;bW5#5&#=_0R5ydvYV=G?>G3?vQftuBmRggZt^MaE zwH_y9EVVv8$5I>6v6kAHo@=R1=y{gfl%8*?E$9W7+LG#b2vS?oi!7xl=3+~2qv!t; zPLLW*FSXQmbeyHOrxV6+ z{heSb?dcPbuK2A|UYKgHea4xhG^?&=v! z=}snDN>@MGQo8ynmeNH|wUjRMSxf06pR<(S`*}<0y`iSa> zkouVFhmiW3>SvJphU#aK`j+Zvkou15XONmsKe5#J^ixaC(DVNpCrHhtpId4c{lZeS z>6ez8L%*`rT&fE}s*6su)NfQ5h1Bm<7lqUxR2PNRpHvq$o&8V!&4I28=@{jzB%P+Z zD5Nt~7lm||>Y|X&QC$?$5!FQ@U8lMzq#Lx`(oH(g(u>jgExqtfi_-;`UV`>mx*uI= z>80rpmR^SbXz69?PnKSe{%q;x=`WVYotD0vE@tU_DDx%hdnxlJ>HBDFNnVhiLYK1iRLbB<`dP~0N%}du zjHREa%Ub#cx}2q7qzs;@W45Xyr zrK?z)UqG{0wKTtgX0K-H@9655oG&GxAMnkiO zWi%&ST1K-m$TFIZtt_M2*xEA8#q4b?quJQjGVK5C!D#W4(Y|kI8TNVh_LkB9?qC`1 z>5i6Jf$n4(ZQah6VXJ2EVi|4Mu9ndj?PeLR$nKWm!dkP3aDt4kW)I8gV)nGmIdm_} zjHP>9=3KguWzMJjTIK>e)G`;+VV1d!4!6wZbiYMfZJ8@L7-5+!>He0vg&ts;JL!Rz zd6*t#nF;h@%RE94vCKqzsAV3dhgs$^dbnjCr$=c2kKpc~phsHfZK_{7$Y}D9w#>V9 zq-8#!$5`f5daPx>qN6PHH9gKU)9CS*>82-GrZta)6D>2Jo@ALGs^3`1ETpGc=3jcM zW&We5SvH`jTQ)(@uxvnuBnUT;}F=Qmh( zYkH$)x1l##c3XP0We3w+EV~`O)w0{u)@{5Xy8|6>Sv_L6TXr9Mhh>M-J1skm-ep-m zC3jo)Kzfg552g27_Aq*%We=zKTPu462M<{GX!@XKHR%soR+Ij)Wi{y&EUSrr#ImQ+ ziI!zmXX~*;Rs;8#Wtp|vdhC$Zygi})=h4e*-=DOs_Wdc#YOkNRtoHgD%WAJDSyp>J z*|OT}DVEhY=rzk~i(a>^w&D%TYAfDEih%W9e5x2%@= z16wpVA99REbMq0$STr{ubBruE-kSXhC&=nJ7iNtp5Bf z%j(a+wyeH3&9eI1HKx0eb1kcO zSyt`V{&#ct^XUI&=X3mj*##W`U$%$i|I04q`2Vv1a{PnkdeI*(*G_-3TnGKxa!LA& z8Dxg}{|%PmD?mg`63mRp(zmRp7jpUZ6tc>LPQ&vWD11KvaxfN*Eaw}3+NpdSuR!MTJQdUWFt5a4xsg=Mirg_&%Zl76s%1s)I6BaB$J3Q9cLH6-awk$PJ#r_})hu^1UEOl0&^5IG z>_zTWPP7fkokq0{$em8tw%i$X9m}z8bJn%onRGqNokiET+-SOi<<6!XT5b&8$a3fC zjW)L2Sh|Vj&ZV1L?mW7g<<6&@TkZn7h2<`!TUzcSI>>St)2%Fb3EkRqm(p#};w3kZ z0}T~&mr)HBa+gyL6>?Y5?JajD)qo*)71e+tcQw_3A$JYk*>cy?T`YGU)vO_Ry`KNw zI6>|Py1V6Wq(dxs6WzmdH`6^WcMH{UB6lm@+j6&24JvZusfH7|+o^^VxjX2vMOtmS zyExEvB6knfbRu^z9bviq=>C?wpB`Yj2dM@axe4?j%T1&QTkdIkh~=K4hgxnDJxu$5 z7(e_xJ=}6H&?7AO3O&+tuhOF|_ZmIga<9{omV1*PW4X8Kv6g#}>M26*eR`bbS|4z5 zyyZTmCs^)ddZOh%p?Zvv`;4A!xi9D`miwBXYPo6jG|PQMPq*B+^bE^Qr`&u?FS*&A zoN2ka^eoGD(b1Ocrh1H!n@9B+A@>tK$8tZ@v6lOVo@@Dm^gPS2OwYIcDti7e-~{4lbGjb3E=)#=5SUxVr~LVjI(spZ$F<1D`cz0C3((#tKsF}=d_o6swTyU%aR!Bv*u zj9zW|&8Z$EZp$A_^>iUWir#Cjye9EJ%O6kg zx4eez0n2N!9<;m$>mkc)upYL&25W-lS=Bj@SYC5A(ej$BM=h^edQAKO7+0hjc--=u zfhR1l8FcNWXtRQrdVF@H`Ve_=}Moqyx#CR%j->^ zx4eGv1yQ*dqU^Pze9E2;wAqs2X9#ZL;9xW zKc;V4{uBDP<-eeM1LVJ?`g!E1(f2I>9n~)y#XCOaA&;Li9AU~7p1CgIY z^?}I$KtHwok5s>akpGE(Zuy_7E(-Zys4fcmU+Gtt|A&5U`G2V{b{hMi|Br)jtk6ck zwL&kdyFj6x>Ml^|O?4M2bWrX>3Vo<<0)@VGmK9=jwiV)3H-|!o&TZ+1lPvABLXLJ@ zAy4O7p+e_dp-LB6p+S4B&`B3sVF{|$Lt#m()k9%fs?|e5|NQ41EguTYQ!O6~D^o2W z3airJtgt%$-3n{cKdi78{nH9-)4!~+4*lB->r!n43hPm}p{19?`kZJRP}s1ojRjex zun}b&q_8n%8>FxaWgDchDPFx$WgDb0h_Ve*&|m+Z8*qZcwlrad z-Dqfq-D%PaLukqhd(gBM_M+^k6!xZUrxb=$wo?itDBCH8{VBsXSO5EO;Q$VbRydHB ztZ)!5Tj5~J{!8Hy%Kl5?P+GIXVKlPB5wvcFV<|Hth2v<`3dhqH12Y$e6X;@AIFT-H zg_9_QB!yGxl2$mKE@cINGtTX2g)8aOR=9yKV}%>(vR1f_E@!R6cn+4g!tJ!b74D(~ ztZ+A7!3y`#6|Ha|UC9a$(t%cZh%&%Zm_S#t!jqI?mcr9?HSPauoJ`_`$(F)ox`q{| zP^Mf8uhF%v@H$=F3U5%RTncYerd$ed(erXPiFs_94JU%C}qycA;`Y;DCj-NuRm z-PVc;I@pRK-Oh?hy1f-sbO$S@>5f*^ld+Q(vvg-G@>I0u?!pO*i_u-JsG-p#h~lzz zcPlPOhgeZFvxgPg=ec`YQCqZ^6}3fsTTxrIj}^5Q`!3RID{3!>T2V_o%!*oCJ-;Yw zY4@|DmT-g>wS@ayQLA=<6}4)5lu^{`9ArhU$iY_BjUUp|3*V%>J=BW2!^5noJ3QQq zy2B%^s5?B;in_z2tf)IY+KRfvkyg|V9%Dt_;IUS0=?+I(QFnNp6?MhOTagQ#tKSbO z>LO3HqAv0zE9$~dwxTZP6f5ciPPL*g;4~}hy-(Nv^HS8CpJ7G4xmE>5z4@6|)SI7W zMZNiGE1pgNuQ-O||0|xu@&6Uaa{PbA^Ep1(iWlhlKaUd>FQocI6fdF|Sn*P-KSS{{ zsy{>Va(b~9ub`J$@k*+nLGdO!&Wblv{XB}dQT_boy36q#=nYW3gX#@XypvvK#k=X% zR=kH^W5oyQwN`wHUT4J#^m;2kLT|9*M0%qYAEhl_A&Qfzt`Nm3^cE{lrMFt~HL5E| z@pY;TNAV4MyA|K0x(O8DqPhtb-==q2@g1rgLaX>L2f8B^-=p_h@qK!q6+fW&Tk%8s zfE7QYS^*S4rdk0MKcNp>@l&eRK=Dhe)j)ANov8h1Ns8ZdqUAtw27SzmGwI`2oJF6o z;%xe)73a{WtT>lGZN)D7j1_;PT45A_rjxCxfB)lLEjNn4QY|-%ztLx{_&a^hiht1O zt@sap!HWOV7p>GzU$Rnf`m&Y!(pRh$qpzaHODWF5YgS57?JY`qs=Y<2K()6h6{+?X zrHE>8QL0nzElQnKTZ__CR9lNuf2yrTX@H)8?JY_x&=0M&64l~d zYHv|mm40TW)#&F|TAhAjr8TJbAEhWzo3|Q)v*@AfdDs)gYm?HSMy}HniJH z+tPVf8cgR~X*asSN_){BEA2xUT4^}_!Ah@Bd8`7rGx3uRvJlvvC=X0S1TP$ zHK-_!q8e0`j-wh>l#Zu=TImG(mz7SYe{27FDUIPo(~QzNRMU*ox%59Po!8dJLM&3c zi1xD5CA8g2<7jUyT|qmnbS3R$rK@OPD_za=-xcEorF&@HO83&hO83!(mF}mZl^&o; zEAb1cD`lmJXxd88(TtT|rCBSzMsvdcmyY5fZzUe1u7Z``qeUxyL`zosgqE$u^mkRP z#1qg}wGvN2SItU10bP-mnEtN1mA;~_1}`Wv^<7OXF|}QtR+>#0v(g;8xRn^rt|hF* zzH}{VrEa>EmFCfYR$@TAmbTIYx{S3-Ola4#R$54xvl0{9wY-%W(60VgVi3CqScyUG zTER*TVb_XQVhFodvJyks#qSU)F^F9&TZuvJ(*OR)OIwLy>sr-H3|!Z0#wxdUt!`xv z+!|Kaz^!Rz4cuB*W{$enwz6hv9V=^=*0r)`X+0~4?0?t#R@MOV3rNZulnt${LD|U4 z+TV?>to_}@%G%RSt*kBO7m$>-rJGw>TgNXTDQmm<1=Q8zrL6rLWM%EwR#w&?ZEa<3 z(Kc4r7Hw-~ZOdRQYg@LnvbJS=D{EVJurjONwWF0;!B*E!oS>{F+}X-n!dtbX?PX=H+TM$_+R9qCeXOiy+tNqRwu8y~| z?&<_9a}!-BT3Hu=l9hGwCtFz;e~OiL@uzD4c`56gPqVVV`E)C*XINR)YN31NO-el#s=*?FCklteDkLayd{+Qlo z5tin}y zy=|3fmDTANi?rG*YjE(TRo0}MPE^*SUt48eszF6%eX2o4Wn=oSRW_yHS!HuN z-6~s94KONO(iv76L}#}2!XIoyXIW)iI@>CPsU{wk?dV*qY)`wavODdz$`CrwDtplR zR@sy4X+dQ#swV`M*4`ZG5kX}i`h!*Wr9WC_DE-MQ!|2ae8BTw(%6{}$tBjz(S!I9v zyHyURdO}b+g#M}h=cRHeCx2OG6#d&O$I*YRayVG1K3o-yOHgnKZDIer3s$*|7OipxEm`GCTDHnnv|^R3Y1Jy% z(3(}QrLBk;RBonqtK3Q(R=JHftumf=TIF`Sm{snei(BPEx`b68qDxxkVY-x69-;lL zRhh`a(pGtt^6X3HF}kc(9;eG$*RTqYZTFg1nM>EQ%CB^7tNcdS zv5Nlt-`(q4)zO^dIC1FYCGN3s(J!8 zv#Oqe&C%keswZFztLh2Z(yDp_23b{4z*bh()NgH72DN(|t1_tF+geq_I@qcj*6plX zrrTS!!t>v~11G3*3*9?fRfD*bRW*n^TUEoii&ZszyINJlx0_Wpe7jq|m>E$PTfphU`$QYRC?=s)p=vt7^!Ou&QS4NULhb zjR2x9S1(3ae^nuC%IV<|?adX0EoXX672JY6h;gs%D^d9WSUJ zO|Q4A_U;C&YVU5es`lKAUEOU}-PJu-)m`0dRo&HnR@IH%Z&h9V16I|=KWJ55{6pIRhq!xP{liw( z)laahuKp3L>gp$2RagJ0Rdw-?SydPCxK(ulPgqqK@T66B0Z&==A@;xfX{+kZpRuam zaFSK^hLf$TH=JTsz2Q`=>J6W@>Pz%FtG-O1x9V&31*^VEUqp+S>iZn%hfw{T>W5JM zg1%zauj#8+oksO$sD49Vx9W8IhE-=!eF&=g|+WI-h=M)dln;tM<^3t@!4Z<)KXNdfm()YHBigZZ>?6K-&w6lwMwW}>Gv(Ya8jdMGSnhE(`ubm%ZJ)x zRLh6j;*{l++7eW&huV@esST&>t)lQ~mtDQnCRy&ops=T0f8m(FF3>umK1x54fRy&h6tTvi9 zt#&qLP^C78GN@8Jhcc*A8%r5fshvxgv{vmr4wkao1(d0k+J$s!t6fBwvD(FSS*u+_ zm$TZXlqr|mININ8mr;gXYM0X$tab%mQTx9le{dxyD_QL-I?!rY)0M4u4PC`**V0w3 zb{$>KYS+`%t#$)l!)oK{npV4=u4T15w7hFu?M}Lm)$XF}TJ3JSp4IN5>s#$!x`Ea1 zqZ?Z7e!7v>9;6#v?IF5})gGpsqQy(?5e_!9+C;j!)h5v`tTvf$X|*YIkkzKrt*rJe z-P&r;(QT~uJl)o6FVMkOdy(gV-gcaz_7dIRYA@3rtoAD1(Q2>Jovijc-Ptnx(p{|f z2Hn+aZ_(YX_8r~bYSZbEMOtmO?>X4RYBT7bR+~xpvf3=Vx7B9TeXKTz>cdf+ONUym ziw?6|Hyv)Zd2~Ok&8H(;df|r`(EY8}Ll3apLVBRpexL_g?MHgB)qbLfSnU^jsMUU@ zhgt16dbrj8q(@jy|NZxQM_TP)dX&}vqeolRi;lFYH$BFpzVuj&Vsw;6aeACZ0X^QL z1U7h1F=y-0L*OL1_q zMg8a{7A-?BwP;y7&Y}VIGK*HGms_OYBUf0Y-y>IAv@X5MqV?$27OhWP*YJX91A47R z8`A46+L&H%(I)f;i#DY221EcIIHbMMLQA7VSar zuxL+ur$u|yyDZv=-fhvo^d5_b(t9l$M(?v|IKAJZ{pbVQ{|C7H5u7||(f;%yiw>X< zTcpQuf<*_@M=a8#G|?hGNsn4Il0IgUo|wliI+i|R(J1zR-jf#Tk$K7@Ju**QbP|2W zqLb+)i%y}FEjpD>vFLO<)uJ=#vleN%pR;HTeI6}dB2D)T7HPU)wCG&=l0};0mo3s1 zzhaT5_*IKE#IISTA%5K=4e=WmjiYZ`#IUyJy~PP4P3zkhX*G*&?mfFBWNae%1c-66tP#vq*RQyG6RIKP=K+{b`Zz>Mx6QSASci5BPecgdW!1LLiWF&=0JaldY$SIQD2(s&rn~6W~{y}&02jqnzQH{ z(~{L!p{+76sIN*ZR$q-)t-d;~S$z!}S$#dKi$#5X+OYZtv}yGXX{XgUqKjF5GrG97 z>f3U#gw=PUOIm#|x|G%Trv0qG57o+`K8$K*P#;dUGN|uMwKAv=rOR7=Kic2wBj^C_ z{{X&ee@<4g`ax7niTc4*ONsg+R7;8a;Z#eB`Vmx1iTa^bD~bAHR4a-4kyI;*`cYIX ziTcs(|NJ$rek@(f>L=2*t$q?+$Lc53b*+9XUC-)gQf&k3qp7w5^)slp0s3!E%-_iB zXVZ<*;-x-@gH5d0B)X~fnoPA<=rx6IZuJZ37FNHIZfW(4=pd_KOt-T7C3I`6UrM*J z`Z%8d`PzTfFQbF4emULF>Q~V1t$rol!RlAh9j$&f-O1|L(4DP*E!9k+ehb~z>bKI} z7HPHBZ{uKhtB)o`MIAJuT8{s7f*qW&P=*Xj?`p;n(jhgtm* zI$V1)oFAA-HJzwGN;RFRKSB4m`qT6Pt3N{zw7RxTLyNk$Ohb$MRH~sx{Z*>rMEy;w z;Y7Xl9tVe8eHzs?qy7!m)S~_!)zqRsogQuVd32=J=hI`X{s+}Cqy8ryW%a-4aaRAE z9+tVT}ycs-uzB z^M58MXyoWw*2vS*)+o}mtx=|9tWl-sSR1vTBAYFvqmR9-x`b43q)731P2#d zV@Z0EHI||mTcaPn#2WnOn}4Y_mZjsYu^he38q3qmt9y8ajb3Ms)#>%tSeM>ljrHh_*3j>To2)U2-fWGn=q=W2Y|X)~*4T#L zW{qv>cxw!%dK}T%f!<+_9qFCc*ood{jh*S;*4T~SV~ySEz1A2)@6-O@$D8iK$^F*Y zlRjXLz3GG2*oQu3jeY6E))-1BSYsG{#2UltL~HCv^;n}Zf<9)A{n`Kdk6Ysa`h+#~ zY(HrYp6mHfS>tf}v^9>P&sgI~I>{Qx(aF|0o=&mG33RG8PNdJG#Y^KP4xY0HkJtR? zt#Jx{!5XL17p-v`eaRZ9)0eGr27SdEy0KTSaVCAu8fVehtub29{~Mg3aW;L^8e`~N z);Nd0ZH=+?9c!FR-?hg1^gU}_K;O59o{SHyp(o=*Yg|D;TBOz1xRQg9t)VC26KgR2 z^FOtQrv5W)XjnhDh6eNtYiK~fw1x)sD{E*#zqW=3bec6Zpx0E2Hw5MIx&^~rsL;E<-8rsMC z*3dpKum;;UzsDNdx`o!zR{UTMZN-n)&{q6p4Q<8G+J9ae+KXSTp{@AU8rq8AtfA%q z-5OejKdhlu_|qC%g}W9!=oK~#46s=lwXQL~~oJD@1d9stZJO2dWE1b4R+8 zHFu%|t+_MRg`>F(UB#NaQr!fayV2FGxqFL))vY;%u3^nR=$h8ti|Ve?+?(pI(cFiw zW6gc(y4D;@*R$p@s+B==f4YG+5272Q#Y^)94mPspiB!vn=1FuDYo0^3lxU8nT1qs} zrJGyxJi3K7FQi*q^I|&4nwQY6ta&N>zd-wd<~XWtK=U%HZ9wyKI@p?z)9tMJ1l9JS z`6Si$pviB;1==1opP||wG$&DQ51Nyywr3aizd3~i?GKt$srCoW7wPWSe2osV=0{Z9 zgXYI{PiuZcwN+?-O0`vJenz!bXnsz$RcL-mhid=XujbcOdxhpSI@~($p!-?#8#=<8 z)2a3o&F|>})|^2PwB}5zeMWN@J=mJF=^@tCzkhAPq1K#B53^<$J=~hz^ayLtqeohE zK0V5s3+U0-?4cvAxsV=X&7bJ8*8G`{(*E<({DYI@th0?CZ=L!Ttl>mwJJoQavk%p9 zqO&hO**fF&6zdG=sn!|N)2uT|Pq)sLp8qpAL1&I?JJ6Y@XIf{0o@JdyI@&r*^la-a z(=parq32j#M=M$y$9inKxTsP3A4u_b+;@_5GXPW_|ym<1Mbiy4~W(Qavq* zYk2RpU`=|L1?$nfEu~ezNBe&d|3dgCC-+*430!cWr52<2TXqtCz_MD>2Q90Md&u(o z`omUuk4~_P-t!Tw=shP|W$_jVk6QIG`j}M@r;l6p2>OK8_N7l+=aTd(>s*RHZJqt- zGuF8@)rF&T8LA6J=dyGPTD-LB&8M3F_n!sNn*KMR1sW2x&!CzSv}p!ju#TiBz?u=H_=xu&bGG}yv7OQw@?ia;01^w>D!jj9lvAA zFX_9M{EBKDklKyDZ>esoA^(8=PtD`tLrducKC;vT`mv?;OXU+wpGrTqw4VRZEIo#P zZaH1R7nYw(by3K7QC$=Y8uqWX|6lXZdtXeaSy9jPH&z`*zqRNr`kh7kB{bb4y~peh!+0PbgdG*E!wY3^0gw6$?Fi=>LQm!Ble6MD+RF2S((_bziqZ>IcZ$-tv}BbVY1t|_(Tb&X zF;z?HVrrJs#YC3U#ndgeIBi%fwFC!EOD#z|EwvO~%raY3{SYcz1^o;vD^mRoDl1X_ z3@QU@KdZ3cJxg19L%NJ*cBcCGzw*+S*`1T+to{aNsHFZj?QivW=m4v~OINVQQB)U# z#?e$4g2qUy3qj);stZBmSh|We^w)oTR<&NQ(bcTi>vVPNr3+fadM%`DS}z`@9v&&_ z^&{nxl3qX2b*$IVbY1JExmnM8X>Qhs{{HV?8k`NRmj*}6gkBn)jjWdjXJhNdqtvsB zb!Z}-9$p}Sb0Z|Sbq=R3Na^_fmJ zFX;0<9b$dv(><)u0=lR5>7jdBA8pOv)<;{jkM@5b-bj11ul3R147EPmnqk&QTQl7H zXlwSfzW37+*7rfWzxCBpA7HT;>46sL{SLCAr8hj-0=?lOmef)mYDq2CVV2ZN9d7B1 z=@FK`gdS<>ar7ulUq+9%^c8fZrLUyNX#aUhU(Ly}mcE9Lvb0v>I7@3Kj<>W{;si@; zB~G-ghV&%MYDiDEY>J*@*)%=X_-_sH{GY}Na$nKYP5+X(o-?eVq0}}(|Eh+bGfjUF zNY7cO{}bFZ+9Lh=*%m!Q$5`|LJ;$Q^=vd(gBkkY07U`SnzD57h3#{HoFEss? z&Yp`*f2FfWTLS%+(H?CH>e}KSwRzfvlU*Vw^-q2daD(e zqPJPGA02PSrRnWf96;}|;tKRmE3QcI(*EDY|MP1lPVTni*YqB%XmIYeN}6ilP{~kj z8!FvYdyC3E`k+M{(T6PBj6Q6Owrm2&s2`|h*N{Meb3xBU)8Aas^Qh@>M(BCW^fx2) zJZ}1%2zs6{{XGOdPn!OwgPx~Mf73zF)7Hj~_B>-TUBD!?c!}u(CR%+5b>X>wh-}O=!+Krm1+wSEJ?M6NPMs7{}oOUeoJ4q@H_gNC1dn; zOUCINmJH~dmQ2vMEUCMH+mgEbcPy#Zde@R#t@jq;um2v(!TYAaX5aIH>96tkd}xtY z;UjBm={~mR5A+jj*B*ar9UIfntYZ`Uxpizxzp$8|pf9!mU$O<~Q~d%$>;kGGMC?L3 z&0-hPZ!C5()rujmM@cJ&xSpix7A!$oVhME7Gc3?{&$OVW{hnok_ItL4x6wHkj;C`i zyq$Jgaz)y0$(86lOYTDFTXI*rz>>Ss9!u^{7i#}`$@S$#ONsn2`lIECQw_?@hDTz7Ne=`@S@9?L%q7+F9_0MQdll7nZD@ z1z%XUb{2eL#oG6$Rd!AWoz~7SE?mso zkEDxR`%!cWYd@MUY3(D~xrIw{f{s0DKkLv+ENvZHiDj&hK6qK{qYqxr^pr1L-s&T1 zf9umt2Us8Gdf^Hdzmqce3s-FMwB5tON*2GD4z#$oePs)@YpYn0psQLC($y@XH)Ikd zq4!(E!n5d_7LKKBS$GF+t<4M4=h1a6eLh{+(ihP6EPWwe-!kLq29|%0ZfN=E=|)z# zj&5v)>**#|&TT`4~z888bU<+W(^@4w^0ot8uwB?j%YkU^*EyOAl2iD#v^o? zH73&G)_9ceXUz_($Fapr^KcIIJfe97)oP%5B-Lu5c@))Zpm{XaV~TcN{2|t^i$B!b zZ>NV@hdxZ7feu}yJ_8+mUTdK~1by^_`Vhn?P<;sEx|@-f)Lk88sT1k3mO71&veX&$ zI7_`qkGIrI^aM+5`1JE9vj6GLI5^4Do70mmt>HVx(i*-~Ev@0x2O@I=J>4=lQhhiw zH&JaZGI!B4EpsV}Tdj@NUZ^JuZDaKOkLLuv9-_BfuV?8U*82r|r}ci3YCqBYb*lYDhvrB7 zi4M)rz1E@m(RQLk^P}xV$7%EdVgEZcKo45STT~Z<4lVn`)}dveU>#caN33HWooF5N zsh)9kETE5BM-P46Iu_C=tm6mTdXg9P*^xeFeRiTxTOTdmGuCGuon(Dea~Y0P)#{v`cO?d zV*1bzEv66s$l_bmk1alzeq!-Oza&281VKbUvtT9qxdmhC7nUf~FD+50Us<9-zqZ5@ zbebgw(QhoVFa6dM=g{xa;w7Oaoo)#&|M!;AfXuM4ozAqdH=Sk4DxGaf-R&Gp>PB>@ zNNr2IEZvuOTl#-2-35GFS+|Dq1pxwt;E({JV6?(Xi>ai3w_-QC^Y-M(>mcei>+ zd!Ki$`@8qgb54t#v-dtsNcy#>>iX9TM)3@)6^x?hTPqkvy?JN_qcj`U3PvSOb%3aB zNXLfC#&le$Y(mF}%4T#zsN6~?x}McmXJb;RtwAS;+M4vwP+N=s6>4kKze8;u`cJ6o z%>NBFo%w&ErURG~>J2lF*VQ?QF^LNvADvjPcPg9x!KySHtVVOe>NForPcsmV6)~m| zj1@6vFc>RhOfguOmV(Ve%faTP6(&J9HZMDRaoBuRFAkfZ>cwG;QN1{9ajF-GEkTEZ zEk{`uV$0K3uoWmvTx>C_W5W`qWW9ACRN*XhFusdjP9&TWFQcVNwZmMa3-9t4EuzTqO!S16A278Du6zpNT zaInYdBEg=biw5JZZH%T9J$r@?%_rIY)&@_jUDKQA#niR zC?pP~8;8U}bd!)ci*6bcXVcA~zdkK-J{x*sBrc#^gv5n(%aFK)ZWR)j(yc?{GP+Gj zTu!$QiL0m%9Eq#x_91aC)qx{%ovwck4HDPWokHRUx^qbAQrRUWZlSw|gjVlvA)&>) zdq_M<_Xr6sx;;bU89F*7o~3)ao+VypWABi7h3*p)uhM-(;x)QoNW4z>4~aLZh8~GG zsfHejx2T35iMQ#&A@LqPBqZLahid*0#h_eGfmejY7xc=I z_>yYHBdHdbEr zNu9Z_3MBraw}zzA+d{IB-X4-V$U8z(2h+Qg8zgl!cZKBL^#3LAVf+6jGi=`*lC118 z_l2Z-e@H$^9|*~ZsP>UOjA|dr!|B7Jm(<21A^9l%f62$#{(s5G+5UgYC)oaf$!FMp zG9;g+Ple=j^y!d%p6Y%`{*UT@NWMUy)BJPilbw?!i-k>`osrLrm56M@l z?uX=SRQE$t?+v;il5bGm4@tc@=zd7PMRh+U-)8>D=#EIfLv=?qcBHx^nzPWiL-Kv9 z`yu%OeK#aOqVI*|msIyd@++$QA^A1^FeJxNJ*LM^ax5Dkhvt6tlh9;cjQKP)nZq%k zg+5l-n9oCBAJvtBzAV*?qc2bO;^-?-y*T=6^qY`m+IwTZWe3S0>31Rd6a7B)ZApI! zeLGOy4}BUT-4A^i)1O1%CG?ljcPaff^j$`O3w@g2-(Ao8bTog2zQw4927OCYjSTvh zq2of|vUGgt+nP=YecRB9p>JC{DfDeeCx^a0>7SwR9Qv2$|1VzPJoq@%v*FA*EkShLnDdsgl&ql;4z8kIQXrI;5DVv6;~1Ivv{| z(p}2jN_rZa3#sL4KBTsy10g*v)pH@e5*-Zbm1!}gcBdugUpJ{e*eQn;Lo~J$Qv1+q zNbO5mJd)a<) zfYh0ER7jmgrwT(FwW&jz=^U$3LPlq<(Lm~AI$cPyNXJeuJ${2#I(CMTx{}TqQdiNL zLh5Qdb4Xo7X9*b%=d2;cB|3Jtkka(c9#Rj}IYR0&I%h~dPJ454gQ2JC+#&S>ohPI| zr1OT>zzG-65n8 zp*x23p>(H^K8)@h(udPsLiz~0Ye;KEcMIv`>FyzY0@cYOeInh{@yDl5Vq5J+9A$KGRj#?`f_@(j+Qe|UqN-& zNMA{H*2uJ}&Kj9%sm>Z1Ek>O+(l=8*57M{LBSZQ&dQ?c?PLB@hJLoYX)6;4_He@s; z$AyeWLBogihg8Fd^hZ>~hxEr(qlffQ^yHBKo1PLfx)e_hnHD`QWTvC1YyP>(OwZ04 zAu|U(Gi2tZX9fM8tYbAj$jn9037L7RrV5$)>3Jcu06jls7NnX&WEQ2GLSz=x^{+WZ zW^sCP$Sg@O37HkB<`9_`spb%wm8j+rnN_K#5ScZprVyF6=v5)ZS{QqE$Q($o5#}tT z1#xZ2989kZnM3IHA;bKSy&+^+gkx_E8O`-gA)~q0T0ll~t+jxR=K9u<(I{$NAfxH+ z-Odd%nqMspWHi5bhK%O-u8`6E-W@WU-+Mwvb9!&coKEixnKS79A)_gMAY?S94~AZb z`5gOD$Y>rP4jE0$BO#+{c{F4+EsuqaM(y#C(WpHUGCIg7Lq;$9RLJN>pAH$l<};fA zXEvj9%z}A)^<1A!K;`*cU@a_kAg3bl;al=5zW=$b3Ox4H^CQ zIb&Z7nLp_3Av1=)5i(=xn;|ofz7;a#>DwVQfxZ(mQ|P;)KS$pS{dxL+=r7U_(Br1R z#KwnVNJsrq7+!{c9QtRZpM?II=%=B7X8Kv^pM~m7(LXEInWBGo`eo>!lYSNY=hF56 zH9P2EmVOiZSEt{G{x#@#p?_`qeHhmK{t$*W1wV$?rSzxJe;)lg^j}DS2`w()vA;V0 z`n=28_${=qpudOy+o`4m{dZ7J3HmjUniBMD9yKNCe~oHN(EmD}5c)NB6GOkIZc^yi z)J^W`h6iZw{tW$^m%l>4rseO@ui^YB^lLc(4gEU!|3beGU`puc05~q!&jE}}gnpi5 zTr#wJSF+I;vLiGVvR#@E*-z z!43K~%yWkRdFWiBUuQXY=s%9?_mI7T&Kt6~()mL6cB;8W_CBhqMfQHGsYRBz_-%bUkItqWv%zZj==f$Vow>jK#ysMZCtT>s;i4%uJm zG9f#eF01+HjVH?`HcpEN*?*}P53>JJEgod2P%R$h5_F}I>!T}&T$-*Daz(mo$Z5f8 zeIchcwtC1d%>0jABjj}EYlfW8e65hv*{&V3RjMgLwn5hoIUT$f9&$SP^+Qevwn510 z0JI2^(*bOR9yd81z{Vk`2W}E_df=uZrw48pa(bXvFmhUSTZEhz)RrN4Io&GcuAp0o z+_iL@kZtPv-{2stgT9YgL8x>LyAOLq>r`>3uKm zw~%{~?(TY)dz6hmLhdoTXUIKHM~B=~bgz(mmF^vKZ_s^0?k&1+$R0%Z3)zF|{vmq^ zJs{-Xp$BUIIm6rsR1ZS#bE*d+_XX91ko%Gz8nWtPA@>hGJmj=^j|llP)$=1iM2`&l zVR}@^>kna$J38ds^q7#Jn;skT^U&i$eqMTf$m^OvA>`+$S_{Z8K=lTM{DSo4kY9+N z67mbvQ#JqGry&1;BNoX}%r^InCFDVIV~x3Il2Sa2Tl5N5VjpJ{oeG-^W5u^Q+4j10DKA7#N{XhJh}9 zO7s5|cbbZwr$bJ2`Ais?pFSG~7NyUHfyLIeIX32L0=35Ytfg&fEMq| zVPFFd(JNtK6RI~C*z$alqSI4Je;3WE47&w`J9tN~9 zzX$^?%-*;!*};HT=2v0hHu`lKxPyKZ2DJFT4Fj5@@4|ql==(6BY55@xXj*;@0~*br z!hlBeXV}a_BbbpP{=1{VQ~}SpN?C-+zqzCv-HG|AxY}^uJJ;o=ypc z8O%5ym+L*uMk3@m;_)1zkk{|0LVi!04*AhE6AH`F{!mz!W3*GWv37do6*5g*qjzaVM|&Hg`H_R6gd0wl~CA~GOSYAhcc>CIFvG~qQ6jb zJfkZ7g_1oR-(&}cW9U#Q97l&k;ab`Xh3jZL6j;6EJE3qR<;_J3H`8t?+(Ji%0`Icp zrwWDJ>D0pf7oK8cnvmCTFmIB7i%u8vpHSvd@}E=9Aqp?h8AIVkI#VdTL}v~K-e1Pg z5(ZAEvxb4MXm2)dFrc}gJq%2sbA-aXbk0!Vy<+@ap}_ma__;&jLpo0wU@?!MHw>_# z$IllASkU9=4~4OGfzT`Hg%=D3mgV?`LO}7t>aCtWNQ{-leC!e5lD zMGAk@B}3sKx>P7|m5g6n^S?BA`i~v0U=*fMtzfVYZM;@629tF8FxW>|2!mSPD~7=; zT`3G|ZLAyyHC?NOK~2}HVQ?nqfBb4;KxeRe7|1acXw5LF(OD}D=*-p*13I&H z!k`Xd-7vT%T`vr7P1g^D+t3Zr<7RL>HZ}}{+tZE0;Qn;uFnAQ*Bn+NFHw}ZQ(apl3 z7T4xs@Ep2D7`%+?Krna(-6{-TtLtA!g~3~>jtYYh(rv@wlT-(Y!KbJW5Q9(C9m1ei z`;KAoWx7)se4Xwb2H&TF9o;I0xNd^Up9B=cEUO;(YYLP}C)JP$(`)4-UnJ=pmuF zFg-LBHT;K#qK5zQP}J}r5sDg3y)KG94d;=esI$^Mqo}hwIuvzQ$AqHR#Id2M13NAh zFQmtZ;zd+z1jUQ#iJ^E2Jt-6~r6+6txhZOaoDzyx&{IS4DyjvB;x$wY48?2d8KL+9 zJu?&^q-TZVL-g!We3)uMqWB0sHxwV$^?x2a82FE#9|oq-3qrxD&JM*FsLl?>H>l1I z#W$(W4#lsi&JM+|sZI{XZ|UWs_#M@WUZFYoo{cL*@dv82M)7BQbtwKyH3BI9Ml}K` z{!Xt8#Xsovq4*cQAr$|nH-_Rr^rleM3HNU121T9mEuo|{zBQB*^tMn+(%VB}CVEFG zXqfK|9j&FiLg8t8cPORlJ)y*qj=wkbN`q|N7fMBXe<)R`<`$(Y)!d>q71i9LG&R-S zqBJel+@dr+eKeG2q>qKt%=Gb4nw37G`G0~3%*oD^p)?PDDwK37>MT%NfIbsSx)h%c zrG@Bop|mi4J`_Hs{|kfbQLP;eu1{YKrPX?Dyc9}n(w9SNE&56*txaDIgFDdI!r+ed z^)R?I)q+InRQhHpokrgZrPJx#p`=Cs4tm^_wCdjtC06UKD6y`3<3DEyrQ7Kjp`^L{GL$rgUxkuJ@cX+{YxnQMt=>ZF;x2~ zjitYb(m47@DCsiO{ZRUs>V7C2)%{RTP=6mD&rXt^38CCaCx&v0P6}mRLX$%|L;nor ze)?A^XQ{4tl&7LP5R|8;IuHzUMUB_BkD~tk_xLHHIIsV?apO3wxCq-Ep%fRV9H5k! zp&X!;m!%w_lvkh}pp=%R9H5lfrW~M@*P$Gsl-H#>=3h7E_1VdX@`1jU$mL9MJOMu>wimjP(Fih6)L)uu4U-`f5JAQ zas$i+XDO7Yd?d*D1xu1<)LgfLvYp6U(cMFv# z=ho)&C~o*t^2F0CU}HC<ihJnQ2l^j9jYJFYeMxCdTpp`O=o6u~ zH`Voy+P?IuP&=4D9csG7o(Z+XsNM%qJDolkYFd=fhuWF+f1!3(kBt{XO{?<7P&

    09V=Q`3{a9cr2vEj-jd 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≻MXJfhYKDwo6Lg_7qz*sUMR{>1o$&3bodND@PsU0 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 sSaff57xr8-^s%e}69g}j?(^3zfh10qs&Z2$lO delta 182126 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
    bP5qJ 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 - - - - + - - + + @@ -319,9 +316,8 @@ - - + @@ -487,7 +483,7 @@ - + diff --git a/source/RevitLookup.UI/Resources/Theme/HC1.xaml b/source/RevitLookup.UI/Resources/Theme/HC1.xaml index fc241b19..06493c04 100644 --- a/source/RevitLookup.UI/Resources/Theme/HC1.xaml +++ b/source/RevitLookup.UI/Resources/Theme/HC1.xaml @@ -133,18 +133,18 @@ - - - - - - - - - - - - + + + + + + + + + + + + @@ -204,7 +204,6 @@ - @@ -375,7 +374,7 @@ - + diff --git a/source/RevitLookup.UI/Resources/Theme/HC2.xaml b/source/RevitLookup.UI/Resources/Theme/HC2.xaml index af54fb4b..dd9bf09b 100644 --- a/source/RevitLookup.UI/Resources/Theme/HC2.xaml +++ b/source/RevitLookup.UI/Resources/Theme/HC2.xaml @@ -132,18 +132,18 @@ - - - - - - - - - - - - + + + + + + + + + + + + @@ -203,7 +203,6 @@ - @@ -374,7 +373,7 @@ - + diff --git a/source/RevitLookup.UI/Resources/Theme/HCBlack.xaml b/source/RevitLookup.UI/Resources/Theme/HCBlack.xaml index 8f320bb7..f1c98689 100644 --- a/source/RevitLookup.UI/Resources/Theme/HCBlack.xaml +++ b/source/RevitLookup.UI/Resources/Theme/HCBlack.xaml @@ -132,18 +132,18 @@ - - - - - - - - - - - - + + + + + + + + + + + + @@ -203,7 +203,6 @@ - @@ -374,7 +373,7 @@ - + diff --git a/source/RevitLookup.UI/Resources/Theme/HCWhite.xaml b/source/RevitLookup.UI/Resources/Theme/HCWhite.xaml index a25854bf..60ef60dd 100644 --- a/source/RevitLookup.UI/Resources/Theme/HCWhite.xaml +++ b/source/RevitLookup.UI/Resources/Theme/HCWhite.xaml @@ -132,18 +132,18 @@ - - - - - - - - - - - - + + + + + + + + + + + + @@ -203,7 +203,6 @@ - @@ -374,7 +373,7 @@ - + diff --git a/source/RevitLookup.UI/Resources/Theme/Light.xaml b/source/RevitLookup.UI/Resources/Theme/Light.xaml index ee874abf..65af6061 100644 --- a/source/RevitLookup.UI/Resources/Theme/Light.xaml +++ b/source/RevitLookup.UI/Resources/Theme/Light.xaml @@ -260,7 +260,7 @@ - + @@ -268,13 +268,10 @@ - - - - + - - + + @@ -320,9 +317,8 @@ - - + @@ -488,7 +484,7 @@ - + diff --git a/source/RevitLookup.UI/Resources/Typography.xaml b/source/RevitLookup.UI/Resources/Typography.xaml index f32cf17e..14ac44a9 100644 --- a/source/RevitLookup.UI/Resources/Typography.xaml +++ b/source/RevitLookup.UI/Resources/Typography.xaml @@ -3,7 +3,6 @@ diff --git a/source/RevitLookup.UI/Resources/Wpf.Ui.xaml b/source/RevitLookup.UI/Resources/Wpf.Ui.xaml index 4ed078f8..36281534 100644 --- a/source/RevitLookup.UI/Resources/Wpf.Ui.xaml +++ b/source/RevitLookup.UI/Resources/Wpf.Ui.xaml @@ -14,7 +14,9 @@ + + @@ -33,7 +35,7 @@ - + @@ -43,6 +45,7 @@ + @@ -56,6 +59,7 @@ + diff --git a/source/RevitLookup.UI/SimpleContentDialogCreateOptions.cs b/source/RevitLookup.UI/SimpleContentDialogCreateOptions.cs index 35a3f7ed..7715f9e4 100644 --- a/source/RevitLookup.UI/SimpleContentDialogCreateOptions.cs +++ b/source/RevitLookup.UI/SimpleContentDialogCreateOptions.cs @@ -36,4 +36,4 @@ public class SimpleContentDialogCreateOptions /// If not added, or , it will not be displayed. ///
public string SecondaryButtonText { get; set; } = string.Empty; -} \ No newline at end of file +} diff --git a/source/RevitLookup.UI/SnackbarService.cs b/source/RevitLookup.UI/SnackbarService.cs index e6336079..377745f9 100644 --- a/source/RevitLookup.UI/SnackbarService.cs +++ b/source/RevitLookup.UI/SnackbarService.cs @@ -26,13 +26,8 @@ public void SetSnackbarPresenter(SnackbarPresenter contentPresenter) } /// - public SnackbarPresenter GetSnackbarPresenter() + public SnackbarPresenter? GetSnackbarPresenter() { - if (_presenter is null) - { - throw new ArgumentNullException($"The SnackbarPresenter didn't set previously."); - } - return _presenter; } @@ -47,7 +42,7 @@ TimeSpan timeout { if (_presenter is null) { - throw new ArgumentNullException($"The SnackbarPresenter didn't set previously."); + throw new InvalidOperationException($"The SnackbarPresenter was never set"); } _snackbar ??= new Snackbar(_presenter); diff --git a/source/RevitLookup.UI/TaskBarService.cs b/source/RevitLookup.UI/TaskBarService.cs index 2fa3ed4f..03d2df4e 100644 --- a/source/RevitLookup.UI/TaskBarService.cs +++ b/source/RevitLookup.UI/TaskBarService.cs @@ -12,7 +12,7 @@ namespace Wpf.Ui; /// public partial class TaskBarService : ITaskBarService { - private readonly Dictionary _progressStates = new(); + private readonly Dictionary _progressStates = []; /// public virtual TaskBarProgressState GetState(IntPtr hWnd) @@ -33,7 +33,7 @@ public virtual TaskBarProgressState GetState(Window? window) return TaskBarProgressState.None; } - var windowHandle = new WindowInteropHelper(window).Handle; + IntPtr windowHandle = new WindowInteropHelper(window).Handle; if (!_progressStates.TryGetValue(windowHandle, out TaskBarProgressState progressState)) { @@ -78,7 +78,7 @@ public virtual bool SetValue(Window? window, int current, int total) return false; } - var windowHandle = new WindowInteropHelper(window).Handle; + IntPtr windowHandle = new WindowInteropHelper(window).Handle; if (!_progressStates.TryGetValue(windowHandle, out TaskBarProgressState progressState)) { @@ -108,7 +108,7 @@ int total /// public virtual bool SetValue(IntPtr hWnd, int current, int total) { - if (!_progressStates.TryGetValue(hWnd, out var progressState)) + if (!_progressStates.TryGetValue(hWnd, out TaskBarProgressState progressState)) { return TaskBarProgress.SetValue(hWnd, TaskBarProgressState.Normal, current, total); } diff --git a/source/RevitLookup.UI/ThemeService.cs b/source/RevitLookup.UI/ThemeService.cs index a6b5a1a8..713cc944 100644 --- a/source/RevitLookup.UI/ThemeService.cs +++ b/source/RevitLookup.UI/ThemeService.cs @@ -72,7 +72,7 @@ public bool SetAccent(Color accentColor) public bool SetAccent(SolidColorBrush accentSolidBrush) { Color color = accentSolidBrush.Color; - color.A = (byte)Math.Round(accentSolidBrush.Opacity * Byte.MaxValue); + color.A = (byte)Math.Round(accentSolidBrush.Opacity * byte.MaxValue); ApplicationAccentColorManager.Apply(color); diff --git a/source/RevitLookup.UI/UiApplication.cs b/source/RevitLookup.UI/UiApplication.cs new file mode 100644 index 00000000..b12c0b7a --- /dev/null +++ b/source/RevitLookup.UI/UiApplication.cs @@ -0,0 +1,138 @@ +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. +// Copyright (C) Leszek Pomianowski and WPF UI Contributors. +// All Rights Reserved. + +namespace Wpf.Ui; + +/// +/// Represents a UI application. +/// +public class UiApplication +{ + private static UiApplication? _uiApplication; + + private readonly Application? _application; + + private ResourceDictionary? _resources; + + private Window? _mainWindow; + + /// + /// Initializes a new instance of the class. + /// + public UiApplication(Application application) + { + if (application is null) + { + return; + } + + if (!ApplicationHasResources(application)) + { + return; + } + + _application = application; + + System.Diagnostics.Debug.WriteLine( + $"INFO | {typeof(UiApplication)} application is {_application}", + "Wpf.Ui" + ); + } + + /// + /// Gets a value indicating whether the application is running outside of the desktop app context. + /// + public bool IsApplication => _application is not null; + + /// + /// Gets the current application. + /// + public static UiApplication Current + { + get + { + _uiApplication ??= new UiApplication(Application.Current); + + return _uiApplication; + } + } + + /// + /// Gets or sets the application's main window. + /// + public Window? MainWindow + { + get => _application?.MainWindow ?? _mainWindow; + set + { + if (_application != null) + { + _application.MainWindow = value; + } + + _mainWindow = value; + } + } + + /// + /// Gets or sets the application's resources. + /// + public ResourceDictionary Resources + { + get + { + if (_resources is null) + { + _resources = []; + + try + { + Wpf.Ui.Appearance.ApplicationAccentColorManager.ApplySystemAccent(); + var themesDictionary = new Markup.ThemesDictionary(); + var controlsDictionary = new Markup.ControlsDictionary(); + _resources.MergedDictionaries.Add(themesDictionary); + _resources.MergedDictionaries.Add(controlsDictionary); + } + catch { } + } + + return _application?.Resources ?? _resources; + } + set + { + if (_application is not null) + { + _application.Resources = value; + } + + _resources = value; + } + } + + /// + /// Gets or sets the application's main window. + /// + public object TryFindResource(object resourceKey) + { + return Resources[resourceKey]; + } + + /// + /// Turns the application's into shutdown mode. + /// + public void Shutdown() + { + _application?.Shutdown(); + } + + private static bool ApplicationHasResources(Application application) + { + return application + .Resources.MergedDictionaries.Where(e => e.Source is not null) + .Any(e => + e.Source.ToString().ToLower().Contains(Appearance.ApplicationThemeManager.LibraryNamespace) + ); + } +} diff --git a/source/RevitLookup.UI/VisualStudioToolsManifest.xml b/source/RevitLookup.UI/VisualStudioToolsManifest.xml index ed61cc8c..7444460f 100644 --- a/source/RevitLookup.UI/VisualStudioToolsManifest.xml +++ b/source/RevitLookup.UI/VisualStudioToolsManifest.xml @@ -27,6 +27,7 @@ + diff --git a/source/RevitLookup.UI/Win32/Utilities.cs b/source/RevitLookup.UI/Win32/Utilities.cs index 274b9306..97e32c27 100644 --- a/source/RevitLookup.UI/Win32/Utilities.cs +++ b/source/RevitLookup.UI/Win32/Utilities.cs @@ -16,10 +16,17 @@ namespace Wpf.Ui.Win32; /// // ReSharper disable InconsistentNaming // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -public class Utilities +// ReSharper disable once ClassNeverInstantiated.Global +internal sealed class Utilities { private static readonly PlatformID _osPlatform = Environment.OSVersion.Platform; + public static readonly Version Vista = new(6, 0); + + public static readonly Version Windows7 = new(6, 1); + + public static readonly Version Windows8 = new(6, 2); + private static readonly Version _osVersion = #if NET5_0_OR_GREATER Environment.OSVersion.Version; @@ -28,47 +35,47 @@ public class Utilities #endif /// - /// Whether the operating system is NT or newer. + /// Gets a value indicating whether the operating system is NT or newer. /// public static bool IsNT => _osPlatform == PlatformID.Win32NT; /// - /// Whether the operating system version is greater than or equal to 6.0. + /// Gets a value indicating whether the operating system version is greater than or equal to 6.0. /// - public static bool IsOSVistaOrNewer => _osVersion >= new Version(6, 0); + public static bool IsOSVistaOrNewer => _osVersion >= Vista; /// - /// Whether the operating system version is greater than or equal to 6.1. + /// Gets a value indicating whether the operating system version is greater than or equal to 6.1. /// - public static bool IsOSWindows7OrNewer => _osVersion >= new Version(6, 1); + public static bool IsOSWindows7OrNewer => _osVersion >= Windows7; /// - /// Whether the operating system version is greater than or equal to 6.2. + /// Gets a value indicating whether the operating system version is greater than or equal to 6.2. /// - public static bool IsOSWindows8OrNewer => _osVersion >= new Version(6, 2); + public static bool IsOSWindows8OrNewer => _osVersion >= Windows8; /// - /// Whether the operating system version is greater than or equal to 10.0* (build 10240). + /// Gets a value indicating whether the operating system version is greater than or equal to 10.0* (build 10240). /// public static bool IsOSWindows10OrNewer => _osVersion.Build >= 10240; /// - /// Whether the operating system version is greater than or equal to 10.0* (build 22000). + /// Gets a value indicating whether the operating system version is greater than or equal to 10.0* (build 22000). /// public static bool IsOSWindows11OrNewer => _osVersion.Build >= 22000; /// - /// Whether the operating system version is greater than or equal to 10.0* (build 22523). + /// Gets a value indicating whether the operating system version is greater than or equal to 10.0* (build 22523). /// public static bool IsOSWindows11Insider1OrNewer => _osVersion.Build >= 22523; /// - /// Whether the operating system version is greater than or equal to 10.0* (build 22557). + /// Gets a value indicating whether the operating system version is greater than or equal to 10.0* (build 22557). /// public static bool IsOSWindows11Insider2OrNewer => _osVersion.Build >= 22557; /// - /// Indicates whether Desktop Window Manager (DWM) composition is enabled. + /// Gets a value indicating whether Desktop Window Manager (DWM) composition is enabled. /// public static bool IsCompositionEnabled { @@ -79,7 +86,7 @@ public static bool IsCompositionEnabled return false; } - Interop.Dwmapi.DwmIsCompositionEnabled(out var pfEnabled); + _ = Interop.Dwmapi.DwmIsCompositionEnabled(out var pfEnabled); return pfEnabled != 0; } @@ -90,7 +97,7 @@ public static void SafeDispose(ref T disposable) { // Dispose can safely be called on an object multiple times. IDisposable t = disposable; - disposable = default(T); + disposable = default; if (t is null) { @@ -104,15 +111,15 @@ public static void SafeRelease(ref T comObject) where T : class { T t = comObject; - comObject = default(T); + comObject = default; if (t is null) { return; } - Debug.Assert(Marshal.IsComObject(t)); - Marshal.ReleaseComObject(t); + Debug.Assert(Marshal.IsComObject(t), "Object is not a COM object."); + _ = Marshal.ReleaseComObject(t); } #if !NET5_0_OR_GREATER @@ -137,8 +144,8 @@ out var majorObj major = (int)majorObj; } - // When the 'CurrentMajorVersionNumber' value is not present we fallback to reading the previous key used for this: 'CurrentVersion' - else if ( + else // When the 'CurrentMajorVersionNumber' value is not present we fallback to reading the previous key used for this: 'CurrentVersion' + if ( TryGetRegistryKey( @"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentVersion", @@ -146,7 +153,7 @@ out var version ) ) { - version ??= String.Empty; + version ??= string.Empty; var versionParts = ((string)version).Split('.'); @@ -169,12 +176,12 @@ out var minorObj ) ) { - minorObj ??= String.Empty; + minorObj ??= string.Empty; minor = (int)minorObj; } - // When the 'CurrentMinorVersionNumber' value is not present we fallback to reading the previous key used for this: 'CurrentVersion' - else if ( + else // When the 'CurrentMinorVersionNumber' value is not present we fallback to reading the previous key used for this: 'CurrentVersion' + if ( TryGetRegistryKey( @"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentVersion", @@ -182,12 +189,14 @@ out var version ) ) { - version ??= String.Empty; + version ??= string.Empty; var versionParts = ((string)version).Split('.'); if (versionParts.Length >= 2) + { minor = int.TryParse(versionParts[1], out int minorAsInt) ? minorAsInt : 0; + } } } @@ -201,7 +210,7 @@ out var buildObj ) ) { - buildObj ??= String.Empty; + buildObj ??= string.Empty; build = int.TryParse((string)buildObj, out int buildAsInt) ? buildAsInt : 0; } @@ -216,7 +225,7 @@ private static bool TryGetRegistryKey(string path, string key, out object? value try { - using var rk = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(path); + using Microsoft.Win32.RegistryKey rk = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(path); if (rk == null) { From 61de80573b5419b54f3ba9b5f7929b06294158a8 Mon Sep 17 00:00:00 2001 From: Nice3point Date: Sun, 22 Dec 2024 15:26:59 +0300 Subject: [PATCH 049/121] Fix resources manager --- .../Appearance/ThemeWatcherService.cs | 11 +- .../Application/RevitLookupUiService.cs | 222 +++++++++++--- .../Services/RevitLookupUiService.cs | 284 ------------------ ...el.cs => DecompositionSummaryViewModel.cs} | 4 +- ...ViewModel.cs => EventsSummaryViewModel.cs} | 4 +- 5 files changed, 197 insertions(+), 328 deletions(-) delete mode 100644 source/RevitLookup/Services/RevitLookupUiService.cs rename source/RevitLookup/ViewModels/Summary/{MockDecompositionSummaryViewModel.cs => DecompositionSummaryViewModel.cs} (98%) rename source/RevitLookup/ViewModels/Summary/{MockEventsSummaryViewModel.cs => EventsSummaryViewModel.cs} (98%) diff --git a/source/RevitLookup/Services/Appearance/ThemeWatcherService.cs b/source/RevitLookup/Services/Appearance/ThemeWatcherService.cs index 2303d983..1bbcd89a 100644 --- a/source/RevitLookup/Services/Appearance/ThemeWatcherService.cs +++ b/source/RevitLookup/Services/Appearance/ThemeWatcherService.cs @@ -131,10 +131,17 @@ private void OnWatchedElementUnloaded(object sender, RoutedEventArgs e) private static void UpdateDictionary(FrameworkElement frameworkElement) { + var themedResources = frameworkElement.Resources.MergedDictionaries + .Where(dictionary => dictionary.Source.OriginalString.Contains("revitlookup.ui;", StringComparison.OrdinalIgnoreCase)) + .ToArray(); + frameworkElement.Resources.MergedDictionaries.Insert(0, UiApplication.Current.Resources.MergedDictionaries[0]); frameworkElement.Resources.MergedDictionaries.Insert(1, UiApplication.Current.Resources.MergedDictionaries[1]); - frameworkElement.Resources.MergedDictionaries.RemoveAt(2); - frameworkElement.Resources.MergedDictionaries.RemoveAt(2); + + foreach (var themedResource in themedResources) + { + frameworkElement.Resources.MergedDictionaries.Remove(themedResource); + } } private void UpdateBackground(ApplicationTheme theme) diff --git a/source/RevitLookup/Services/Application/RevitLookupUiService.cs b/source/RevitLookup/Services/Application/RevitLookupUiService.cs index 430d6fe9..8e9af468 100644 --- a/source/RevitLookup/Services/Application/RevitLookupUiService.cs +++ b/source/RevitLookup/Services/Application/RevitLookupUiService.cs @@ -1,6 +1,7 @@ using System.Collections; using System.Windows; using System.Windows.Controls; +using System.Windows.Threading; using Microsoft.Extensions.DependencyInjection; using RevitLookup.Abstractions.Models.Summary; using RevitLookup.Abstractions.ObservableModels.Decomposition; @@ -13,103 +14,248 @@ namespace RevitLookup.Services.Application; public sealed class RevitLookupUiService : IRevitLookupUiService { - private Window? _parent; - private readonly Task _activeTask = Task.CompletedTask; - private readonly IServiceScope _scope; - private readonly IVisualDecompositionService _decompositionService; - private readonly INavigationService _navigationService; - private readonly Window _host; + private static readonly Dispatcher Dispatcher; + private UiServiceImpl _uiService = null!; //Late init in constructor - public RevitLookupUiService(IServiceScopeFactory scopeFactory) + static RevitLookupUiService() { - _scope = scopeFactory.CreateScope(); + var uiThread = new Thread(Dispatcher.Run); + uiThread.SetApartmentState(ApartmentState.STA); + uiThread.Start(); - _host = _scope.ServiceProvider.GetRequiredService(); - _decompositionService = _scope.ServiceProvider.GetRequiredService(); - _navigationService = _scope.ServiceProvider.GetRequiredService(); + Dispatcher = EnsureDispatcherStart(uiThread); + } - _host.Closed += (_, _) => _scope.Dispose(); + public RevitLookupUiService(IServiceScopeFactory scopeFactory) + { + if (Dispatcher.CheckAccess()) + { + _uiService = new UiServiceImpl(scopeFactory); + } + else + { + Dispatcher.Invoke(() => _uiService = new UiServiceImpl(scopeFactory)); + } } public ILookupServiceDependsStage Decompose(KnownDecompositionObject decompositionObject) { - _activeTask.ContinueWith(_ => _decompositionService.VisualizeDecompositionAsync(decompositionObject), TaskScheduler.FromCurrentSynchronizationContext()); + if (Dispatcher.CheckAccess()) + { + _uiService.Decompose(decompositionObject); + } + else + { + Dispatcher.Invoke(() => _uiService.Decompose(decompositionObject)); + } + return this; } public ILookupServiceDependsStage Decompose(object? obj) { - _activeTask.ContinueWith(_ => _decompositionService.VisualizeDecompositionAsync(obj), TaskScheduler.FromCurrentSynchronizationContext()); + if (Dispatcher.CheckAccess()) + { + _uiService.Decompose(obj); + } + else + { + Dispatcher.Invoke(() => _uiService.Decompose(obj)); + } + return this; } public ILookupServiceDependsStage Decompose(IEnumerable objects) { - _activeTask.ContinueWith(_ => _decompositionService.VisualizeDecompositionAsync(objects), TaskScheduler.FromCurrentSynchronizationContext()); + if (Dispatcher.CheckAccess()) + { + _uiService.Decompose(objects); + } + else + { + Dispatcher.Invoke(() => _uiService.Decompose(objects)); + } + return this; } public ILookupServiceDependsStage Decompose(ObservableDecomposedObject decomposedObject) { - _activeTask.ContinueWith(_ => _decompositionService.VisualizeDecompositionAsync(decomposedObject), TaskScheduler.FromCurrentSynchronizationContext()); + if (Dispatcher.CheckAccess()) + { + _uiService.Decompose(decomposedObject); + } + else + { + Dispatcher.Invoke(() => _uiService.Decompose(decomposedObject)); + } + return this; } public ILookupServiceDependsStage Decompose(List decomposedObjects) { - _activeTask.ContinueWith(_ => _decompositionService.VisualizeDecompositionAsync(decomposedObjects), TaskScheduler.FromCurrentSynchronizationContext()); + if (Dispatcher.CheckAccess()) + { + _uiService.Decompose(decomposedObjects); + } + else + { + Dispatcher.Invoke(() => _uiService.Decompose(decomposedObjects)); + } + return this; } public ILookupServiceShowStage DependsOn(Window parent) { - _parent = parent; + if (Dispatcher.CheckAccess()) + { + _uiService.DependsOn(parent); + } + else + { + Dispatcher.Invoke(() => _uiService.DependsOn(parent)); + } + return this; } public ILookupServiceRunStage Show() where T : Page { - _activeTask.ContinueWith(_ => + if (Dispatcher.CheckAccess()) + { + _uiService.Show(); + } + else { - ShowHost(false); - _navigationService.Navigate(typeof(T)); - }, TaskScheduler.FromCurrentSynchronizationContext()); + Dispatcher.Invoke(() => _uiService.Show()); + } return this; } public void RunService(Action handler) where T : class { - _activeTask.ContinueWith(_ => InvokeService(handler), TaskScheduler.FromCurrentSynchronizationContext()); + if (Dispatcher.CheckAccess()) + { + _uiService.RunService(handler); + } + else + { + Dispatcher.Invoke(() => _uiService.RunService(handler)); + } } - private void InvokeService(Action handler) where T : class + private static Dispatcher EnsureDispatcherStart(Thread thread) { - var service = _scope.ServiceProvider.GetRequiredService(); - handler.Invoke(service); + Dispatcher? dispatcher = null; + SpinWait spinWait = new(); + while (dispatcher is null) + { + spinWait.SpinOnce(); + dispatcher = Dispatcher.FromThread(thread); + } + + // We must yield + // Sometimes the Dispatcher is unavailable for current thread + Thread.Sleep(1); + + return dispatcher; } - private void ShowHost(bool modal) + private sealed class UiServiceImpl { - if (_parent is null) + private Window? _parent; + private readonly Task _activeTask = Task.CompletedTask; + private readonly IServiceScope _scope; + private readonly IVisualDecompositionService _decompositionService; + private readonly INavigationService _navigationService; + private readonly Window _host; + + public UiServiceImpl(IServiceScopeFactory scopeFactory) { - _host.WindowStartupLocation = WindowStartupLocation.CenterScreen; + _scope = scopeFactory.CreateScope(); + + _host = _scope.ServiceProvider.GetRequiredService(); + _decompositionService = _scope.ServiceProvider.GetRequiredService(); + _navigationService = _scope.ServiceProvider.GetRequiredService(); + + _host.Closed += (_, _) => _scope.Dispose(); } - else + + public void Decompose(KnownDecompositionObject decompositionObject) { - _host.WindowStartupLocation = WindowStartupLocation.Manual; - _host.Left = _parent.Left + 47; - _host.Top = _parent.Top + 49; + _activeTask.ContinueWith(_ => _decompositionService.VisualizeDecompositionAsync(decompositionObject), TaskScheduler.FromCurrentSynchronizationContext()); } - if (modal) + public void Decompose(object? obj) { - _host.ShowDialog(); + _activeTask.ContinueWith(_ => _decompositionService.VisualizeDecompositionAsync(obj), TaskScheduler.FromCurrentSynchronizationContext()); } - else + + public void Decompose(IEnumerable objects) { - _host.Show(); - _host.Focus(); + _activeTask.ContinueWith(_ => _decompositionService.VisualizeDecompositionAsync(objects), TaskScheduler.FromCurrentSynchronizationContext()); + } + + public void Decompose(ObservableDecomposedObject decomposedObject) + { + _activeTask.ContinueWith(_ => _decompositionService.VisualizeDecompositionAsync(decomposedObject), TaskScheduler.FromCurrentSynchronizationContext()); + } + + public void Decompose(List decomposedObjects) + { + _activeTask.ContinueWith(_ => _decompositionService.VisualizeDecompositionAsync(decomposedObjects), TaskScheduler.FromCurrentSynchronizationContext()); + } + + public void DependsOn(Window parent) + { + _parent = parent; + } + + public void Show() where T : Page + { + _activeTask.ContinueWith(_ => + { + ShowHost(false); + _navigationService.Navigate(typeof(T)); + }, TaskScheduler.FromCurrentSynchronizationContext()); + } + + public void RunService(Action handler) where T : class + { + _activeTask.ContinueWith(_ => InvokeService(handler), TaskScheduler.FromCurrentSynchronizationContext()); + } + + private void InvokeService(Action handler) where T : class + { + var service = _scope.ServiceProvider.GetRequiredService(); + handler.Invoke(service); + } + + private void ShowHost(bool modal) + { + if (_parent is null) + { + _host.WindowStartupLocation = WindowStartupLocation.CenterScreen; + } + else + { + _host.WindowStartupLocation = WindowStartupLocation.Manual; + _host.Left = _parent.Left + 47; + _host.Top = _parent.Top + 49; + } + + if (modal) + { + _host.ShowDialog(); + } + else + { + _host.Show(Context.UiApplication.MainWindowHandle); + } } } } \ No newline at end of file diff --git a/source/RevitLookup/Services/RevitLookupUiService.cs b/source/RevitLookup/Services/RevitLookupUiService.cs deleted file mode 100644 index f778b1a3..00000000 --- a/source/RevitLookup/Services/RevitLookupUiService.cs +++ /dev/null @@ -1,284 +0,0 @@ -namespace RevitLookup.Services; - -// public sealed class RevitLookupUiService : IRevitLookupUiService -// { -// private static readonly Dispatcher Dispatcher; -// private UiServiceImpl _uiService = default!; -// -// static RevitLookupUiService() -// { -// var uiThread = new Thread(Dispatcher.Run); -// uiThread.SetApartmentState(ApartmentState.STA); -// uiThread.Start(); -// -// Dispatcher = EnsureDispatcherStart(uiThread); -// } -// -// public RevitLookupUiService(IServiceScopeFactory scopeFactory) -// { -// if (Dispatcher.CheckAccess()) -// { -// _uiService = new UiServiceImpl(scopeFactory); -// } -// else -// { -// Dispatcher.Invoke(() => _uiService = new UiServiceImpl(scopeFactory)); -// } -// } -// -// public ILookupServiceDependsStage Decompose(KnownDecompositionObject decompositionObject) -// { -// if (Dispatcher.CheckAccess()) -// { -// _uiService.Decompose(decompositionObject); -// } -// else -// { -// Dispatcher.Invoke(() => _uiService.Decompose(decompositionObject)); -// } -// -// return this; -// } -// public ILookupServiceDependsStage Decompose(object obj) -// { -// if (Dispatcher.CheckAccess()) -// { -// _uiService.Decompose(obj); -// } -// else -// { -// Dispatcher.Invoke(() => _uiService.Decompose(obj)); -// } -// -// return this; -// } -// -// public ILookupServiceDependsStage Decompose(IEnumerable objects) -// { -// if (Dispatcher.CheckAccess()) -// { -// _uiService.Decompose(objects); -// } -// else -// { -// Dispatcher.Invoke(() => _uiService.Decompose(objects)); -// } -// -// return this; -// } -// -// public ILookupServiceDependsStage Decompose(ObservableDecomposedObject decomposedObject) -// { -// if (Dispatcher.CheckAccess()) -// { -// _uiService.Decompose(decomposedObject); -// } -// else -// { -// Dispatcher.Invoke(() => _uiService.Decompose(decomposedObject)); -// } -// -// return this; -// } -// -// public ILookupServiceDependsStage Decompose(List decomposedObjects) -// { -// if (Dispatcher.CheckAccess()) -// { -// _uiService.Decompose(decomposedObjects); -// } -// else -// { -// Dispatcher.Invoke(() => _uiService.Decompose(decomposedObjects)); -// } -// -// return this; -// } -// -// public ILookupServiceShowStage DependsOn(Window parent) -// { -// if (Dispatcher.CheckAccess()) -// { -// _uiService.DependsOn(parent); -// } -// else -// { -// Dispatcher.Invoke(() => _uiService.DependsOn(parent)); -// } -// -// return this; -// } -// -// public ILookupServiceRunStage Show() where T : Page -// { -// if (Dispatcher.CheckAccess()) -// { -// _uiService.Show(); -// } -// else -// { -// Dispatcher.Invoke(() => _uiService.Show()); -// } -// -// return this; -// } -// -// public void RunService(Action handler) where T : class -// { -// if (Dispatcher.CheckAccess()) -// { -// _uiService.RunService(handler); -// } -// else -// { -// Dispatcher.Invoke(() => _uiService.RunService(handler)); -// } -// } -// -// private static Dispatcher EnsureDispatcherStart(Thread thread) -// { -// Dispatcher? dispatcher = null; -// SpinWait spinWait = new(); -// while (dispatcher is null) -// { -// spinWait.SpinOnce(); -// dispatcher = Dispatcher.FromThread(thread); -// } -// -// // We must yield -// // Sometimes the Dispatcher is unavailable for current thread -// Thread.Sleep(1); -// -// return dispatcher; -// } -// -// private sealed class UiServiceImpl -// { -// private Window? _parent; -// private Task? _activeTask; -// private readonly IServiceScope _scope; -// private readonly IVisualDecompositionService _decompositionService; -// private readonly INavigationService _navigationService; -// private readonly Window _host; -// -// public UiServiceImpl(IServiceScopeFactory scopeFactory) -// { -// _scope = scopeFactory.CreateScope(); -// -// _host = _scope.ServiceProvider.GetRequiredService(); -// _decompositionService = _scope.ServiceProvider.GetRequiredService(); -// _navigationService = _scope.ServiceProvider.GetRequiredService(); -// -// _host.Closed += (_, _) => _scope.Dispose(); -// } -// -// public void Decompose(KnownDecompositionObject decompositionObject) -// { -// if (_activeTask is null || _activeTask.IsCompleted) -// { -// _activeTask = _decompositionService.VisualizeDecompositionAsync(decompositionObject); -// } -// else -// { -// _activeTask = _activeTask.ContinueWith(_ => _decompositionService.VisualizeDecompositionAsync(decompositionObject), TaskScheduler.FromCurrentSynchronizationContext()); -// } -// } -// -// public void Decompose(object obj) -// { -// if (_activeTask is null || _activeTask.IsCompleted) -// { -// _activeTask = _decompositionService.VisualizeDecompositionAsync(obj); -// } -// else -// { -// _activeTask = _activeTask.ContinueWith(_ => _decompositionService.VisualizeDecompositionAsync(obj), TaskScheduler.FromCurrentSynchronizationContext()); -// } -// } -// -// public void Decompose(IEnumerable enumerable) -// { -// if (_activeTask is null || _activeTask.IsCompleted) -// { -// _activeTask = _decompositionService.VisualizeDecompositionAsync(enumerable); -// } -// else -// { -// _activeTask = _activeTask.ContinueWith(_ => _decompositionService.VisualizeDecompositionAsync(enumerable), TaskScheduler.FromCurrentSynchronizationContext()); -// } -// } -// -// public void Decompose(ObservableDecomposedObject decomposedObject) -// { -// _decompositionService.VisualizeDecompositionAsync(decomposedObject); -// } -// -// public void Decompose(List decomposedObjects) -// { -// _decompositionService.VisualizeDecompositionAsync(decomposedObjects); -// } -// -// public void DependsOn(Window parent) -// { -// _parent = parent; -// } -// -// public void Show() where T : Page -// { -// if (_activeTask is null || _activeTask.IsCompleted) -// { -// ShowHost(false); -// _navigationService.Navigate(typeof(T)); -// } -// else -// { -// _activeTask = _activeTask.ContinueWith(_ => -// { -// ShowHost(false); -// _navigationService.Navigate(typeof(T)); -// }, TaskScheduler.FromCurrentSynchronizationContext()); -// } -// } -// -// public void RunService(Action handler) where T : class -// { -// if (_activeTask is null || _activeTask.IsCompleted) -// { -// InvokeService(handler); -// } -// else -// { -// _activeTask = _activeTask.ContinueWith(_ => InvokeService(handler), TaskScheduler.FromCurrentSynchronizationContext()); -// } -// } -// -// private void InvokeService(Action handler) where T : class -// { -// var service = _scope.ServiceProvider.GetRequiredService(); -// handler.Invoke(service); -// } -// -// private void ShowHost(bool modal) -// { -// if (_parent is null) -// { -// _host.WindowStartupLocation = WindowStartupLocation.CenterScreen; -// } -// else -// { -// _host.WindowStartupLocation = WindowStartupLocation.Manual; -// _host.Left = _parent.Left + 47; -// _host.Top = _parent.Top + 49; -// } -// -// if (modal) -// { -// _host.ShowDialog(); -// } -// else -// { -// _host.Show(); -// } -// } -// } -// } \ No newline at end of file diff --git a/source/RevitLookup/ViewModels/Summary/MockDecompositionSummaryViewModel.cs b/source/RevitLookup/ViewModels/Summary/DecompositionSummaryViewModel.cs similarity index 98% rename from source/RevitLookup/ViewModels/Summary/MockDecompositionSummaryViewModel.cs rename to source/RevitLookup/ViewModels/Summary/DecompositionSummaryViewModel.cs index 160cee2f..48b0b056 100644 --- a/source/RevitLookup/ViewModels/Summary/MockDecompositionSummaryViewModel.cs +++ b/source/RevitLookup/ViewModels/Summary/DecompositionSummaryViewModel.cs @@ -16,11 +16,11 @@ namespace RevitLookup.ViewModels.Summary; [UsedImplicitly] -public sealed partial class MockDecompositionSummaryViewModel( +public sealed partial class DecompositionSummaryViewModel( ISettingsService settingsService, IWindowIntercomService intercomService, INotificationService notificationService, - ILogger logger) + ILogger logger) : ObservableObject, IDecompositionSummaryViewModel { [ObservableProperty] private string _searchText = string.Empty; diff --git a/source/RevitLookup/ViewModels/Summary/MockEventsSummaryViewModel.cs b/source/RevitLookup/ViewModels/Summary/EventsSummaryViewModel.cs similarity index 98% rename from source/RevitLookup/ViewModels/Summary/MockEventsSummaryViewModel.cs rename to source/RevitLookup/ViewModels/Summary/EventsSummaryViewModel.cs index e6d26f1d..54c93fbd 100644 --- a/source/RevitLookup/ViewModels/Summary/MockEventsSummaryViewModel.cs +++ b/source/RevitLookup/ViewModels/Summary/EventsSummaryViewModel.cs @@ -20,12 +20,12 @@ namespace RevitLookup.ViewModels.Summary; [UsedImplicitly] -public sealed partial class MockEventsSummaryViewModel( +public sealed partial class EventsSummaryViewModel( ISettingsService settingsService, IWindowIntercomService intercomService, INotificationService notificationService, EventsMonitoringService monitoringService, - ILogger logger) + ILogger logger) : ObservableObject, IEventsSummaryViewModel { [ObservableProperty] private string _searchText = string.Empty; From 3d3d73bbad336d242a4285c759f6aa4519c379db Mon Sep 17 00:00:00 2001 From: Nice3point Date: Sun, 22 Dec 2024 15:27:09 +0300 Subject: [PATCH 050/121] Fix service type --- source/RevitLookup.UI.Framework/Views/Tools/UnitsDialog.xaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/RevitLookup.UI.Framework/Views/Tools/UnitsDialog.xaml.cs b/source/RevitLookup.UI.Framework/Views/Tools/UnitsDialog.xaml.cs index 86304b8d..cd4ad6a2 100644 --- a/source/RevitLookup.UI.Framework/Views/Tools/UnitsDialog.xaml.cs +++ b/source/RevitLookup.UI.Framework/Views/Tools/UnitsDialog.xaml.cs @@ -121,7 +121,7 @@ private void CreateTreeContextMenu(UnitInfo info, FrameworkElement row) { Hide(); await _viewModel.DecomposeAsync(unitInfo); - _navigationService.Navigate(typeof(SummaryViewBase)); + _navigationService.Navigate(typeof(DecompositionSummaryPage)); }); row.ContextMenu = contextMenu; From 62b2daf354e24bbc2467c0698a10fd1e2fd947f5 Mon Sep 17 00:00:00 2001 From: Nice3point Date: Sun, 22 Dec 2024 17:17:21 +0300 Subject: [PATCH 051/121] Rearrange theme applying order --- RevitLookup.slnx.DotSettings | 3 +- .../AboutProgram/OpenSourceDialog.xaml.cs | 4 +- .../EditDialogs/EditIniEntryDialog.xaml.cs | 2 +- .../Views/EditDialogs/EditValueDialog.xaml.cs | 2 +- .../Settings/ResetSettingsDialog.xaml.cs | 2 +- ...rnal.cs => SummaryViewBase.Compability.cs} | 0 .../Views/Tools/ModulesDialog.xaml.cs | 4 +- .../Views/Tools/RevitSettingsPage.xaml | 11 +- .../Views/Tools/RevitSettingsPage.xaml.cs | 3 +- .../Views/Tools/SearchElementsDialog.xaml.cs | 4 +- .../Views/Tools/UnitsDialog.xaml.cs | 4 +- .../BoundingBoxVisualizationDialog.xaml.cs | 4 +- .../FaceVisualizationDialog.xaml.cs | 4 +- .../MeshVisualizationDialog.xaml.cs | 4 +- .../PolylineVisualizationDialog.xaml.cs | 4 +- .../SolidVisualizationDialog.xaml.cs | 4 +- .../XyzVisualizationDialog.xaml.cs | 4 +- .../RevitLookupView.Compability.xaml.cs | 55 ++ .../Views/Windows/RevitLookupView.xaml.cs | 3 + .../Controls/Button/Button.xaml | 613 +++++++++++++----- .../Appearance/ThemeWatcherService.cs | 1 + .../Summary/EventsSummaryViewModel.cs | 11 +- 22 files changed, 537 insertions(+), 209 deletions(-) rename source/RevitLookup.UI.Framework/Views/Summary/{SummaryViewBase.Internal.cs => SummaryViewBase.Compability.cs} (100%) create mode 100644 source/RevitLookup.UI.Framework/Views/Windows/RevitLookupView.Compability.xaml.cs diff --git a/RevitLookup.slnx.DotSettings b/RevitLookup.slnx.DotSettings index 81f7b771..87c0f021 100644 --- a/RevitLookup.slnx.DotSettings +++ b/RevitLookup.slnx.DotSettings @@ -1,2 +1,3 @@  - True \ No newline at end of file + True + True \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/AboutProgram/OpenSourceDialog.xaml.cs b/source/RevitLookup.UI.Framework/Views/AboutProgram/OpenSourceDialog.xaml.cs index 2f415f11..23965830 100644 --- a/source/RevitLookup.UI.Framework/Views/AboutProgram/OpenSourceDialog.xaml.cs +++ b/source/RevitLookup.UI.Framework/Views/AboutProgram/OpenSourceDialog.xaml.cs @@ -35,10 +35,10 @@ public OpenSourceDialog( IThemeWatcherService themeWatcherService) : base(dialogService.GetDialogHost()) { - themeWatcherService.Watch(this); - DataContext = viewModel; InitializeComponent(); + + themeWatcherService.Watch(this); } private void OpenLink(object sender, RoutedEventArgs args) diff --git a/source/RevitLookup.UI.Framework/Views/EditDialogs/EditIniEntryDialog.xaml.cs b/source/RevitLookup.UI.Framework/Views/EditDialogs/EditIniEntryDialog.xaml.cs index 985ea603..d8ee7b13 100644 --- a/source/RevitLookup.UI.Framework/Views/EditDialogs/EditIniEntryDialog.xaml.cs +++ b/source/RevitLookup.UI.Framework/Views/EditDialogs/EditIniEntryDialog.xaml.cs @@ -34,8 +34,8 @@ public EditSettingsEntryDialog( IThemeWatcherService themeWatcherService) : base(dialogService.GetDialogHost()) { - themeWatcherService.Watch(this); InitializeComponent(); + themeWatcherService.Watch(this); } public ObservableIniEntry Entry diff --git a/source/RevitLookup.UI.Framework/Views/EditDialogs/EditValueDialog.xaml.cs b/source/RevitLookup.UI.Framework/Views/EditDialogs/EditValueDialog.xaml.cs index ef3164da..88c0bff5 100644 --- a/source/RevitLookup.UI.Framework/Views/EditDialogs/EditValueDialog.xaml.cs +++ b/source/RevitLookup.UI.Framework/Views/EditDialogs/EditValueDialog.xaml.cs @@ -28,8 +28,8 @@ public sealed partial class EditValueDialog { public EditValueDialog(IContentDialogService dialogService, IThemeWatcherService themeWatcherService) : base(dialogService.GetDialogHost()) { - themeWatcherService.Watch(this); InitializeComponent(); + themeWatcherService.Watch(this); } public async Task ShowAsync(string name, string value) diff --git a/source/RevitLookup.UI.Framework/Views/Settings/ResetSettingsDialog.xaml.cs b/source/RevitLookup.UI.Framework/Views/Settings/ResetSettingsDialog.xaml.cs index 5ad78a20..5e2d642d 100644 --- a/source/RevitLookup.UI.Framework/Views/Settings/ResetSettingsDialog.xaml.cs +++ b/source/RevitLookup.UI.Framework/Views/Settings/ResetSettingsDialog.xaml.cs @@ -27,8 +27,8 @@ public sealed partial class ResetSettingsDialog { public ResetSettingsDialog(IContentDialogService dialogService, IThemeWatcherService themeWatcherService) : base(dialogService.GetDialogHost()) { - themeWatcherService.Watch(this); InitializeComponent(); + themeWatcherService.Watch(this); } public bool CanResetGeneralSettings => GeneralBox.IsChecked == true; diff --git a/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Internal.cs b/source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Compability.cs similarity index 100% rename from source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Internal.cs rename to source/RevitLookup.UI.Framework/Views/Summary/SummaryViewBase.Compability.cs diff --git a/source/RevitLookup.UI.Framework/Views/Tools/ModulesDialog.xaml.cs b/source/RevitLookup.UI.Framework/Views/Tools/ModulesDialog.xaml.cs index 7b88958c..7535431a 100644 --- a/source/RevitLookup.UI.Framework/Views/Tools/ModulesDialog.xaml.cs +++ b/source/RevitLookup.UI.Framework/Views/Tools/ModulesDialog.xaml.cs @@ -31,11 +31,11 @@ public ModulesDialog( IModulesViewModel viewModel, IThemeWatcherService themeWatcherService) : base(dialogService.GetDialogHost()) { - themeWatcherService.Watch(this); - DataContext = viewModel; InitializeComponent(); + themeWatcherService.Watch(this); + #if NETFRAMEWORK ContainerColumn.Header = "Domain"; #endif diff --git a/source/RevitLookup.UI.Framework/Views/Tools/RevitSettingsPage.xaml b/source/RevitLookup.UI.Framework/Views/Tools/RevitSettingsPage.xaml index 10b03d40..26977b57 100644 --- a/source/RevitLookup.UI.Framework/Views/Tools/RevitSettingsPage.xaml +++ b/source/RevitLookup.UI.Framework/Views/Tools/RevitSettingsPage.xaml @@ -19,6 +19,9 @@ + + + @@ -280,11 +283,9 @@ - + ()!; + contentPresenter.LoadCompleted += ContentPresenterOnContentRendered; + } + + private void ContentPresenterOnContentRendered(object? sender, EventArgs e) + { + var contentPresenter = (NavigationViewContentPresenter) sender!; + if (!contentPresenter.IsDynamicScrollViewerEnabled) return; + + if (VisualTreeHelper.GetChildrenCount(contentPresenter) == 0) + { + contentPresenter.ApplyTemplate(); + } + + var scrollViewer = (ScrollViewer) VisualTreeHelper.GetChild(contentPresenter, 0); + _themeWatcherService.Watch(scrollViewer); + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/Windows/RevitLookupView.xaml.cs b/source/RevitLookup.UI.Framework/Views/Windows/RevitLookupView.xaml.cs index 63b2f446..a5b11ef2 100644 --- a/source/RevitLookup.UI.Framework/Views/Windows/RevitLookupView.xaml.cs +++ b/source/RevitLookup.UI.Framework/Views/Windows/RevitLookupView.xaml.cs @@ -34,6 +34,7 @@ public sealed partial class RevitLookupView private readonly IWindowIntercomService _intercomService; private readonly ISoftwareUpdateService _updateService; private readonly ISettingsService _settingsService; + private readonly IThemeWatcherService _themeWatcherService; public RevitLookupView( INavigationService navigationService, @@ -47,6 +48,7 @@ public RevitLookupView( _intercomService = intercomService; _updateService = updateService; _settingsService = settingsService; + _themeWatcherService = themeWatcherService; themeWatcherService.Watch(this); InitializeComponent(); @@ -60,6 +62,7 @@ public RevitLookupView( AddShortcuts(); AddBadges(); ApplyWindowSize(); + FixComponentsTheme(); } private void AddBadges() diff --git a/source/RevitLookup.UI/Controls/Button/Button.xaml b/source/RevitLookup.UI/Controls/Button/Button.xaml index bf033902..ebeac1ff 100644 --- a/source/RevitLookup.UI/Controls/Button/Button.xaml +++ b/source/RevitLookup.UI/Controls/Button/Button.xaml @@ -13,31 +13,74 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:controls="clr-namespace:Wpf.Ui.Controls"> - 11,5,11,6 - 1 - 0,0,8,0 + + 11,5,11,6 + + + 1 + + + 0,0,8,0 + - - - - - + - - + + - + - - + + - - + + - - + + - - - + + + - - + + - - + + - + - + @@ -145,7 +200,8 @@ To="0" Duration="00:00:00.167"> - + @@ -156,16 +212,21 @@ - + - + - - + + - - + + - - + + - - - + + + - - + + - + @@ -205,18 +288,26 @@ - + - + - - - - + + + + - - + + - - + + - - + + - - + + - - + + - + - - + + - + - - + + - + - - - + + + - + - - + + - - - + + + - - + + - - + + - + - + - + - + @@ -409,7 +565,8 @@ To="0" Duration="00:00:00.167"> - + @@ -419,24 +576,52 @@ - @@ -451,7 +636,9 @@ --> - + - - + + - + - - - + + + - - + + - - - + + + - - - + + + - - + + - - + + - + - + @@ -558,7 +782,8 @@ To="0" Duration="00:00:00.167"> - + @@ -569,17 +794,23 @@ - + - + - - - + + + - - + + - - - + + + - - - + + + - - + + - + @@ -623,19 +879,28 @@ - + - + - - - - - + + + + + - @@ -694,40 +963,79 @@ - - + + - - + + - - - - + + + + - - + + - - + + - + - - + + - + - - + + - + - - - - + + + + + RecognizesAccessKey="True"> + + + + - + @@ -793,8 +1120,12 @@ - - + + - - - + + + - - + + - - + + - + - + - + - + @@ -842,7 +1192,8 @@ To="0" Duration="00:00:00.167"> - + @@ -853,28 +1204,58 @@ - - + From af0a7c10c662756624c806b1a4bbd008e69303ae Mon Sep 17 00:00:00 2001 From: Nice3point Date: Sun, 26 Jan 2025 16:39:18 +0300 Subject: [PATCH 116/121] Add modules context menu --- .../Views/Tools/ModulesDialog.xaml.cs | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/source/RevitLookup.UI.Framework/Views/Tools/ModulesDialog.xaml.cs b/source/RevitLookup.UI.Framework/Views/Tools/ModulesDialog.xaml.cs index 7535431a..3402ae65 100644 --- a/source/RevitLookup.UI.Framework/Views/Tools/ModulesDialog.xaml.cs +++ b/source/RevitLookup.UI.Framework/Views/Tools/ModulesDialog.xaml.cs @@ -18,8 +18,15 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. +using System.IO; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using RevitLookup.Abstractions.Models.Tools; using RevitLookup.Abstractions.Services.Appearance; using RevitLookup.Abstractions.ViewModels.Tools; +using RevitLookup.Common.Tools; +using RevitLookup.UI.Framework.Extensions; using Wpf.Ui; namespace RevitLookup.UI.Framework.Views.Tools; @@ -40,4 +47,58 @@ public ModulesDialog( ContainerColumn.Header = "Domain"; #endif } + + private void OnMouseEnter(object sender, RoutedEventArgs args) + { + var element = (FrameworkElement) sender; + var moduleInfo = (ModuleInfo) element.DataContext; + CreateRowContextMenu(moduleInfo, element); + } + + private void CreateRowContextMenu(ModuleInfo module, FrameworkElement row) + { + var contextMenu = new ContextMenu + { + Resources = UiApplication.Current.Resources, + PlacementTarget = row + }; + + var copyMenu = contextMenu.AddMenuItem("CopyMenuItem") + .SetHeader("Copy"); + + copyMenu.AddMenuItem() + .SetHeader("Module name") + .SetCommand(module, moduleInfo => Clipboard.SetDataObject(moduleInfo.Name)) + .SetShortcut(ModifierKeys.Control, Key.C); + + copyMenu.AddMenuItem() + .SetHeader("Path to module") + .SetCommand(module, moduleInfo => Clipboard.SetDataObject(moduleInfo.Path)) + .SetShortcut(ModifierKeys.Control | ModifierKeys.Shift, Key.C); + + copyMenu.AddMenuItem() + .SetHeader("Module version") + .SetCommand(module, moduleInfo => Clipboard.SetDataObject(moduleInfo.Version)); + +#if NETCOREAPP + copyMenu.AddMenuItem() + .SetHeader("AssemblyLoadContext name") + .SetCommand(module, moduleInfo => Clipboard.SetDataObject(moduleInfo.Container)); +#endif + + var navigateMenu = contextMenu.AddMenuItem() + .SetHeader("Navigate"); + + navigateMenu.AddMenuItem() + .SetHeader("Module directory") + .SetAvailability(File.Exists(module.Path)) + .SetCommand(module, moduleInfo => ProcessTasks.StartShell(Path.GetDirectoryName(moduleInfo.Path)!)); + + navigateMenu.AddMenuItem() + .SetHeader("Module location") + .SetAvailability(File.Exists(module.Path)) + .SetCommand(module, moduleInfo => ProcessTasks.StartShell("explorer.exe", $"/select,{moduleInfo.Path}")); + + row.ContextMenu = contextMenu; + } } \ No newline at end of file From 07f61faa3f67d4f0b31136ad37fdb55cee421e05 Mon Sep 17 00:00:00 2001 From: Nice3point Date: Sun, 26 Jan 2025 16:39:29 +0300 Subject: [PATCH 117/121] Cleanup --- .../RevitLookup.UI.Framework/Views/Tools/UnitsDialog.xaml.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/RevitLookup.UI.Framework/Views/Tools/UnitsDialog.xaml.cs b/source/RevitLookup.UI.Framework/Views/Tools/UnitsDialog.xaml.cs index 99140d8a..8ffa47c4 100644 --- a/source/RevitLookup.UI.Framework/Views/Tools/UnitsDialog.xaml.cs +++ b/source/RevitLookup.UI.Framework/Views/Tools/UnitsDialog.xaml.cs @@ -87,10 +87,10 @@ private void OnMouseEnter(object sender, RoutedEventArgs routedEventArgs) { var element = (FrameworkElement) sender; var unitInfo = (UnitInfo) element.DataContext; - CreateTreeContextMenu(unitInfo, element); + CreateRowContextMenu(unitInfo, element); } - private void CreateTreeContextMenu(UnitInfo info, FrameworkElement row) + private void CreateRowContextMenu(UnitInfo info, FrameworkElement row) { var contextMenu = new ContextMenu { From 24d1f96212736867981815c32ee6746dfdb3e04b Mon Sep 17 00:00:00 2001 From: Nice3point Date: Sun, 26 Jan 2025 16:39:37 +0300 Subject: [PATCH 118/121] Submenu extensions --- .../Extensions/ContextMenuExtensions.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/source/RevitLookup.UI.Framework/Extensions/ContextMenuExtensions.cs b/source/RevitLookup.UI.Framework/Extensions/ContextMenuExtensions.cs index e8fe532c..ba371000 100644 --- a/source/RevitLookup.UI.Framework/Extensions/ContextMenuExtensions.cs +++ b/source/RevitLookup.UI.Framework/Extensions/ContextMenuExtensions.cs @@ -62,6 +62,24 @@ public static MenuItem AddMenuItem(this ContextMenu menu, string resource) return item; } + public static MenuItem AddMenuItem(this MenuItem menu) + { + var item = new Wpf.Ui.Controls.MenuItem(); + menu.Items.Add(item); + + return item; + } + + public static MenuItem AddMenuItem(this MenuItem menu, string resource) + { + var item = (MenuItem?) menu.FindLogicalParent()!.Resources[resource]; + if (item is null) throw new InvalidOperationException($"Resource \"{resource}\" not found"); + + menu.Items.Add(item); + + return item; + } + public static MenuItem SetCommand(this MenuItem item, Action command) { item.Command = new RelayCommand(command); From 0a09f9cdae30fd3ab828b2b3db4a5619328dd02f Mon Sep 17 00:00:00 2001 From: Nice3point Date: Sun, 26 Jan 2025 17:10:57 +0300 Subject: [PATCH 119/121] Handle exception types --- .../RevitLookup.Abstractions.csproj | 4 ---- .../DecompositionSummaryPage.xaml | 7 ++++++- source/RevitLookup/Host.cs | 2 +- .../Application/RevitLookupUiService.cs | 18 ++++++++++++++++++ .../DecompositionService.cs | 2 +- .../EventsMonitoringService.cs | 2 +- .../VisualDecompositionService.cs | 2 +- .../DecompositionSummaryViewModel.cs | 18 ++++++++++++++++++ .../Decomposition/EventsSummaryViewModel.cs | 2 +- 9 files changed, 47 insertions(+), 10 deletions(-) rename source/RevitLookup/Services/{Summary => Decomposition}/DecompositionService.cs (99%) rename source/RevitLookup/Services/{Summary => Decomposition}/EventsMonitoringService.cs (98%) rename source/RevitLookup/Services/{Summary => Decomposition}/VisualDecompositionService.cs (98%) diff --git a/source/RevitLookup.Abstractions/RevitLookup.Abstractions.csproj b/source/RevitLookup.Abstractions/RevitLookup.Abstractions.csproj index a9d6178b..466087f5 100644 --- a/source/RevitLookup.Abstractions/RevitLookup.Abstractions.csproj +++ b/source/RevitLookup.Abstractions/RevitLookup.Abstractions.csproj @@ -20,8 +20,4 @@ - - - - \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/Decomposition/DecompositionSummaryPage.xaml b/source/RevitLookup.UI.Framework/Views/Decomposition/DecompositionSummaryPage.xaml index 5de44190..aa953ec6 100644 --- a/source/RevitLookup.UI.Framework/Views/Decomposition/DecompositionSummaryPage.xaml +++ b/source/RevitLookup.UI.Framework/Views/Decomposition/DecompositionSummaryPage.xaml @@ -57,9 +57,11 @@ Margin="0 8 0 0" ItemTemplate="{StaticResource DefaultSummaryTreeGroupTemplate}" ItemsSource="{Binding Path=ViewModel.(viewModels:IDecompositionSummaryViewModel.FilteredDecomposedObjects)}" - VirtualizingPanel.IsVirtualizing="True"> + VirtualizingPanel.IsVirtualizing="True" + VirtualizingPanel.VirtualizationMode="Recycling"> + + @@ -113,6 +117,7 @@ + diff --git a/source/RevitLookup/Host.cs b/source/RevitLookup/Host.cs index 75287bd1..df701340 100644 --- a/source/RevitLookup/Host.cs +++ b/source/RevitLookup/Host.cs @@ -12,8 +12,8 @@ using RevitLookup.Services; using RevitLookup.Services.Appearance; using RevitLookup.Services.Application; +using RevitLookup.Services.Decomposition; using RevitLookup.Services.Settings; -using RevitLookup.Services.Summary; using RevitLookup.UI.Framework.Services; using RevitLookup.UI.Framework.Services.Presentation; using Wpf.Ui; diff --git a/source/RevitLookup/Services/Application/RevitLookupUiService.cs b/source/RevitLookup/Services/Application/RevitLookupUiService.cs index c49483ab..f3f2df02 100644 --- a/source/RevitLookup/Services/Application/RevitLookupUiService.cs +++ b/source/RevitLookup/Services/Application/RevitLookupUiService.cs @@ -1,7 +1,9 @@ using System.Collections; +using System.Runtime.InteropServices; using System.Windows; using System.Windows.Controls; using System.Windows.Threading; +using Autodesk.Revit.Exceptions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using RevitLookup.Abstractions.Models.Decomposition; @@ -327,6 +329,22 @@ public async void Show() where T : Page { await Task.WhenAll(_activeTasks); } + catch (InvalidObjectException exception) + { + _notificationService.ShowError("Invalid object", exception); + } + catch (InternalException) + { + _notificationService.ShowError( + "Invalid object", + "A problem in the Revit code. Usually occurs when a managed API object is no longer valid and is unloaded from memory"); + } + catch (SEHException) + { + _notificationService.ShowError( + "Revit API internal error", + "A problem in the Revit code. Usually occurs when a managed API object is no longer valid and is unloaded from memory"); + } catch (Exception exception) { _logger.LogError(exception, "RevitLookup new instance startup error"); diff --git a/source/RevitLookup/Services/Summary/DecompositionService.cs b/source/RevitLookup/Services/Decomposition/DecompositionService.cs similarity index 99% rename from source/RevitLookup/Services/Summary/DecompositionService.cs rename to source/RevitLookup/Services/Decomposition/DecompositionService.cs index a27f24fd..0efeb6c4 100644 --- a/source/RevitLookup/Services/Summary/DecompositionService.cs +++ b/source/RevitLookup/Services/Decomposition/DecompositionService.cs @@ -9,7 +9,7 @@ using RevitLookup.Core.Decomposition; using RevitLookup.Mappers; -namespace RevitLookup.Services.Summary; +namespace RevitLookup.Services.Decomposition; [SuppressMessage("ReSharper", "LoopCanBeConvertedToQuery")] [SuppressMessage("ReSharper", "ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator")] diff --git a/source/RevitLookup/Services/Summary/EventsMonitoringService.cs b/source/RevitLookup/Services/Decomposition/EventsMonitoringService.cs similarity index 98% rename from source/RevitLookup/Services/Summary/EventsMonitoringService.cs rename to source/RevitLookup/Services/Decomposition/EventsMonitoringService.cs index a9c6d9bd..70a00986 100644 --- a/source/RevitLookup/Services/Summary/EventsMonitoringService.cs +++ b/source/RevitLookup/Services/Decomposition/EventsMonitoringService.cs @@ -24,7 +24,7 @@ using Microsoft.Extensions.Logging; using RevitLookup.Core; -namespace RevitLookup.Services.Summary; +namespace RevitLookup.Services.Decomposition; public sealed class EventsMonitoringService(ILogger logger) { diff --git a/source/RevitLookup/Services/Summary/VisualDecompositionService.cs b/source/RevitLookup/Services/Decomposition/VisualDecompositionService.cs similarity index 98% rename from source/RevitLookup/Services/Summary/VisualDecompositionService.cs rename to source/RevitLookup/Services/Decomposition/VisualDecompositionService.cs index 4e5bffa8..686e4f07 100644 --- a/source/RevitLookup/Services/Summary/VisualDecompositionService.cs +++ b/source/RevitLookup/Services/Decomposition/VisualDecompositionService.cs @@ -11,7 +11,7 @@ using OperationCanceledException = Autodesk.Revit.Exceptions.OperationCanceledException; using Visibility = System.Windows.Visibility; -namespace RevitLookup.Services.Summary; +namespace RevitLookup.Services.Decomposition; [SuppressMessage("ReSharper", "LoopCanBeConvertedToQuery")] [SuppressMessage("ReSharper", "ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator")] diff --git a/source/RevitLookup/ViewModels/Decomposition/DecompositionSummaryViewModel.cs b/source/RevitLookup/ViewModels/Decomposition/DecompositionSummaryViewModel.cs index 5ccd576e..6aeeff6d 100644 --- a/source/RevitLookup/ViewModels/Decomposition/DecompositionSummaryViewModel.cs +++ b/source/RevitLookup/ViewModels/Decomposition/DecompositionSummaryViewModel.cs @@ -1,4 +1,6 @@ using System.Collections.ObjectModel; +using System.Runtime.InteropServices; +using Autodesk.Revit.Exceptions; using Microsoft.Extensions.Logging; using RevitLookup.Abstractions.ObservableModels.Decomposition; using RevitLookup.Abstractions.Services.Application; @@ -148,6 +150,22 @@ async partial void OnSelectedDecomposedObjectChanged(ObservableDecomposedObject? { await FetchMembersAsync(value); } + catch (InvalidObjectException exception) + { + notificationService.ShowError("Invalid object", exception); + } + catch (InternalException) + { + notificationService.ShowError( + "Invalid object", + "A problem in the Revit code. Usually occurs when a managed API object is no longer valid and is unloaded from memory"); + } + catch (SEHException) + { + notificationService.ShowError( + "Revit API internal error", + "A problem in the Revit code. Usually occurs when a managed API object is no longer valid and is unloaded from memory"); + } catch (Exception exception) { logger.LogError(exception, "Members decomposing failed"); diff --git a/source/RevitLookup/ViewModels/Decomposition/EventsSummaryViewModel.cs b/source/RevitLookup/ViewModels/Decomposition/EventsSummaryViewModel.cs index 3cfb2859..d9035c03 100644 --- a/source/RevitLookup/ViewModels/Decomposition/EventsSummaryViewModel.cs +++ b/source/RevitLookup/ViewModels/Decomposition/EventsSummaryViewModel.cs @@ -5,7 +5,7 @@ using RevitLookup.Abstractions.Services.Decomposition; using RevitLookup.Abstractions.Services.Presentation; using RevitLookup.Abstractions.ViewModels.Decomposition; -using RevitLookup.Services.Summary; +using RevitLookup.Services.Decomposition; using RevitLookup.UI.Framework.Extensions; using RevitLookup.UI.Framework.Views.Decomposition; From 841a7afa1cbaca37950e42ace30f0f78d8798da9 Mon Sep 17 00:00:00 2001 From: Nice3point Date: Sun, 26 Jan 2025 21:35:08 +0300 Subject: [PATCH 120/121] Fix ColorMediaDescriptor name green color --- .../Core/Decomposition/Descriptors/ColorMediaDescriptor.cs | 2 +- .../Core/Decomposition/Descriptors/ColorMediaDescriptor.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/RevitLookup.UI.Playground/Mockups/Core/Decomposition/Descriptors/ColorMediaDescriptor.cs b/source/RevitLookup.UI.Playground/Mockups/Core/Decomposition/Descriptors/ColorMediaDescriptor.cs index f6cf3a8f..22c41e13 100644 --- a/source/RevitLookup.UI.Playground/Mockups/Core/Decomposition/Descriptors/ColorMediaDescriptor.cs +++ b/source/RevitLookup.UI.Playground/Mockups/Core/Decomposition/Descriptors/ColorMediaDescriptor.cs @@ -41,7 +41,7 @@ public sealed class ColorMediaDescriptor : Descriptor, IDescriptorExtension, ICo public ColorMediaDescriptor(Color color) { _color = color; - Name = $"RGB: {color.R} {color.B} {color.B}"; + Name = $"RGB: {color.R} {color.G} {color.B}"; } public void RegisterExtensions(IExtensionManager manager) diff --git a/source/RevitLookup/Core/Decomposition/Descriptors/ColorMediaDescriptor.cs b/source/RevitLookup/Core/Decomposition/Descriptors/ColorMediaDescriptor.cs index d50afb14..75d444e3 100644 --- a/source/RevitLookup/Core/Decomposition/Descriptors/ColorMediaDescriptor.cs +++ b/source/RevitLookup/Core/Decomposition/Descriptors/ColorMediaDescriptor.cs @@ -32,7 +32,7 @@ public sealed class ColorMediaDescriptor : Descriptor, IDescriptorExtension public ColorMediaDescriptor(Color color) { _color = color; - Name = $"RGB: {color.R} {color.B} {color.B}"; + Name = $"RGB: {color.R} {color.G} {color.B}"; } public void RegisterExtensions(IExtensionManager manager) From f0fa652af7acb5a35413d74c5a479ad29fa42246 Mon Sep 17 00:00:00 2001 From: Nice3point Date: Wed, 29 Jan 2025 01:28:25 +0300 Subject: [PATCH 121/121] Search integration --- .../IDecompositionSearchService.cs | 13 ++ .../DecompositionSummaryPage.xaml | 2 +- .../SummaryViewBase.Navigation.cs | 1 + source/RevitLookup.UI.Playground/Host.cs | 7 +- .../MockDecompositionSearchService.cs | 129 ++++++++++++++++++ .../MockDecompositionService.cs | 2 +- .../MockVisualDecompositionService.cs | 2 +- .../MockDecompositionSummaryViewModel.cs | 70 +++++----- source/RevitLookup/Host.cs | 5 +- .../DecompositionSearchService.cs | 129 ++++++++++++++++++ .../DecompositionSummaryViewModel.cs | 78 +++++------ 11 files changed, 354 insertions(+), 84 deletions(-) create mode 100644 source/RevitLookup.Abstractions/Services/Decomposition/IDecompositionSearchService.cs create mode 100644 source/RevitLookup.UI.Playground/Mockups/Services/Decomposition/MockDecompositionSearchService.cs rename source/RevitLookup.UI.Playground/Mockups/Services/{Summary => Decomposition}/MockDecompositionService.cs (98%) rename source/RevitLookup.UI.Playground/Mockups/Services/{Summary => Decomposition}/MockVisualDecompositionService.cs (98%) create mode 100644 source/RevitLookup/Services/Decomposition/DecompositionSearchService.cs diff --git a/source/RevitLookup.Abstractions/Services/Decomposition/IDecompositionSearchService.cs b/source/RevitLookup.Abstractions/Services/Decomposition/IDecompositionSearchService.cs new file mode 100644 index 00000000..9cb9b551 --- /dev/null +++ b/source/RevitLookup.Abstractions/Services/Decomposition/IDecompositionSearchService.cs @@ -0,0 +1,13 @@ +using RevitLookup.Abstractions.ObservableModels.Decomposition; + +namespace RevitLookup.Abstractions.Services.Decomposition; + +public interface IDecompositionSearchService +{ + (List, List) Search( + string query, + ObservableDecomposedObject? selectedObject, + List objects); + + List SearchMembers(string query, ObservableDecomposedObject value); +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Framework/Views/Decomposition/DecompositionSummaryPage.xaml b/source/RevitLookup.UI.Framework/Views/Decomposition/DecompositionSummaryPage.xaml index aa953ec6..5e058679 100644 --- a/source/RevitLookup.UI.Framework/Views/Decomposition/DecompositionSummaryPage.xaml +++ b/source/RevitLookup.UI.Framework/Views/Decomposition/DecompositionSummaryPage.xaml @@ -58,7 +58,7 @@ ItemTemplate="{StaticResource DefaultSummaryTreeGroupTemplate}" ItemsSource="{Binding Path=ViewModel.(viewModels:IDecompositionSummaryViewModel.FilteredDecomposedObjects)}" VirtualizingPanel.IsVirtualizing="True" - VirtualizingPanel.VirtualizationMode="Recycling"> + VirtualizingPanel.VirtualizationMode="Standard"> diff --git a/source/RevitLookup.UI.Framework/Views/Decomposition/SummaryViewBase.Navigation.cs b/source/RevitLookup.UI.Framework/Views/Decomposition/SummaryViewBase.Navigation.cs index 84ca9383..6c602ad3 100644 --- a/source/RevitLookup.UI.Framework/Views/Decomposition/SummaryViewBase.Navigation.cs +++ b/source/RevitLookup.UI.Framework/Views/Decomposition/SummaryViewBase.Navigation.cs @@ -43,6 +43,7 @@ private void OnTreeItemSelected(object sender, RoutedPropertyChangedEventArgs(); services.AddSingleton(); services.AddSingleton(); + + //Composer services services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddTransient(); return services.BuildServiceProvider(); diff --git a/source/RevitLookup.UI.Playground/Mockups/Services/Decomposition/MockDecompositionSearchService.cs b/source/RevitLookup.UI.Playground/Mockups/Services/Decomposition/MockDecompositionSearchService.cs new file mode 100644 index 00000000..7cd47484 --- /dev/null +++ b/source/RevitLookup.UI.Playground/Mockups/Services/Decomposition/MockDecompositionSearchService.cs @@ -0,0 +1,129 @@ +using System.Diagnostics.CodeAnalysis; +using RevitLookup.Abstractions.ObservableModels.Decomposition; +using RevitLookup.Abstractions.Services.Decomposition; + +namespace RevitLookup.UI.Playground.Mockups.Services.Decomposition; + +[SuppressMessage("ReSharper", "LoopCanBeConvertedToQuery")] +[SuppressMessage("ReSharper", "ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator")] +public sealed class MockDecompositionSearchService : IDecompositionSearchService +{ + private ObservableDecomposedObject? _previousSelection; + + public (List, List) Search( + string query, + ObservableDecomposedObject? selectedObject, + List objects) + { + try + { + if (selectedObject is not null) + { + return SearchMembers(query, selectedObject, objects); + } + + var fetchedObject = FindPreviousSelectedType(objects); + if (fetchedObject is not null) + { + if (fetchedObject.Members.Count > 0) + { + return SearchMembers(query, fetchedObject, objects); + } + } + + var filteredObjects = FilterObjects(query, objects); + return (filteredObjects, []); + } + finally + { + if (selectedObject is not null) + { + _previousSelection = selectedObject; + } + } + } + + public List SearchMembers(string query, ObservableDecomposedObject value) + { + var filteredMembers = FilterMembers(query, value.Members); + return filteredMembers.Count == 0 ? value.Members : filteredMembers; + } + + private static (List, List) SearchMembers( + string query, + ObservableDecomposedObject obj, + List objects) + { + var filterData = FilterMembers(query, obj.Members); + if (query.Length > 0 && filterData.Count > 0) + { + var typedObjects = FilterTypes(obj, objects); + return (typedObjects, filterData); + } + + var filterObjects = FilterObjects(query, objects); + return (filterObjects, obj.Members); + } + + private static List FilterTypes(ObservableDecomposedObject query, IEnumerable data) + { + var filteredObjects = new List(); + foreach (var item in data) + { + if (item.TypeFullName == query.TypeFullName) + { + filteredObjects.Add(item); + } + } + + return filteredObjects; + } + + private static List FilterObjects(string query, List objects) + { + if (query.Length == 0) return objects; + + var filteredObjects = new List(); + foreach (var item in objects) + { + if (item.Name.Contains(query, StringComparison.OrdinalIgnoreCase)) + { + filteredObjects.Add(item); + } + } + + return filteredObjects; + } + + private static List FilterMembers(string query, List members) + { + if (query.Length == 0) return members; + + var filteredMembers = new List(); + foreach (var item in members) + { + if (item.Name.Contains(query, StringComparison.OrdinalIgnoreCase)) + { + filteredMembers.Add(item); + } + } + + return filteredMembers; + } + + private ObservableDecomposedObject? FindPreviousSelectedType(List decomposedObjects) + { + ObservableDecomposedObject? fetchedObject = null; + foreach (var decomposedObject in decomposedObjects) + { + if (_previousSelection is null) break; + if (decomposedObject.TypeFullName != _previousSelection.TypeFullName) continue; + if (decomposedObject.Members.Count == 0) continue; + + fetchedObject = decomposedObject; + break; + } + + return fetchedObject; + } +} \ No newline at end of file diff --git a/source/RevitLookup.UI.Playground/Mockups/Services/Summary/MockDecompositionService.cs b/source/RevitLookup.UI.Playground/Mockups/Services/Decomposition/MockDecompositionService.cs similarity index 98% rename from source/RevitLookup.UI.Playground/Mockups/Services/Summary/MockDecompositionService.cs rename to source/RevitLookup.UI.Playground/Mockups/Services/Decomposition/MockDecompositionService.cs index 6f4889d2..d2b673cf 100644 --- a/source/RevitLookup.UI.Playground/Mockups/Services/Summary/MockDecompositionService.cs +++ b/source/RevitLookup.UI.Playground/Mockups/Services/Decomposition/MockDecompositionService.cs @@ -7,7 +7,7 @@ using RevitLookup.UI.Playground.Mockups.Core.Decomposition; using RevitLookup.UI.Playground.Mockups.Mappers; -namespace RevitLookup.UI.Playground.Mockups.Services.Summary; +namespace RevitLookup.UI.Playground.Mockups.Services.Decomposition; [SuppressMessage("ReSharper", "LoopCanBeConvertedToQuery")] [SuppressMessage("ReSharper", "ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator")] diff --git a/source/RevitLookup.UI.Playground/Mockups/Services/Summary/MockVisualDecompositionService.cs b/source/RevitLookup.UI.Playground/Mockups/Services/Decomposition/MockVisualDecompositionService.cs similarity index 98% rename from source/RevitLookup.UI.Playground/Mockups/Services/Summary/MockVisualDecompositionService.cs rename to source/RevitLookup.UI.Playground/Mockups/Services/Decomposition/MockVisualDecompositionService.cs index 8b8ee4e6..6c0934cf 100644 --- a/source/RevitLookup.UI.Playground/Mockups/Services/Summary/MockVisualDecompositionService.cs +++ b/source/RevitLookup.UI.Playground/Mockups/Services/Decomposition/MockVisualDecompositionService.cs @@ -8,7 +8,7 @@ using RevitLookup.Abstractions.Services.Presentation; using RevitLookup.Abstractions.ViewModels.Decomposition; -namespace RevitLookup.UI.Playground.Mockups.Services.Summary; +namespace RevitLookup.UI.Playground.Mockups.Services.Decomposition; [SuppressMessage("ReSharper", "LoopCanBeConvertedToQuery")] [SuppressMessage("ReSharper", "ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator")] diff --git a/source/RevitLookup.UI.Playground/Mockups/ViewModels/Decomposition/MockDecompositionSummaryViewModel.cs b/source/RevitLookup.UI.Playground/Mockups/ViewModels/Decomposition/MockDecompositionSummaryViewModel.cs index dfc9e384..5676f686 100644 --- a/source/RevitLookup.UI.Playground/Mockups/ViewModels/Decomposition/MockDecompositionSummaryViewModel.cs +++ b/source/RevitLookup.UI.Playground/Mockups/ViewModels/Decomposition/MockDecompositionSummaryViewModel.cs @@ -19,6 +19,7 @@ namespace RevitLookup.UI.Playground.Mockups.ViewModels.Decomposition; public sealed partial class MockDecompositionSummaryViewModel( IServiceProvider serviceProvider, IDecompositionService decompositionService, + IDecompositionSearchService searchService, INotificationService notificationService, ILogger logger) : ObservableObject, IDecompositionSummaryViewModel @@ -62,7 +63,10 @@ public async Task RefreshMembersAsync() try { + if (SelectedDecomposedObject is null) return; + await FetchMembersAsync(SelectedDecomposedObject); + SelectedDecomposedObject.FilteredMembers = searchService.SearchMembers(SearchText, SelectedDecomposedObject); } catch (Exception exception) { @@ -81,18 +85,18 @@ public void RemoveItem(object target) var groupToRemove = FilteredDecomposedObjects[i]; if (!groupToRemove.GroupItems.Remove(decomposedObject)) continue; + //Remove the empty group if (groupToRemove.GroupItems.Count == 0) { - //Remove the empty group FilteredDecomposedObjects.Remove(groupToRemove); } } if (DecomposedObjects.Remove(decomposedObject)) { + //Notify UI to update placeholders if (DecomposedObjects.Count == 0) { - //Notify UI to update placeholders OnPropertyChanged(nameof(DecomposedObjects)); } } @@ -110,65 +114,57 @@ partial void OnDecomposedObjectsChanged(List value) OnSearchTextChanged(SearchText); } - async partial void OnSearchTextChanged(string value) + async partial void OnSelectedDecomposedObjectChanged(ObservableDecomposedObject? value) { try { - if (string.IsNullOrEmpty(SearchText)) - { - FilteredDecomposedObjects = ApplyGrouping(DecomposedObjects); - return; - } - - FilteredDecomposedObjects = await Task.Run(() => - { - var formattedText = value.Trim(); - var searchResults = new List(); - // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator - foreach (var item in DecomposedObjects) - { - if (item.Name.Contains(formattedText, StringComparison.OrdinalIgnoreCase) || item.Name.Contains(formattedText, StringComparison.OrdinalIgnoreCase)) - { - searchResults.Add(item); - } - } - - return ApplyGrouping(searchResults); - }); + if (value is null) return; - if (FilteredDecomposedObjects.Count == 0) - { - SelectedDecomposedObject = null; - } + await FetchMembersAsync(value); + value.FilteredMembers = searchService.SearchMembers(SearchText, value); } - catch + catch (Exception exception) { - // ignored + logger.LogError(exception, "Members decomposing failed"); + notificationService.ShowError("Lookup engine error", exception); } } - async partial void OnSelectedDecomposedObjectChanged(ObservableDecomposedObject? value) + async partial void OnSearchTextChanged(string value) { try { - await FetchMembersAsync(value); + var (filteredObjects, filteredMembers) = await Task.Run(() => + searchService.Search(value, SelectedDecomposedObject, DecomposedObjects)); + + if (FilteredDecomposedObjects.Sum(group => group.GroupItems.Count) != filteredObjects.Count) + { + FilteredDecomposedObjects = await Task.Run(() => ApplyGrouping(filteredObjects)); + } + + if (SelectedDecomposedObject is not null) + { + if (filteredObjects.Contains(SelectedDecomposedObject)) + { + SelectedDecomposedObject.FilteredMembers = filteredMembers; + } + } } catch (Exception exception) { - logger.LogError(exception, "Members decomposing failed"); - notificationService.ShowError("Lookup engine error", exception); + logger.LogError(exception, "Search error"); + notificationService.ShowError("Search error", exception); } } - private async Task FetchMembersAsync(ObservableDecomposedObject? value) + private async Task FetchMembersAsync(ObservableDecomposedObject value) { - if (value is null) return; if (value.Members.Count > 0) return; value.Members = await decompositionService.DecomposeMembersAsync(value); } - private ObservableCollection ApplyGrouping(List objects) + private static ObservableCollection ApplyGrouping(List objects) { return objects .OrderBy(data => data.TypeName) diff --git a/source/RevitLookup/Host.cs b/source/RevitLookup/Host.cs index df701340..32f7c711 100644 --- a/source/RevitLookup/Host.cs +++ b/source/RevitLookup/Host.cs @@ -74,10 +74,13 @@ public static void Start() builder.Services.AddSingleton(); builder.Services.AddHostedService(); - //Services + //Composer services builder.Services.AddScoped(); builder.Services.AddScoped(); + builder.Services.AddScoped(); builder.Services.AddTransient(); + + //Revit tools services builder.Services.AddTransient(); _host = builder.Build(); diff --git a/source/RevitLookup/Services/Decomposition/DecompositionSearchService.cs b/source/RevitLookup/Services/Decomposition/DecompositionSearchService.cs new file mode 100644 index 00000000..dd06a73f --- /dev/null +++ b/source/RevitLookup/Services/Decomposition/DecompositionSearchService.cs @@ -0,0 +1,129 @@ +using System.Diagnostics.CodeAnalysis; +using RevitLookup.Abstractions.ObservableModels.Decomposition; +using RevitLookup.Abstractions.Services.Decomposition; + +namespace RevitLookup.Services.Decomposition; + +[SuppressMessage("ReSharper", "LoopCanBeConvertedToQuery")] +[SuppressMessage("ReSharper", "ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator")] +public sealed class DecompositionSearchService : IDecompositionSearchService +{ + private ObservableDecomposedObject? _previousSelection; + + public (List, List) Search( + string query, + ObservableDecomposedObject? selectedObject, + List objects) + { + try + { + if (selectedObject is not null) + { + return SearchMembers(query, selectedObject, objects); + } + + var fetchedObject = FindPreviousSelectedType(objects); + if (fetchedObject is not null) + { + if (fetchedObject.Members.Count > 0) + { + return SearchMembers(query, fetchedObject, objects); + } + } + + var filteredObjects = FilterObjects(query, objects); + return (filteredObjects, []); + } + finally + { + if (selectedObject is not null) + { + _previousSelection = selectedObject; + } + } + } + + public List SearchMembers(string query, ObservableDecomposedObject value) + { + var filteredMembers = FilterMembers(query, value.Members); + return filteredMembers.Count == 0 ? value.Members : filteredMembers; + } + + private static (List, List) SearchMembers( + string query, + ObservableDecomposedObject obj, + List objects) + { + var filterData = FilterMembers(query, obj.Members); + if (query.Length > 0 && filterData.Count > 0) + { + var typedObjects = FilterTypes(obj, objects); + return (typedObjects, filterData); + } + + var filterObjects = FilterObjects(query, objects); + return (filterObjects, obj.Members); + } + + private static List FilterTypes(ObservableDecomposedObject query, IEnumerable data) + { + var filteredObjects = new List(); + foreach (var item in data) + { + if (item.TypeFullName == query.TypeFullName) + { + filteredObjects.Add(item); + } + } + + return filteredObjects; + } + + private static List FilterObjects(string query, List objects) + { + if (query.Length == 0) return objects; + + var filteredObjects = new List(); + foreach (var item in objects) + { + if (item.Name.Contains(query, StringComparison.OrdinalIgnoreCase)) + { + filteredObjects.Add(item); + } + } + + return filteredObjects; + } + + private static List FilterMembers(string query, List members) + { + if (query.Length == 0) return members; + + var filteredMembers = new List(); + foreach (var item in members) + { + if (item.Name.Contains(query, StringComparison.OrdinalIgnoreCase)) + { + filteredMembers.Add(item); + } + } + + return filteredMembers; + } + + private ObservableDecomposedObject? FindPreviousSelectedType(List decomposedObjects) + { + ObservableDecomposedObject? fetchedObject = null; + foreach (var decomposedObject in decomposedObjects) + { + if (_previousSelection is null) break; + if (decomposedObject.TypeFullName != _previousSelection.TypeFullName) continue; + if (decomposedObject.Members.Count == 0) continue; + + fetchedObject = decomposedObject; + break; + } + + return fetchedObject; + } +} \ No newline at end of file diff --git a/source/RevitLookup/ViewModels/Decomposition/DecompositionSummaryViewModel.cs b/source/RevitLookup/ViewModels/Decomposition/DecompositionSummaryViewModel.cs index 6aeeff6d..94c13d81 100644 --- a/source/RevitLookup/ViewModels/Decomposition/DecompositionSummaryViewModel.cs +++ b/source/RevitLookup/ViewModels/Decomposition/DecompositionSummaryViewModel.cs @@ -16,6 +16,7 @@ namespace RevitLookup.ViewModels.Decomposition; public sealed partial class DecompositionSummaryViewModel( IServiceProvider serviceProvider, IDecompositionService decompositionService, + IDecompositionSearchService searchService, INotificationService notificationService, ILogger logger) : ObservableObject, IDecompositionSummaryViewModel @@ -59,7 +60,10 @@ public async Task RefreshMembersAsync() try { + if (SelectedDecomposedObject is null) return; + await FetchMembersAsync(SelectedDecomposedObject); + SelectedDecomposedObject.FilteredMembers = searchService.SearchMembers(SearchText, SelectedDecomposedObject); } catch (Exception exception) { @@ -78,18 +82,18 @@ public void RemoveItem(object target) var groupToRemove = FilteredDecomposedObjects[i]; if (!groupToRemove.GroupItems.Remove(decomposedObject)) continue; + //Remove the empty group if (groupToRemove.GroupItems.Count == 0) { - //Remove the empty group FilteredDecomposedObjects.Remove(groupToRemove); } } if (DecomposedObjects.Remove(decomposedObject)) { + //Notify UI to update placeholders if (DecomposedObjects.Count == 0) { - //Notify UI to update placeholders OnPropertyChanged(nameof(DecomposedObjects)); } } @@ -107,48 +111,14 @@ partial void OnDecomposedObjectsChanged(List value) OnSearchTextChanged(SearchText); } - async partial void OnSearchTextChanged(string value) - { - try - { - if (string.IsNullOrEmpty(SearchText)) - { - FilteredDecomposedObjects = ApplyGrouping(DecomposedObjects); - return; - } - - FilteredDecomposedObjects = await Task.Run(() => - { - var formattedText = value.Trim(); - var searchResults = new List(); - // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator - foreach (var item in DecomposedObjects) - { - if (item.Name.Contains(formattedText, StringComparison.OrdinalIgnoreCase) || item.Name.Contains(formattedText, StringComparison.OrdinalIgnoreCase)) - { - searchResults.Add(item); - } - } - - return ApplyGrouping(searchResults); - }); - - if (FilteredDecomposedObjects.Count == 0) - { - SelectedDecomposedObject = null; - } - } - catch - { - // ignored - } - } - async partial void OnSelectedDecomposedObjectChanged(ObservableDecomposedObject? value) { try { + if (value is null) return; + await FetchMembersAsync(value); + value.FilteredMembers = searchService.SearchMembers(SearchText, value); } catch (InvalidObjectException exception) { @@ -173,6 +143,33 @@ async partial void OnSelectedDecomposedObjectChanged(ObservableDecomposedObject? } } + async partial void OnSearchTextChanged(string value) + { + try + { + var (filteredObjects, filteredMembers) = await Task.Run(() => + searchService.Search(value, SelectedDecomposedObject, DecomposedObjects)); + + if (FilteredDecomposedObjects.Sum(group => group.GroupItems.Count) != filteredObjects.Count) + { + FilteredDecomposedObjects = await Task.Run(() => ApplyGrouping(filteredObjects)); + } + + if (SelectedDecomposedObject is not null) + { + if (filteredObjects.Contains(SelectedDecomposedObject)) + { + SelectedDecomposedObject.FilteredMembers = filteredMembers; + } + } + } + catch (Exception exception) + { + logger.LogError(exception, "Search error"); + notificationService.ShowError("Search error", exception); + } + } + private async Task FetchMembersAsync(ObservableDecomposedObject? value) { if (value is null) return; @@ -181,8 +178,7 @@ private async Task FetchMembersAsync(ObservableDecomposedObject? value) value.Members = await decompositionService.DecomposeMembersAsync(value); } - [Pure] - private ObservableCollection ApplyGrouping(List objects) + private static ObservableCollection ApplyGrouping(List objects) { return objects .OrderBy(data => data.TypeName)